import express from "express"; import path from "path"; import { createServer as createViteServer } from "vite"; import { GoogleGenAI } from "@google/genai"; import { defaultMLProvider } from "./src/lib/mlProvider"; import { Pet } from "./src/types"; import { authMiddleware } from "./src/auth/middleware"; import authRouter from "./src/auth/routes"; async function startServer() { const app = express(); const PORT = 3000; app.use(express.json({ limit: "10mb" })); // Auth middleware — protects /api/* except register, login, health app.use(authMiddleware); // Auth routes (register, login, me) app.use("/api", authRouter); // API Routes app.get("/api/health", (_req, res) => { res.json({ status: "ok", app: "PetTalk", timestamp: new Date().toISOString() }); }); /** * Analyze Pet Signal API endpoint. * Leverages Gemini API when GEMINI_API_KEY is available, or uses local ML Provider. */ app.post("/api/analyze", async (req, res) => { try { const { pet, selectedZoneId, audio, video, customNote } = req.body as { pet: Pet; selectedZoneId?: string; audio?: any; video?: any; customNote?: string; }; if (!pet) { return res.status(400).json({ error: "Pet profile is required for analysis." }); } // Base ML prediction const baseResult = defaultMLProvider.combineSignals({ pet, timeOfDay: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), selectedZoneId, audio, video, customNote }); // If GEMINI_API_KEY is configured, enrich interpretation reasoning with Gemini if (process.env.GEMINI_API_KEY) { try { const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); const zoneObj = pet.homeZones.find(z => z.id === selectedZoneId) || pet.homeZones[0]; const zoneName = zoneObj ? zoneObj.name : "Home Area"; const prompt = `You are a certified veterinary behaviorist and pet communication expert assisting a pet owner on PetTalk. Pet Name: ${pet.name} Species: ${pet.species} Breed: ${pet.breed} Age: ${pet.ageYears} years old Personality: ${pet.personality.join(", ")} Feeding Schedule: ${pet.feedingSchedule} Current Home Zone: ${zoneName} Acoustic Trait Detected: ${audio?.pitchVariation || 'rising pitch'} sound (${audio?.averageVolume ? Math.round(audio.averageVolume * 100) : 50}% volume) Primary Predicted Intent: ${baseResult.primaryIntent.title} (${baseResult.primaryIntent.confidence}% confidence) Write a short, empathetic, scientifically grounded "Why" reasoning breakdown (maximum 3 sentences). Follow this STRICT honesty rule: - Frame everything as a probabilistic likelihood based on sound, body language, routine, location, and owner feedback. - NEVER claim pets speak English or that this is a literal translation. - Maintain a warm, modern, intelligent tone. - Do NOT make medical diagnoses or claims.`; const response = await ai.models.generateContent({ model: "gemini-2.5-flash", contents: prompt }); if (response.text) { baseResult.reasoning.summary = response.text.trim(); } } catch (geminiErr) { console.warn("Gemini API enrichment skipped/failed, using ML provider output:", geminiErr); } } return res.json({ success: true, result: baseResult }); } catch (err: any) { console.error("Error in /api/analyze:", err); return res.status(500).json({ error: "Failed to analyze pet signal", details: err?.message }); } }); /** * Train Pet Profile Feedback API endpoint */ app.post("/api/train", (req, res) => { try { const { pet, feedback, correctedCategory } = req.body as { pet: Pet; feedback: 'confirmed' | 'corrected'; correctedCategory?: string; }; if (!pet || !feedback) { return res.status(400).json({ error: "Pet and feedback are required." }); } const updatedPet = defaultMLProvider.trainPetProfile(pet, feedback, correctedCategory); return res.json({ success: true, pet: updatedPet }); } catch (err: any) { return res.status(500).json({ error: "Failed to process training feedback" }); } }); // Vite development vs production setup if (process.env.NODE_ENV !== "production") { const vite = await createViteServer({ server: { middlewareMode: true }, appType: "spa", }); app.use(vite.middlewares); } else { const distPath = path.join(process.cwd(), "dist"); app.use(express.static(distPath)); app.get("*", (_req, res) => { res.sendFile(path.join(distPath, "index.html")); }); } app.listen(PORT, "0.0.0.0", () => { console.log(`PetTalk Server running on http://localhost:${PORT}`); }); } startServer();