Size baştan sona bir Next.js OpenAI chatbot uygulaması oluşturacağım. Modern ve kullanışlı bir chatbot yapacağız.
1. Proje Kurulumu
npx create-next-app@latest chatbot-app
cd chatbot-app
Kurulum sırasında şu seçenekleri seçin:
TypeScript: Yes
ESLint: Yes
Tailwind CSS: Yes
App Router: Yes
2. Gerekli Paketleri Yükleyin
npm install openai react-markdown
3. Ortam Değişkenleri
.env.local dosyası oluşturun:
OPENAI_API_KEY=your_openai_api_key_here
4. API Route Oluşturma
app/api/chat/route.ts:
import { NextRequest, NextResponse } from 'next/server';
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export async function POST(req: NextRequest) {
try {
const { messages } = await req.json();
const completion = await openai.chat.completions.create({
model: "gpt-3.5-turbo",
messages: messages,
temperature: 0.7,
max_tokens: 1000,
});
return NextResponse.json({
message: completion.choices[0].message.content
});
} catch (error) {
console.error('OpenAI API error:', error);
return NextResponse.json(
{ error: 'API çağrısında hata oluştu' },
{ status: 500 }
);
}
}
5. Tip Tanımlamaları
types/chat.ts:
export interface Message {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: Date;
}
export interface ChatState {
messages: Message[];
isLoading: boolean;
}
6. Chat Bileşeni
components/Chat.tsx:
'use client';
import { useState, useRef, useEffect } from 'react';
import ReactMarkdown from 'react-markdown';
import { Message } from '@/types/chat';
export default function Chat() {
const [messages, setMessages] = useState<Message[]>([
{
id: '1',
role: 'assistant',
content: 'Merhaba! Size nasıl yardımcı olabilirim?',
timestamp: new Date()
}
]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || isLoading) return;
const userMessage: Message = {
id: Date.now().toString(),
role: 'user',
content: input,
timestamp: new Date()
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
messages: [...messages, userMessage].map(({ role, content }) => ({
role,
content
}))
}),
});
const data = await response.json();
if (response.ok) {
const assistantMessage: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: data.message,
timestamp: new Date()
};
setMessages(prev => [...prev, assistantMessage]);
} else {
throw new Error(data.error || 'Bir hata oluştu');
}
} catch (error) {
console.error('Chat error:', error);
const errorMessage: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: 'Üzgünüm, bir hata oluştu. Lütfen tekrar deneyin.',
timestamp: new Date()
};
setMessages(prev => [...prev, errorMessage]);
} finally {
setIsLoading(false);
}
};
return (
<div className="flex flex-col h-screen bg-gray-50">
{/* Header */}
<div className="bg-white shadow-sm border-b">
<div className="max-w-4xl mx-auto px-4 py-4">
<h1 className="text-2xl font-bold text-gray-800">AI Chatbot</h1>
</div>
</div>
{/* Messages Container */}
<div className="flex-1 overflow-y-auto">
<div className="max-w-4xl mx-auto px-4 py-8">
{messages.map((message) => (
<div
key={message.id}
className={'mb-6 flex {
message.role === 'user' ? 'justify-end' : 'justify-start'
}'}
>
<div
className={'max-w-[70%] rounded-lg px-4 py-3 {
message.role === 'user'
? 'bg-blue-600 text-white'
: 'bg-white border border-gray-200 text-gray-800'
}'}
>
<div className="flex items-start space-x-2">
<div className="flex-shrink-0">
{message.role === 'user' ? (
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
<span className="text-white text-sm">U</span>
</div>
) : (
<div className="w-8 h-8 bg-gray-600 rounded-full flex items-center justify-center">
<span className="text-white text-sm">AI</span>
</div>
)}
</div>
<div className="flex-1">
{message.role === 'assistant' ? (
<ReactMarkdown className="prose prose-sm max-w-none">
{message.content}
</ReactMarkdown>
) : (
<p className="text-sm">{message.content}</p>
)}
</div>
</div>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start mb-6">
<div className="bg-white border border-gray-200 rounded-lg px-4 py-3">
<div className="flex items-center space-x-2">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-100"></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-200"></div>
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
</div>
{/* Input Form */}
<div className="bg-white border-t">
<div className="max-w-4xl mx-auto px-4 py-4">
<form onSubmit={handleSubmit} className="flex space-x-4">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Mesajınızı yazın..."
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isLoading}
/>
<button
type="submit"
disabled={isLoading || !input.trim()}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Gönder
</button>
</form>
</div>
</div>
</div>
);
}
7. Ana Sayfa
app/page.tsx:
import Chat from '@/components/Chat';
export default function Home() {
return <Chat />;
}
8. Global Stiller (Opsiyonel)
app/globals.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Özel animasyonlar */
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-4px);
}
}
.delay-100 {
animation-delay: 0.1s;
}
.delay-200 {
animation-delay: 0.2s;
}
/* Markdown stilleri */
.prose h1,
.prose h2,
.prose h3 {
@apply font-bold mb-2;
}
.prose p {
@apply mb-2;
}
.prose ul,
.prose ol {
@apply ml-4 mb-2;
}
.prose code {
@apply bg-gray-100 px-1 py-0.5 rounded text-sm;
}
.prose pre {
@apply bg-gray-100 p-3 rounded-lg overflow-x-auto;
}
9. Layout Düzenlemesi
app/layout.tsx:
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'AI Chatbot',
description: 'Next.js ve OpenAI ile geliştirilmiş chatbot',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="tr">
<body className={inter.className}>{children}</body>
</html>
);
}
10. Ek Özellikler (Opsiyonel)
Mesaj Geçmişini Kaydetme
hooks/useLocalStorage.ts:
import { useState, useEffect } from 'react';
export function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(initialValue);
useEffect(() => {
try {
const item = window.localStorage.getItem(key);
if (item) {
setStoredValue(JSON.parse(item));
}
} catch (error) {
console.error('LocalStorage error:', error);
}
}, [key]);
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error('LocalStorage error:', error);
}
};
return [storedValue, setValue] as const;
}
Kullanım
OpenAI API anahtarınızı .env.local dosyasına ekleyin
npm run dev komutu ile uygulamayı başlatın
http://localhost:3000 adresinden chatbot'u kullanın
Özellikler
✅ Modern ve responsive tasarım
✅ Gerçek zamanlı mesajlaşma
✅ Markdown desteği
✅ Yükleme animasyonları
✅ Hata yönetimi
✅ TypeScript ile tip güvenliği
✅ Tailwind CSS ile stil
Bu chatbot uygulaması tamamen çalışır durumda ve production'a hazırdır. İhtiyaçlarınıza göre özelleştirip geliştirebilirsiniz.