Snapshot: full project state
This commit is contained in:
142
pettalk-server-new.ts
Normal file
142
pettalk-server-new.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user