Files
sitemente/app/api/chat/agent/route.ts
T
Haitham Khalifa 11252e6520 Initial commit
2026-02-16 12:02:45 +01:00

177 lines
6.2 KiB
TypeScript

import { NextResponse } from "next/server";
import { GoogleGenerativeAI } from "@google/generative-ai";
import { siteMenteKnowledge } from "../../../../lib/sitemente-knowledge";
type Message = {
role: "user" | "assistant";
content: string;
};
type RequestBody = {
message?: string;
locale?: "es" | "en";
history?: Message[];
};
export const runtime = "nodejs";
const getClient = () => {
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
throw new Error("GEMINI_API_KEY is not set.");
}
return new GoogleGenerativeAI(apiKey);
};
const buildSystemPrompt = (locale: "es" | "en") => {
const knowledge = JSON.stringify(siteMenteKnowledge, null, 2);
if (locale === "en") {
return [
"You are the smart brain of SiteMente, the first agency with a fully living website. You speak in a natural, professional but friendly tone.",
"",
"YOUR COMPLETE KNOWLEDGE:",
knowledge,
"",
"PERSONALITY:",
"- Enthusiastic but not over the top",
"- Use real data: '67% sales increase', 'positive ROI in month one', 'restaurant in Marbella increased bookings by 43%'",
"- Give concrete Costa del Sol examples",
"- If you don't know something exactly, admit it: 'Let me connect you with the team for that'",
"- Look for natural lead capture opportunities, never forced",
"",
"CONVERSATION HANDLING:",
"- First 2-3 responses: answer general questions WITHOUT asking for personal data",
"- If user asks pricing/services/how it works: explain clearly",
"- If user shows high intent (asks for demo, implementation, wants to start):",
" -> Then ask for name and email: 'I'd love to help more. Could you share your name and email so I can send detailed info?'",
"- If user gives contact info: thank them and offer to schedule a demo",
"- If user is only exploring: keep the conversation useful, no pressure",
"",
"TONE:",
"- Short, clear sentences",
"- Occasionally use a relevant emoji (💡 🚀 ✨ 🎯)",
"- Natural, like a friendly expert consultant",
"",
"RESPONSE FORMAT:",
"Return STRICT JSON ONLY with keys: response (string), shouldCaptureEmail (boolean), suggestedActions (array of strings).",
"Max 3-4 sentences in response so it is easy to listen to.",
"Always respond in English.",
].join("\n");
}
return [
"Eres el cerebro inteligente de SiteMente, la primera agencia con una web completamente viva. Hablas de forma natural, profesional pero cercana.",
"",
"TU CONOCIMIENTO COMPLETO:",
knowledge,
"",
"TU PERSONALIDAD:",
"- Entusiasta pero no exagerado",
"- Usas datos reales: '67% aumento en ventas', 'ROI en primer mes', 'restaurante en Marbella aumentó 43% reservas'",
"- Das ejemplos concretos de Costa del Sol",
"- Si no sabes algo exacto, lo admites: 'Déjame conectarte con el equipo para eso'",
"- Buscas oportunidades naturales para captar leads, nunca forzado",
"",
"MANEJO DE CONVERSACIÓN:",
"- Primeras 2-3 respuestas: Responde preguntas generales SIN pedir datos",
"- Si usuario pregunta pricing/servicios/cómo funciona: Explica con claridad",
"- Si usuario muestra interés alto (pregunta por demo, implementación, quiere empezar):",
" → Entonces pide nombre y email: 'Me encantaría ayudarte más. ¿Me compartes tu nombre y email para enviarte info detallada?'",
"- Si usuario da info de contacto: Agradece y ofrece agendar demo",
"- Si usuario parece solo explorar: Mantén conversación útil, no presiones",
"",
"TONO:",
"- Frases cortas y claras",
"- Ocasionalmente emoji relevante (💡 🚀 ✨ 🎯)",
"- Natural, como un consultor experto amigable",
"",
"FORMATO DE RESPUESTA:",
"Devuelve SOLO JSON ESTRICTO con claves: response (string), shouldCaptureEmail (boolean), suggestedActions (array of strings).",
"Máximo 3-4 frases por respuesta para que sea fácil de escuchar en voz.",
"Responde SIEMPRE en español.",
].join("\n");
};
const extractJson = (text: string) => {
const trimmed = text.trim();
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
return trimmed;
}
const match = trimmed.match(/\{[\s\S]*\}/);
return match ? match[0] : null;
};
export async function POST(request: Request) {
try {
const body = (await request.json()) as RequestBody;
if (!body.message || typeof body.message !== "string") {
return NextResponse.json(
{ error: "message is required." },
{ status: 400 }
);
}
const locale = body.locale === "en" ? "en" : "es";
const history = Array.isArray(body.history) ? body.history : [];
const model = getClient().getGenerativeModel({
model: "gemini-1.5-flash",
systemInstruction: buildSystemPrompt(locale),
});
const contents = [
...history.map((message) => ({
role: message.role === "assistant" ? "model" : "user",
parts: [{ text: message.content }],
})),
{ role: "user", parts: [{ text: body.message }] },
];
const result = await model.generateContent({
contents,
});
const rawText =
result.response?.text?.() ??
result.response?.candidates?.[0]?.content?.parts
?.map((part) => part.text ?? "")
.join("")
.trim() ??
"";
const jsonPayload = extractJson(rawText);
if (!jsonPayload) {
return NextResponse.json(
{
response: rawText || "Lo siento, hubo un problema generando respuesta.",
shouldCaptureEmail: false,
suggestedActions: [],
},
{ status: 200 }
);
}
const parsed = JSON.parse(jsonPayload) as {
response?: string;
shouldCaptureEmail?: boolean;
suggestedActions?: string[];
};
return NextResponse.json({
response: parsed.response ?? rawText,
shouldCaptureEmail: Boolean(parsed.shouldCaptureEmail),
suggestedActions: Array.isArray(parsed.suggestedActions)
? parsed.suggestedActions
: [],
});
} catch (error) {
console.error("[SiteMente][API] Agent route failed", error);
return NextResponse.json(
{ error: "Failed to generate response." },
{ status: 500 }
);
}
}