diff --git a/src/app/ai/page.tsx b/src/app/ai/page.tsx new file mode 100644 index 0000000..f080a8c --- /dev/null +++ b/src/app/ai/page.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useState, useRef, useEffect } from "react"; + +interface Message { + role: "user" | "assistant"; + content: string; +} + +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!" }, + ]); + const [input, setInput] = useState(""); + const [loading, setLoading] = useState(false); + const messagesEndRef = useRef(null); + + const childId = "5ad3b16a-1e0d-45ab-bc91-038397d75d0a"; + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages]); + + const handleSend = async () => { + if (!input.trim() || loading) return; + + const userMessage = input.trim(); + setInput(""); + setMessages((prev) => [...prev, { role: "user", content: userMessage }]); + 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." }]); + } + + setLoading(false); + }; + + return ( +
+ {/* Header */} +
+ +

🤖 Tia AI

+
+ + {/* Messages */} +
+ {messages.map((msg, i) => ( +
+
+ {msg.content} +
+
+ ))} + {loading && ( +
+
+ Thinking... +
+
+ )} +
+
+ + {/* 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} + /> + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/api/ai/route.ts b/src/app/api/ai/route.ts new file mode 100644 index 0000000..ec399c4 --- /dev/null +++ b/src/app/api/ai/route.ts @@ -0,0 +1,82 @@ +import { NextResponse } from "next/server"; +import { sql } from "@/db"; + +interface Message { + role: "user" | "assistant"; + content: string; +} + +const LITELLM_BASE_URL = process.env.OPENAI_API_BASE_URL || "http://litellm-gateway:4000/v1"; +const LITELLM_API_KEY = process.env.LITELLM_MASTER_KEY || "sk-tiger-gateway-289bf7d1cf0c0b12ff5ccf48d95ff3c3"; + +export async function POST(request: Request) { + try { + const body = await request.json(); + const { messages, childId } = body; + + if (!messages || !Array.isArray(messages)) { + return NextResponse.json({ error: "messages array required" }, { status: 400 }); + } + + // Get child's profile context + let context = ""; + if (childId) { + const children = await sql.unsafe( + `SELECT name, birth_date, sex FROM children WHERE id = $1`, + [childId] + ); + if (children.length > 0) { + const child = children[0]; + const age = calculateAge(child.birth_date); + context = `The child's name is ${child.name}, they are ${age} old. `; + } + } + + // Build messages with system prompt + const systemMessage = { + role: "system", + content: `You are Tia, a helpful baby care assistant. ${context} Give caring, practical advice for new parents. Keep responses brief and helpful.`, + }; + + const allMessages = [systemMessage, ...messages]; + + // Call LiteLLM + const response = await fetch(`${LITELLM_BASE_URL}/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${LITELLM_API_KEY}`, + }, + body: JSON.stringify({ + model: "gpt-4o-mini", + messages: allMessages, + max_tokens: 500, + }), + }); + + if (!response.ok) { + const error = await response.text(); + return NextResponse.json({ error: error }, { status: response.status }); + } + + const data = await response.json(); + const reply = data.choices?.[0]?.message?.content || "Sorry, I couldn't get a response."; + + return NextResponse.json({ reply }); + } catch (error) { + console.error(error); + return NextResponse.json({ error: String(error) }, { status: 500 }); + } +} + +function calculateAge(birthDate: string) { + const birth = new Date(birthDate); + const now = new Date(); + 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 (months > 0) return `${months} month${months > 1 ? "s" : ""} old`; + return `${days} day${days > 1 ? "s" : ""} old`; +} \ No newline at end of file