| import gradio as gr |
| import fitz |
| import requests |
| import io |
| import asyncio |
| import os |
| from backend.synthesis import SynthesisEngine, PROVIDERS |
|
|
| def extract_pdf_text(url: str) -> str: |
| try: |
| response = requests.get(url, timeout=15) |
| response.raise_for_status() |
| doc = fitz.open(stream=response.content, filetype="pdf") |
| text = "" |
| for page in doc: |
| text += page.get_text() |
| return text[:20000] |
| except Exception as e: |
| return f"[Error extrayendo PDF: {str(e)}]" |
|
|
| async def handle_classic_chat(message, history, report_md, provider, model): |
| if not message.strip(): |
| yield history |
| return |
| |
| env_key = PROVIDERS.get(provider, {}).get("env_key", "") |
| api_key = os.environ.get(env_key, "") |
| |
| engine = SynthesisEngine(provider=provider, model=model, api_key=api_key) |
| |
| system_prompt = f"""Eres un asistente de investigación académica. |
| El usuario ha generado un reporte de investigación. |
| Responde a las preguntas del usuario BASÁNDOTE ÚNICAMENTE en este reporte. |
| Si la respuesta no está en el reporte, dilo claramente. |
| |
| REPORTE ACTUAL: |
| {report_md} |
| """ |
|
|
| history.append([message, "Analizando reporte..."]) |
| yield history |
| |
| try: |
| history_text = "\n".join([f"User: {h[0]}\nAssistant: {h[1]}" for h in history[:-1]]) |
| user_prompt = f"Historial:\n{history_text}\n\nNueva pregunta: {message}" |
| |
| response = await engine._call_llm(system_prompt, user_prompt, temperature=0.0) |
| history[-1][1] = response |
| yield history |
| except Exception as e: |
| history[-1][1] = f"❌ Error: {str(e)}" |
| yield history |
|
|
| async def handle_paper_chat(message, history, selected_paper, use_pdf, docs_df, provider, model): |
| if not message.strip() or docs_df is None or docs_df.empty or not selected_paper: |
| yield history |
| return |
| |
| try: |
| idx = int(selected_paper.split("-")[0].strip()) - 1 |
| row = docs_df.iloc[idx] |
| except: |
| history.append([message, "❌ Error: Selecciona un paper válido de la lista."]) |
| yield history |
| return |
|
|
| env_key = PROVIDERS.get(provider, {}).get("env_key", "") |
| api_key = os.environ.get(env_key, "") |
| engine = SynthesisEngine(provider=provider, model=model, api_key=api_key) |
| |
| context = f"TÍTULO: {row.get('Título', '')}\nAUTORES: {row.get('Autores', '')}\nAÑO: {row.get('Año', '')}\nDOI: {row.get('DOI', '')}\n" |
| |
| history.append([message, "Preparando contexto..."]) |
| yield history |
|
|
| if use_pdf and row.get('PDF URL'): |
| history[-1][1] = "Descargando y extrayendo PDF..." |
| yield history |
| |
| pdf_text = await asyncio.to_thread(extract_pdf_text, row.get('PDF URL')) |
| context += f"\nCONTENIDO PDF:\n{pdf_text}" |
| else: |
| context += f"\nFUENTE: {row.get('Fuente', '')}\nGRADE: {row.get('GRADE', '')}\n" |
| if row.get('Abstract'): |
| context += f"\nABSTRACT: {row.get('Abstract', '')}" |
|
|
| system_prompt = f"""Eres un experto analizando papers académicos. |
| Responde preguntas específicamente sobre este paper basándote en el contexto proporcionado. |
| Si te preguntan algo que no está en el contexto, indícalo. |
| |
| CONTEXTO DEL PAPER: |
| {context} |
| """ |
|
|
| history[-1][1] = "Analizando con IA..." |
| yield history |
| |
| try: |
| history_text = "\n".join([f"User: {h[0]}\nAssistant: {h[1]}" for h in history[:-1]]) |
| user_prompt = f"Historial:\n{history_text}\n\nNueva pregunta: {message}" |
| |
| response = await engine._call_llm(system_prompt, user_prompt, temperature=0.0) |
| history[-1][1] = response |
| yield history |
| except Exception as e: |
| history[-1][1] = f"❌ Error: {str(e)}" |
| yield history |
|
|
| def update_paper_choices(docs_df): |
| if docs_df is None or docs_df.empty: |
| return gr.update(choices=[], value=None) |
| choices = [f"{i+1} - {row.get('Título', 'Sin título')[:80]}..." for i, row in docs_df.iterrows()] |
| return gr.update(choices=choices, value=choices[0] if choices else None) |
|
|
| def create_chat_tabs(report_md_state, docs_df_state, provider_state, model_state): |
| """ |
| Agrega los tabs de chat clásico e IA. |
| Retorna None, se asume que se llama dentro de un bloque gr.Tabs() |
| """ |
| with gr.TabItem("💬 Chat"): |
| gr.Markdown("### 💬 Chat con el Reporte\nHaz preguntas sobre el informe de investigación generado.") |
| chatbot_classic = gr.Chatbot(height=450, elem_classes=["glass-panel"]) |
| with gr.Row(): |
| msg_classic = gr.Textbox(placeholder="Pregunta sobre los hallazgos...", show_label=False, scale=4, container=False) |
| btn_classic = gr.Button("Enviar", variant="primary", scale=1) |
| clear_classic = gr.Button("🗑️ Limpiar", scale=1) |
| |
| |
| msg_classic.submit( |
| handle_classic_chat, |
| inputs=[msg_classic, chatbot_classic, report_md_state, provider_state, model_state], |
| outputs=[chatbot_classic] |
| ).then(lambda: "", None, [msg_classic]) |
| |
| btn_classic.click( |
| handle_classic_chat, |
| inputs=[msg_classic, chatbot_classic, report_md_state, provider_state, model_state], |
| outputs=[chatbot_classic] |
| ).then(lambda: "", None, [msg_classic]) |
| |
| clear_classic.click(lambda: [], None, chatbot_classic, queue=False) |
|
|
| with gr.TabItem("🤖 Chat IA (Paper)"): |
| gr.Markdown("### 🤖 Chat con Paper Individual\nSelecciona un paper para analizarlo en profundidad.") |
| with gr.Row(): |
| paper_dropdown = gr.Dropdown(choices=[], label="Seleccionar Paper", scale=3, interactive=True) |
| use_pdf = gr.Checkbox(label="📄 Leer PDF (si está disponible)", value=False, scale=1) |
| refresh_btn = gr.Button("🔄 Refrescar", scale=1) |
| |
| chatbot_paper = gr.Chatbot(height=400, elem_classes=["glass-panel"]) |
| with gr.Row(): |
| msg_paper = gr.Textbox(placeholder="Pregunta sobre este paper específico...", show_label=False, scale=4, container=False) |
| btn_paper = gr.Button("Enviar", variant="primary", scale=1) |
| clear_paper = gr.Button("🗑️", scale=1) |
|
|
| |
| refresh_btn.click(update_paper_choices, inputs=[docs_df_state], outputs=[paper_dropdown]) |
| |
| msg_paper.submit( |
| handle_paper_chat, |
| inputs=[msg_paper, chatbot_paper, paper_dropdown, use_pdf, docs_df_state, provider_state, model_state], |
| outputs=[chatbot_paper] |
| ).then(lambda: "", None, [msg_paper]) |
| |
| btn_paper.click( |
| handle_paper_chat, |
| inputs=[msg_paper, chatbot_paper, paper_dropdown, use_pdf, docs_df_state, provider_state, model_state], |
| outputs=[chatbot_paper] |
| ).then(lambda: "", None, [msg_paper]) |
| |
| clear_paper.click(lambda: [], None, chatbot_paper, queue=False) |
| |
| return refresh_btn, paper_dropdown |
|
|