caarleexx commited on
Commit
51c55e2
·
verified ·
1 Parent(s): 010b35e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -110
app.py CHANGED
@@ -10,27 +10,27 @@ import gradio as gr
10
  import google.generativeai as genai
11
 
12
  # ============================================================================
13
- # CONFIGURAÇÃO
14
  # ============================================================================
15
- # Filtra avisos do gRPC para limpar o console
16
  warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
17
 
18
- # API KEY
19
  API_KEY = os.getenv("GOOGLE_API_KEY", "")
 
20
  if not API_KEY:
21
- print(" ERRO CRÍTICO: GOOGLE_API_KEY não encontrada nas variáveis de ambiente.")
22
- print("Defina com: export GOOGLE_API_KEY='sua_chave'")
23
- # Não paramos o script, mas vai dar erro na chamada
24
  else:
25
  print(f"✅ API Key carregada (termina em ...{API_KEY[-4:]})")
26
 
27
  genai.configure(api_key=API_KEY)
28
- model = genai.GenerativeModel("gemini-2.0-flash")
29
 
30
- TITLE = "# 🛠️ Anise v10.2 DEBUG MODE\n**Console mostra SAÍDA BRUTA**"
31
 
32
  # ============================================================================
33
- # SISTEMA DE LOGS & DEBUG
34
  # ============================================================================
35
  def debug_print(titulo: str, conteudo: any):
36
  """Imprime no console com formatação visível para debug"""
@@ -43,9 +43,6 @@ def debug_print(titulo: str, conteudo: any):
43
  print(str(conteudo))
44
  print(f"{'='*60}\n")
45
 
46
- # ============================================================================
47
- # HELPERS (SEM TRY/EXCEPT PARA DEBUG)
48
- # ============================================================================
49
  def processar_anexo(arquivo) -> Tuple[str, str]:
50
  if arquivo is None:
51
  return "", "nenhum"
@@ -54,92 +51,98 @@ def processar_anexo(arquivo) -> Tuple[str, str]:
54
  print(f"📂 Processando arquivo: {caminho}")
55
 
56
  if caminho.lower().endswith('.pdf'):
57
- import PyPDF2 # Se falhar aqui, queremos ver o erro de importação
58
- with open(caminho, 'rb') as f:
59
- leitor = PyPDF2.PdfReader(f)
60
- texto = "".join(pagina.extract_text() + "\n" for pagina in leitor.pages[:5])
61
- print(f"📄 PDF extraído: {len(texto)} caracteres")
62
- return texto[:5000], "pdf"
 
 
 
 
 
 
 
63
 
64
  elif any(caminho.lower().endswith(ext) for ext in ['.png','.jpg','.jpeg','.gif','.webp']):
65
- with open(caminho, 'rb') as f:
66
- encoded = base64.b64encode(f.read()).decode()
67
- print(f"🖼️ Imagem codificada: {len(encoded)} bytes")
68
- return encoded, "imagem"
 
 
 
 
69
 
70
  return "", "nao_suportado"
71
 
72
  def limpar_json_raw(texto: str) -> str:
73
- """Limpa markdown ```json ... ``` para tentar parsear"""
74
  texto = re.sub(r'^```json\s*', '', texto, flags=re.MULTILINE)
75
  texto = re.sub(r'^```\s*', '', texto, flags=re.MULTILINE)
76
  texto = re.sub(r'```$', '', texto, flags=re.MULTILINE)
77
  return texto.strip()
78
 
79
- def chamar_gemini_json(prompt_base: str, etapa: str, temperatura=0.2, max_tokens=2000) -> Dict:
80
- """
81
- Chama o Gemini e imprime o RAW OUTPUT.
82
- Se falhar o JSON, explode o erro para análise.
83
- """
84
  full_prompt = f"""{prompt_base}
85
 
86
  ---
87
- **INSTRUÇÃO DE SISTEMA OBRIGATÓRIA:**
88
  1. Responda APENAS com um JSON válido.
89
- 2. NÃO use blocos de código markdown (```json).
90
- 3. NÃO escreva texto antes ou depois do JSON.
91
  """
92
 
93
- # 1. Chamada API
94
- print(f"📡 Enviando requisição para {etapa}...")
95
- response = model.generate_content(
96
- full_prompt,
97
- generation_config=genai.types.GenerationConfig(
98
- temperature=temperatura,
99
- max_output_tokens=max_tokens,
100
- # response_mime_type="application/json" # Opcional: força modo JSON estrito do 1.5
101
  )
102
- )
103
-
 
 
104
  raw_text = response.text
