diff --git a/docs/chat-sessions.md b/docs/chat-sessions.md new file mode 100644 index 0000000..60170a5 --- /dev/null +++ b/docs/chat-sessions.md @@ -0,0 +1,43 @@ +# Chat Session Structure (AI Feature) + +## Design Pattern (like ChatGPT) +- **Sidebar**: List of past chat sessions with title + timestamp +- **Main area**: Active conversation + +## Data Structure +```typescript +interface ChatSession { + id: string; + title: string; // First question (truncated to ~30 chars) + messages: AIChat[]; + createdAt: number; + updatedAt: number; +} + +interface AIChat { + id: string; + role: "user" | "assistant"; + content: string; + timestamp: number; +} +``` + +## Storage +- `tia_chat_sessions`: Array of ChatSession in localStorage +- Current session ID: `tia_current_session` + +## UI Pattern +- **Left sidebar (w-64)**: List of sessions with title + date, delete option +- **Right main area**: Chat interface with message bubbles + +## User Flow +1. User opens /ai → loads most recent session or creates new +2. User asks question → adds to current session +3. First question becomes session title (truncated) +4. Can switch sessions via sidebar +5. Can start new conversation + +## Implementation Notes +- Use React state for current session +- Save to localStorage on each message +- Auto-title from first user message \ No newline at end of file diff --git a/src/app/ai/page.tsx b/src/app/ai/page.tsx index f080a8c..6cf01eb 100644 --- a/src/app/ai/page.tsx +++ b/src/app/ai/page.tsx @@ -1,105 +1,176 @@ "use client"; -import { useState, useRef, useEffect } from "react"; +import { useState, useEffect } from "react"; -interface Message { +interface AIChat { + id: string; role: "user" | "assistant"; content: string; + timestamp: number; } -export default function AIPage() { - const [messages, setMessages] = useState([ - { role: "assistant", content: "Hi! I'm Tia - your baby care assistant. Ask me anything about your baby's health, feeding, sleep, or development!" }, - ]); +interface ChatSession { + id: string; + title: string; + messages: AIChat[]; + createdAt: number; + updatedAt: number; +} + +const CHATS_KEY = "tia_chat_sessions"; + +function getSessions(): ChatSession[] { + if (typeof window === "undefined") return []; + try { + const data = localStorage.getItem(CHATS_KEY); + return data ? JSON.parse(data) : []; + } catch { return []; } +} + +function saveSessions(sessions: ChatSession[]) { + localStorage.setItem(CHATS_KEY, JSON.stringify(sessions)); +} + +function createNewSession(): ChatSession { + return { + id: crypto.randomUUID(), + title: "New conversation", + messages: [], + createdAt: Date.now(), + updatedAt: Date.now(), + }; +} + +export default function AIChatPage() { + const [sessions, setSessions] = useState([]); + const [currentSessionId, setCurrentSessionId] = useState(""); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); - const messagesEndRef = useRef(null); - - const childId = "5ad3b16a-1e0d-45ab-bc91-038397d75d0a"; + const [sidebarOpen, setSidebarOpen] = useState(true); useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [messages]); + const loaded = getSessions(); + if (loaded.length === 0 || !currentSessionId) { + const newS = createNewSession(); + loaded.unshift(newS); + saveSessions(loaded); + setCurrentSessionId(newS.id); + } + setSessions(loaded); + if (!currentSessionId && loaded.length > 0) { + setCurrentSessionId(loaded[0].id); + } + }, []); + + const currentSession = sessions.find(s => s.id === currentSessionId); const handleSend = async () => { - if (!input.trim() || loading) return; - - const userMessage = input.trim(); - setInput(""); - setMessages((prev) => [...prev, { role: "user", content: userMessage }]); + if (!input.trim() || loading || !currentSession) return; setLoading(true); - try { - const res = await fetch("/api/ai", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - messages: [...messages, { role: "user", content: userMessage }], - childId, - }), - }); - - const data = await res.json(); - setMessages((prev) => [...prev, { role: "assistant", content: data.reply || data.error }]); - } catch (err) { - setMessages((prev) => [...prev, { role: "assistant", content: "Sorry, something went wrong. Try again." }]); + const userMsg: AIChat = { id: crypto.randomUUID(), role: "user", content: input.trim(), timestamp: Date.now() }; + const updated: ChatSession = { ...currentSession, messages: [...currentSession.messages, userMsg], updatedAt: Date.now() }; + if (updated.messages.length === 1) { + updated.title = input.trim().slice(0, 40) + (input.trim().length > 40 ? "..." : ""); } + const newSessions = sessions.map(s => s.id === currentSessionId ? updated : s); + const inputVal = input.trim(); + setInput(""); + const tempId = currentSessionId; + + try { + const res = await fetch("/api/ai", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: [{ role: "user", content: inputVal }] }) }); + const data = await res.json(); + const assistantMsg: AIChat = { id: crypto.randomUUID(), role: "assistant", content: data.reply || "Sorry, I couldn't help with that.", timestamp: Date.now() }; + const final = { ...updated, messages: [...updated.messages, assistantMsg], updatedAt: Date.now() }; + const finalSessions = newSessions.map(s => s.id === tempId ? final : s); + saveSessions(finalSessions); + setSessions(finalSessions); + } catch { + const errMsg: AIChat = { id: crypto.randomUUID(), role: "assistant", content: "Something went wrong. Try again.", timestamp: Date.now() }; + const errS = { ...updated, messages: [...updated.messages, errMsg], updatedAt: Date.now() }; + const errSessions = newSessions.map(s => s.id === tempId ? errS : s); + saveSessions(errSessions); + setSessions(errSessions); + } setLoading(false); }; + const switchSession = (id: string) => setCurrentSessionId(id); + + const newChat = () => { + const ns = createNewSession(); + const upd = [ns, ...sessions]; + saveSessions(upd); + setSessions(upd); + setCurrentSessionId(ns.id); + }; + + const deleteSession = (id: string, e: React.MouseEvent) => { + e.stopPropagation(); + const filtered = sessions.filter(s => s.id !== id); + saveSessions(filtered); + setSessions(filtered); + if (id === currentSessionId && filtered.length > 0) setCurrentSessionId(filtered[0].id); + else if (filtered.length === 0) newChat(); + }; + + const formatDate = (ts: number) => { + const d = new Date(ts); + const now = new Date(); + return d.toDateString() === now.toDateString() ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString([], { month: "short", day: "numeric" }); + }; + return ( -
- {/* Header */} -
- -

