|
|
| 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 }); |
| |
| |
| 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); |
| } |
|
|