105
-
106
- # 2. PRINT DA SAÍDA BRUTA (O que você pediu)
107
  print(f"\n🛑 SAÍDA BRUTA GEMINI [{etapa}]:")
108
- print(f">>> INICIO RAW <<<\n{raw_text}\n>>> FIM RAW <<<")
109
 
110
- # 3. Tentativa de Parse (sem try/except silencioso)
111
  texto_limpo = limpar_json_raw(raw_text)
112
 
113
  if not texto_limpo:
114
- raise ValueError(f"[{etapa}] Gemini retornou texto vazio!")
115
 
116
  try:
117
- dados_json = json.loads(texto_limpo)
118
- return dados_json
119
  except json.JSONDecodeError as e:
120
- print(f"❌ FALHA NO PARSE JSON [{etapa}]")
121
- print(f"Erro: {e}")
122
- print("Tentando corrigir manualmente strings...")
123
- # Fallback simples se for apenas texto solto (opcional)
124
- raise e # Relança o erro para parar o script e ver o traceback
125
 
126
  def historico_compacto(historico: List) -> str:
127
  if not historico: return "Nenhum."
128
- return "\n".join([f"{m['role']}: {str(m['content'])[:100]}..." for m in historico[-4:]])
129
 
130
  def criar_dna() -> Dict:
131
- return {"historico": [], "meta": {"turnos": 0}}
 
 
 
132
 
133
  # ============================================================================
134
- # PIPELINE - PASSOS (COM LOGS EXPLÍCITOS)
135
  # ============================================================================
136
 
137
  def passo_0_aluno(pergunta: str, historico: List) -> Dict:
138
  prompt = f"""ETAPA: P0-INTENÇÃO
139
  HISTÓRICO: {historico_compacto(historico)}
140
  PERGUNTA: {pergunta}
141
-
142
- Analise a intenção do usuário.
143
  JSON: {{"relacao": "continua|nova", "intent": "resumo"}}"""
144
  return chamar_gemini_json(prompt, "P0")
145
 
@@ -147,8 +150,6 @@ def passo_1_triagem(pergunta: str, p0: Dict) -> Dict:
147
  prompt = f"""ETAPA: P1-TRIAGEM
148
  P0: {json.dumps(p0)}
149
  PERGUNTA: {pergunta}
150
-
151
- Classifique a pergunta.
152
  JSON: {{"tipo": "factual|analitica", "complexidade": "alta|baixa"}}"""
153
  return chamar_gemini_json(prompt, "P1")
154
 
@@ -156,124 +157,134 @@ def passo_x1_lacunas(pergunta: str, p1: Dict) -> Dict:
156
  prompt = f"""ETAPA: X1-LACUNAS
157
  P1: {json.dumps(p1)}
158
  PERGUNTA: {pergunta}
159
-
160
- Quais perguntas o assistente deve fazer a si mesmo para responder isso perfeitamente?
161
  JSON: {{"perguntas_internas": ["pergunta1", "pergunta2"]}}"""
162
  return chamar_gemini_json(prompt, "X1")
163
 
164
  def passo_x2_resolver(x1: Dict, historico: List) -> Dict:
165
  perguntas = x1.get("perguntas_internas", [])
166
  if not perguntas: return {"respostas": []}
167
-
168
  prompt = f"""ETAPA: X2-RESOLUÇÃO
169
  PERGUNTAS: {json.dumps(perguntas)}
170
  CONTEXTO: {historico_compacto(historico)}
171
-
172
- Responda as perguntas internas.
173
- JSON: {{"respostas": [{{"p": "pergunta", "r": "resposta", "confianca": "alta|baixa"}}]}}"""
174
  return chamar_gemini_json(prompt, "X2")
175
 
176
  def passo_2_cenarios(pergunta: str, x2: Dict) -> Dict:
177
  prompt = f"""ETAPA: P2-CENÁRIOS
178
  DADOS X2: {json.dumps(x2)}
179
- PERGUNTA ORIGINAL: {pergunta}
180
-
181
- Crie cenários de resposta ou decida parar se faltar informação crítica.
182
- JSON: {{"decisao": "continuar|parar", "cenarios": ["C1: ...", "C2: ..."], "motivo_parada": "..."}}"""
183
  return chamar_gemini_json(prompt, "P2")
184
 
185
  def passo_7_sintese(p2: Dict, pergunta: str) -> Dict:
186
  prompt = f"""ETAPA: P7-FINAL
187
  CENÁRIOS: {json.dumps(p2)}
188
  PERGUNTA: {pergunta}
189
-
190
- Escreva a resposta final para o usuário.
191
  JSON: {{"resposta_final": "texto aqui"}}"""
192
- return chamar_gemini_json(prompt, "P7", temperatura=0.7)
193
 
194
  # ============================================================================
195
- # ORQUESTADOR (SEM REDE DE SEGURANÇA)
196
  # ============================================================================
197
  def processar_pipeline(pergunta: str, historico: List, arquivo_anexo=None, dna=None) -> Tuple[str, List, Dict]:
198
- # Debug inicial
199
- debug_print("INICIO PIPELINE", f"Pergunta: {pergunta}\nAnexo: {arquivo_anexo}")
200
 
201
- if dna is None: dna = criar_dna()
 
 
 
 
202
 
203
- # 1. Processar Anexo
204
  conteudo_anexo, tipo_anexo = processar_anexo(arquivo_anexo)
205
 
206
- # 2. Montar Prompt com Contexto Visual/Documental
207
  if tipo_anexo == "pdf":
208
- prompt_final = f"CONTEXTO DO PDF:\n{conteudo_anexo}\n---\nPERGUNTA: {pergunta}"
209
  elif tipo_anexo == "imagem":
210
- prompt_final = f"[IMAGEM BASE64: {conteudo_anexo}]\nAnalise a imagem.\nPERGUNTA: {pergunta}"
211
  else:
212
  prompt_final = pergunta
213
 
214
- # 3. Execução Linear (Se falhar, o erro aparece no console)
215
  p0 = passo_0_aluno(prompt_final, historico)
216
- debug_print("P0 Resultado", p0)
217
 
218
  p1 = passo_1_triagem(prompt_final, p0)
219
- debug_print("P1 Resultado", p1)
220
 
221
  x1 = passo_x1_lacunas(prompt_final, p1)
222
- debug_print("X1 Resultado", x1)
223
 
224
  x2 = passo_x2_resolver(x1, historico)
225
- debug_print("X2 Resultado", x2)
226
 
227
  p2 = passo_2_cenarios(prompt_final, x2)
228
- debug_print("P2 Resultado", p2)
229
 
230
- # Lógica de Parada
231
  if p2.get("decisao") == "parar":
232
- resposta = f"⚠️ Não consigo responder com certeza.\nMotivo: {p2.get('motivo_parada')}"
233
  else:
234
  p7 = passo_7_sintese(p2, prompt_final)
235
- debug_print("P7 Resultado", p7)
236
- resposta = p7.get("resposta_final", "Erro na síntese P7")
 
237
 
238
- # Atualiza Histórico
239
  novo_hist = historico + [
240
  {"role": "user", "content": pergunta},
241
  {"role": "assistant", "content": resposta}
242
  ]
243
 
244
- dna["historico"].append({"turn": dna["meta"]["turnos"], "p": pergunta, "r": resposta[:20]})
 
 
 
 
 
245
  dna["meta"]["turnos"] += 1
246
 
247
  return resposta, novo_hist, dna
248
 
249
  # ============================================================================
250
- # INTERFACE
251
  # ============================================================================
252
  def chat_interface(msg, hist, anexo, dna_json):
253
- # Converte string JSON do DNA de volta para dict
254
- dna = json.loads(dna_json) if dna_json else {}
255
- if hist is None: hist = []
 
 
256
 
257
- # Chama o pipeline (Erros vão aparecer no console do servidor)
258
- resp, novo_hist, dna_new = processar_pipeline(msg, hist, anexo, dna)
259
 
260
- return novo_hist, "", json.dumps(dna_new, indent=2), None
 
 
 
 
 
 
 
 
261
 
262
  if __name__ == "__main__":
263
- with gr.Blocks(title="Anise Debug", theme=gr.themes.Base()) as demo:
264
  gr.Markdown(TITLE)
265
 
266
  with gr.Row():
267
- chat = gr.Chatbot(height=500, type="messages", label="Chat")
268
- dna_box = gr.Code(label="DNA (JSON State)", language="json")
269
 
270
  with gr.Row():
271
- txt_in = gr.Textbox(label="Pergunta", scale=2)
272
- file_in = gr.File(label="Anexo")
273
- btn = gr.Button("Enviar", variant="primary")
274
-
275
- btn.click(chat_interface, [txt_in, chat, file_in, dna_box], [chat, txt_in, dna_box, file_in])
276
- txt_in.submit(chat_interface, [txt_in, chat, file_in, dna_box], [chat, txt_in, dna_box, file_in])
 
 
 
 
 