🤖 Tia AI

+
+
+
+
+

Chats

+ +
+
+ {sessions.map(s => ( +
switchSession(s.id)} className={`p-3 border-b cursor-pointer hover:bg-gray-50 ${s.id === currentSessionId ? "bg-rose-50" : ""}`}> +
+
+
{s.title}
+
{formatDate(s.createdAt)}
+
+ +
+
+ ))} +
+
- {/* Messages */} -
- {messages.map((msg, i) => ( -
-
- {msg.content} -
-
- ))} - {loading && ( -
-
- Thinking... -
-
- )} -
-
+
+
+ + ← Home +
- {/* Input */} -
-
- setInput(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && handleSend()} - placeholder="Ask about your baby..." - className="flex-1 p-3 border rounded-full" - disabled={loading} - /> - +
+ {(!currentSession || currentSession.messages.length === 0) ? ( +
+

Start a conversation

+

Ask me anything about your baby!

+
+ ) : currentSession.messages.map(msg => ( +
+
{msg.content}
+
+ ))} + {loading &&
Thinking...
} +
+ +
+
+ setInput(e.target.value)} onKeyDown={e => e.key === "Enter" && handleSend()} placeholder="Ask anything..." className="flex-1 p-3 border rounded-full" disabled={loading} /> + +