From e7411ee31f7896c6f444399e6234bbbc072504bc Mon Sep 17 00:00:00 2001 From: Mannu Date: Sun, 10 May 2026 13:13:17 +0530 Subject: [PATCH] Unify homepage AI card with session system - Uses tia_chat_sessions localStorage (shared with /ai page) - First question becomes session title - All chats now saved for future reference in /ai Co-Authored-By: Claude Opus 4.7 --- src/app/page.tsx | 182 +++++++++++++++++++++-------------------------- 1 file changed, 81 insertions(+), 101 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 71a0926..21877aa 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,7 +4,22 @@ import { useState, useEffect } from "react"; import Link from "next/link"; const OFFLINE_QUEUE_KEY = "tia_offline_queue"; -const AI_CHAT_KEY = "tia_ai_chats"; +const CHATS_KEY = "tia_chat_sessions"; + +interface AIChat { + id: string; + role: "user" | "assistant"; + content: string; + timestamp: number; +} + +interface ChatSession { + id: string; + title: string; + messages: AIChat[]; + createdAt: number; + updatedAt: number; +} export interface OfflineEntry { id: string; @@ -13,13 +28,6 @@ export interface OfflineEntry { timestamp: number; } -export interface AIChat { - id: string; - role: "user" | "assistant"; - content: string; - timestamp: number; -} - export function getOfflineQueue(): OfflineEntry[] { if (typeof window === "undefined") return []; try { @@ -46,30 +54,28 @@ export async function processOfflineQueue() { localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(failed)); } -function getAIChats(): AIChat[] { +// --- AI Session Functions (shared with /ai page) --- +function getSessions(): ChatSession[] { if (typeof window === "undefined") return []; try { - const data = localStorage.getItem(AI_CHAT_KEY); + const data = localStorage.getItem(CHATS_KEY); return data ? JSON.parse(data) : []; } catch { return []; } } -function saveAIChats(chats: AIChat[]) { - localStorage.setItem(AI_CHAT_KEY, JSON.stringify(chats)); +function saveSessions(sessions: ChatSession[]) { + localStorage.setItem(CHATS_KEY, JSON.stringify(sessions)); } -interface LogModalProps { - type: "feed" | "diaper" | "sleep" | null; - childId: string; - onClose: () => void; +function createNewSession(): ChatSession { + return { id: crypto.randomUUID(), title: "New conversation", messages: [], createdAt: Date.now(), updatedAt: Date.now() }; } -function LogModal({ type, childId, onClose }: LogModalProps) { +function LogModal({ type, childId, onClose }: { type: "feed" | "diaper" | "sleep" | null; childId: string; onClose: () => void }) { const [loading, setLoading] = useState(false); const [subType, setSubType] = useState("breast_milk"); const [amountMl, setAmountMl] = useState(""); const [notes, setNotes] = useState(""); - if (!type) return null; const handleSubmit = async () => { @@ -77,7 +83,7 @@ function LogModal({ type, childId, onClose }: LogModalProps) { const data = { type, childId, subType, amountMl: amountMl ? Number(amountMl) : undefined, notes: notes || undefined }; try { const res = await fetch("/api/logs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }); - if (!res.ok && !navigator.onLine) { addToOfflineQueue({ type: type as any, data }); } + if (!res.ok && !navigator.onLine) addToOfflineQueue({ type: type as any, data }); onClose(); } catch { addToOfflineQueue({ type: type as any, data }); onClose(); } setLoading(false); @@ -86,33 +92,12 @@ function LogModal({ type, childId, onClose }: LogModalProps) { return (
-

- {type === "feed" && "Log Feed"}{type === "diaper" && "Log Diaper"}{type === "sleep" && "Log Sleep"} -

- {type === "feed" && ( - <> - - setAmountMl(e.target.value)} className="w-full p-3 border rounded-xl mb-3" /> - - )} - {type === "diaper" && ( - - )} - {type === "sleep" && ( - - )} - setNotes(e.target.value)} className="w-full p-3 border rounded-xl mb-4" /> -
- - -
+

{type === "feed" && "Log Feed"}{type === "diaper" && "Log Diaper"}{type === "sleep" && "Log Sleep"}

+ {type === "feed" && (<> setAmountMl(e.target.value)} className="w-full p-3 border rounded-xl mb-3" />)} + {type === "diaper" && ()} + {type === "sleep" && ()} + setNotes(e.target.value)} className="w-full p-3 border rounded-xl mb-4" /> +
); @@ -136,14 +121,7 @@ function getGreeting() { return "Good evening"; } -const QUICK_QUESTIONS = [ - "How much should my baby eat?", - "When should baby sleep?", - "Is fever normal?", - "How to increase milk supply?", - "Baby won't sleep", - "Starting solids?", -]; +const QUICK_QUESTIONS = ["How much should my baby eat?", "When should baby sleep?", "Is fever normal?", "How to increase milk supply?", "Baby won't sleep", "Starting solids?"]; export default function HomePage() { const [modalType, setModalType] = useState<"feed" | "diaper" | "sleep" | null>(null); @@ -163,8 +141,12 @@ export default function HomePage() { if (saved === "dark") { setDarkMode(true); document.documentElement.classList.add("dark"); } }, []); + // Load latest session on mount useEffect(() => { - setAiChats(getAIChats()); + const sessions = getSessions(); + if (sessions.length > 0) { + setAiChats(sessions[0].messages); + } }, []); useEffect(() => { @@ -172,7 +154,7 @@ export default function HomePage() { setPendingCount(queue.length); const handleOnline = () => processOfflineQueue(); window.addEventListener("online", handleOnline); - if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/sw.js").catch(console.error); } + if ("serviceWorker" in navigator) navigator.serviceWorker.register("/sw.js").catch(console.error); return () => window.removeEventListener("online", handleOnline); }, []); @@ -181,40 +163,57 @@ export default function HomePage() { fetch(`/api/logs?type=feed&childId=${childId}&limit=1`).then(r => r.json()), fetch(`/api/logs?type=sleep&childId=${childId}&limit=1`).then(r => r.json()), fetch(`/api/logs?type=diaper&childId=${childId}&limit=1`).then(r => r.json()), - ]).then(([feed, sleep, diaper]) => { - setLastLogs([feed.entries?.[0], sleep.entries?.[0], diaper.entries?.[0]].filter(Boolean)); - }).catch(() => {}); + ]).then(([feed, sleep, diaper]) => setLastLogs([feed.entries?.[0], sleep.entries?.[0], diaper.entries?.[0]].filter(Boolean))); }, [childId]); const toggleDarkMode = () => { const next = !darkMode; setDarkMode(next); localStorage.setItem("tia_theme", next ? "dark" : "light"); - if (next) { document.documentElement.classList.add("dark"); } - else { document.documentElement.classList.remove("dark"); } + next ? document.documentElement.classList.add("dark") : document.documentElement.classList.remove("dark"); }; + // Unified AI chat that saves to sessions const handleAiChat = async (question?: string) => { const q = question || aiInput; if (!q.trim() || aiLoading) return; setAiLoading(true); setAiOpen(true); - const newChats: AIChat[] = [...aiChats, { id: crypto.randomUUID(), role: "user", content: q, timestamp: Date.now() }]; - setAiChats(newChats); - setAiInput(""); + const sessions = getSessions(); + let currentSession = sessions[0]; + + // Create new session if none exists + if (!currentSession) { + currentSession = createNewSession(); + sessions.unshift(currentSession); + } + + const userMsg: AIChat = { id: crypto.randomUUID(), role: "user", content: q, timestamp: Date.now() }; + const updatedSession: ChatSession = { + ...currentSession, + messages: [...currentSession.messages, userMsg], + title: currentSession.messages.length === 0 ? q.slice(0, 40) + (q.length > 40 ? "..." : "") : currentSession.title, + updatedAt: Date.now() + }; + + const inputVal = q.trim(); + if (!question) setAiInput(""); try { - const res = await fetch("/api/ai", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: [{ role: "user", content: q }] }) }); + 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 reply = data.reply || "Sorry, I couldn't help with that."; - const updatedChats: AIChat[] = [...newChats, { id: crypto.randomUUID(), role: "assistant", content: reply, timestamp: Date.now() }]; - setAiChats(updatedChats); - saveAIChats(updatedChats); + const assistantMsg: AIChat = { id: crypto.randomUUID(), role: "assistant", content: data.reply || "Sorry, I couldn't help with that.", timestamp: Date.now() }; + const finalSession = { ...updatedSession, messages: [...updatedSession.messages, assistantMsg], updatedAt: Date.now() }; + const finalSessions = [finalSession, ...sessions.filter(s => s.id !== currentSession!.id)]; + saveSessions(finalSessions); + setAiChats(finalSession.messages); } catch { - const errorChats: AIChat[] = [...newChats, { id: crypto.randomUUID(), role: "assistant", content: "Something went wrong. Try again.", timestamp: Date.now() }]; - setAiChats(errorChats); - saveAIChats(errorChats); + const errMsg: AIChat = { id: crypto.randomUUID(), role: "assistant", content: "Something went wrong. Try again.", timestamp: Date.now() }; + const errSession = { ...updatedSession, messages: [...updatedSession.messages, errMsg], updatedAt: Date.now() }; + const errSessions = [errSession, ...sessions.filter(s => s.id !== currentSession!.id)]; + saveSessions(errSessions); + setAiChats(errSession.messages); } setAiLoading(false); }; @@ -243,22 +242,13 @@ export default function HomePage() {