277
 
278
- print("🚀 Servidor Iniciado! Verifique este console para logs.")
279
  demo.launch(server_name="0.0.0.0", server_port=7860)
 
10
  import google.generativeai as genai
11
 
12
  # ============================================================================
13
+ # CONFIGURAÇÃO GERAL
14
  # ============================================================================
15
+ # Filtra avisos do sistema para limpar o console
16
  warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
17
 
18
+ # API KEY - Tenta pegar do ambiente, senão avisa
19
  API_KEY = os.getenv("GOOGLE_API_KEY", "")
20
+
21
  if not API_KEY:
22
+ print("\n⚠️ AVISO: GOOGLE_API_KEY não encontrada!")
23
+ print("👉 Para funcionar, defina a chave ou cole no código (não recomendado em prod).\n")
 
24
  else:
25
  print(f"✅ API Key carregada (termina em ...{API_KEY[-4:]})")
26
 
27
  genai.configure(api_key=API_KEY)
28
+ model = genai.GenerativeModel("gemini-1.5-flash")
29
 
30
+ TITLE = "# 🛠️ Anise v10.3 (Stable Debug)\n**Logs Brutos no Terminal | Correção de Estado**"
31
 
32
  # ============================================================================
33
+ # SISTEMA DE LOGS & UTILITÁRIOS
34
  # ============================================================================
35
  def debug_print(titulo: str, conteudo: any):
36
  """Imprime no console com formatação visível para debug"""
 
43
  print(str(conteudo))
44
  print(f"{'='*60}\n")
45
 
 
 
 
46
  def processar_anexo(arquivo) -> Tuple[str, str]:
47
  if arquivo is None:
48
  return "", "nenhum"
 
51
  print(f"📂 Processando arquivo: {caminho}")
52
 
53
  if caminho.lower().endswith('.pdf'):
54
+ try:
55
+ import PyPDF2
56
+ with open(caminho, 'rb') as f:
57
+ leitor = PyPDF2.PdfReader(f)
58
+ texto = "".join(pagina.extract_text() + "\n" for pagina in leitor.pages[:5])
59
+ print(f"📄 PDF extraído: {len(texto)} caracteres (5 primeiras págs)")
60
+ return texto[:5000], "pdf"
61
+ except ImportError:
62
+ print("❌ Erro: PyPDF2 não instalado. O PDF será ignorado.")
63
+ return "Erro: Instale PyPDF2", "erro"
64
+ except Exception as e:
65
+ print(f"❌ Erro ao ler PDF: {e}")
66
+ return str(e), "erro"
67
 
68
  elif any(caminho.lower().endswith(ext) for ext in ['.png','.jpg','.jpeg','.gif','.webp']):
69
+ try:
70
+ with open(caminho, 'rb') as f:
71
+ encoded = base64.b64encode(f.read()).decode()
72
+ print(f"🖼️ Imagem codificada: {len(encoded)} bytes")
73
+ return encoded, "imagem"
74
+ except Exception as e:
75
+ print(f"❌ Erro ao ler imagem: {e}")
76
+ return str(e), "erro"
77
 
78
  return "", "nao_suportado"
79
 
80
  def limpar_json_raw(texto: str) -> str:
81
+ """Limpa blocos markdown do texto"""
82
  texto = re.sub(r'^```json\s*', '', texto, flags=re.MULTILINE)
83
  texto = re.sub(r'^```\s*', '', texto, flags=re.MULTILINE)
84
  texto = re.sub(r'```$', '', texto, flags=re.MULTILINE)
85
  return texto.strip()
86
 
87
+ def chamar_gemini_json(prompt_base: str, etapa: str, temperatura=0.2) -> Dict:
88
+ """Chama a API e exibe o retorno BRUTO antes de processar."""
89
+
 
 
90
  full_prompt = f"""{prompt_base}
91
 
92
  ---
93
+ **SISTEMA OBRIGATÓRIO:**
94
  1. Responda APENAS com um JSON válido.
95
+ 2. NÃO use Markdown.
96
+ 3. SEM comentários extras.
97
  """
98
 
99
+ print(f"📡 Request para {etapa}...")
100
+ try:
101
+ response = model.generate_content(
102
+ full_prompt,
103
+ generation_config=genai.types.GenerationConfig(
104
+ temperature=temperatura,
105
+ max_output_tokens=2000
106
+ )
107
  )
