caarleexx commited on
Commit
bb02c59
·
verified ·
1 Parent(s): 3afb498

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -713
app.py DELETED
@@ -1,713 +0,0 @@
1
- import os
2
- import json
3
- import time
4
- import hashlib
5
- from datetime import datetime
6
- from concurrent.futures import ThreadPoolExecutor, as_completed
7
-
8
- import gradio as gr
9
- import google.generativeai as genai
10
-
11
- # =================================================================================
12
- # METADADOS DO PROJETO - HACKATHON OAB 2025
13
- # --------------------------------------------------------------------------------
14
- # Desenvolvedor: Carlos Rodrigues dos Santos
15
- # Contato: carlex22@gmaill.com
16
- # GitHub: github.com/carlex22
17
- # Licença: GPLv3
18
- # --------------------------------------------------------------------------------
19
- # FUNÇÃO DE ORQUESTRAÇÃO, CADEIA COGNITIVA E GOVERNANÇA (Transparência Causal)
20
- # --------------------------------------------------------------------------------
21
- # O orquestrador gerencia a 'Cadeia Cognitiva' através de 'Agentes Causais',
22
- # onde a saída de uma Fase (JSON de contexto) alimenta a entrada da próxima.
23
- # Isso garante 'Transparência' e 'Governanca' por meio da:
24
- #
25
- # 1. Pipeline Estruturada: Execução sequencial das missões definidas em
26
- # 'protocolo.json' (Fase 0 a 7), garantindo que cada agente cumpra um papel.
27
- # 2. Saída Auditorável (JSON): As fases retornam um JSON padronizado para
28
- # o painel 'Auditoria', permitindo rastrear o raciocínio de cada agente.
29
- # 3. Ponto de Controle (STOP): A Fase 0 (e a lógica da pipeline) pode solicitar
30
- # input do usuário (gatilho STOP), pausando a cadeia para validar fatos
31
- # críticos ou inserir contexto faltante, garantindo governança humana
32
- # em momentos de alta incerteza.
33
- # 4. Formato Envolvente (Fase 7): A fase final (Relatório) recebe tratamento
34
- # especial para apresentar a valoração de forma dinâmica e clara, conforme
35
- # o propósito de valorar a dignidade humana.
36
- # 5. Contexto Antecipatório: Opcionalmente inclui um passo inicial com dados
37
- # de 'data.txt' para estabelecer regras e 'verdades factuais' para os agentes.
38
- # =================================================================================
39
-
40
-
41
- # Dependências para PDF
42
- try:
43
- import PyPDF2
44
- PDF_SUPPORT = True
45
- except ImportError:
46
- PDF_SUPPORT = False
47
- print("⚠️ PyPDF2 não instalado. PDFs serão lidos como texto simples.")
48
-
49
- # ==================== 1. CONFIGURAÇÃO ====================
50
-
51
- api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
52
- if api_key and api_key != "SUA_API_KEY_AQUI":
53
- genai.configure(api_key=api_key)
54
-
55
- # Modelos do Gemini
56
- model_flash = genai.GenerativeModel("gemini-flash-latest")
57
- model_pro = genai.GenerativeModel("gemini-pro-latest")
58
-
59
- ARQUIVO_CONFIG = "protocolo.json"
60
- ARQUIVO_CONTEXTO_ANTECIPATORIO = "data.txt" # Arquivo com regras/verdade factual
61
- ARQUIVO_DOCUMENTACAO = "help.md" # Arquivo com documentação (pode ser .txt)
62
- PASTA_TRANSCRICOES = "transcricoes"
63
- PAGES_PER_CHUNK = 10
64
- MAX_WORKERS = 5 # Limite de chamadas paralelas
65
-
66
- os.makedirs(PASTA_TRANSCRICOES, exist_ok=True)
67
-
68
- # ==================== 2. UTILIDADES ====================
69
-
70
- # Define as constantes de autoria
71
- DEVS_NAME = "Carlos Rodrigues dos Santos"
72
- DEVS_EMAIL = "carlex22@gmaill.com"
73
- LICENSE_INFO = "GPLv3"
74
-
75
- def ler_arquivo_texto(arquivo_path):
76
- # Função auxiliar que lê um arquivo de texto pelo caminho
77
- if not os.path.exists(arquivo_path):
78
- return None
79
- try:
80
- with open(arquivo_path, "r", encoding="utf-8") as f:
81
- conteudo = f.read()
82
- return conteudo
83
- except: return None
84
-
85
- def carregar_protocolo():
86
- """ Carrega o protocolo. Se não existir, cria um com um esqueleto limpo. """
87
- return ler_arquivo_texto(ARQUIVO_CONFIG) or json.dumps(
88
- [{"fase": 0, "nome": "INICIAR_ANALISE", "modelo": "flash", "tipo_saida": "json", "missao": "Leia o input do usuário e os documentos anexados. Identifique quem é a vítima, qual foi o dano (ex: Morte, Lesão Grave) e quem é o réu. Se faltarem informações críticas para uma análise de valoração (ex: os fatos do caso), defina DUVIDA_DETECTADA como true e use TESTE_REFLEXAO para perguntar ao usuário."}],
89
- indent=2,
90
- ensure_ascii=False
91
- )
92
-
93
- def carregar_documentacao():
94
- """ Carrega o arquivo de documentação. """
95
- return ler_arquivo_texto(ARQUIVO_DOCUMENTACAO) or "Documentação não encontrada. Crie um arquivo 'help.md' ou 'help.txt' na pasta principal para exibir aqui."
96
-
97
-
98
- def limpar_nome_arquivo(nome):
99
- # (Implementação existente)
100
- nome_base = os.path.basename(nome)
101
- nome_limpo = "".join([c for c in nome_base if c.isalnum() or c in (' ', '.', '_', '-')]).strip()
102
- return nome_limpo + ".json"
103
-
104
- def extrair_texto_pdf(caminho_pdf):
105
- # (Implementação existente)
106
- try:
107
- with open(caminho_pdf, 'rb') as f:
108
- reader = PyPDF2.PdfReader(f)
109
- paginas = []
110
- for i, page in enumerate(reader.pages):
111
- texto = page.extract_text()
112
- paginas.append({
113
- "numero": i + 1,
114
- "texto": texto,
115
- "metadata": str(page)[:200]
116
- })
117
- return paginas, None
118
- except Exception as e:
119
- return None, str(e)
120
-
121
-
122
- def fragmentar_pdf(paginas, tamanho_chunk=PAGES_PER_CHUNK):
123
- # (Implementação existente)
124
- chunks = []
125
- for i in range(0, len(paginas), tamanho_chunk):
126
- chunk = paginas[i:i + tamanho_chunk]
127
- num_inicio = chunk[0]["numero"]
128
- num_fim = chunk[-1]["numero"]
129
-
130
- texto_consolidado = "\n---QUEBRA DE PÁGINA---\n".join(
131
- [f"[PÁGINA {p['numero']}]\n{p['texto']}" for p in chunk]
132
- )
133
-
134
- chunks.append({
135
- "id": f"chunk_{num_inicio}_{num_fim}",
136
- "paginas": f"{num_inicio}-{num_fim}",
137
- "num_paginas": len(chunk),
138
- "texto": texto_consolidado,
139
- "metadata": [p["metadata"] for p in chunk]
140
- })
141
- return chunks
142
-
143
- def processar_pdf_completo(arquivo_pdf):
144
- # (Implementação existente)
145
- if not PDF_SUPPORT:
146
- return None, "❌ PyPDF2 não disponível"
147
-
148
- try:
149
- paginas, erro = extrair_texto_pdf(arquivo_pdf.name if hasattr(arquivo_pdf, 'name') else arquivo_pdf)
150
- if erro:
151
- return None, f"❌ Erro ao ler PDF: {erro}"
152
-
153
- chunks = fragmentar_pdf(paginas)
154
- nome_arquivo = os.path.basename(arquivo_pdf.name if hasattr(arquivo_pdf, 'name') else arquivo_pdf)
155
-
156
- return {
157
- "arquivo": nome_arquivo,
158
- "total_paginas": len(paginas),
159
- "total_chunks": len(chunks),
160
- "chunks": chunks,
161
- "tipo": "pdf"
162
- }, None
163
- except Exception as e:
164
- return None, f"❌ Erro no processamento: {str(e)}"
165
-
166
- def limpar_markdown_basico(texto):
167
- """ Remove caracteres comuns de Markdown (negrito, itálico, cabeçalhos, listas) """
168
- texto = str(texto)
169
- # Negrito/Itálico/Sublinhado
170
- texto = texto.replace('**', '').replace('__', '')
171
- texto = texto.replace('*', '').replace('_', '')
172
- # Cabeçalhos/Listas (simples)
173
- texto = texto.replace('#', '').replace('-', '')
174
- # Tenta remover bloco de código (ex: ```json)
175
- texto = texto.replace('```', '')
176
- # Quebras de linha excessivas
177
- texto = texto.replace('\n\n\n', '\n\n')
178
- return texto.strip()
179
-
180
- # ==================== 3. PIPELINE DE IA ====================
181
-
182
- def transcrever_chunk(chunk_data, config_agentes):
183
- # (Implementação existente)
184
- modelo = model_flash
185
- try:
186
- if config_agentes and isinstance(config_agentes, list):
187
- if config_agentes[0].get("modelo") == "pro":
188
- modelo = model_pro
189
- except:
190
- pass
191
-
192
- prompt = f"""
193
- ANÁLISE DE DOCUMENTO (OCR/LEITURA):
194
- Transcreva e estruture o conteúdo das páginas {chunk_data['paginas']}.
195
- Texto extraído:
196
- {chunk_data['texto']}
197
-
198
- Retorne JSON: {{ "transcricao": "...", "objetos": ["..."], "resumo": "..." }}
199
- """
200
- try:
201
- for tentativa in range(3):
202
- try:
203
- resposta = modelo.generate_content(prompt)
204
- texto_resp = resposta.text.replace("```json", "").replace("```", "")
205
- return json.loads(texto_resp.strip()), None
206
- except Exception as inner_e:
207
- if "429" in str(inner_e):
208
- time.sleep(2 * (tentativa + 1))
209
- continue
210
- raise inner_e
211
- except Exception as e:
212
- return None, str(e)
213
-
214
- # ==================== 4. GERENCIADOR DE ARQUIVOS ====================
215
-
216
- class GerenciadorArquivos:
217
- def __init__(self):
218
- self.arquivos = {}
219
-
220
- def adicionar(self, arquivo, arquivo_id):
221
- # (Implementação existente)
222
- self.arquivos[arquivo_id] = {
223
- "arquivo": arquivo,
224
- "nome": os.path.basename(arquivo.name),
225
- "status": "adicionado",
226
- "processado": None,
227
- "transcricao": None
228
- }
229
-
230
- def gerar_prompt_com_transcricoes(self, texto_usuario):
231
- # (Implementação existente)
232
- prompt = texto_usuario + "\n\n--- CONTEXTO DOS ARQUIVOS ---\n"
233
- count = 0
234
- for _, item in self.arquivos.items():
235
- if item["status"] == "processado" and item["transcricao"]:
236
- count += 1
237
- trans = item["transcricao"]
238
- nome = item["nome"]
239
- prompt += f"\n[ARQUIVO: {nome}]\n"
240
-
241
- if isinstance(trans, dict) and "chunks_processados" in trans:
242
- for chunk in trans["chunks_processados"]:
243
- if chunk.get("status") == "OK":
244
- resumo = chunk.get('resumo', '')
245
- resumo = str(resumo) if resumo else ""
246
- prompt += f"Páginas {chunk['paginas']}: {resumo}\n"
247
-
248
- texto_full = chunk.get('transcricao', '')
249
- if texto_full:
250
- texto_seguro = str(texto_full)
251
- prompt += f"Trecho: {texto_seguro[:400]}...\n"
252
- else:
253
- prompt += "Trecho: (vazio)\n"
254
-
255
- elif isinstance(trans, dict) and "conteudo" in trans:
256
- conteudo = str(trans['conteudo'])
257
- prompt += f"Conteúdo: {conteudo[:1000]}...\n"
258
-
259
- if count == 0:
260
- prompt += "(Nenhum arquivo processado ainda)"
261
- return prompt
262
-
263
- # Instância Global
264
- gerenciador = GerenciadorArquivos()
265
-
266
- # ==================== 5. FUNÇÕES DE ORQUESTRAÇÃO ====================
267
-
268
- def automacao_upload_processamento(files, history, config_json):
269
- # (Função de processamento de arquivos)
270
- if not files:
271
- return history
272
-
273
- try:
274
- config_agentes = json.loads(config_json)
275
- except:
276
- config_agentes = []
277
-
278
- if history is None:
279
- history = []
280
-
281
- history.append([None, f"📂 **SISTEMA:** Recebi {len(files)} arquivo(s). Verificando cache e processando..."])
282
- yield history
283
-
284
- ids_para_processar = []
285
-
286
- for f in files:
287
- # Usa um hash do conteúdo para ID de arquivo de texto simples para melhorar o cache
288
- if f.name.lower().endswith(('.txt', '.json', '.md')):
289
- with open(f.name, 'r', encoding='utf-8') as file:
290
- file_hash = hashlib.sha256(file.read().encode('utf-8')).hexdigest()
291
- arquivo_id = f"txt_{file_hash}"
292
- else:
293
- arquivo_id = f"arq_{int(time.time()*1000)}_{f.name}"
294
-
295
- gerenciador.adicionar(f, arquivo_id)
296
- ids_para_processar.append(arquivo_id)
297
-
298
- for arq_id in ids_para_processar:
299
- item = gerenciador.arquivos[arq_id]
300
- nome = item["nome"]
301
-
302
- # Cria um nome de cache consistente
303
- nome_cache = limpar_nome_arquivo(nome)
304
- caminho_cache = os.path.join(PASTA_TRANSCRICOES, nome_cache)
305
-
306
- # Lógica de Cache e Reprocessamento
307
- if os.path.exists(caminho_cache):
308
- try:
309
- with open(caminho_cache, "r", encoding="utf-8") as cache_file:
310
- dados_cache = json.load(cache_file)
311
- item["transcricao"] = dados_cache
312
- item["status"] = "processado"
313
- if nome.lower().endswith('.pdf') and "chunks_processados" in dados_cache:
314
- item["processado"] = {"tipo": "pdf", "chunks": []}
315
- history.append([None, f"♻️ **Cache Encontrado:** `{nome}` já foi processado. Carregando..."])
316
- yield history
317
- continue
318
- except Exception as e:
319
- history.append([None, f"⚠️ Erro cache `{nome}`: {e}. Reprocessando..."])
320
-
321
- history.append([None, f"⚙️ **Processando:** `{nome}`..."])
322
- yield history
323
-
324
- if nome.lower().endswith('.pdf'):
325
- if not PDF_SUPPORT:
326
- history.append([None, f"❌ Erro em `{nome}`: Biblioteca PDF ausente."])
327
- yield history
328
- continue
329
-
330
- pdf_proc, erro = processar_pdf_completo(item["arquivo"])
331
- if erro:
332
- history.append([None, f"❌ Erro em `{nome}`: {erro}"])
333
- yield history
334
- continue
335
-
336
- item["processado"] = pdf_proc
337
- chunks = pdf_proc["chunks"]
338
- total_chunks = len(chunks)
339
-
340
- chunks_ordenados = [None] * total_chunks
341
-
342
- history.append([None, f"📄 `{nome}` fragmentado em {total_chunks} partes. Iniciando IA (Paralelo: {MAX_WORKERS} threads)..."])
343
- yield history
344
-
345
- with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
346
- futures_map = {}
347
- for i, chunk in enumerate(chunks):
348
- future = executor.submit(transcrever_chunk, chunk, config_agentes)
349
- futures_map[future] = i
350
-
351
- concluidos = 0
352
- for future in as_completed(futures_map):
353
- index_original = futures_map[future]
354
- res, err = future.result()
355
-
356
- if err:
357
- chunks_ordenados[index_original] = {"status": "ERRO", "paginas": chunks[index_original]["paginas"]}
358
- else:
359
- chunks_ordenados[index_original] = {
360
- "status": "OK",
361
- "paginas": chunks[index_original]["paginas"],
362
- "transcricao": res.get("transcricao"),
363
- "resumo": res.get("resumo")
364
- }
365
-
366
- concluidos += 1
367
- if concluidos % 2 == 0 or concluidos == total_chunks:
368
- msg_base = f"📄 `{nome}`: Processando partes... ({concluidos}/{total_chunks})"
369
- history[-1][1] = msg_base
370
- yield history
371
-
372
- dados_finais = {
373
- "arquivo": nome,
374
- "data_processamento": str(datetime.now()),
375
- "chunks_processados": chunks_ordenados
376
- }
377
-
378
- item["transcricao"] = dados_finais
379
- item["status"] = "processado"
380
-
381
- try:
382
- with open(caminho_cache, "w", encoding="utf-8") as f_out:
383
- json.dump(dados_finais, f_out, indent=2, ensure_ascii=False)
384
- history.append([None, f"💾 `{nome}` processado e salvo no cache."])
385
- except Exception as e:
386
- history.append([None, f"⚠️ Erro ao salvar cache: {e}"])
387
-
388
- yield history
389
-
390
- else:
391
- res_content = ler_arquivo_texto(item["arquivo"].name)
392
- if res_content:
393
- item["processado"] = res_content
394
- dados_finais = {"conteudo": res_content, "data_processamento": str(datetime.now())}
395
- item["transcricao"] = dados_finais
396
- item["status"] = "processado"
397
-
398
- with open(caminho_cache, "w", encoding="utf-8") as f_out:
399
- json.dump(dados_finais, f_out, indent=2, ensure_ascii=False)
400
-
401
- history.append([None, f"✅ `{nome}` (Texto) lido e salvo."])
402
- else:
403
- history.append([None, f"❌ Falha ao ler `{nome}`."])
404
- yield history
405
-
406
- history.append([None, "🏁 **Processamento de lote finalizado.** Os arquivos estão prontos para análise."])
407
- yield history
408
-
409
-
410
- def chat_orquestrador(message, history, config_json, pipeline_state, incluir_passo_antecipatorio):
411
- if pipeline_state.get("is_paused"):
412
- # (Lógica de continuação após STOP)
413
- history.append([message, None])
414
-
415
- timeline_execucao = pipeline_state["timeline"]
416
- agentes_restantes = pipeline_state["remaining_agents"]
417
-
418
- timeline_execucao.append({
419
- "passo": len(timeline_execucao) + 1,
420
- "tipo": "resposta_usuario",
421
- "conteudo": message
422
- })
423
-
424
- pipeline_state["is_paused"] = False
425
-
426
- yield from executar_pipeline(history, timeline_execucao, agentes_restantes, pipeline_state)
427
- return
428
-
429
- # --- LÓGICA DE INÍCIO DE UMA NOVA CONVERSA ---
430
-
431
- # 1. Monta o Prompt de Contexto dos Arquivos
432
- try:
433
- prompt_contexto = gerenciador.gerar_prompt_com_transcricoes(message)
434
- except Exception as e:
435
- history.append([message, f"❌ Erro ao gerar contexto: {str(e)}"])
436
- yield history, [], pipeline_state
437
- return
438
-
439
- # 2. Carrega o Protocolo
440
- try:
441
- protocolo = json.loads(config_json)
442
- except:
443
- history.append([message, "❌ Erro no JSON de Configuração do Protocolo."])
444
- yield history, [], pipeline_state
445
- return
446
-
447
- history.append([message, None])
448
-
449
- # 3. Lógica do Passo Antecipatório
450
- timeline_execucao = []
451
- agentes_a_executar = protocolo
452
-
453
- if incluir_passo_antecipatorio:
454
- conteudo_antecipatorio = ler_arquivo_texto(ARQUIVO_CONTEXTO_ANTECIPATORIO)
455
- if conteudo_antecipatorio:
456
- # Injeta o contexto na timeline como o primeiro passo
457
- timeline_execucao.append({
458
- "passo": 1,
459
- "tipo": "passo_antecipatorio_data",
460
- "agente": "GOVERNANCA_INICIAL",
461
- "conteudo": f"REGRAS E VERDADE FACTUAL INICIAL (data.txt):\n{conteudo_antecipatorio}"
462
- })
463
- history.append([None, f"ℹ️ **GOVERNANÇA INICIAL:** Contexto de `{ARQUIVO_CONTEXTO_ANTECIPATORIO}` injetado na cadeia cognitiva."])
464
- history[-1][1] += f"\n\nInstrução do usuário: {message}"
465
- else:
466
- # Se o arquivo não existe, apenas adiciona a instrução do usuário
467
- history[-1][1] = f"⚠️ Instrução do usuário: {message}"
468
- history[-1][1] += f"\n\n**Aviso:** Passo Antecipatório ativado, mas arquivo `{ARQUIVO_CONTEXTO_ANTECIPATORIO}` não encontrado."
469
-
470
- # Ajusta o número do passo inicial na timeline
471
- passo_inicial_prompt = len(timeline_execucao) + 1
472
- else:
473
- passo_inicial_prompt = 1
474
-
475
- # Adiciona a instrução do usuário (com contexto de arquivos) à trilha de auditoria
476
- timeline_execucao.append({"passo": passo_inicial_prompt, "tipo": "prompt_usuario_base", "conteudo": prompt_contexto})
477
-
478
- yield history, timeline_execucao, pipeline_state
479
-
480
- # Inicia a execução dos agentes
481
- yield from executar_pipeline(history, timeline_execucao, agentes_a_executar, pipeline_state)
482
-
483
-
484
- def executar_pipeline(history, timeline_execucao, agentes_a_executar, pipeline_state):
485
- # (Implementação de execução da pipeline e simulação de digitação)
486
- passo_atual = len(timeline_execucao) + 1
487
-
488
- for i, cfg in enumerate(agentes_a_executar):
489
- nome_agente = cfg.get("nome", "Agente")
490
- modelo_agente = model_pro if cfg.get("modelo") == "pro" else model_flash
491
- tipo_saida = cfg.get("tipo_saida", "json")
492
-
493
- msg_atual = history[-1][1] or ""
494
- history[-1][1] = msg_atual + f"⏳ **{nome_agente}** está analisando...\n"
495
- yield history, timeline_execucao, pipeline_state
496
-
497
- prompt_agente = f"""
498
- --- HISTÓRICO DA CONVERSA ATÉ AGORA ---
499
- {json.dumps(timeline_execucao, ensure_ascii=False, indent=2)}
500
- -----------------
501
- Sua Identidade: {nome_agente}
502
- Sua Missão Específica Agora: {cfg['missao']}
503
- Se o tipo de saída exigido for 'json', sua resposta DEVE ser APENAS o JSON.
504
- """
505
-
506
- try:
507
- inicio = time.time()
508
- resp = modelo_agente.generate_content(prompt_agente)
509
- texto_resp = resp.text
510
- duracao = time.time() - inicio
511
-
512
- # --- LÓGICA DE DETECÇÃO DE SAÍDA NÃO-JSON (STOP ou Relatório) ---
513
- if tipo_saida == "json":
514
- try:
515
- # Tenta interpretar a resposta como JSON
516
- texto_json_limpo = texto_resp.replace("```json", "").replace("```", "").strip()
517
- resposta_json = json.loads(texto_json_limpo)
518
-
519
- # 1. LÓGICA DE PAUSA (STOP)
520
- if resposta_json.get("PROXIMA_ACAO") == "PERGUNTAR_USUARIO" and resposta_json.get("DUVIDA_DETECTADA") == True:
521
-
522
- # VERIFICA REGRAS DE STOP - Fora de escopo?
523
- if "STOP:" in texto_resp:
524
- # Caso de STOP por fora do escopo
525
- stop_message = texto_resp.split("STOP:")[1].strip() if "STOP:" in texto_resp else "Análise interrompida por dúvida crítica ou tema fora de escopo."
526
- history[-1][1] = stop_message
527
- yield history, timeline_execucao, pipeline_state
528
- return
529
-
530
- # Caso de STOP por dúvida crítica (regra do protocolo da Fase 0)
531
- perguntas = resposta_json.get("TESTE_REFLEXAO", {}).get("perguntas", [])
532
-
533
- # Formata a resposta STOP
534
- stop_response = "STOP: preciso que você esclareça até 3 pontos antes de continuar:\n\n"
535
- for idx, p in enumerate(perguntas):
536
- if idx < 3:
537
- stop_response += f"{idx + 1}) {p}\n"
538
-
539
- # Salva o estado atual da pipeline
540
- pipeline_state["is_paused"] = True
541
- pipeline_state["timeline"] = timeline_execucao
542
- pipeline_state["remaining_agents"] = agentes_a_executar[i+1:]
543
-
544
- # Adiciona o passo à auditoria e ao chat
545
- timeline_execucao.append({"passo": passo_atual, "tipo": "STOP_USUARIO_REQUERIDO", "agente": nome_agente, "detalhes": stop_response})
546
-
547
- # Prepara a mensagem para o usuário no chat
548
- msg_para_usuario = f"**{nome_agente}** precisa de mais informações. Por favor, responda aos pontos abaixo para seguirmos com a análise:\n\n"
549
- for idx, p in enumerate(perguntas):
550
- if idx < 3:
551
- msg_para_usuario += f"**{idx + 1})** {p}\n"
552
-
553
- msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
554
- history[-1][1] = msg_atual + msg_para_usuario
555
-
556
- yield history, timeline_execucao, pipeline_state
557
- return # Sai da função para esperar o input do usuário
558
-
559
- # 2. Resposta JSON normal
560
- texto_para_auditoria = texto_json_limpo
561
- timeline_execucao.append({"passo": passo_atual, "tipo": "resposta_agente", "agente": nome_agente, "resposta": texto_para_auditoria})
562
-
563
- msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
564
- novo_trecho = f"✅ **[{nome_agente}]** concluiu sua análise em ({duracao:.1f}s). (JSON para Auditoria)\n"
565
- history[-1][1] = msg_atual + novo_trecho
566
- yield history, timeline_execucao, pipeline_state
567
-
568
- except json.JSONDecodeError:
569
- # Se deveria ser JSON e não é, trata como erro na auditoria
570
- timeline_execucao.append({"passo": passo_atual, "tipo": "erro_resposta_json", "agente": nome_agente, "resposta_raw": texto_resp, "erro": "Esperado JSON, mas recebeu texto não-JSON."})
571
- msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
572
- history[-1][1] = msg_atual + f"❌ **[{nome_agente}]** falhou: Resposta não era JSON válido. ({duracao:.1f}s).\n"
573
- yield history, timeline_execucao, pipeline_state
574
-
575
- elif tipo_saida == "texto":
576
- # Lógica para a Fase 7 (RELATORIO_VALOR_VIDA)
577
-
578
- # APLICAÇÃO DA CORREÇÃO: Limpa o markdown e caracteres antes de usar a resposta
579
- texto_resp_limpo = limpar_markdown_basico(texto_resp)
580
-
581
- # Adiciona o passo à auditoria
582
- timeline_execucao.append({"passo": passo_atual, "tipo": "relatorio_final", "agente": nome_agente, "relatorio": texto_resp_limpo})
583
-
584
- # Simula a digitação envolvente
585
- msg_final = f"**[RELATÓRIO FINAL DO {nome_agente}]**\n\n"
586
- history[-1][1] = msg_final
587
- yield history, timeline_execucao, pipeline_state
588
-
589
- # Divide o texto do relatório em blocos (parágrafos ou seções)
590
- blocos = texto_resp_limpo.split('\n\n')
591
-
592
- for bloco in blocos:
593
- if bloco.strip():
594
- # Simula a digitação bloco por bloco
595
- msg_final += f"{bloco}\n\n"
596
- history[-1][1] = msg_final
597
- # Pequena pausa para o efeito dinâmico
598
- time.sleep(0.5)
599
- yield history, timeline_execucao, pipeline_state
600
-
601
- # Finalização da digitação no chat
602
- msg_final += "\n\n--- FIM DO RELATÓRIO DE VALORAÇÃO ---"
603
- history[-1][1] = msg_final
604
- yield history, timeline_execucao, pipeline_state
605
-
606
- else:
607
- # Caso de tipo_saida não definido (erro)
608
- timeline_execucao.append({"passo": passo_atual, "tipo": "erro_config", "agente": nome_agente, "erro": "Tipo de saída não reconhecido ('json' ou 'texto')."})
609
- msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
610
- history[-1][1] = msg_atual + f"\n❌ Erro de Configuração em {nome_agente}: Tipo de saída inválido. ({duracao:.1f}s).\n"
611
- yield history, timeline_execucao, pipeline_state
612
-
613
- except Exception as e:
614
- timeline_execucao.append({"passo": passo_atual, "tipo": "erro_agente", "agente": nome_agente, "erro": str(e)})
615
- msg_atual = history[-1][1]
616
- history[-1][1] = msg_atual.replace(f"⏳ **{nome_agente}** está analisando...\n", "") + f"\n❌ Erro em {nome_agente}: {str(e)}\n"
617
- yield history, timeline_execucao, pipeline_state
618
-
619
- passo_atual += 1
620
-
621
- # ==================== 6. UI (Gradio) ====================
622
-
623
- def ui_v29_stop_logic():
624
- css = """
625
- footer {display: none !important;}
626
- .contain {border: none !important;}
627
- """
628
-
629
- config_inicial = carregar_protocolo()
630
- documentacao = carregar_documentacao()
631
-
632
- # Define o objeto do tema
633
- app_theme = gr.themes.Soft()
634
-
635
- # CORREÇÃO: Removido 'css' e 'theme' do construtor gr.Blocks().
636
- with gr.Blocks(title="AI Forensics Auto") as app:
637
-
638
- # Estado da configuração dos agentes
639
- state_config = gr.State(config_inicial)
640
- # NOVO: Estado para controlar a pausa/continuação da pipeline
641
- pipeline_state = gr.State({"is_paused": False, "timeline": [], "remaining_agents": []})
642
-
643
- with gr.Tabs():
644
- with gr.Tab("💬 Investigação"):
645
-
646
- chatbot = gr.Chatbot(
647
- height=400,
648
- show_label=False,
649
- # CORREÇÃO: show_copy_button e render_markdown removidos para compatibilidade.
650
- label="Chat de Investigação"
651
- )
652
-
653
- with gr.Row():
654
- txt_input = gr.Textbox(
655
- scale=8,
656
- show_label=False,
657
- placeholder="Digite sua instrução ou responda à pergunta do agente...",
658
- lines=1
659
- )
660
- btn_enviar = gr.Button("Enviar 📨", variant="primary", scale=1)
661
-
662
- # Caixa de Opções Avançadas
663
- with gr.Accordion("⚙️ Opções Avançadas de Governança", open=False):
664
- chk_antecipatorio = gr.Checkbox(
665
- label=f"Incluir Contexto Antecipatório (`{ARQUIVO_CONTEXTO_ANTECIPATORIO}`) no início da análise (Recomendado).",
666
- value=True, # Ativado por padrão
667
- show_label=True
668
- )
669
-
670
- with gr.Accordion("📂 Adicionar Arquivos para Análise", open=False):
671
- gr.Markdown("Selecione arquivos (PDF, TXT). A transcrição iniciará **automaticamente**.")
672
- file_uploader = gr.File(
673
- file_count="multiple",
674
- file_types=[".pdf", ".txt", ".json", ".md"],
675
- label="Arraste arquivos aqui ou clique para selecionar"
676
- )
677
-
678
- with gr.Tab("🕵️ Auditoria"):
679
- gr.Markdown("### Trilha de Auditoria\nExibe o histórico completo de prompts e respostas de cada agente na última execução.")
680
- json_audit = gr.JSON(label="Timeline da Execução da Última Mensagem")
681
-
682
- with gr.Tab("⚙️ Protocolo & Autoria"):
683
- gr.Markdown(f"### Protocolo Causal (Diretrizes de Agentes)\nDesenvolvedor: **{DEVS_NAME}** ({DEVS_EMAIL}) | Licença: **{LICENSE_INFO}**")
684
- gr.Markdown("Visualize as missões dos agentes de IA. O número de fases é totalmente configurável na estrutura JSON abaixo.")
685
- code_config = gr.Code(value=config_inicial, language="json", label="protocolo.json (Visualização de Diretrizes)", interactive=False)
686
-
687
- with gr.Tab("📚 Documentação"):
688
- gr.Markdown("### Documentação da Aplicação (help.md)")
689
- gr.Markdown(documentacao)
690
-
691
-
692
- # Ação de clique agora passa o novo checkbox para o orquestrador
693
- btn_enviar.click(
694
- chat_orquestrador,
695
- inputs=[txt_input, chatbot, state_config, pipeline_state, chk_antecipatorio],
696
- outputs=[chatbot, json_audit, pipeline_state]
697
- ).then(
698
- lambda: "", outputs=[txt_input]
699
- )
700
-
701
- file_uploader.upload(
702
- automacao_upload_processamento,
703
- inputs=[file_uploader, chatbot, state_config],
704
- outputs=[chatbot]
705
- )
706
-
707
- # CORREÇÃO: Retorna o app, o css e o tema para serem passados para launch()
708
- return app, css, app_theme
709
-
710
- if __name__ == "__main__":
711
- # CORREÇÃO: Recebe o app, o css e o tema e os passa para app.launch()
712
- app, css_styles, app_theme = ui_v29_stop_logic()
713
- app.launch(css=css_styles, theme=app_theme)