caarleexx commited on
Commit
783be1b
·
verified ·
1 Parent(s): cb7613c

Upload ai_studio_code (3).py

Browse files
Files changed (1) hide show
  1. ai_studio_code (3).py +836 -0
ai_studio_code (3).py ADDED
@@ -0,0 +1,836 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Pipeline v10 Refatorada e Comentada - Chatbot com Metacognição Pura.
4
+
5
+ Este arquivo implementa um chatbot avançado utilizando a API Google Gemini.
6
+ A arquitetura se baseia em uma pipeline de múltiplos passos (P0-P8, X1-X2)
7
+ que analisa, raciocina, gera cenários e verifica as respostas antes de
8
+ entregá-las ao usuário.
9
+
10
+ Esta versão inclui:
11
+ - A lógica completa da pipeline com todos os passos.
12
+ - Correções para a compatibilidade com o componente gr.Chatbot.
13
+ - Logs de depuração detalhados para as chamadas da API Gemini.
14
+ - Comentários extensivos em todo o código para fins didáticos.
15
+ """
16
+
17
+ # ============================================================================
18
+ # 1. IMPORTAÇÕES E CONFIGURAÇÃO INICIAL
19
+ # ============================================================================
20
+
21
+ # Módulos padrão do Python
22
+ import json
23
+ import os
24
+ import base64
25
+ import re
26
+ import warnings
27
+ from datetime import datetime
28
+ from typing import Dict, List, Tuple, Any
29
+
30
+ # Bibliotecas de terceiros
31
+ import gradio as gr # Para a criação da interface web
32
+ import google.generativeai as genai # SDK oficial do Google para a API Gemini
33
+
34
+ # Ignora avisos de "FutureWarning" que podem ser gerados por dependências
35
+ # da API do Google, mantendo o console mais limpo.
36
+ warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
37
+
38
+ # --- Configuração da API Gemini ---
39
+
40
+ # Carrega a chave da API a partir de uma variável de ambiente chamada "GOOGLE_API_KEY".
41
+ # Esta é uma prática de segurança essencial para não expor chaves secretas no código.
42
+ API_KEY = os.getenv("GOOGLE_API_KEY")
43
+ if not API_KEY:
44
+ # Se a chave não for encontrada, levanta um erro claro para o usuário.
45
+ raise ValueError("A variável de ambiente GOOGLE_API_KEY não foi configurada.")
46
+
47
+ # Configura o SDK do Google com a chave de API fornecida.
48
+ genai.configure(api_key=API_KEY)
49
+
50
+ # --- Definição dos Modelos ---
51
+
52
+ # Define os modelos a serem usados na pipeline.
53
+ # O "Counselor" é responsável pela maior parte do raciocínio e geração.
54
+ COUNSELOR_MODEL = genai.GenerativeModel("gemini-1.5-flash")
55
+ # O "Supervisor" é um modelo focado na verificação final e garantia de qualidade.
56
+ SUPERVISOR_MODEL = genai.GenerativeModel("gemini-1.5-flash")
57
+
58
+ # Título que será exibido na interface do Gradio.
59
+ TITLE = "# 🚀 Pipeline v10 REATORADA E COMENTADA\n**P0-P1 → X1-X2 → P2-P8 (com Metacognição Pura e Verificação)**"
60
+
61
+
62
+ # ============================================================================
63
+ # 2. PROMPTS CENTRALIZADOS
64
+ # ============================================================================
65
+ # Centralizar os prompts em um dicionário torna o código mais limpo, fácil de ler
66
+ # e de manter. Cada chave corresponde a um passo da pipeline.
67
+
68
+ PROMPTS = {
69
+ "P0_ALUNO": """
70
+ Você é um METACOGNITIVO (pensamento interno, NÃO comunicação).
71
+
72
+ TURNO ANTERIOR:
73
+ User: {turno_anterior_user}
74
+ Assistant: {turno_anterior_assistant}
75
+
76
+ NOVA MENSAGEM: {pergunta}
77
+ CONTEXTO VAGO: {historico_compacto}
78
+
79
+ ---
80
+
81
+ Responda EM METACOGNIÇÃO PURA - TELEGRÁFICO
82
+ NÃO use frases completas. APENAS essência semântica com conectores mínimos.
83
+
84
+ EXEMPLO CERTO: entendeu-sim | pergunta-nova | avança-tópico | não-reformulou
85
+
86
+ RETORNE JSON:
87
+ {{
88
+ "usuario_entendeu": "sim|não",
89
+ "evidencias": ["entendeu-pergunta", "pediu-clarificação"],
90
+ "usuario_corrigiu": "sim|não",
91
+ "correcao_detectada": null|"texto-correção",
92
+ "correcao_valida": "sim|não|null",
93
+ "o_que_melhorar": null|"explicar-X-melhor",
94
+ "decisao": "prosseguir-passo1|reexplicar-passo6|atualizar-resposta-anterior",
95
+ "motivo": "texto-curtíssimo"
96
+ }}
97
+ """,
98
+ "P1_TRIAGEM": """
99
+ METACOGNIÇÃO - TRIAGEM INICIAL.
100
+
101
+ CONTEXTO VAGO: {contexto_vago}
102
+ HISTÓRICO RECENTE (últimas 3): {historico_recente}
103
+ P0: {p0}
104
+ PERGUNTA: {pergunta}
105
+
106
+ ---
107
+
108
+ CLASSIFIQUE EM TELEGRÁFICO (sem frases).
109
+
110
+ RETORNE JSON:
111
+ {{
112
+ "tipo": "objetiva|factual|subjetiva|aberta",
113
+ "sinais": ["tem-resposta-única-verificável", "sem-contexto-pessoal"],
114
+ "confianca": "alta|média|baixa",
115
+ "decisao": "responder-direto|analisar-profundamente",
116
+ "razao": "curtíssima",
117
+ "dados_fatuais": ["fato1", "fato2"],
118
+ "divergencias_fatuais": ["possível-ambiguidade-1"],
119
+ "objetivo_principal": "objetivo-primário-identificado",
120
+ "objetivo_secundario": ["objetivo-secundário-1"]
121
+ }}
122
+ """,
123
+ "X1_PERGUNTAS_NECESSARIAS": """
124
+ X1-PERGUNTAS CRÍTICAS - TELEGRÁFICO
125
+ P1: {p1}
126
+ CONTEXTO: {historico_compacto}
127
+ PERGUNTA PRINCIPAL: {pergunta}
128
+
129
+ ---
130
+ Analise as lacunas factuais e subjetivas na pergunta do usuário e no contexto.
131
+ Liste as perguntas essenciais que você precisa responder internamente antes de formular a resposta final.
132
+
133
+ RETORNE JSON:
134
+ {{"perguntas": [
135
+ {{"texto": "pergunta-curta-e-essencial", "necessidade": "alta|média|baixa", "relevancia": "alta|média"}}
136
+ ]}}
137
+ """,
138
+ "X2_RESOLVER_PERGUNTAS": """
139
+ X2-RESOLUÇÃO INTERNA - TELEGRÁFICO
140
+ P1: {p1}
141
+ PERGUNTAS CRÍTICAS (X1): {perguntas_x1}
142
+ CONTEXTO: {historico_compacto}
143
+
144
+ ---
145
+ Para cada pergunta crítica levantada no passo anterior, forneça uma resposta curta e direta baseada no seu conhecimento.
146
+ Avalie sua confiança e o potencial de conflito ou ambiguidade em cada resposta.
147
+
148
+ RETORNE JSON:
149
+ {{"respostas": [
150
+ {{"pergunta": "texto-original-da-pergunta-x1",
151
+ "resposta": "resposta-curta-e-direta",
152
+ "confianca": "alta|média|baixa",
153
+ "conflito": "alto|médio|baixo",
154
+ "razao": "explicação-em-1-2-palavras"}}
155
+ ]}}
156
+ """,
157
+ "P2_CENARIOS": """
158
+ METACOGNIÇÃO - GERAÇÃO DE CENÁRIOS.
159
+ CONTEXTO VAGO: {historico_compacto}
160
+ TRIAGEM P1: {p1}
161
+ X1-PERGUNTAS: {x1}
162
+ X2-RESPOSTAS: {x2}
163
+ PERGUNTA ORIGINAL: {pergunta}
164
+
165
+ ---
166
+
167
+ Mapeie cenários possíveis onde a resposta à pergunta original mudaria significativamente. Pense nas diferentes perspectivas, contextos ou premissas que alteram a conclusão. Use formato telegráfico.
168
+
169
+ RETORNE JSON:
170
+ {{
171
+ "cenarios": {{
172
+ "provaveis": [{{"id": "C1", "desc": "cenário-provável-1-comprimido", "contexto-relevante": "descreva-o-contexto"}}]
173
+ }},
174
+ "total": 1,
175
+ "tipo_resposta": "múltipla|unívoca",
176
+ "confianca": "alta|média|baixa",
177
+ "decisao": "prosseguir|pedir-esclarecimento",
178
+ "pergunta_esclarecimento": null|"texto-da-pergunta-para-o-usuario"
179
+ }}
180
+ """,
181
+ "P3_ISOLAR_CENARIOS": """
182
+ METACOGNIÇÃO - EXPLORAÇÃO DE CENÁRIO ISOLADO.
183
+ CENÁRIO P2: {cenario}
184
+
185
+ ---
186
+ Para este cenário específico, defina a essência da resposta em formato telegráfico. Qual seria a conclusão principal e quais as lacunas de informação restantes?
187
+
188
+ RETORNE JSON:
189
+ {{"id": "{cenario_id}",
190
+ "resposta_essencia": "conclusão-principal-e-razoes-em-palavras-chave",
191
+ "confianca": "alta|média|baixa",
192
+ "lacunas": "contexto-ainda-ausente|null"
193
+ }}
194
+ """,
195
+ "P4_CRUZAR_VALIDACOES": """
196
+ METACOGNIÇÃO - ABSTRAÇÃO DE CONHECIMENTO.
197
+ P1 (Triagem): {p1}
198
+ P2 (Cenários): {p2}
199
+ P3 (Exploração): {p3}
200
+ X2 (Respostas Internas): {x2}
201
+
202
+ ---
203
+
204
+ Identifique os princípios fundamentais, teorias ou símbolos arquetípicos que sustentam as respostas nos cenários explorados. Abstraia o conhecimento para um nível mais alto. Use formato telegráfico.
205
+
206
+ RETORNE JSON:
207
+ {{"principios": [{{"nome": "Custo-Oportunidade", "essencia": "escolher-X-implica-renunciar-Y"}}],
208
+ "simbolos": [{{"nome": "Jornada-do-Herói", "essencia": "transformação-ocorre-através-de-desafios"}}],
209
+ "principio_central": "nome-do-principio-mais-importante",
210
+ "simbolo_dominante": "nome-do-simbolo-mais-relevante"
211
+ }}
212
+ """,
213
+ "P5_LACUNAS_FINAIS": """
214
+ METACOGNIÇÃO - ANÁLISE DE INCERTEZA.
215
+ P1 (Triagem): {p1}
216
+ P4 (Princípios): {p4}
217
+
218
+ ---
219
+
220
+ Avalie o balanço entre certezas e dúvidas com base em toda a análise feita até agora. A informação disponível é suficiente para dar uma resposta confiante? Use formato telegráfico.
221
+
222
+ RETORNE JSON:
223
+ {{"analise_cenarios": [{{"cenario": "C1", "certezas": ["certeza1"], "duvidas": ["dúvida1"]}}],
224
+ "confianca_global": "alta|média|baixa",
225
+ "balanco": "certezas-superam|equilibrado|duvidas-superam",
226
+ "decisao": "responder|questionar",
227
+ "questionamento": null|"texto-da-pergunta-para-o-usuario-se-a-confianca-for-baixa"
228
+ }}
229
+ """,
230
+ "P6_PONDERAR": """
231
+ METACOGNIÇÃO - JULGAMENTO FINAL (JUIZ DA VERDADE).
232
+ P2 (Cenários): {p2}
233
+ P4 (Princípios): {p4}
234
+ P5 (Lacunas): {p5}
235
+
236
+ ---
237
+
238
+ Aja como um árbitro socrático. Com base em toda a metacognição, valide as "verdades" encontradas e decida o nível de consciência sobre a complexidade da resposta. Use formato telegráfico.
239
+
240
+ RETORNE JSON:
241
+ {{"verdade_principal": "a-conclusao-mais-provavel-e-confiavel",
242
+ "nuances_importantes": ["nuance1", "nuance2"],
243
+ "confianca_final": "alta|média|baixa",
244
+ "decisao": "exibir-resposta-completa|exibir-resposta-com-ressalvas|reprocessar",
245
+ "nivel_consciencia": "alto|médio|baixo"
246
+ }}
247
+ """,
248
+ "P7_SINTETIZAR": """
249
+ Você é um SINTETIZADOR especialista em transformar METACOGNIÇÃO CRUA em PROSA HUMANIZADA e empática.
250
+
251
+ DADOS DO JULGAMENTO (P6): {p6}
252
+
253
+ ---
254
+
255
+ TAREFA: Converta a análise telegráfica do 'Juiz da Verdade' em uma resposta textual fluida, natural e útil para o usuário.
256
+
257
+ INSTRUÇÕES:
258
+ 1. Use conectores naturais (ex: "porque", "portanto", "isso significa que").
259
+ 2. Expanda abreviações e jargões para uma linguagem clara.
260
+ 3. Estruture a resposta em parágrafos lógicos (introdução, desenvolvimento, nuances/conclusão).
261
+ 4. Incorpore os princípios e nuances de forma natural na explicação.
262
+ 5. Adote um tom de conselheiro: amigável, empático e empoderador.
263
+ 6. NÃO invente informações. Baseie-se estritamente nos dados do P6.
264
+
265
+ RETORNE A RESPOSTA EM PROSA DENTRO DE UM JSON:
266
+ {{"resposta": "Aqui vai o texto fluido, natural e humano..."}}
267
+ """,
268
+ "P8_VERIFICAR": """
269
+ Você é um VERIFICADOR FINAL, um guardião rigoroso da qualidade da resposta.
270
+
271
+ RESPOSTA SINTETIZADA (P7):
272
+ {resposta_sintetizada}
273
+
274
+ ANÁLISE DO JUIZ (P6):
275
+ {p6}
276
+
277
+ ---
278
+
279
+ Realize uma verificação tripla na resposta sintetizada. Seja crítico.
280
+
281
+ 1. **VERIFICAÇÃO FACTUAL**: A resposta contém fatos incorretos ou não sustentados pela análise do P6?
282
+ 2. **VERIFICAÇÃO LÓGICA**: Existem falácias, saltos de lógica ou contradições? A conclusão segue a linha de raciocínio?
283
+ 3. **VERIFICAÇÃO ÉTICA**: A resposta é apropriada, segura e imparcial? Inclui os avisos ou ressalvas necessários?
284
+
285
+ RETORNE SEU VEREDITO EM JSON:
286
+ {{"verificacao_factual": {{"aprovada": true|false, "problemas": ["descrição do problema se houver"]}},
287
+ "verificacao_logica": {{"aprovada": true|false, "problemas": []}},
288
+ "verificacao_etica": {{"aprovada": true|false, "problemas": []}},
289
+ "todas_aprovadas": true|false,
290
+ "decisao": "exibir-resposta-original|corrigir-e-exibir",
291
+ "resposta_corrigida": null|"texto da versão corrigida e melhorada da resposta"
292
+ }}
293
+ """
294
+ }
295
+
296
+ # ============================================================================
297
+ # 3. CLASSES E FUNÇÕES HELPERS (UTILITÁRIOS)
298
+ # ============================================================================
299
+
300
+ class Logger:
301
+ """
302
+ Uma classe simples para registrar logs formatados no console.
303
+ Ajuda a visualizar o fluxo de execução e a depurar problemas.
304
+ """
305
+ def __init__(self, verbose: bool = True):
306
+ self.verbose = verbose
307
+ self.logs = []
308
+
309
+ def log(self, msg: str, level: str = "INFO"):
310
+ """Registra uma mensagem de log com timestamp, nível e formatação."""
311
+ timestamp = datetime.now().strftime("%H:%M:%S")
312
+ log_msg = f"[{timestamp}] [{level.upper()}] {msg}"
313
+ self.logs.append(log_msg)
314
+ if self.verbose:
315
+ print(log_msg)
316
+ # Imprime uma linha divisória para logs importantes, melhorando a visualização.
317
+ if level.upper() in ["TASK", "START", "SUCCESS", "ERROR"]:
318
+ print("=" * 70)
319
+
320
+ # Instância global do Logger para ser usada em todo o script.
321
+ logger = Logger(verbose=True)
322
+
323
+ def processar_anexo(arquivo: Any) -> Tuple[str, str]:
324
+ """
325
+ Processa um arquivo enviado pela interface do Gradio.
326
+ Atualmente, a lógica de extração está simplificada, mas a estrutura
327
+ permite a implementação de leitores de PDF, imagens, etc.
328
+
329
+ Args:
330
+ arquivo: O objeto de arquivo vindo do Gradio.
331
+
332
+ Returns:
333
+ Uma tupla contendo (conteúdo_processado, tipo_do_arquivo).
334
+ """
335
+ if arquivo is None:
336
+ return "", "nenhum"
337
+
338
+ # O objeto 'arquivo' do Gradio tem um atributo '.name' que contém o caminho temporário do arquivo.
339
+ caminho_arquivo = arquivo.name
340
+
341
+ try:
342
+ if caminho_arquivo.lower().endswith('.pdf'):
343
+ # A lógica real de leitura de PDF (com PyPDF2, por exemplo) iria aqui.
344
+ logger.log("Arquivo PDF detectado.", "INFO")
345
+ return "[Conteúdo do PDF iria aqui]", "pdf"
346
+ elif any(caminho_arquivo.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif']):
347
+ # A lógica real de processamento de imagem (com Pillow, por exemplo) iria aqui.
348
+ logger.log("Arquivo de imagem detectado.", "INFO")
349
+ return "[Dados da imagem iriam aqui]", "imagem"
350
+ return "", "nao_suportado"
351
+ except Exception as e:
352
+ logger.log(f"Erro ao processar anexo: {e}", "ERROR")
353
+ return "", "erro"
354
+
355
+ def construir_prompt_com_anexo(pergunta: str, anexo_conteudo: str, tipo_anexo: str) -> str:
356
+ """
357
+ Adiciona o conteúdo do anexo ao prompt da pergunta inicial para dar contexto ao modelo.
358
+
359
+ Args:
360
+ pergunta: A pergunta original do usuário.
361
+ anexo_conteudo: O conteúdo extraído do anexo.
362
+ tipo_anexo: O tipo do anexo ('pdf', 'imagem', etc.).
363
+
364
+ Returns:
365
+ O prompt final combinado.
366
+ """
367
+ if not anexo_conteudo or tipo_anexo in ["nenhum", "erro", "nao_suportado"]:
368
+ return pergunta
369
+ # Formata o prompt de maneira diferente dependendo do tipo de arquivo.
370
+ if tipo_anexo == "pdf":
371
+ return f"Com base no documento PDF abaixo, responda à pergunta.\n\nDOCUMENTO:\n---\n{anexo_conteudo}\n---\n\nPERGUNTA: {pergunta}"
372
+ if tipo_anexo == "imagem":
373
+ return f"Com base na imagem anexada, responda à pergunta: {pergunta}"
374
+ return pergunta
375
+
376
+ def parse_json_ultra_robusto(texto: str) -> Dict:
377
+ """
378
+ Extrai um objeto JSON de uma string, mesmo que ela contenha texto adicional
379
+ ou formatação incorreta (como os marcadores ```json).
380
+
381
+ Args:
382
+ texto: A string retornada pela API que pode conter um JSON.
383
+
384
+ Returns:
385
+ Um dicionário Python com o JSON extraído ou um dicionário de erro.
386
+ """
387
+ if not texto:
388
+ return {"erro": "Texto de entrada vazio"}
389
+
390
+ # 1. Tenta extrair JSON de blocos de código (```json ... ```), que é comum em modelos de linguagem.
391
+ match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', texto, re.DOTALL)
392
+ if match:
393
+ json_str = match.group(1)
394
+ else:
395
+ json_str = texto
396
+
397
+ # 2. Tenta carregar a string extraída como JSON.
398
+ try:
399
+ return json.loads(json_str)
400
+ except json.JSONDecodeError:
401
+ # 3. Se falhar, tenta um método de "fallback": encontrar o primeiro '{' e o último '}'
402
+ # e tentar fazer o parse do conteúdo entre eles. Isso ajuda a limpar lixo no início/fim.
403
+ try:
404
+ inicio = json_str.find('{')
405
+ fim = json_str.rfind('}') + 1
406
+ if inicio != -1 and fim != 0:
407
+ return json.loads(json_str[inicio:fim])
408
+ except json.JSONDecodeError:
409
+ logger.log("Falha na extração robusta de JSON.", "WARN")
410
+ return {"erro": "parse_falhou", "fallback_text": texto[:500]}
411
+
412
+ def chamar_gemini_json(modelo: genai.GenerativeModel, prompt: str, temperatura: float = 0.5, max_tokens: int = 2000) -> Dict:
413
+ """
414
+ Envia um prompt para o modelo Gemini, solicita uma saída JSON, analisa a resposta
415
+ e inclui logs detalhados para depuração.
416
+
417
+ Args:
418
+ modelo: A instância do modelo Gemini a ser usada.
419
+ prompt: O prompt formatado para a tarefa específica.
420
+ temperatura: Controla a criatividade da resposta (valores mais altos = mais criativo).
421
+ max_tokens: O número máximo de tokens na resposta.
422
+
423
+ Returns:
424
+ Um dicionário Python com a resposta do modelo ou um dicionário de erro.
425
+ """
426
+ # Adiciona uma instrução final e explícita ao prompt para garantir que o modelo retorne JSON.
427
+ prompt_completo = f"{prompt}\n\n---\n\n**INSTRUÇÃO OBRIGATÓRIA: Sua resposta DEVE ser um único e válido objeto JSON. Não inclua texto antes ou depois do JSON.**"
428
+
429
+ # === INÍCIO DO LOG DE DEPURAÇÃO (INPUT) ===
430
+ # Imprime o prompt exato que está sendo enviado para a API.
431
+ # Essencial para depurar o comportamento do modelo.
432
+ print("\n" + "="*25 + f" 💬 API INPUT PARA [{modelo.model_name}] " + "="*25)
433
+ print(prompt_completo)
434
+ print("="*78 + "\n")
435
+ logger.log(f"Enviando prompt ({len(prompt_completo)} chars) para {modelo.model_name}", "DEBUG")
436
+ # === FIM DO LOG DE DEPURAÇÃO (INPUT) ===
437
+
438
+ try:
439
+ # Realiza a chamada para a API Gemini.
440
+ response = modelo.generate_content(
441
+ prompt_completo,
442
+ generation_config=genai.types.GenerationConfig(
443
+ temperature=temperatura,
444
+ max_output_tokens=max_tokens,
445
+ )
446
+ )
447
+
448
+ # Extrai o texto da resposta.
449
+ resposta_bruta = response.text or ""
450
+
451
+ # === INÍCIO DO LOG DE DEPURAÇÃO (OUTPUT) ===
452
+ # Imprime a resposta bruta recebida da API antes de qualquer processamento.
453
+ # Crucial para ver o que o modelo realmente retornou.
454
+ print("\n" + "="*25 + f" 📥 API RAW OUTPUT DE [{modelo.model_name}] " + "="*25)
455
+ print(resposta_bruta)
456
+ print("="*78 + "\n")
457
+ logger.log(f"Gemini RAW ({len(resposta_bruta)} chars): {resposta_bruta[:400]}...", "DEBUG")
458
+ # === FIM DO LOG DE DEPURAÇÃO (OUTPUT) ===
459
+
460
+ # Usa o parser robusto para converter a resposta de texto em um dicionário Python.
461
+ resultado_json = parse_json_ultra_robusto(resposta_bruta)
462
+ return resultado_json
463
+
464
+ except Exception as e:
465
+ # Captura qualquer erro durante a chamada da API (ex: problemas de conexão, erros de permissão).
466
+ logger.log(f"Erro na chamada da API Gemini: {e}", "ERROR")
467
+ return {"erro": f"API_ERROR: {str(e)}"}
468
+
469
+ def historico_compacto(historico: List[Dict]) -> str:
470
+ """
471
+ Gera uma string curta com as últimas interações do chat para usar como contexto nos prompts.
472
+
473
+ Args:
474
+ historico: O histórico de conversa no formato interno.
475
+
476
+ Returns:
477
+ Uma string resumida da conversa recente.
478
+ """
479
+ if not historico:
480
+ return "Nenhuma conversa anterior."
481
+
482
+ # Pega as últimas 4 mensagens, formata e junta em uma única string.
483
+ compacto = []
484
+ for msg in historico[-4:]:
485
+ role = "Usuário" if msg["role"] == "user" else "Assistente"
486
+ content = msg["content"].split('\n')[:80] # Pega só a primeira linha, até 80 caracteres.
487
+ compacto.append(f"{role}: {content}")
488
+
489
+ return "\n".join(compacto)
490
+
491
+ def criar_dna() -> Dict:
492
+ """
493
+ Inicializa a estrutura de dados 'DNA' que armazena o estado e metadados da conversa.
494
+
495
+ Returns:
496
+ Um dicionário com a estrutura inicial do DNA.
497
+ """
498
+ return {
499
+ "historico_chat": [],
500
+ "meta": {"total_turnos": 0}
501
+ }
502
+
503
+ # ============================================================================
504
+ # 4. PASSOS DA PIPELINE (P0-P8, X1-X2)
505
+ # ============================================================================
506
+ # Cada função representa um passo de raciocínio da pipeline. Elas preparam
507
+ # um prompt, chamam a API e retornam o resultado processado.
508
+
509
+ def passo_0_aluno(pergunta: str, historico: List[Dict]) -> Dict:
510
+ """P0: Analisa a pergunta atual em relação à resposta anterior (metacognição)."""
511
+ logger.log("🧠 P0-ALUNO - Analisando feedback do usuário", "TASK")
512
+ turno_anterior_user = historico[-2]['content'] if len(historico) >= 2 else 'N/A'
513
+ turno_anterior_assistant = historico[-1]['content'] if len(historico) >= 1 else 'N/A'
514
+
515
+ prompt = PROMPTS["P0_ALUNO"].format(
516
+ turno_anterior_user=turno_anterior_user,
517
+ turno_anterior_assistant=turno_anterior_assistant,
518
+ pergunta=pergunta,
519
+ historico_compacto=historico_compacto(historico)
520
+ )
521
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.3)
522
+
523
+ def passo_1_triagem(pergunta: str, p0: Dict, historico: List[Dict]) -> Dict:
524
+ """P1: Faz uma triagem inicial da pergunta para classificar tipo e complexidade."""
525
+ logger.log("📊 P1-TRIAGEM - Classificando a pergunta", "TASK")
526
+ historico_recente_json = json.dumps(historico[-6:], indent=2, ensure_ascii=False)
527
+ p0_json = json.dumps(p0, indent=2, ensure_ascii=False)
528
+
529
+ prompt = PROMPTS["P1_TRIAGEM"].format(
530
+ contexto_vago=historico_compacto(historico),
531
+ historico_recente=historico_recente_json,
532
+ p0=p0_json,
533
+ pergunta=pergunta
534
+ )
535
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.4)
536
+
537
+ def passo_x1_perguntas_necessarias(pergunta: str, p1: Dict, historico: List[Dict]) -> Dict:
538
+ """X1: Identifica quais perguntas internas precisam ser respondidas para resolver a questão."""
539
+ logger.log("❓ X1-PERGUNTAS CRÍTICAS - Identificando lacunas", "TASK")
540
+ prompt = PROMPTS["X1_PERGUNTAS_NECESSARIAS"].format(
541
+ p1=json.dumps(p1, indent=2),
542
+ historico_compacto=historico_compacto(historico),
543
+ pergunta=pergunta
544
+ )
545
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, max_tokens=1500)
546
+
547
+ def passo_x2_resolver_perguntas(p1: Dict, x1: Dict, historico: List[Dict]) -> Dict:
548
+ """X2: Responde internamente às perguntas levantadas em X1."""
549
+ logger.log("✅ X2-RESOLVER PERGUNTAS - Buscando conhecimento interno", "TASK")
550
+ perguntas_x1 = x1.get("perguntas", [])
551
+ prompt = PROMPTS["X2_RESOLVER_PERGUNTAS"].format(
552
+ p1=json.dumps(p1, indent=2),
553
+ perguntas_x1=json.dumps(perguntas_x1, indent=2),
554
+ historico_compacto=historico_compacto(historico)
555
+ )
556
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, max_tokens=2000)
557
+
558
+ def passo_2_cenarios(pergunta: str, p1: Dict, x1: Dict, x2: Dict, historico: List[Dict]) -> Dict:
559
+ """P2: Gera diferentes cenários ou perspectivas para a resposta."""
560
+ logger.log("🎯 P2-CENÁRIOS - Mapeando possibilidades", "TASK")
561
+ prompt = PROMPTS["P2_CENARIOS"].format(
562
+ historico_compacto=historico_compacto(historico),
563
+ p1=json.dumps(p1, indent=2),
564
+ x1=json.dumps(x1, indent=2),
565
+ x2=json.dumps(x2, indent=2),
566
+ pergunta=pergunta
567
+ )
568
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.6)
569
+
570
+ def passo_3_isolar_cenarios(p2: Dict) -> Dict:
571
+ """P3: Explora cada cenário gerado em P2 de forma isolada."""
572
+ logger.log("🔍 P3-ISOLAR CENÁRIOS - Explorando cada cenário", "TASK")
573
+ exploracoes = []
574
+ cenarios = p2.get('cenarios', {}).get('provaveis', [])
575
+ for c in cenarios[:3]: # Limita a 3 cenários para evitar complexidade e custos excessivos.
576
+ prompt = PROMPTS["P3_ISOLAR_CENARIOS"].format(
577
+ cenario=json.dumps(c, indent=2),
578
+ cenario_id=c.get('id')
579
+ )
580
+ exploracoes.append(chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.5))
581
+
582
+ return {"exploracoes_isoladas": exploracoes}
583
+
584
+ def passo_4_cruzar_validacoes(p1: Dict, p2: Dict, p3: Dict, x2: Dict) -> Dict:
585
+ """P4: Abstrai princípios e símbolos a partir da análise dos cenários."""
586
+ logger.log("🔗 P4-VALIDAÇÃO CRUZADA - Identificando princípios", "TASK")
587
+ prompt = PROMPTS["P4_CRUZAR_VALIDACOES"].format(
588
+ p1=json.dumps(p1, indent=2),
589
+ p2=json.dumps(p2, indent=2),
590
+ p3=json.dumps(p3, indent=2),
591
+ x2=json.dumps(x2, indent=2)
592
+ )
593
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.4)
594
+
595
+ def passo_5_lacunas_finais(p1: Dict, p4: Dict) -> Dict:
596
+ """P5: Realiza uma análise final de certezas vs. dúvidas."""
597
+ logger.log("🚨 P5-LACUNAS FINAIS - Avaliando confiança global", "TASK")
598
+ prompt = PROMPTS["P5_LACUNAS_FINAIS"].format(
599
+ p1=json.dumps(p1, indent=2),
600
+ p4=json.dumps(p4, indent=2)
601
+ )
602
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.3)
603
+
604
+ def passo_6_ponderar(p2: Dict, p4: Dict, p5: Dict) -> Dict:
605
+ """P6: Atua como um 'Juiz da Verdade', ponderando toda a análise para uma decisão final."""
606
+ logger.log("⚖️ P6-PONDERAR (JUIZ) - Tomando a decisão final", "TASK")
607
+ prompt = PROMPTS["P6_PONDERAR"].format(
608
+ p2=json.dumps(p2, indent=2),
609
+ p4=json.dumps(p4, indent=2),
610
+ p5=json.dumps(p5, indent=2)
611
+ )
612
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.4)
613
+
614
+ def passo_7_sintetizar(p6: Dict) -> Dict:
615
+ """P7: Converte a análise metacognitiva final em uma resposta em prosa humanizada."""
616
+ logger.log("✍️ P7-SINTETIZAR - Gerando prosa humanizada", "TASK")
617
+ prompt = PROMPTS["P7_SINTETIZAR"].format(p6=json.dumps(p6, indent=2))
618
+ # Usa uma temperatura mais alta para uma resposta mais criativa e fluida.
619
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.7, max_tokens=2500)
620
+
621
+ def passo_8_verificar(p6: Dict, p7: Dict) -> Dict:
622
+ """P8: Realiza uma verificação tripla (factual, lógica, ética) na resposta final."""
623
+ logger.log("🛡️ P8-VERIFICAR (SUPERVISOR) - Garantindo a qualidade", "TASK")
624
+ resposta_sintetizada = p7.get("resposta", "")
625
+ prompt = PROMPTS["P8_VERIFICAR"].format(
626
+ resposta_sintetizada=resposta_sintetizada,
627
+ p6=json.dumps(p6, indent=2)
628
+ )
629
+ # Usa o modelo Supervisor e uma temperatura baixa para uma verificação mais objetiva.
630
+ return chamar_gemini_json(SUPERVISOR_MODEL, prompt, temperatura=0.2)
631
+
632
+ # ============================================================================
633
+ # 5. ORQUESTRADOR PRINCIPAL
634
+ # ============================================================================
635
+
636
+ def executar_pipeline(pergunta: str, historico: List[Dict], arquivo_anexo: Any, dna: Dict) -> Tuple[str, List, Dict]:
637
+ """
638
+ Orquestra a execução de todos os passos da pipeline, desde a análise
639
+ inicial até a verificação e entrega da resposta final.
640
+
641
+ Args:
642
+ pergunta: A pergunta enviada pelo usuário.
643
+ historico: O histórico da conversa no formato interno.
644
+ arquivo_anexo: O arquivo enviado pelo usuário (se houver).
645
+ dna: O dicionário de estado da conversa.
646
+
647
+ Returns:
648
+ Uma tupla contendo (resposta_final, novo_historico_interno, dna_atualizado).
649
+ """
650
+ logger.log(f"PIPELINE v10 INICIADA: '{pergunta[:50]}...'", "START")
651
+
652
+ if not pergunta or not pergunta.strip():
653
+ return "Por favor, digite uma pergunta válida.", historico, dna
654
+
655
+ # 1. Processamento de Anexos (se houver).
656
+ conteudo_anexo, tipo_anexo = processar_anexo(arquivo_anexo)
657
+ pergunta_final = construir_prompt_com_anexo(pergunta, conteudo_anexo, tipo_anexo)
658
+
659
+ # Bloco try-except para capturar qualquer erro inesperado durante a complexa execução da pipeline.
660
+ try:
661
+ # 2. Execução sequencial dos passos da pipeline. O output de um passo serve de input para o próximo.
662
+ p0 = passo_0_aluno(pergunta_final, historico)
663
+ p1 = passo_1_triagem(pergunta_final, p0, historico)
664
+ x1 = passo_x1_perguntas_necessarias(pergunta_final, p1, historico)
665
+ x2 = passo_x2_resolver_perguntas(p1, x1, historico)
666
+ p2 = passo_2_cenarios(pergunta_final, p1, x1, x2, historico)
667
+
668
+ # 3. Ponto de Interrupção: Se o modelo decidir que precisa de mais informações, ele interrompe aqui.
669
+ if p2.get("decisao") == "pedir-esclarecimento":
670
+ esclarecimento = p2.get("pergunta_esclarecimento", "Poderia fornecer mais detalhes?")
671
+ logger.log(f"Pipeline interrompida para pedir esclarecimento: {esclarecimento}", "INFO")
672
+ return f"❓ Para te dar uma resposta mais precisa, preciso de um esclarecimento:\n\n> {esclarecimento}", historico, dna
673
+
674
+ # 4. Continuação da pipeline com os passos de aprofundamento.
675
+ p3 = passo_3_isolar_cenarios(p2)
676
+ p4 = passo_4_cruzar_validacoes(p1, p2, p3, x2)
677
+ p5 = passo_5_lacunas_finais(p1, p4)
678
+
679
+ # 5. Ponto de Interrupção: Se a confiança global for muito baixa, o modelo pode se recusar a responder.
680
+ if p5.get("decisao") == "questionar":
681
+ questionamento = p5.get("questionamento", "Não tenho informações suficientes para responder.")
682
+ logger.log(f"Pipeline interrompida por baixa confiança: {questionamento}", "INFO")
683
+ return f"🤔 {questionamento}", historico, dna
684
+
685
+ # 6. Passos finais de Julgamento, Geração e Verificação.
686
+ p6 = passo_6_ponderar(p2, p4, p5)
687
+ p7 = passo_7_sintetizar(p6)
688
+ p8 = passo_8_verificar(p6, p7)
689
+
690
+ # 7. Seleção da Resposta Final com base na verificação do Supervisor (P8).
691
+ if p8.get("todas_aprovadas") and p8.get("decisao") != "corrigir-e-exibir":
692
+ resposta_final = p7.get("resposta", "Não foi possível gerar uma resposta.")
693
+ else:
694
+ # Se a verificação falhou, usa a versão corrigida pelo supervisor.
695
+ resposta_final = p8.get("resposta_corrigida", p7.get("resposta", "Ocorreu um erro na verificação final."))
696
+ logger.log("Resposta foi corrigida pelo Supervisor (P8).", "WARN")
697
+
698
+ except Exception as e:
699
+ logger.log(f"Erro crítico na execução da pipeline: {e}", "ERROR")
700
+ return f"❌ Ocorreu um erro inesperado durante o processamento: {e}", historico, dna
701
+
702
+ # 8. Atualiza o histórico da conversa e o estado (DNA).
703
+ novo_historico = historico + [
704
+ {"role": "user", "content": pergunta},
705
+ {"role": "assistant", "content": resposta_final}
706
+ ]
707
+ dna["historico_chat"].append({"user": pergunta, "assistant": resposta_final})
708
+ dna["meta"]["total_turnos"] += 1
709
+
710
+ logger.log("PIPELINE CONCLUÍDA COM SUCESSO", "SUCCESS")
711
+ return resposta_final, novo_historico, dna
712
+
713
+ # ============================================================================
714
+ # 6. INTERFACE COM GRADIO
715
+ # ============================================================================
716
+
717
+ def converter_historico_de_gradio(historico_gradio: List[List[str]]) -> List[Dict]:
718
+ """
719
+ Converte o formato de histórico do Chatbot Gradio para o formato interno da pipeline.
720
+ - Formato Gradio: [ ["pergunta1", "resposta1"], ["pergunta2", "resposta2"] ]
721
+ - Formato Interno: [ {"role": "user", "content": "p1"}, {"role": "assistant", "content": "r1"} ]
722
+ """
723
+ historico_interno = []
724
+ if not historico_gradio:
725
+ return historico_interno
726
+ for turno in historico_gradio:
727
+ pergunta, resposta = turno if turno and turno else "", turno if turno and len(turno) > 1 and turno else ""
728
+ if pergunta:
729
+ historico_interno.append({"role": "user", "content": pergunta})
730
+ if resposta:
731
+ historico_interno.append({"role": "assistant", "content": resposta})
732
+ return historico_interno
733
+
734
+ def converter_historico_para_gradio(historico_interno: List[Dict]) -> List[List[str]]:
735
+ """
736
+ Converte o formato de histórico interno da pipeline para o formato do Chatbot Gradio.
737
+ """
738
+ historico_gradio = []
739
+ if not historico_interno:
740
+ return historico_gradio
741
+ # Itera de 2 em 2 para formar os pares [pergunta_usuario, resposta_assistente].
742
+ for i in range(0, len(historico_interno), 2):
743
+ if i + 1 < len(historico_interno):
744
+ pergunta = historico_interno[i]['content']
745
+ resposta = historico_interno[i+1]['content']
746
+ historico_gradio.append([pergunta, resposta])
747
+ return historico_gradio
748
+
749
+ def chat_interface(pergunta: str, historico_gradio: List[List[str]], anexo: Any, dna_json_str: str) -> Tuple[List, str, str, None]:
750
+ """
751
+ Função de callback que conecta a lógica da pipeline com a interface do Gradio.
752
+ É chamada toda vez que o usuário envia uma mensagem.
753
+
754
+ Args:
755
+ pergunta: O texto da caixa de input.
756
+ historico_gradio: O estado atual do componente chatbot (formato Gradio).
757
+ anexo: O arquivo enviado pelo componente de upload.
758
+ dna_json_str: O estado do DNA em formato de string JSON.
759
+
760
+ Returns:
761
+ Uma tupla com os novos valores para os componentes da interface:
762
+ (novo_historico_gradio, texto_input_limpo, novo_dna_json, anexo_limpo)
763
+ """
764
+ logger.log(f"Nova mensagem recebida: '{pergunta[:80]}...'", "INFO")
765
+
766
+ # Carrega o estado do DNA a partir da string JSON. Se falhar, cria um novo.
767
+ try:
768
+ dna = json.loads(dna_json_str) if dna_json_str and dna_json_str.strip() else criar_dna()
769
+ except (json.JSONDecodeError, TypeError):
770
+ dna = criar_dna()
771
+
772
+ # 1. CONVERTE o histórico do formato Gradio para o nosso formato interno.
773
+ historico_interno = converter_historico_de_gradio(historico_gradio)
774
+
775
+ # 2. EXECUTA a pipeline com os dados de entrada.
776
+ _ , novo_historico_interno, dna_atualizado = executar_pipeline(pergunta, historico_interno, anexo, dna)
777
+
778
+ # 3. CONVERTE o histórico resultante de volta para o formato que o Gradio entende.
779
+ novo_historico_gradio = converter_historico_para_gradio(novo_historico_interno)
780
+
781
+ logger.log("Resposta formatada para Gradio.", "INFO")
782
+
783
+ # Retorna os novos estados para atualizar a interface do Gradio.
784
+ return novo_historico_gradio, "", json.dumps(dna_atualizado, indent=2, ensure_ascii=False), None
785
+
786
+ # --- Bloco de Execução Principal ---
787
+ if __name__ == "__main__":
788
+ # 'gr.Blocks' é o container principal para criar interfaces complexas com Gradio.
789
+ with gr.Blocks(title="Pipeline v10 Refatorada", theme=gr.themes.Soft()) as demo:
790
+ # Exibe o título usando Markdown.
791
+ gr.Markdown(TITLE)
792
+
793
+ # Define o layout da interface em linhas e colunas.
794
+ with gr.Row():
795
+ with gr.Column(scale=3): # Coluna principal para o chat.
796
+ chatbot = gr.Chatbot(label="Chat", height=600, bubble_full_width=False)
797
+ with gr.Column(scale=1): # Coluna lateral para informações adicionais.
798
+ dna_view = gr.Code(label="DNA (Estado da Conversa)", language="json", interactive=False,
799
+ value=json.dumps(criar_dna(), indent=2, ensure_ascii=False))
800
+ file_upload = gr.File(label="Anexar PDF ou Imagem", file_types=[".pdf", ".png", ".jpg", ".jpeg"])
801
+
802
+ with gr.Row(): # Linha para os controles de input.
803
+ input_textbox = gr.Textbox(
804
+ label="Digite sua pergunta aqui...",
805
+ lines=3,
806
+ scale=4, # Ocupa mais espaço na linha.
807
+ )
808
+ submit_button = gr.Button("🚀 Enviar (v10)", variant="primary", scale=1)
809
+
810
+ # Componente oculto para armazenar o estado do DNA entre as chamadas da interface.
811
+ # É uma forma de contornar a natureza sem estado do Gradio.
812
+ dna_json_hidden = gr.Textbox(value=json.dumps(criar_dna()), visible=False)
813
+
814
+ # --- Conexão dos Eventos da Interface com as Funções ---
815
+
816
+ # Define o que acontece quando o botão de "Enviar" é clicado.
817
+ submit_button.click(
818
+ fn=chat_interface, # Função a ser chamada.
819
+ inputs=[input_textbox, chatbot, file_upload, dna_json_hidden], # Componentes de entrada.
820
+ outputs=[chatbot, input_textbox, dna_json_hidden, file_upload] # Componentes de saída a serem atualizados.
821
+ )
822
+ # Permite que o usuário envie a mensagem apertando "Enter" na caixa de texto.
823
+ input_textbox.submit(
824
+ fn=chat_interface,
825
+ inputs=[input_textbox, chatbot, file_upload, dna_json_hidden],
826
+ outputs=[chatbot, input_textbox, dna_json_hidden, file_upload]
827
+ )
828
+ # Sincroniza o visualizador de DNA com o estado oculto sempre que ele mudar.
829
+ dna_json_hidden.change(
830
+ fn=lambda x: x, # Uma função simples que apenas repassa o valor.
831
+ inputs=[dna_json_hidden],
832
+ outputs=[dna_view]
833
+ )
834
+
835
+ # Inicia a aplicação web do Gradio.
836
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)