108
+ except Exception as e:
109
+ print(f"🔥 ERRO NA API: {e}")
110
+ raise e
111
+
112
  raw_text = response.text
 
 
113
  print(f"\n🛑 SAÍDA BRUTA GEMINI [{etapa}]:")
114
+ print(f">>>\n{raw_text}\n<<<")
115
 
 
116
  texto_limpo = limpar_json_raw(raw_text)
117
 
118
  if not texto_limpo:
119
+ raise ValueError(f"[{etapa}] Retorno vazio da API")
120
 
121
  try:
122
+ return json.loads(texto_limpo)
 
123
  except json.JSONDecodeError as e:
124
+ print(f"❌ FALHA NO JSON PARSE [{etapa}] - Erro: {e}")
125
+ # Relançar erro para parar o script e ver o problema
126
+ raise e
 
 
127
 
128
  def historico_compacto(historico: List) -> str:
129
  if not historico: return "Nenhum."
130
+ return "\n".join([f"{m.get('role','?')}: {str(m.get('content',''))[:100]}..." for m in historico[-4:]])
131
 
132
  def criar_dna() -> Dict:
133
+ return {
134
+ "historico": [],
135
+ "meta": {"turnos": 0}
136
+ }
137
 
138
  # ============================================================================
139
+ # PASSOS DO PIPELINE
140
  # ============================================================================
141
 
142
  def passo_0_aluno(pergunta: str, historico: List) -> Dict:
143
  prompt = f"""ETAPA: P0-INTENÇÃO
144
  HISTÓRICO: {historico_compacto(historico)}
145
  PERGUNTA: {pergunta}
 
 
146
  JSON: {{"relacao": "continua|nova", "intent": "resumo"}}"""
147
  return chamar_gemini_json(prompt, "P0")
148
 
 
150
  prompt = f"""ETAPA: P1-TRIAGEM
151
  P0: {json.dumps(p0)}
152
  PERGUNTA: {pergunta}
 
 
153
  JSON: {{"tipo": "factual|analitica", "complexidade": "alta|baixa"}}"""
154
  return chamar_gemini_json(prompt, "P1")
155
 
 
157
  prompt = f"""ETAPA: X1-LACUNAS
158
  P1: {json.dumps(p1)}
159
  PERGUNTA: {pergunta}
 
 
160
  JSON: {{"perguntas_internas": ["pergunta1", "pergunta2"]}}"""
161
  return chamar_gemini_json(prompt, "X1")
162
 
163
  def passo_x2_resolver(x1: Dict, historico: List) -> Dict:
164
  perguntas = x1.get("perguntas_internas", [])
165
  if not perguntas: return {"respostas": []}
 
166
  prompt = f"""ETAPA: X2-RESOLUÇÃO
167
  PERGUNTAS: {json.dumps(perguntas)}
168
  CONTEXTO: {historico_compacto(historico)}
169
+ JSON: {{"respostas": [{{"p": "...", "r": "...", "confianca": "alta|baixa"}}]}}"""
 
 
170
  return chamar_gemini_json(prompt, "X2")
171
 
172
  def passo_2_cenarios(pergunta: str, x2: Dict) -> Dict:
173
  prompt = f"""ETAPA: P2-CENÁRIOS
174
  DADOS X2: {json.dumps(x2)}
175
+ PERGUNTA: {pergunta}
176
+ JSON: {{"decisao": "continuar|parar", "cenarios": ["C1...", "C2..."], "motivo_parada": "..."}}"""
 
 
177
  return chamar_gemini_json(prompt, "P2")
178
 
179
  def passo_7_sintese(p2: Dict, pergunta: str) -> Dict:
180
  prompt = f"""ETAPA: P7-FINAL
181
  CENÁRIOS: {json.dumps(p2)}
182
  PERGUNTA: {pergunta}
 
 
183
  JSON: {{"resposta_final": "texto aqui"}}"""
184
+ return chamar_gemini_json(prompt, "P7", temperatura=0.6)
185
 
186
  # ============================================================================
187
+ # ORQUESTADOR (CORRIGIDO)
188
  # ============================================================================
189
  def processar_pipeline(pergunta: str, historico: List, arquivo_anexo=None, dna=None) -> Tuple[str, List, Dict]:
190
+ debug_print("INICIO PROCESSAMENTO", f"Msg: {pergunta}")
 
191
 