Quick Log

- - - - - 💊Medical - + + + + 💊Medical
- {/* AI Chat Card */}

Ask AI

@@ -266,13 +256,11 @@ export default function HomePage() {
- setAiInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleAiChat()} placeholder="Ask anything..." className="flex-1 p-2 border rounded-xl text-sm" disabled={aiLoading} /> + setAiInput(e.target.value)} onKeyDown={e => e.key === "Enter" && handleAiChat()} placeholder="Ask anything..." className="flex-1 p-2 border rounded-xl text-sm" disabled={aiLoading} />
- {QUICK_QUESTIONS.map((q, i) => ( - - ))} + {QUICK_QUESTIONS.map((q, i) => )}
@@ -294,24 +282,16 @@ export default function HomePage() { setModalType(null)} /> - {/* AI Chat Popup */} - {aiOpen && ( + {aiOpen && aiChats.length > 0 && (
setAiOpen(false)}>
e.stopPropagation()}> -
-

Ask AI

- -
+

Ask AI

- {aiChats.length === 0 ?

Ask a question to start chatting

: aiChats.map((chat) => ( -
- {chat.content} -
- ))} + {aiChats.map((chat) => (
{chat.content}
))} {aiLoading &&
Thinking...
}
- setAiInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleAiChat()} placeholder="Ask a question..." className="flex-1 p-2 border rounded-xl text-sm" disabled={aiLoading} /> + setAiInput(e.target.value)} onKeyDown={e => e.key === "Enter" && handleAiChat()} placeholder="Ask a question..." className="flex-1 p-2 border rounded-xl text-sm" disabled={aiLoading} />