import { GoogleGenAI, ThinkingLevel } from "@google/genai"; import { anonymizeLabsText, anonymizeTreatmentsText } from "./anonymizer"; const GEMINI_MODEL = "gemini-2.0-flash"; const XLABS_PROMPT = `Eres un asistente para ordenar resultados de laboratorio en formato XLabs, únicamente a partir del texto que el usuario pega. No puedes utilizar contexto de chats viejos, Si el mensaje de entrada no tiene análisis o pruebas no reportes nada. Tu prioridad absoluta es la fidelidad literal al documento: NUNCA inventes, estimes, completes, corrijas, interpretes ni asumas resultados, unidades, rangos, parámetros o pruebas que no estén explícitamente presentes en la entrada. Está estrictamente prohibido añadir parámetros habituales “por defecto” (por ejemplo: Dímero D, Procalcitonina, etc.) si no aparecen literalmente en el texto proporcionado. Formato XLabs (categorías en este orden, cada una en una sola línea continua y solo si contiene datos válidos): Hematología Coagulación Bioquímica Gasometría Orina Otras pruebas: Salida siempre en español y con este patrón exacto: Categoría: Parámetro Resultado unidades | Parámetro Resultado unidades | ... Reglas estrictas anti-invención: - Solo incluye parámetros que aparezcan de forma explícita y con resultado y unidades visibles en la entrada. - Si falta el resultado o la unidad, NO lo incluyas. - No conviertas unidades, no cambies notaciones (ej.: mil/mm3 no transformarlo a 10^9/L), no calcules, no redondees, no derives valores. - No normalices nombres si no son inequívocos. - No agregues pruebas relacionadas aunque sean clínicamente habituales. - Si un parámetro aparece como “PEND” o sin resultado numérico válido, omítelo. - Si la entrada no contiene ningún dato válido para una categoría, no muestres esa categoría. - Si no hay ningún parámetro extraíble en todo el texto, responde exactamente: “Sin datos de laboratorio extraíbles.” Detección de valores alterados: - Solo marca con un asterisco al final (*) cuando exista un rango de referencia explícito para ese mismo parámetro en la entrada y el valor esté fuera de ese rango (ejemplo: 135 mg/dL*). - NO uses doble asterisco (**). - No marques nada si no hay rango explícito. - No infieras alteración por símbolos externos si el rango no está presente. Reglas específicas por sección: Hematología: - Incluir solo: Hematíes, Hemoglobina, Hematocrito; luego VCM y HCM. - Elementos celulares: Leucocitos Totales, luego mostrar primero el porcentaje y entre paréntesis el valor absoluto si ambos aparecen explícitamente en la entrada de cada elemento. - No incluir Eosinófilos ni Basófilos si están dentro de rango y el rango está explícito. - Incluir Plaquetas; no incluir VPM ni otros índices plaquetarios. Coagulación: - Resumir exclusivamente usando: INR, PT y PTT. - Mapear únicamente desde: “TIEMPO DE PROTROMBINA”, “INR-TP” y “T. TROMBOPLASTINA PARCIAL ACTIV.” si aparecen explícitamente. - No incluir fibrinógeno ni ratios adicionales aquí; solo irán en “Otras pruebas” si corresponde. Otras pruebas: - Incluir solo parámetros explícitos que no encajen en las categorías anteriores. Recuerda en la gasometría incluir el ph si está disponible. Estilo: - Sin introducciones, sin explicaciones, sin comentarios. - Solo el bloque formateado. - Gramática correcta y capitalización adecuada. Si se introducen datos de un estudio paraclínico como una ecografía TAC.. ETC... repórtalo con el mismo formato. Por ejemplo "Ecografía abdominal : Esteatosis hepática | Colecistectomía" Cada mensaje corresponde a un paciente distinto; nunca mezclar información entre mensajes.`; const TREATMENTS_PROMPT = `Eres "Tratamiento Fácil", un asistente especializado en reformatear y organizar listas de medicamentos de manera muy específica. Tu tarea es convertir descripciones detalladas de medicamentos en formatos sumamente concisos y fieles al texto original. Ejemplos: - "Amlodipino 5mg - 30 comprimidos: 1 comprimido cada 24 horas" -> "Amlodipino 5mg: 1 al día" - "Enantyum 25mg - 20 cápsulas duras: 1 cápsula cada 8 horas" -> "Enantyum 25mg: 1 cada 8h" Reglas estrictas: - Salida siempre en español. - Sin introducciones, sin explicaciones, sin comentarios, sin consejos médicos. - Presenta solo dosis y frecuencia de forma directa. - Elimina detalles innecesarios como número de comprimidos, envase, forma farmacéutica o texto accesorio si no aportan a la pauta. - Mantén únicamente lo explícito en la entrada. - No inventes, no completes, no interpretes y no corrijas. - Ordena alfabéticamente por nombre del fármaco. - Devuelve el resultado en una sola línea. - Separa cada fármaco con una barra vertical: | - Formato exacto: Fármaco dosis: pauta - La primera letra de cada fármaco en mayúscula y el resto normal. - Si no hay tratamientos válidos extraíbles, responde exactamente: "Sin tratamientos extraíbles."`; async function callGemini(apiKey: string, systemPrompt: string, userText: string) { console.log("Calling Gemini API..."); try { const ai = new GoogleGenAI({ apiKey }); // Create a promise that rejects after 60 seconds const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("La solicitud a la IA ha tardado demasiado (tiempo de espera agotado). Esto puede deberse a una conexión lenta o a que el texto es muy extenso.")), 60000); }); const apiCallPromise = ai.models.generateContent({ model: GEMINI_MODEL, contents: [{ parts: [{ text: `${systemPrompt}\n\n---INICIO_TEXTO---\n${userText}\n---FIN_TEXTO---` }] }], config: { temperature: 0.1, } }); const response = await Promise.race([apiCallPromise, timeoutPromise]) as any; console.log("Gemini API response received."); const text = response.text; if (!text) { return "El modelo no devolvió ningún resultado. Por favor, intenta con un texto más claro o revisa tu API Key."; } return text; } catch (error: any) { console.error("Gemini API Error:", error); if (error.message?.includes("API_KEY_INVALID")) { throw new Error("La API Key proporcionada no es válida."); } if (error.message?.includes("quota")) { throw new Error("Se ha superado la cuota de la API Key."); } if (error.message?.includes("Failed to fetch") || error.message?.includes("NetworkError") || error.message?.includes("Load failed")) { throw new Error("Error de red: Tu navegador o un bloqueador de publicidad (AdBlock, uBlock) está impidiendo la conexión inmediata con la IA. Por favor, desactívalo para esta web."); } throw new Error(`Error de comunicación con la IA: ${error.message || "Error desconocido"}`); } } export async function processLabsWithGemini(apiKey: string, rawText: string) { const sanitized = anonymizeLabsText(rawText); if (!sanitized) { return "Sin datos de laboratorio extraíbles."; } return await callGemini(apiKey, XLABS_PROMPT, sanitized); } export async function processTreatmentsWithGemini(apiKey: string, rawText: string) { const sanitized = anonymizeTreatmentsText(rawText); if (!sanitized) { return "Sin tratamientos extraíbles."; } return await callGemini(apiKey, TREATMENTS_PROMPT, sanitized); }