|
|
@@ -99,11 +99,11 @@ export async function POST(request: NextRequest) {
|
|
|
});
|
|
|
|
|
|
try {
|
|
|
- // Llamada a OpenRouter
|
|
|
+ // Llamada a OpenRouter con STREAMING habilitado
|
|
|
const openRouterStartTime = Date.now();
|
|
|
- console.log("🤖 [API] Iniciando llamada a OpenRouter...");
|
|
|
+ console.log("🤖 [API] Iniciando llamada a OpenRouter con streaming...");
|
|
|
|
|
|
- const completion = await openai.chat.completions.create({
|
|
|
+ const stream = await openai.chat.completions.create({
|
|
|
model: OPENROUTER_MODEL,
|
|
|
messages: [
|
|
|
{
|
|
|
@@ -190,100 +190,160 @@ RECUERDA: Eres un asistente médico virtual, NO un asistente general. Tu especia
|
|
|
},
|
|
|
...conversationHistory,
|
|
|
],
|
|
|
- max_tokens: 3000, // Espero no olvidarme de cambiar esto
|
|
|
+ max_tokens: 3000,
|
|
|
temperature: 0.7,
|
|
|
+ stream: true, // 🔥 STREAMING HABILITADO
|
|
|
});
|
|
|
|
|
|
- const openRouterTime = Date.now() - openRouterStartTime;
|
|
|
- console.log("✅ [API] Respuesta de OpenRouter recibida en:", openRouterTime, "ms");
|
|
|
+ console.log("📡 [API] Stream iniciado, comenzando a procesar chunks...");
|
|
|
+
|
|
|
+ // Crear un ReadableStream para enviar datos al cliente
|
|
|
+ const encoder = new TextEncoder();
|
|
|
+ let fullResponse = "";
|
|
|
+ let lastSentLength = 0;
|
|
|
+
|
|
|
+ const readableStream = new ReadableStream({
|
|
|
+ async start(controller) {
|
|
|
+ try {
|
|
|
+ for await (const chunk of stream) {
|
|
|
+ const content = chunk.choices[0]?.delta?.content || "";
|
|
|
+ if (content) {
|
|
|
+ fullResponse += content;
|
|
|
+
|
|
|
+ // Intentar extraer solo el contenido del campo "response" del JSON en tiempo real
|
|
|
+ // Buscar el patrón: "response": "TEXTO AQUI"
|
|
|
+ const responseMatch = fullResponse.match(/"response"\s*:\s*"([\s\S]*?)(?:"|$)/);
|
|
|
+
|
|
|
+ if (responseMatch) {
|
|
|
+ // Decodificar secuencias de escape JSON
|
|
|
+ const extractedText = responseMatch[1]
|
|
|
+ .replace(/\\n/g, '\n')
|
|
|
+ .replace(/\\r/g, '\r')
|
|
|
+ .replace(/\\t/g, '\t')
|
|
|
+ .replace(/\\"/g, '"')
|
|
|
+ .replace(/\\\\/g, '\\');
|
|
|
+
|
|
|
+ // Solo enviar el texto nuevo (no repetir lo que ya enviamos)
|
|
|
+ if (extractedText.length > lastSentLength) {
|
|
|
+ const newContent = extractedText.substring(lastSentLength);
|
|
|
+ const data = JSON.stringify({ type: "content", content: newContent });
|
|
|
+ controller.enqueue(encoder.encode(`data: ${data}\n\n`));
|
|
|
+ lastSentLength = extractedText.length;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- const assistantResponse = completion.choices[0]?.message?.content;
|
|
|
- console.log("📝 [API] Contenido de respuesta:", {
|
|
|
- hasContent: !!assistantResponse,
|
|
|
- contentLength: assistantResponse?.length || 0,
|
|
|
- isEmpty: !assistantResponse || assistantResponse.trim() === ""
|
|
|
- });
|
|
|
+ const openRouterTime = Date.now() - openRouterStartTime;
|
|
|
+ console.log("✅ [API] Stream completado en:", openRouterTime, "ms");
|
|
|
+ console.log("📝 [API] Respuesta completa recibida, longitud:", fullResponse.length);
|
|
|
+
|
|
|
+ // Procesar la respuesta completa para extraer metadatos
|
|
|
+ if (!fullResponse || fullResponse.trim() === "") {
|
|
|
+ console.log("⚠️ [API] Respuesta vacía de OpenRouter, enviando fallback");
|
|
|
+ const fallbackData = JSON.stringify({
|
|
|
+ type: "metadata",
|
|
|
+ medicalAlert: "NO_AGENDAR",
|
|
|
+ suggestions: [
|
|
|
+ {
|
|
|
+ title: "Síntomas de Gripe",
|
|
|
+ emoji: "🤒",
|
|
|
+ prompt: "¿Cuáles son los síntomas más comunes de la gripe y cómo diferenciarla de un resfriado?"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "Fortalecer Inmunidad",
|
|
|
+ emoji: "🛡️",
|
|
|
+ prompt: "¿Qué alimentos y hábitos me ayudan a fortalecer mi sistema inmunológico?"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "Cuándo Consultar",
|
|
|
+ emoji: "🩺",
|
|
|
+ prompt: "¿Cuándo debo consultar a un médico por síntomas de gripe o resfriado?"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ controller.enqueue(encoder.encode(`data: ${fallbackData}\n\n`));
|
|
|
+ controller.enqueue(encoder.encode('data: [DONE]\n\n'));
|
|
|
+ controller.close();
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- if (!assistantResponse || assistantResponse.trim() === "") {
|
|
|
- console.log("⚠️ [API] Respuesta vacía de OpenRouter, usando fallback");
|
|
|
- const fallbackResponse = {
|
|
|
- response: "Gracias por tu consulta. Como asistente médico virtual, puedo ayudarte con información general sobre salud, pero recuerda que no puedo reemplazar la consulta con un profesional médico. ¿Hay algo específico que te gustaría saber?",
|
|
|
- medicalAlert: "NO_AGENDAR" as const,
|
|
|
- suggestions: [
|
|
|
- {
|
|
|
- title: "Síntomas de Gripe",
|
|
|
- emoji: "🤒",
|
|
|
- prompt: "¿Cuáles son los síntomas más comunes de la gripe y cómo diferenciarla de un resfriado?"
|
|
|
- },
|
|
|
- {
|
|
|
- title: "Fortalecer Inmunidad",
|
|
|
- emoji: "🛡️",
|
|
|
- prompt: "¿Qué alimentos y hábitos me ayudan a fortalecer mi sistema inmunológico?"
|
|
|
- },
|
|
|
- {
|
|
|
- title: "Cuándo Consultar",
|
|
|
- emoji: "🩺",
|
|
|
- prompt: "¿Cuándo debo consultar a un médico por síntomas de gripe o resfriado?"
|
|
|
+ try {
|
|
|
+ console.log("🔄 [API] Procesando metadatos JSON...");
|
|
|
+ // Limpiar respuesta de posibles bloques de código markdown
|
|
|
+ let cleanedResponse = fullResponse.trim();
|
|
|
+
|
|
|
+ if (cleanedResponse.startsWith('```json')) {
|
|
|
+ cleanedResponse = cleanedResponse.replace(/^```json\s*/, '').replace(/\s*```$/, '');
|
|
|
+ console.log("🧹 [API] Removidos bloques de código markdown");
|
|
|
+ }
|
|
|
+
|
|
|
+ const parsedResponse = JSON.parse(cleanedResponse);
|
|
|
+ console.log("✅ [API] JSON parseado exitosamente:", {
|
|
|
+ hasResponse: !!parsedResponse.response,
|
|
|
+ medicalAlert: parsedResponse.medicalAlert,
|
|
|
+ suggestionsCount: parsedResponse.suggestions?.length || 0
|
|
|
+ });
|
|
|
+
|
|
|
+ // Enviar metadatos al final del stream
|
|
|
+ const metadataEvent = JSON.stringify({
|
|
|
+ type: "metadata",
|
|
|
+ medicalAlert: parsedResponse.medicalAlert || "NO_AGENDAR",
|
|
|
+ suggestions: parsedResponse.suggestions || []
|
|
|
+ });
|
|
|
+ controller.enqueue(encoder.encode(`data: ${metadataEvent}\n\n`));
|
|
|
+
|
|
|
+ } catch (parseError) {
|
|
|
+ console.log("❌ [API] Error parseando JSON:", parseError);
|
|
|
+ console.log("📄 [API] Contenido que falló al parsear:", fullResponse.substring(0, 200) + '...');
|
|
|
+
|
|
|
+ // Enviar metadatos por defecto si falla el parsing
|
|
|
+ const fallbackMetadata = JSON.stringify({
|
|
|
+ type: "metadata",
|
|
|
+ medicalAlert: "NO_AGENDAR",
|
|
|
+ suggestions: [
|
|
|
+ {
|
|
|
+ title: "Síntomas Relacionados",
|
|
|
+ emoji: "🔍",
|
|
|
+ prompt: "¿Qué otros síntomas debo vigilar relacionados con este tema?"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "Tratamiento Casero",
|
|
|
+ emoji: "🏠",
|
|
|
+ prompt: "¿Qué cuidados puedo hacer en casa para este problema?"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "Cuándo Preocuparse",
|
|
|
+ emoji: "⚠️",
|
|
|
+ prompt: "¿En qué momento debería consultar a un médico por este tema?"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+ controller.enqueue(encoder.encode(`data: ${fallbackMetadata}\n\n`));
|
|
|
}
|
|
|
- ]
|
|
|
- };
|
|
|
- return NextResponse.json(fallbackResponse);
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- console.log("🔄 [API] Procesando respuesta JSON...");
|
|
|
- // Limpiar respuesta de posibles bloques de código markdown
|
|
|
- let cleanedResponse = assistantResponse.trim();
|
|
|
-
|
|
|
- // Remover bloques de código markdown si existen
|
|
|
- if (cleanedResponse.startsWith('```json')) {
|
|
|
- cleanedResponse = cleanedResponse.replace(/^```json\s*/, '').replace(/\s*```$/, '');
|
|
|
- console.log("🧹 [API] Removidos bloques de código markdown");
|
|
|
- }
|
|
|
|
|
|
- const parsedResponse = JSON.parse(cleanedResponse);
|
|
|
- console.log("✅ [API] JSON parseado exitosamente:", {
|
|
|
- hasResponse: !!parsedResponse.response,
|
|
|
- medicalAlert: parsedResponse.medicalAlert,
|
|
|
- suggestionsCount: parsedResponse.suggestions?.length || 0
|
|
|
- });
|
|
|
-
|
|
|
- // Validar estructura de respuesta
|
|
|
- if (parsedResponse.response && parsedResponse.medicalAlert && parsedResponse.suggestions) {
|
|
|
- const totalTime = Date.now() - requestStartTime;
|
|
|
- console.log("🎉 [API] Respuesta exitosa enviada en:", totalTime, "ms");
|
|
|
- return NextResponse.json(parsedResponse);
|
|
|
- } else {
|
|
|
- throw new Error("Estructura de respuesta inválida");
|
|
|
+ // Enviar señal de finalización
|
|
|
+ controller.enqueue(encoder.encode('data: [DONE]\n\n'));
|
|
|
+ controller.close();
|
|
|
+
|
|
|
+ const totalTime = Date.now() - requestStartTime;
|
|
|
+ console.log("🎉 [API] Stream finalizado exitosamente en:", totalTime, "ms");
|
|
|
+
|
|
|
+ } catch (streamError) {
|
|
|
+ console.error("💥 [API] Error durante streaming:", streamError);
|
|
|
+ controller.error(streamError);
|
|
|
+ }
|
|
|
}
|
|
|
- } catch (parseError) {
|
|
|
- console.log("❌ [API] Error parseando JSON:", parseError);
|
|
|
- console.log("📄 [API] Contenido que falló al parsear:", assistantResponse.substring(0, 200) + '...');
|
|
|
- console.log("🔄 [API] Usando respuesta como texto plano");
|
|
|
-
|
|
|
- const fallbackStructuredResponse = {
|
|
|
- response: assistantResponse.trim(),
|
|
|
- medicalAlert: "NO_AGENDAR" as const,
|
|
|
- suggestions: [
|
|
|
- {
|
|
|
- title: "Síntomas Relacionados",
|
|
|
- emoji: "🔍",
|
|
|
- prompt: "¿Qué otros síntomas debo vigilar relacionados con este tema?"
|
|
|
- },
|
|
|
- {
|
|
|
- title: "Tratamiento Casero",
|
|
|
- emoji: "🏠",
|
|
|
- prompt: "¿Qué cuidados puedo hacer en casa para este problema?"
|
|
|
- },
|
|
|
- {
|
|
|
- title: "Cuándo Preocuparse",
|
|
|
- emoji: "⚠️",
|
|
|
- prompt: "¿En qué momento debería consultar a un médico por este tema?"
|
|
|
- }
|
|
|
- ]
|
|
|
- };
|
|
|
- return NextResponse.json(fallbackStructuredResponse);
|
|
|
- }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Retornar Response con el stream
|
|
|
+ return new Response(readableStream, {
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'text/event-stream',
|
|
|
+ 'Cache-Control': 'no-cache',
|
|
|
+ 'Connection': 'keep-alive',
|
|
|
+ },
|
|
|
+ });
|
|
|
} catch (openRouterError) {
|
|
|
const openRouterTime = Date.now() - requestStartTime;
|
|
|
console.error("💥 [API] Error con OpenRouter después de", openRouterTime, "ms:", openRouterError);
|