Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import fitz # pymupdf | |
| 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] # Limit to avoid excessive tokens | |
| 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 | |
| # Ejecutar extracción en un thread para no bloquear el loop asíncrono | |
| 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) | |
| # Events | |
| 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) | |
| # Events | |
| 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 | |