Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sqlite3
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
from bs4 import BeautifulSoup
|
| 6 |
+
import requests
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import traceback # Para melhor formatação de erros
|
| 9 |
+
import tempfile # Para lidar com arquivos enviados
|
| 10 |
+
|
| 11 |
+
# Importações LangChain específicas
|
| 12 |
+
from langchain_community.document_loaders import WebBaseLoader, PyPDFLoader
|
| 13 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 14 |
+
from langchain_community.vectorstores import FAISS
|
| 15 |
+
from langchain_community.embeddings import HuggingFaceEmbeddings
|
| 16 |
+
from langchain_openai import ChatOpenAI
|
| 17 |
+
from langchain.chains import ConversationalRetrievalChain
|
| 18 |
+
from langchain.memory import ConversationBufferMemory
|
| 19 |
+
|
| 20 |
+
# --- Configuração Inicial ---
|
| 21 |
+
# Carrega chave da API (ajuste conforme sua necessidade)
|
| 22 |
+
load_dotenv()
|
| 23 |
+
# Certifique-se que as variáveis de ambiente estão corretas!
|
| 24 |
+
# Exemplo genérico, use as suas variáveis:
|
| 25 |
+
# os.environ["OPENAI_API_KEY"] = os.getenv("OPENROUTER_API_KEY") # Ou OPENAI_API_KEY
|
| 26 |
+
# os.environ["OPENAI_API_BASE"] = os.getenv("OPENROUTER_API_BASE") # Ou omita se usar OpenAI direto
|
| 27 |
+
|
| 28 |
+
# Verifique se a chave API está carregada (adicione um check)
|
| 29 |
+
api_key = os.getenv("OPENROUTER_API_KEY") or os.getenv("OPENAI_API_KEY")
|
| 30 |
+
if not api_key:
|
| 31 |
+
print("⚠️ Atenção: Nenhuma chave de API encontrada nas variáveis de ambiente (OPENROUTER_API_KEY ou OPENAI_API_KEY).")
|
| 32 |
+
# Você pode querer parar a execução aqui ou usar um modelo local se configurado.
|
| 33 |
+
# exit() # Descomente para parar se a API for essencial
|
| 34 |
+
|
| 35 |
+
# Use as variáveis corretas para seu endpoint (OpenRouter ou OpenAI)
|
| 36 |
+
openai_api_key = os.getenv("OPENROUTER_API_KEY") # Ou os.getenv("OPENAI_API_KEY")
|
| 37 |
+
openai_api_base = os.getenv("OPENROUTER_API_BASE") # Opcional, remova se usar OpenAI direto
|
| 38 |
+
|
| 39 |
+
# Embeddings (modelo local, não requer API)
|
| 40 |
+
print("Carregando modelo de embeddings (pode levar um tempo)...")
|
| 41 |
+
embeddings_model_name = "all-MiniLM-L6-v2"
|
| 42 |
+
try:
|
| 43 |
+
embeddings = HuggingFaceEmbeddings(model_name=embeddings_model_name)
|
| 44 |
+
print(f"Modelo de embeddings '{embeddings_model_name}' carregado.")
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"❌ Erro ao carregar embeddings: {e}")
|
| 47 |
+
print("Verifique sua conexão com a internet ou se o modelo está disponível.")
|
| 48 |
+
embeddings = None # Define como None para checagem posterior
|
| 49 |
+
|
| 50 |
+
# LLM (ajuste o modelo conforme disponibilidade/preferência)
|
| 51 |
+
# Use um modelo disponível no seu endpoint (OpenRouter ou OpenAI)
|
| 52 |
+
# Ex: "gpt-3.5-turbo", "deepseek/deepseek-r1:free", etc.
|
| 53 |
+
llm_model_name = "deepseek/deepseek-r1:free" # Exemplo OpenRouter - TROQUE SE NECESSÁRIO
|
| 54 |
+
try:
|
| 55 |
+
llm = ChatOpenAI(
|
| 56 |
+
model=llm_model_name,
|
| 57 |
+
temperature=0.5,
|
| 58 |
+
openai_api_key=openai_api_key,
|
| 59 |
+
base_url=openai_api_base # Passe None se estiver usando OpenAI diretamente
|
| 60 |
+
)
|
| 61 |
+
print(f"LLM '{llm_model_name}' configurado.")
|
| 62 |
+
except Exception as e:
|
| 63 |
+
print(f"❌ Erro ao configurar LLM: {e}")
|
| 64 |
+
llm = None # Define como None para checagem posterior
|
| 65 |
+
|
| 66 |
+
# Memória da conversa (pode ser global)
|
| 67 |
+
memoria = ConversationBufferMemory(memory_key="chat_history", return_messages=True, output_key="answer")
|
| 68 |
+
|
| 69 |
+
# --- Banco de Dados ---
|
| 70 |
+
DB_FILE = "historico_conversas_multidoc.db"
|
| 71 |
+
|
| 72 |
+
def inicializar_db():
|
| 73 |
+
conn = sqlite3.connect(DB_FILE)
|
| 74 |
+
cursor = conn.cursor()
|
| 75 |
+
cursor.execute('''
|
| 76 |
+
CREATE TABLE IF NOT EXISTS conversas (
|
| 77 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 78 |
+
aluno TEXT,
|
| 79 |
+
documento TEXT, -- Nova coluna
|
| 80 |
+
pergunta TEXT,
|
| 81 |
+
resposta TEXT,
|
| 82 |
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
|
| 83 |
+
)
|
| 84 |
+
''')
|
| 85 |
+
conn.commit()
|
| 86 |
+
conn.close()
|
| 87 |
+
print(f"Banco de dados '{DB_FILE}' inicializado/verificado.")
|
| 88 |
+
|
| 89 |
+
inicializar_db() # Garante que a DB exista ao iniciar
|
| 90 |
+
|
| 91 |
+
def salvar_conversa(nome, documento, pergunta, resposta):
|
| 92 |
+
if not documento:
|
| 93 |
+
documento = "Nenhum Documento Carregado"
|
| 94 |
+
try:
|
| 95 |
+
conn = sqlite3.connect(DB_FILE)
|
| 96 |
+
cursor = conn.cursor()
|
| 97 |
+
cursor.execute("INSERT INTO conversas (aluno, documento, pergunta, resposta) VALUES (?, ?, ?, ?)",
|
| 98 |
+
(nome or "Anônimo", documento, pergunta, resposta))
|
| 99 |
+
conn.commit()
|
| 100 |
+
conn.close()
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f"❌ Erro ao salvar conversa no DB: {e}")
|
| 103 |
+
# Não retorna o erro para a interface, apenas loga no console
|
| 104 |
+
|
| 105 |
+
# --- Funções Principais ---
|
| 106 |
+
|
| 107 |
+
def processar_documento(arquivo_pdf, url, progress=gr.Progress(track_tqdm=True)):
|
| 108 |
+
"""Carrega, divide e cria o vector store para um PDF ou URL."""
|
| 109 |
+
if not embeddings or not llm:
|
| 110 |
+
return None, None, "❌ Erro: Embeddings ou LLM não foram carregados corretamente. Verifique o console.", ""
|
| 111 |
+
|
| 112 |
+
docs = []
|
| 113 |
+
documento_nome = None
|
| 114 |
+
temp_dir = None # Para limpar arquivos temporários
|
| 115 |
+
|
| 116 |
+
progress(0, desc="Iniciando...")
|
| 117 |
+
try:
|
| 118 |
+
if arquivo_pdf is not None:
|
| 119 |
+
documento_nome = os.path.basename(arquivo_pdf.name)
|
| 120 |
+
progress(0.1, desc=f"Carregando PDF: {documento_nome}")
|
| 121 |
+
# Gradio fornece um objeto de arquivo temporário.
|
| 122 |
+
# PyPDFLoader precisa do caminho do arquivo.
|
| 123 |
+
loader = PyPDFLoader(arquivo_pdf.name)
|
| 124 |
+
docs = loader.load()
|
| 125 |
+
print(f"PDF '{documento_nome}' carregado, {len(docs)} páginas.")
|
| 126 |
+
|
| 127 |
+
elif url and url.strip():
|
| 128 |
+
documento_nome = url.strip()
|
| 129 |
+
progress(0.1, desc=f"Carregando URL: {documento_nome}")
|
| 130 |
+
loader = WebBaseLoader(documento_nome)
|
| 131 |
+
docs = loader.load()
|
| 132 |
+
print(f"URL '{documento_nome}' carregada, {len(docs)} documentos (partes).")
|
| 133 |
+
|
| 134 |
+
else:
|
| 135 |
+
return None, None, "⚠️ Por favor, forneça um arquivo PDF ou uma URL.", ""
|
| 136 |
+
|
| 137 |
+
if not docs:
|
| 138 |
+
return None, None, f"❌ Erro: Não foi possível extrair conteúdo de '{documento_nome}'.", documento_nome
|
| 139 |
+
|
| 140 |
+
progress(0.4, desc="Dividindo documento...")
|
| 141 |
+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
|
| 142 |
+
documents = text_splitter.split_documents(docs)
|
| 143 |
+
print(f"Documento dividido em {len(documents)} chunks.")
|
| 144 |
+
|
| 145 |
+
if not documents:
|
| 146 |
+
return None, None, "❌ Erro: Documento vazio após divisão.", documento_nome
|
| 147 |
+
|
| 148 |
+
progress(0.6, desc="Criando embeddings e vector store (pode levar tempo)...")
|
| 149 |
+
vectordb = FAISS.from_documents(documents, embeddings)
|
| 150 |
+
retriever = vectordb.as_retriever()
|
| 151 |
+
print("Vector store FAISS criado.")
|
| 152 |
+
|
| 153 |
+
progress(0.9, desc="Limpando memória da conversa anterior...")
|
| 154 |
+
memoria.clear() # Limpa o histórico ao carregar novo doc
|
| 155 |
+
print("Memória da conversa resetada.")
|
| 156 |
+
|
| 157 |
+
progress(1, desc="Documento processado!")
|
| 158 |
+
status = f"✅ Documento '{documento_nome}' carregado e pronto para consulta."
|
| 159 |
+
return retriever, documento_nome, status, "" # Limpa campo de pergunta
|
| 160 |
+
|
| 161 |
+
except Exception as e:
|
| 162 |
+
print(f"❌ Erro detalhado no processamento: {traceback.format_exc()}")
|
| 163 |
+
return None, None, f"❌ Erro ao processar o documento: {e}", ""
|
| 164 |
+
finally:
|
| 165 |
+
# Limpeza do arquivo temporário do Gradio (se aplicável)
|
| 166 |
+
# O Gradio geralmente cuida disso, mas podemos garantir
|
| 167 |
+
if arquivo_pdf is not None and hasattr(arquivo_pdf, 'name') and os.path.exists(arquivo_pdf.name):
|
| 168 |
+
# Não deletar aqui diretamente, Gradio pode precisar dele.
|
| 169 |
+
# Apenas certifique-se de que não há vazamento se o Gradio falhar.
|
| 170 |
+
pass
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def responder(pergunta, nome_aluno, state_retriever, state_doc_nome):
|
| 174 |
+
"""Responde a pergunta usando o RAG com o documento carregado."""
|
| 175 |
+
if not state_retriever:
|
| 176 |
+
return "⚠️ Por favor, carregue um documento (PDF ou URL) primeiro usando o botão 'Carregar Documento'."
|
| 177 |
+
if not pergunta or not pergunta.strip():
|
| 178 |
+
return "⚠️ Por favor, digite sua pergunta."
|
| 179 |
+
if not llm:
|
| 180 |
+
return "❌ Erro: LLM não está configurado corretamente."
|
| 181 |
+
|
| 182 |
+
print(f"\nRecebida pergunta sobre '{state_doc_nome}': {pergunta}")
|
| 183 |
+
|
| 184 |
+
try:
|
| 185 |
+
# Cria a cadeia DENTRO da função para usar o retriever do estado atual
|
| 186 |
+
qa_chain = ConversationalRetrievalChain.from_llm(
|
| 187 |
+
llm=llm,
|
| 188 |
+
retriever=state_retriever,
|
| 189 |
+
memory=memoria,
|
| 190 |
+
return_source_documents=True, # Pode ser útil para debug
|
| 191 |
+
output_key="answer" # Garante que a chave de saída seja 'answer'
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
# Invoca a cadeia
|
| 195 |
+
resultado = qa_chain.invoke({"question": pergunta})
|
| 196 |
+
resposta_bruta = resultado.get("answer", "Desculpe, não consegui gerar uma resposta.")
|
| 197 |
+
fontes = resultado.get("source_documents", []) # Pega as fontes se houver
|
| 198 |
+
|
| 199 |
+
# LangChain pode retornar objetos AIMessage, extrai o conteúdo se necessário
|
| 200 |
+
resposta = resposta_bruta.content if hasattr(resposta_bruta, "content") else str(resposta_bruta)
|
| 201 |
+
|
| 202 |
+
print(f"Resposta gerada: {resposta}")
|
| 203 |
+
if fontes:
|
| 204 |
+
print(f"Fontes encontradas: {len(fontes)} chunks.")
|
| 205 |
+
# print("Exemplo de fonte:", fontes[0].page_content[:200]) # Para debug
|
| 206 |
+
|
| 207 |
+
# Salva no banco de dados
|
| 208 |
+
salvar_conversa(nome_aluno, state_doc_nome, pergunta, resposta)
|
| 209 |
+
|
| 210 |
+
return resposta
|
| 211 |
+
|
| 212 |
+
except Exception as e:
|
| 213 |
+
print(f"❌ Erro detalhado ao responder: {traceback.format_exc()}")
|
| 214 |
+
# Retorna erro formatado para a interface
|
| 215 |
+
return f"❌ **Erro ao gerar resposta:**\n```\n{traceback.format_exc()}\n```"
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def resetar_memoria_app():
|
| 219 |
+
"""Reseta a memória da conversa."""
|
| 220 |
+
memoria.clear()
|
| 221 |
+
print("Memória resetada manualmente.")
|
| 222 |
+
return "✅ Memória da conversa atual resetada!"
|
| 223 |
+
|
| 224 |
+
def exportar_conversas():
|
| 225 |
+
"""Exporta o histórico de conversas para CSV e Excel."""
|
| 226 |
+
try:
|
| 227 |
+
conn = sqlite3.connect(DB_FILE)
|
| 228 |
+
# Ordena pelas mais recentes primeiro e seleciona todas as colunas
|
| 229 |
+
df = pd.read_sql_query("SELECT id, timestamp, aluno, documento, pergunta, resposta FROM conversas ORDER BY timestamp DESC", conn)
|
| 230 |
+
csv_file = "conversas_exportadas.csv"
|
| 231 |
+
excel_file = "conversas_exportadas.xlsx"
|
| 232 |
+
df.to_csv(csv_file, index=False, encoding='utf-8') # Especifica encoding
|
| 233 |
+
df.to_excel(excel_file, index=False, engine="openpyxl")
|
| 234 |
+
conn.close()
|
| 235 |
+
print(f"Histórico exportado para '{csv_file}' e '{excel_file}'.")
|
| 236 |
+
return f"✅ Histórico exportado para '{csv_file}' e '{excel_file}'!"
|
| 237 |
+
except Exception as e:
|
| 238 |
+
print(f"❌ Erro ao exportar histórico: {e}")
|
| 239 |
+
return f"❌ Erro ao exportar histórico: {e}"
|
| 240 |
+
|
| 241 |
+
# --- Interface Gradio ---
|
| 242 |
+
with gr.Blocks(theme=gr.themes.Soft()) as app:
|
| 243 |
+
gr.Markdown("# 🧠 Tutor Multidisciplinar / Analista de Documentos Genérico 📄")
|
| 244 |
+
gr.Markdown("Faça upload de um PDF ou insira uma URL para começar a conversar sobre o conteúdo.")
|
| 245 |
+
|
| 246 |
+
# Estado para manter o retriever e o nome do documento atual
|
| 247 |
+
state_retriever = gr.State(None)
|
| 248 |
+
state_doc_nome = gr.State(None)
|
| 249 |
+
|
| 250 |
+
with gr.Row():
|
| 251 |
+
with gr.Column(scale=1):
|
| 252 |
+
pdf_upload = gr.File(label="Upload de PDF", file_types=[".pdf"])
|
| 253 |
+
url_input = gr.Textbox(label="Ou Insira a URL do Documento")
|
| 254 |
+
btn_carregar = gr.Button("🚀 Carregar Documento", variant="primary")
|
| 255 |
+
status_carregamento = gr.Markdown("") # Para mensagens de status do carregamento
|
| 256 |
+
|
| 257 |
+
with gr.Column(scale=2):
|
| 258 |
+
chatbot_display = gr.Textbox(label="Resposta do Assistente", lines=15, interactive=False) # Usar Textbox para formatar melhor erros
|
| 259 |
+
nome_aluno = gr.Textbox(label="Seu nome (opcional)", placeholder="Ex: Maria")
|
| 260 |
+
pergunta_input = gr.Textbox(label="Sua Pergunta sobre o Documento Carregado", placeholder="Faça sua pergunta aqui...")
|
| 261 |
+
with gr.Row():
|
| 262 |
+
btn_enviar = gr.Button("✉️ Enviar Pergunta", variant="primary")
|
| 263 |
+
btn_resetar = gr.Button("🔁 Resetar Memória")
|
| 264 |
+
btn_exportar = gr.Button("📤 Exportar Histórico")
|
| 265 |
+
|
| 266 |
+
# --- Conexões da Interface ---
|
| 267 |
+
|
| 268 |
+
# Botão Carregar Documento
|
| 269 |
+
btn_carregar.click(
|
| 270 |
+
fn=processar_documento,
|
| 271 |
+
inputs=[pdf_upload, url_input],
|
| 272 |
+
outputs=[state_retriever, state_doc_nome, status_carregamento, pergunta_input] # Limpa pergunta ao carregar
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
# Botão Enviar Pergunta
|
| 276 |
+
btn_enviar.click(
|
| 277 |
+
fn=responder,
|
| 278 |
+
inputs=[pergunta_input, nome_aluno, state_retriever, state_doc_nome],
|
| 279 |
+
outputs=chatbot_display
|
| 280 |
+
).then(lambda: "", outputs=pergunta_input) # Limpa o campo de pergunta após enviar
|
| 281 |
+
|
| 282 |
+
# Botão Resetar Memória
|
| 283 |
+
btn_resetar.click(
|
| 284 |
+
fn=resetar_memoria_app,
|
| 285 |
+
outputs=chatbot_display # Mostra mensagem de reset na caixa de resposta
|
| 286 |
+
)
|
| 287 |
+
|
| 288 |
+
# Botão Exportar Histórico
|
| 289 |
+
btn_exportar.click(
|
| 290 |
+
fn=exportar_conversas,
|
| 291 |
+
outputs=chatbot_display # Mostra mensagem de exportação na caixa de resposta
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
# Limpar campos de input ao usar o outro (PDF vs URL)
|
| 295 |
+
def limpar_outro_input(input_data):
|
| 296 |
+
# Se o input veio do upload (não é None), retorna None para o textbox da URL
|
| 297 |
+
if input_data is not None:
|
| 298 |
+
return None
|
| 299 |
+
return gr.update() # Não muda nada se o input veio do textbox
|
| 300 |
+
|
| 301 |
+
pdf_upload.change(fn=limpar_outro_input, inputs=pdf_upload, outputs=url_input)
|
| 302 |
+
url_input.change(fn=limpar_outro_input, inputs=url_input, outputs=pdf_upload)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
# --- Lançar a Aplicação ---
|
| 306 |
+
if __name__ == "__main__":
|
| 307 |
+
if embeddings and llm: # Só lança se componentes essenciais carregaram
|
| 308 |
+
print("Iniciando interface Gradio...")
|
| 309 |
+
app.launch(share=True, debug=True) # Share=True para link público, Debug=True para mais logs
|
| 310 |
+
else:
|
| 311 |
+
print("❌ Aplicação não iniciada devido a falha no carregamento de Embeddings ou LLM.")
|