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() {
@@ -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} />