177 lines
6.2 KiB
TypeScript
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 }
|
|
);
|
|
}
|
|
}
|