Add AI quick chat to home page

This commit is contained in:
Manohar Gupta 2026-05-10 12:17:46 +05:30
parent 5cf0303999
commit a599116118

View file

@ -152,17 +152,16 @@ function LogModal({ type, childId, onClose }: LogModalProps) {
); );
} }
// Helper to calculate age
function calculateAge(birthDate: string) { function calculateAge(birthDate: string) {
const birth = new Date(birthDate); const birth = new Date(birthDate);
const now = new Date(); const now = new Date();
const years = now.getFullYear() - birth.getFullYear();
const months = now.getMonth() - birth.getMonth();
const days = Math.floor((now.getTime() - birth.getTime()) / (1000 * 60 * 60 * 24)); const days = Math.floor((now.getTime() - birth.getTime()) / (1000 * 60 * 60 * 24));
const months = Math.floor(days / 30);
const years = Math.floor(months / 12);
if (years > 0) return `${years} year${years > 1 ? "s" : ""} old`; if (years > 0) return `${years} year${years > 1 ? "s" : ""} old`;
if (months > 0) return `${months} month${months > 1 ? "s" : ""} old`; if (months > 0) return `${months} month${months > 1 ? "s" : ""} old`;
return `${days} days old`; return `${days} day${days > 1 ? "s" : ""} old`;
} }
function getGreeting() { function getGreeting() {
@ -174,6 +173,10 @@ function getGreeting() {
export default function HomePage() { export default function HomePage() {
const [modalType, setModalType] = useState<"feed" | "diaper" | "sleep" | null>(null); const [modalType, setModalType] = useState<"feed" | "diaper" | "sleep" | null>(null);
const [aiOpen, setAiOpen] = useState(false);
const [aiInput, setAiInput] = useState("");
const [aiReply, setAiReply] = useState("");
const [aiLoading, setAiLoading] = useState(false);
const [childId] = useState("5ad3b16a-1e0d-45ab-bc91-038397d75d0a"); const [childId] = useState("5ad3b16a-1e0d-45ab-bc91-038397d75d0a");
const [pendingCount, setPendingCount] = useState(0); const [pendingCount, setPendingCount] = useState(0);
const [lastLogs, setLastLogs] = useState<any[]>([]); const [lastLogs, setLastLogs] = useState<any[]>([]);
@ -212,23 +215,35 @@ export default function HomePage() {
}, []); }, []);
useEffect(() => { useEffect(() => {
// Fetch recent logs
Promise.all([ Promise.all([
fetch(`/api/logs?type=feed&childId=${childId}&limit=1`).then(r => r.json()), 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=sleep&childId=${childId}&limit=1`).then(r => r.json()),
fetch(`/api/logs?type=diaper&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]) => { ]).then(([feed, sleep, diaper]) => {
setLastLogs([ setLastLogs([feed.entries?.[0], sleep.entries?.[0], diaper.entries?.[0]].filter(Boolean));
feed.entries?.[0],
sleep.entries?.[0],
diaper.entries?.[0],
].filter(Boolean));
}).catch(() => {}); }).catch(() => {});
}, [childId]); }, [childId]);
const handleAiChat = async () => {
if (!aiInput.trim() || aiLoading) return;
setAiLoading(true);
try {
const res = await fetch("/api/ai", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages: [{ role: "user", content: aiInput }] }),
});
const data = await res.json();
setAiReply(data.reply || "Sorry, I couldn't help with that.");
} catch {
setAiReply("Something went wrong. Try again.");
}
setAiLoading(false);
setAiInput("");
};
return ( return (
<div className="min-h-screen bg-gradient-to-br from-rose-50 to-amber-50 dark:from-gray-900 dark:to-gray-800"> <div className="min-h-screen bg-gradient-to-br from-rose-50 to-amber-50 dark:from-gray-900 dark:to-gray-800">
{/* Header */}
<div className="p-4 flex justify-between items-center"> <div className="p-4 flex justify-between items-center">
<button className="p-2"> <button className="p-2">
<Link href="/menu"> <Link href="/menu">
@ -238,30 +253,18 @@ export default function HomePage() {
</Link> </Link>
</button> </button>
<button onClick={toggleDarkMode} className="p-2"> <button onClick={toggleDarkMode} className="p-2">
{darkMode ? ( {darkMode ? "☀️" : "🌙"}
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
) : (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.75 9.75 0 0012 21.75 9.75 9.75 0 0015.75 8.646 9 9 0 0020.354 15.354z" />
</svg>
)}
</button> </button>
</div> </div>
{/* Welcome */}
<div className="px-6 pb-4"> <div className="px-6 pb-4">
<h1 className="text-2xl font-bold">{getGreeting()} 👋</h1> <h1 className="text-2xl font-bold">{getGreeting()} 👋</h1>
<p className="text-gray-600">How is {child.name} doing today?</p> <p className="text-gray-600">How is {child.name} doing today?</p>
</div> </div>
{/* Age Card */}
<div className="mx-4 mb-4 p-4 bg-white rounded-2xl shadow-md"> <div className="mx-4 mb-4 p-4 bg-white rounded-2xl shadow-md">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-16 h-16 bg-rose-100 rounded-full flex items-center justify-center text-2xl"> <div className="w-16 h-16 bg-rose-100 rounded-full flex items-center justify-center text-2xl">👶</div>
👶
</div>
<div> <div>
<div className="text-lg font-semibold">{child.name}</div> <div className="text-lg font-semibold">{child.name}</div>
<div className="text-gray-500">{calculateAge(child.birthDate)}</div> <div className="text-gray-500">{calculateAge(child.birthDate)}</div>
@ -269,14 +272,12 @@ export default function HomePage() {
</div> </div>
</div> </div>
{/* Pending Offline */}
{pendingCount > 0 && ( {pendingCount > 0 && (
<div className="mx-4 mb-4 bg-amber-100 text-amber-800 px-4 py-2 rounded-xl text-center"> <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" : ""} (will sync when online) {pendingCount} pending log{pendingCount > 1 ? "s" : ""} (will sync when online)
</div> </div>
)} )}
{/* Quick Actions */}
<div className="px-4 mb-4"> <div className="px-4 mb-4">
<h2 className="font-semibold mb-3 ml-1">Quick Log</h2> <h2 className="font-semibold mb-3 ml-1">Quick Log</h2>
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
@ -292,14 +293,13 @@ export default function HomePage() {
<span className="text-2xl">👶</span> <span className="text-2xl">👶</span>
<span className="text-xs mt-1">Diaper</span> <span className="text-xs mt-1">Diaper</span>
</button> </button>
<Link href="/medical" className="flex flex-col items-center p-3 bg-white rounded-xl shadow-sm"> <button onClick={() => setAiOpen(true)} className="flex flex-col items-center p-3 bg-white rounded-xl shadow-sm">
<span className="text-2xl">💊</span> <span className="text-2xl">🤖</span>
<span className="text-xs mt-1">Medical</span> <span className="text-xs mt-1">AI</span>
</Link> </button>
</div> </div>
</div> </div>
{/* Recent Activity */}
<div className="px-4"> <div className="px-4">
<h2 className="font-semibold mb-3 ml-1">Recent Activity</h2> <h2 className="font-semibold mb-3 ml-1">Recent Activity</h2>
<div className="space-y-2"> <div className="space-y-2">
@ -329,6 +329,44 @@ export default function HomePage() {
</div> </div>
<LogModal type={modalType} childId={childId} onClose={() => setModalType(null)} /> <LogModal type={modalType} childId={childId} onClose={() => setModalType(null)} />
{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 max-h-[80vh] flex flex-col" onClick={e => e.stopPropagation()}>
<div className="flex justify-between items-center mb-3">
<h2 className="font-bold">🤖 Ask Tia AI</h2>
<button onClick={() => setAiOpen(false)} className="text-gray-500"></button>
</div>
<div className="flex-1 overflow-y-auto mb-3 min-h-[200px]">
{aiReply && (
<div className="bg-rose-50 dark:bg-gray-700 p-3 rounded-xl text-sm">
{aiReply}
</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 quick 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 text-sm"
>
{aiLoading ? "..." : "Send"}
</button>
</div>
</div>
</div>
)}
</div> </div>
); );
} }