diff --git a/app/(dashboard)/chat/page.tsx b/app/(dashboard)/chat/page.tsx index 36f4210..cc75779 100644 --- a/app/(dashboard)/chat/page.tsx +++ b/app/(dashboard)/chat/page.tsx @@ -1,176 +1,378 @@ "use client"; -import { useState } from "react"; -import { useLiveQuery } from "dexie-react-hooks"; -import { db } from "@/lib/db"; +import { useEffect, useState, useRef } from "react"; +import { Brain, Send, Paperclip, MoreHorizontal, Search, MessageSquare, Plus, FileText, CheckCircle2, Trash2 } from "lucide-react"; +import { createClient } from "@/lib/supabase/client"; import { v4 as uuidv4 } from "uuid"; -import { Send, Bot, User, Trash2 } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -export default function ChatPage() { +interface ChatSession { + id: string; + title: string; + created_at: string; +} + +interface ChatMessage { + id: string; + session_id: string; + role: "user" | "assistant" | "system"; + content: string; + created_at: string; +} + +export default function AIChatPage() { + const [supabase] = useState(() => createClient()); + const [sessions, setSessions] = useState([]); + const [activeSessionId, setActiveSessionId] = useState(null); + const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); + const messagesEndRef = useRef(null); - // Mesajları eski tarihten yeniye göre sıralayarak al (Sohbet akışı) - const messages = useLiveQuery( - () => db.chat_messages.orderBy("created_at").toArray(), - [], - ); + // Auto-scroll to bottom + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; - // Ollama'ya veya harici API'ye mesaj gönder - const handleSend = async (e: React.FormEvent) => { - e.preventDefault(); - if (!input.trim()) return; + useEffect(() => { + scrollToBottom(); + }, [messages]); + + // 1. Fetch Sessions on mount + useEffect(() => { + const fetchSessions = async () => { + const { data: { user } } = await supabase.auth.getUser(); + if (!user) return; + + const { data } = await supabase + .from("chat_sessions") + .select("*") + .eq("user_id", user.id) + .order("updated_at", { ascending: false }); + + if (data) { + setSessions(data); + if (data.length > 0) { + setActiveSessionId(data[0].id); + } + } + }; + fetchSessions(); + }, [supabase]); + + // 2. Fetch Messages when session changes + useEffect(() => { + if (!activeSessionId) return; + + const fetchMessages = async () => { + const { data } = await supabase + .from("chat_messages") + .select("*") + .eq("session_id", activeSessionId) + .order("created_at", { ascending: true }); + + if (data) { + setMessages(data as ChatMessage[]); + } + }; + fetchMessages(); + + // Subscribe to real-time changes + const channel = supabase + .channel(`chat:${activeSessionId}`) + .on('postgres_changes', { + event: 'INSERT', + schema: 'public', + table: 'chat_messages', + filter: `session_id=eq.${activeSessionId}` + }, (payload) => { + setMessages(prev => { + if (prev.find(m => m.id === payload.new.id)) return prev; + return [...prev, payload.new as ChatMessage]; + }); + }) + .subscribe(); + + return () => { + supabase.removeChannel(channel); + }; + }, [activeSessionId, supabase]); + + const handleNewChat = async () => { + const { data: { user } } = await supabase.auth.getUser(); + if (!user) return; + + const newSession = { + user_id: user.id, + title: "Yeni Sohbet", + updated_at: new Date().toISOString() + }; + + const { data, error } = await supabase + .from("chat_sessions") + .insert(newSession) + .select() + .single(); + + if (data) { + setSessions([data, ...sessions]); + setActiveSessionId(data.id); + setMessages([]); + } + }; + + const handleSend = async (e?: React.FormEvent) => { + if (e) e.preventDefault(); + if (!input.trim() || isLoading) return; const userMessageContent = input.trim(); setInput(""); + + // Ensure we have a session + let sessionId = activeSessionId; + const { data: { user } } = await supabase.auth.getUser(); + if (!user) return; + + if (!sessionId) { + const { data: newSess } = await supabase + .from("chat_sessions") + .insert({ user_id: user.id, title: userMessageContent.slice(0, 30) + "..." }) + .select() + .single(); + if (newSess) { + sessionId = newSess.id; + setActiveSessionId(sessionId); + setSessions([newSess, ...sessions]); + } else return; + } + setIsLoading(true); - const userMessage = { + // 1. Optimistic Update: Add User Message locally + const optimisticUserMsg: ChatMessage = { id: uuidv4(), - role: "user" as const, + session_id: sessionId, + role: "user", content: userMessageContent, - created_at: new Date().toISOString(), + created_at: new Date().toISOString() }; + setMessages(prev => [...prev, optimisticUserMsg]); + + // 2. Save User Message to Supabase + await supabase.from("chat_messages").insert({ + id: optimisticUserMsg.id, + session_id: sessionId, + role: "user", + content: userMessageContent + }); try { - await db.chat_messages.add(userMessage); + // 3. Fetch AI preferences for API call + const { data: settings } = await supabase + .from("user_settings") + .select("*") + .eq("user_id", user.id) + .single(); - // LocalStorage'dan veya Environment ayarlarını okuyup backend API'ye gönderiyoruz - const provider = localStorage.getItem("mindspace_ai_provider") || "groq"; - const apiKey = - localStorage.getItem("mindspace_api_key") || - process.env.NEXT_PUBLIC_GROQ_API_KEY || - ""; + const provider = settings?.ai_model || localStorage.getItem("mindspace_ai_provider") || "gemini"; + const apiKey = settings?.api_key || localStorage.getItem("mindspace_api_key") || ""; + // 4. Call AI API const res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - provider, - apiKey, - userMessageContent, - }), + body: JSON.stringify({ provider, apiKey, userMessageContent }), }); - if (!res.ok) { - const errData = await res.json(); - throw new Error(errData.error || "Sunucu hatası oluştu."); - } - + if (!res.ok) throw new Error("AI API Error"); const data = await res.json(); - const assistantMessage = { + // 5. Optimistic Update: Add Assistant Message locally + const assistantMsg: ChatMessage = { id: uuidv4(), - role: "assistant" as const, - content: - data.reply || - "Şu an cevap veremiyorum, lütfen ayarlarınızı kontrol edin.", - created_at: new Date().toISOString(), + session_id: sessionId, + role: "assistant", + content: data.reply || "Cevap üretilemedi.", + created_at: new Date().toISOString() }; + setMessages(prev => [...prev, assistantMsg]); + + // 6. Save Assistant Message to Supabase + await supabase.from("chat_messages").insert({ + id: assistantMsg.id, + session_id: sessionId, + role: "assistant", + content: assistantMsg.content + }); + + // 7. Update session title if it was default + const currentSession = sessions.find(s => s.id === sessionId); + if (currentSession?.title === "Yeni Sohbet") { + const newTitle = userMessageContent.slice(0, 40); + await supabase.from("chat_sessions").update({ title: newTitle }).eq("id", sessionId); + setSessions(prev => prev.map(s => s.id === sessionId ? { ...s, title: newTitle } : s)); + } - await db.chat_messages.add(assistantMessage); } catch (error) { console.error(error); - await db.chat_messages.add({ + const errorMsg: ChatMessage = { id: uuidv4(), - role: "assistant" as const, - content: - "API Bağlantısı kurulamadı. Lütfen internetini veya Ayarlar sayfasından API anahtarını kontrol et.", - created_at: new Date().toISOString(), + session_id: sessionId, + role: "assistant", + content: "Hata oluştu. Lütfen Ayarlar sayfasından API anahtarınızı kontrol edin.", + created_at: new Date().toISOString() + }; + setMessages(prev => [...prev, errorMsg]); + await supabase.from("chat_messages").insert({ + id: errorMsg.id, + session_id: sessionId, + role: "assistant", + content: errorMsg.content }); } finally { setIsLoading(false); } }; - const handleClearChat = async () => { - await db.chat_messages.clear(); + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }; + + const deleteSession = async (id: string, e: React.MouseEvent) => { + e.stopPropagation(); + await supabase.from("chat_sessions").delete().eq("id", id); + setSessions(prev => prev.filter(s => s.id !== id)); + if (activeSessionId === id) { + setActiveSessionId(null); + setMessages([]); + } }; return ( -
-
-
-

- AI Terapist (Sohbet) -

-

- Ollama veya bulut destekli akıllı asistanınızla konuşun. -

+
+ + {/* Top Header */} +
+

+ + MindSpace / AI Assistant +

+
+
-
-
- {(!messages || messages.length === 0) && ( -
- -

Sohbet henüz başlamadı. İlk mesajınızı yollayın!

+ {/* Main Chat Interface */} +
+ + {/* Left Sidebar: Chat Sessions */} +
+
+
+ + +
- )} - - {messages?.map((m) => ( -
-
-
+
Recent Conversations
+ {sessions.map((session) => ( +
setActiveSessionId(session.id)} + className={`group flex items-center gap-3 p-3 rounded-sm cursor-pointer transition-colors ${activeSessionId === session.id ? 'bg-[#1F172B] border border-primary/30 text-primary' : 'hover:bg-white/5 text-muted-foreground hover:text-foreground border border-transparent'}`} > - {m.role === "user" ? ( - - ) : ( - + +
+
{session.title}
+
{new Date(session.created_at).toLocaleDateString()}
+
+ +
+ ))} +
+
+ + {/* Right Side: Active Chat Area */} +
+ + {/* Chat Messages Area */} +
+ {messages.length === 0 && !isLoading && ( +
+ +

Hello! I am your MindSpace AI Assistant.

+

Start a conversation or select a session from history.

+
+ )} + + {messages.map((m) => ( +
+ {m.role === "assistant" && ( +
+ +
+ )} +
+

{m.content}

+
+ {m.role === "user" && ( +
ME
)}
-
- {m.content} + ))} + {isLoading && ( +
+ + + + + MindSpace AI is thinking...
-
+ )} +
- ))} - {isLoading && ( -
-
-
- -
-
-
-
-
-
-
-
- )} -
-
- setInput(e.target.value)} - placeholder="Bugün nasıl hissediyorsun? Ya da eski günlüklere dayanarak bir şeyler sor..." - className="flex-1" - /> - -
+ {/* Chat Input Box */} +
+
+