Improve AI card - chat popup, saved chats, quick questions below

This commit is contained in:
Manohar Gupta 2026-05-10 12:34:41 +05:30
parent 415e92ada7
commit c1057830b1

View file

@ -4,19 +4,19 @@ import { useState, useEffect } from "react";
import Link from "next/link";
const OFFLINE_QUEUE_KEY = "tia_offline_queue";
const AI_CHAT_KEY = "tia_ai_chats";
export interface OfflineEntry {
id: string;
type: "feed" | "diaper" | "sleep";
data: {
type: "feed" | "diaper" | "sleep";
childId: string;
subType: string;
amountMl?: number;
notes?: string;
startedAt?: string;
endedAt?: string;
};
data: any;
timestamp: number;
}
export interface AIChat {
id: string;
role: "user" | "assistant";
content: string;
timestamp: number;
}
@ -25,9 +25,7 @@ export function getOfflineQueue(): OfflineEntry[] {
try {
const data = localStorage.getItem(OFFLINE_QUEUE_KEY);
return data ? JSON.parse(data) : [];
} catch {
return [];
}
} catch { return []; }
}
export function addToOfflineQueue(entry: Omit<OfflineEntry, "id" | "timestamp">) {
@ -48,6 +46,18 @@ export async function processOfflineQueue() {
localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(failed));
}
function getAIChats(): AIChat[] {
if (typeof window === "undefined") return [];
try {
const data = localStorage.getItem(AI_CHAT_KEY);
return data ? JSON.parse(data) : [];
} catch { return []; }
}
function saveAIChats(chats: AIChat[]) {
localStorage.setItem(AI_CHAT_KEY, JSON.stringify(chats));
}
interface LogModalProps {
type: "feed" | "diaper" | "sleep" | null;
childId: string;
@ -77,9 +87,7 @@ function LogModal({ type, childId, onClose }: LogModalProps) {
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white rounded-2xl p-6 w-full max-w-sm mx-4">
<h2 className="text-xl font-bold mb-4">
{type === "feed" && "Log Feed"}
{type === "diaper" && "Log Diaper"}
{type === "sleep" && "Log Sleep"}
{type === "feed" && "Log Feed"}{type === "diaper" && "Log Diaper"}{type === "sleep" && "Log Sleep"}
</h2>
{type === "feed" && (
<>
@ -128,13 +136,12 @@ function getGreeting() {
return "Good evening";
}
// Quick questions for mama
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 😴",
"Baby won't sleep",
"Starting solids?",
];
@ -142,7 +149,7 @@ export default function HomePage() {
const [modalType, setModalType] = useState<"feed" | "diaper" | "sleep" | null>(null);
const [aiOpen, setAiOpen] = useState(false);
const [aiInput, setAiInput] = useState("");
const [aiReply, setAiReply] = useState("");
const [aiChats, setAiChats] = useState<AIChat[]>([]);
const [aiLoading, setAiLoading] = useState(false);
const [childId] = useState("5ad3b16a-1e0d-45ab-bc91-038397d75d0a");
const [pendingCount, setPendingCount] = useState(0);
@ -156,13 +163,9 @@ export default function HomePage() {
if (saved === "dark") { setDarkMode(true); document.documentElement.classList.add("dark"); }
}, []);
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"); }
};
useEffect(() => {
setAiChats(getAIChats());
}, []);
useEffect(() => {
const queue = getOfflineQueue();
@ -183,17 +186,37 @@ export default function HomePage() {
}).catch(() => {});
}, [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"); }
};
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("");
try {
const res = await fetch("/api/ai", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: [{ role: "user", content: q }] }) });
const data = await res.json();
setAiReply(data.reply || "Sorry, I couldn't help with that.");
} catch { setAiReply("Something went wrong. Try again."); }
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);
} catch {
const errorChats: AIChat[] = [...newChats, { id: crypto.randomUUID(), role: "assistant", content: "Something went wrong. Try again.", timestamp: Date.now() }];
setAiChats(errorChats);
saveAIChats(errorChats);
}
setAiLoading(false);
if (!question) setAiInput("");
};
return (
@ -217,7 +240,6 @@ export default function HomePage() {
{pendingCount > 0 && <div className="mx-4 mb-4 bg-amber-100 text-amber-800 px-4 py-2 rounded-xl text-center">{pendingCount} pending log{pendingCount > 1 ? "s" : ""}</div>}
{/* Quick Log - Keep as is */}
<div className="px-4 mb-4">
<h2 className="font-semibold mb-3 ml-1">Quick Log</h2>
<div className="grid grid-cols-4 gap-2">
@ -238,24 +260,23 @@ export default function HomePage() {
{/* AI Chat Card */}
<div className="px-4">
<h2 className="font-semibold mb-3 ml-1">🤖 Ask Tia AI</h2>
<div className="flex justify-between items-center mb-3">
<h2 className="font-semibold ml-1">Ask AI</h2>
<Link href="/ai" className="text-rose-500 text-sm">View all chats </Link>
</div>
<div className="p-4 bg-white rounded-2xl shadow-md">
<p className="text-sm text-gray-600 mb-3">Quick questions:</p>
<div className="flex flex-wrap gap-2 mb-3">
{QUICK_QUESTIONS.map((q, i) => (
<button key={i} onClick={() => { setAiOpen(true); handleAiChat(q); }} className="px-3 py-1 bg-rose-50 text-rose-600 rounded-full text-sm">
{q}
</button>
))}
</div>
<div className="flex gap-2">
<div className="flex gap-2 mb-3">
<input type="text" value={aiInput} onChange={(e) => 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} />
<button onClick={() => { setAiOpen(true); handleAiChat(); }} disabled={aiLoading || !aiInput.trim()} className="px-4 bg-rose-400 text-white rounded-xl text-sm">{aiLoading ? "..." : "Ask"}</button>
<button onClick={() => handleAiChat()} disabled={aiLoading || !aiInput.trim()} className="px-4 bg-rose-400 text-white rounded-xl text-sm">{aiLoading ? "..." : "Ask"}</button>
</div>
<div className="flex flex-wrap gap-2">
{QUICK_QUESTIONS.map((q, i) => (
<button key={i} onClick={() => handleAiChat(q)} className="px-3 py-1 bg-rose-50 text-rose-600 rounded-full text-sm">{q}</button>
))}
</div>
</div>
</div>
{/* Recent Activity */}
<div className="px-4 mt-4">
<h2 className="font-semibold mb-3 ml-1">Recent Activity</h2>
<div className="space-y-2">
@ -273,11 +294,26 @@ export default function HomePage() {
<LogModal type={modalType} childId={childId} onClose={() => setModalType(null)} />
{aiOpen && aiReply && (
{/* AI Chat Popup */}
{aiOpen && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={() => setAiOpen(false)}>
<div className="bg-white dark:bg-gray-800 rounded-2xl p-4 w-full max-w-sm mx-4" onClick={e => e.stopPropagation()}>
<div className="flex justify-between items-center mb-3"><h2 className="font-bold">🤖 Tia says:</h2><button onClick={() => setAiOpen(false)}></button></div>
<div className="text-sm">{aiReply}</div>
<div className="bg-white dark:bg-gray-800 rounded-2xl p-4 w-full max-w-sm mx-4 max-h-[80vh] flex flex-col" onClick={e => e.stopPropagation()}>
<div className="flex justify-between items-center mb-3">
<h2 className="font-bold">Ask AI</h2>
<button onClick={() => setAiOpen(false)}></button>
</div>
<div className="flex-1 overflow-y-auto space-y-3 mb-3 min-h-[250px]">
{aiChats.length === 0 ? <p className="text-gray-400 text-sm text-center mt-10">Ask a question to start chatting</p> : aiChats.map((chat) => (
<div key={chat.id} className={`max-w-[85%] p-3 rounded-xl text-sm ${chat.role === "user" ? "ml-auto bg-rose-400 text-white" : "bg-rose-50 dark:bg-gray-700"}`}>
{chat.content}
</div>
))}
{aiLoading && <div className="bg-rose-50 dark:bg-gray-700 p-3 rounded-xl text-sm animate-pulse">Thinking...</div>}
</div>
<div className="flex gap-2">
<input type="text" value={aiInput} onChange={(e) => 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} />
<button onClick={() => handleAiChat()} disabled={aiLoading || !aiInput.trim()} className="px-3 bg-rose-400 text-white rounded-xl"></button>
</div>
</div>
</div>
)}