192
+ # --- CORREÇÃO DE ESTADO CRÍTICA ---
193
+ # Se DNA for None, ou vazio {}, ou faltar a chave "historico", recria.
194
+ if dna is None or not isinstance(dna, dict) or "historico" not in dna:
195
+ print("🔄 Inicializando nova estrutura de DNA...")
196
+ dna = criar_dna()
197
 
198
+ # 1. Processamento Anexo
199
  conteudo_anexo, tipo_anexo = processar_anexo(arquivo_anexo)
200
 
 
201
  if tipo_anexo == "pdf":
202
+ prompt_final = f"DOCUMENTO PDF:\n{conteudo_anexo}\n---\nPERGUNTA: {pergunta}"
203
  elif tipo_anexo == "imagem":
204
+ prompt_final = f"[IMAGEM EM BASE64: {conteudo_anexo}]\nAnalise visualmente.\nPERGUNTA: {pergunta}"
205
  else:
206
  prompt_final = pergunta
207
 
208
+ # 2. Execução (Debug via logs de console)
209
  p0 = passo_0_aluno(prompt_final, historico)
210
+ debug_print("P0", p0)
211
 
212
  p1 = passo_1_triagem(prompt_final, p0)
 
213
 
214
  x1 = passo_x1_lacunas(prompt_final, p1)
215
+ debug_print("X1 (Perguntas Internas)", x1)
216
 
217
  x2 = passo_x2_resolver(x1, historico)
 
218
 
219
  p2 = passo_2_cenarios(prompt_final, x2)
220
+ debug_print("P2 (Cenários)", p2)
221
 
 
222
  if p2.get("decisao") == "parar":
223
+ resposta = f"🛑 Parada Solicitada.\nMotivo: {p2.get('motivo_parada')}"
224
  else:
225
  p7 = passo_7_sintese(p2, prompt_final)
226
+ resposta = p7.get("resposta_final", "Erro na geração final (P7)")
227
+
228
+ debug_print("RESPOSTA FINAL", resposta)
229
 
230
+ # 3. Atualização Segura
231
  novo_hist = historico + [
232
  {"role": "user", "content": pergunta},
233
  {"role": "assistant", "content": resposta}
234
  ]
235
 
236
+ # Agora é seguro acessar dna["historico"] pois garantimos a inicialização acima
237
+ dna["historico"].append({
238
+ "turn": dna["meta"]["turnos"],
239
+ "pergunta": pergunta,
240
+ "resposta_abrv": resposta[:50]
241
+ })
242
  dna["meta"]["turnos"] += 1
243
 
244
  return resposta, novo_hist, dna
245
 
246
  # ============================================================================
247
+ # INTERFACE GRÁFICA
248
  # ============================================================================
249
  def chat_interface(msg, hist, anexo, dna_json):
250
+ # Desserializa
251
+ try:
252
+ dna = json.loads(dna_json) if dna_json else {}
253
+ except:
254
+ dna = {} # Garante pelo menos dict vazio para ser tratado no pipeline
255
 
256
+ if hist is None: hist = []
 
257
 
258
+ # Processa
259
+ try:
260
+ resp, novo_hist, dna_new = processar_pipeline(msg, hist, anexo, dna)
261
+ return novo_hist, "", json.dumps(dna_new, indent=2), None
262
+ except Exception as e:
263
+ import traceback
264
+ erro_str = f"ERRO FATAL: {str(e)}"
265
+ print(traceback.format_exc())
266
+ return hist, erro_str, dna_json, None
267
 
268
  if __name__ == "__main__":
269
+ with gr.Blocks(title="Anise Stable", theme=gr.themes.Base()) as demo:
270
  gr.Markdown(TITLE)
271
 
272
  with gr.Row():
273
+ chat = gr.Chatbot(height=500, type="messages", label="Chat Anise")
274
+ dna_box = gr.Code(label="DNA (JSON State)", language="json", lines=20)
275
 
276
  with gr.Row():
277
+ txt_in = gr.Textbox(label="Mensagem", scale=4)
278
+ file_in = gr.File(label="Anexo", scale=1)
279
+
280
+ btn = gr.Button("Enviar", variant="primary")
281
+
282
+ # Conexões
283
+ entradas = [txt_in, chat, file_in, dna_box]
284
+ saidas = [chat, txt_in, dna_box, file_in]
285
+
286
+ btn.click(chat_interface, inputs=entradas, outputs=saidas)
287
+ txt_in.submit(chat_interface, inputs=entradas, outputs=saidas)
288
 
289
+ print("🚀 Servidor online. Acesse o link local abaixo.")
290
  demo.launch(server_name="0.0.0.0", server_port=7860)