#!/usr/bin/env python3 import sys import base64 from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) from dotenv import load_dotenv load_dotenv(Path(__file__).parent / ".env", override=True) import gradio as gr from src.labdaps.ingestion.embedder import Embedder from src.labdaps.chat.session import ChatSession from src.labdaps.retrieval.vector_store import collection_count ASSETS_DIR = Path(__file__).parent / "assets" with open(ASSETS_DIR / "logo.png", "rb") as f: LOGO_B64 = base64.b64encode(f.read()).decode() LOGO_SRC = f"data:image/png;base64,{LOGO_B64}" with open(ASSETS_DIR / "icon.png", "rb") as f: ICON_B64 = base64.b64encode(f.read()).decode() ICON_SRC = f"data:image/png;base64,{ICON_B64}" print("[INFO] Inicializando modelo de embeddings...") embedder = Embedder() session = ChatSession(embedder) _last_sources_md = "As fontes aparecerão aqui após cada resposta." def format_sources(chunks) -> str: if not chunks: return "_Nenhuma fonte consultada._" seen = set() lines = [] for c in chunks: key = (c.source_file, c.page_number) if key not in seen: seen.add(key) lines.append(f"- **Sinal de Alerta** -- p. {c.page_number}") return "**Fontes consultadas:**\n" + "\n".join(lines) def respond(message: str, history: list): global _last_sources_md if not message.strip(): yield history, _last_sources_md, gr.update() return # Gradio 5: history e lista de dicts {"role": "user"|"assistant", "content": str} new_history = history + [ {"role": "user", "content": message}, {"role": "assistant", "content": ""}, ] yield new_history, _last_sources_md, "" session.history = [ {"role": msg["role"], "content": msg["content"]} for msg in history ] partial = "" final_chunks = [] for text_delta, chunks in session.ask(message): partial += text_delta final_chunks = chunks new_history[-1]["content"] = partial yield new_history, format_sources(final_chunks), gr.update() _last_sources_md = format_sources(final_chunks) yield new_history, _last_sources_md, gr.update() def reset(): global _last_sources_md session.reset() _last_sources_md = "As fontes aparecerão aqui após cada resposta." return [], _last_sources_md, "" # Paleta LABDAPS: azul marinho #1C2D6E, verde-limão #8DC63F, fundo escuro #080E1E LABDAPS_CSS = f""" /* Loading splash */ #labdaps-splash {{ position: fixed; inset: 0; background: #1C2D6E; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 99999; transition: opacity 0.5s ease; }} #labdaps-splash img {{ width: 260px; margin-bottom: 28px; filter: brightness(0) invert(1); }} #labdaps-splash .spinner {{ width: 40px; height: 40px; border: 4px solid rgba(255,255,255,0.25); border-top-color: #8DC63F; border-radius: 50%; animation: spin 0.8s linear infinite; }} @keyframes spin {{ to {{ transform: rotate(360deg); }} }} /* Global */ body, .gradio-container {{ background: #080E1E !important; color: #E8EBF5 !important; font-family: 'Inter', 'Segoe UI', sans-serif !important; }} /* Header */ #labdaps-header {{ background: linear-gradient(135deg, #1C2D6E 0%, #0F1A45 100%); padding: 8px 16px; border-radius: 10px; display: flex; align-items: center; gap: 16px; margin-bottom: 4px; border-bottom: 3px solid #8DC63F; flex-shrink: 0; }} #labdaps-header img {{ height: 34px; filter: brightness(0) invert(1); }} #labdaps-header .tagline {{ color: rgba(255,255,255,0.8); font-size: 0.88em; margin: 0; }} #labdaps-header .book-title {{ color: #8DC63F; font-weight: 700; font-size: 0.95em; margin: 2px 0 0 0; }} /* Status */ #labdaps-status p, #labdaps-status em, #labdaps-status span, #labdaps-status * {{ color: #a8b4d0 !important; font-size: 13px !important; margin: 4px 0 12px 0; }} /* Chatbot */ .gradio-chatbot {{ background: #0D1530 !important; border: 1px solid #2D3F7A !important; border-radius: 12px !important; }} .gradio-chatbot .message.user {{ background: #ffffff !important; color: #111111 !important; border-radius: 12px 12px 4px 12px !important; border: 1px solid #c8d0e8 !important; }} .gradio-chatbot .message.bot {{ background: #ffffff !important; color: #111111 !important; border-radius: 12px 12px 12px 4px !important; border: 1px solid #c8d0e8 !important; }} .gradio-chatbot .message.user *, .gradio-chatbot .message.bot * {{ color: #111111 !important; }} /* Input */ textarea, input[type=text] {{ background: #ffffff !important; color: #111111 !important; border: 1px solid #2D3F7A !important; border-radius: 8px !important; }} textarea:focus, input[type=text]:focus {{ border-color: #8DC63F !important; box-shadow: 0 0 0 2px rgba(141, 198, 63, 0.25) !important; }} /* Buttons */ button.primary {{ background: #1C2D6E !important; border: none !important; color: #fff !important; border-radius: 8px !important; font-weight: 600 !important; transition: background 0.2s; }} button.primary:hover {{ background: #8DC63F !important; color: #0D1530 !important; }} button.secondary {{ background: transparent !important; border: 1px solid #2D3F7A !important; color: #8DC63F !important; border-radius: 8px !important; }} button.secondary:hover {{ background: rgba(141, 198, 63, 0.1) !important; }} /* Sources panel */ #sources-panel {{ background: #0D1530; border: 1px solid #2D3F7A; border-radius: 12px; padding: 16px; }} #sources-panel * {{ color: #ffffff !important; }} #sources-panel p, #sources-panel li, #sources-panel span, #sources-panel strong, #sources-panel em {{ color: #ffffff !important; font-size: 13px !important; }} /* Labels */ label span {{ color: #8DC63F !important; font-weight: 600 !important; }} /* Footer */ #labdaps-footer p {{ color: rgba(168, 180, 208, 0.7) !important; font-size: 12px !important; text-align: center; margin-top: 8px; margin-bottom: 0; max-width: 65%; margin-left: auto; margin-right: auto; }} """ HEAD_HTML = f"""
Sinal de Alerta
Assistente de pesquisa baseado no livro do LABDAPS / FSP-USP