caarleexx commited on
Commit
fcc1484
·
verified ·
1 Parent(s): b1c75f6

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -716
app.py DELETED
@@ -1,716 +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
- # A função limpar_markdown_basico foi removida, pois reativamos o render_markdown no Chatbot.
167
-
168
-
169
- # ==================== 3. PIPELINE DE IA ====================
170
-
171
- def transcrever_chunk(chunk_data, config_agentes):
172
- # (Implementação existente)
173
- modelo = model_flash
174
- try:
175
- if config_agentes and isinstance(config_agentes, list):
176
- if config_agentes[0].get("modelo") == "pro":
177
- modelo = model_pro
178
- except:
179
- pass
180
-
181
- prompt = f"""
182
- ANÁLISE DE DOCUMENTO (OCR/LEITURA):
183
- Transcreva e estruture o conteúdo das páginas {chunk_data['paginas']}.
184
- Texto extraído:
185
- {chunk_data['texto']}
186
-
187
- Retorne JSON: {{ "transcricao": "...", "objetos": ["..."], "resumo": "..." }}
188
- """
189
- try:
190
- for tentativa in range(3):
191
- try:
192
- resposta = modelo.generate_content(prompt)
193
- texto_resp = resposta.text.replace("```json", "").replace("```", "")
194
- return json.loads(texto_resp.strip()), None
195
- except Exception as inner_e:
196
- if "429" in str(inner_e):
197
- time.sleep(2 * (tentativa + 1))
198
- continue
199
- raise inner_e
200
- except Exception as e:
201
- return None, str(e)
202
-
203
- # ==================== 4. GERENCIADOR DE ARQUIVOS ====================
204
-
205
- class GerenciadorArquivos:
206
- def __init__(self):
207
- self.arquivos = {}
208
-
209
- def adicionar(self, arquivo, arquivo_id):
210
- # (Implementação existente)
211
- self.arquivos[arquivo_id] = {
212
- "arquivo": arquivo,
213
- "nome": os.path.basename(arquivo.name),
214
- "status": "adicionado",
215
- "processado": None,
216
- "transcricao": None
217
- }
218
-
219
- def gerar_prompt_com_transcricoes(self, texto_usuario):
220
- # (Implementação existente)
221
- prompt = texto_usuario + "\n\n--- CONTEXTO DOS ARQUIVOS ---\n"
222
- count = 0
223
- for _, item in self.arquivos.items():
224
- if item["status"] == "processado" and item["transcricao"]:
225
- count += 1
226
- trans = item["transcricao"]
227
- nome = item["nome"]
228
- prompt += f"\n[ARQUIVO: {nome}]\n"
229
-
230
- if isinstance(trans, dict) and "chunks_processados" in trans:
231
- for chunk in trans["chunks_processados"]:
232
- if chunk.get("status") == "OK":
233
- resumo = chunk.get('resumo', '')
234
- resumo = str(resumo) if resumo else ""
235
- prompt += f"Páginas {chunk['paginas']}: {resumo}\n"
236
-
237
- texto_full = chunk.get('transcricao', '')
238
- if texto_full:
239
- texto_seguro = str(texto_full)
240
- prompt += f"Trecho: {texto_seguro[:400]}...\n"
241
- else:
242
- prompt += "Trecho: (vazio)\n"
243
-
244
- elif isinstance(trans, dict) and "conteudo" in trans:
245
- conteudo = str(trans['conteudo'])
246
- prompt += f"Conteúdo: {conteudo[:1000]}...\n"
247
-
248
- if count == 0:
249
- prompt += "(Nenhum arquivo processado ainda)"
250
- return prompt
251
-
252
- # Instância Global
253
- gerenciador = GerenciadorArquivos()
254
-
255
- # ==================== 5. FUNÇÕES DE ORQUESTRAÇÃO ====================
256
-
257
- def automacao_upload_processamento(files, history, config_json):
258
- # (Função de processamento de arquivos)
259
- if not files:
260
- return history
261
-
262
- try:
263
- config_agentes = json.loads(config_json)
264
- except:
265
- config_agentes = []
266
-
267
- if history is None:
268
- history = []
269
-
270
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
271
- history.append(["", f"📂 **SISTEMA:** Recebi {len(files)} arquivo(s). Verificando cache e processando..."])
272
- yield history
273
-
274
- ids_para_processar = []
275
-
276
- for f in files:
277
- # Usa um hash do conteúdo para ID de arquivo de texto simples para melhorar o cache
278
- if f.name.lower().endswith(('.txt', '.json', '.md')):
279
- with open(f.name, 'r', encoding='utf-8') as file:
280
- file_hash = hashlib.sha256(file.read().encode('utf-8')).hexdigest()
281
- arquivo_id = f"txt_{file_hash}"
282
- else:
283
- arquivo_id = f"arq_{int(time.time()*1000)}_{f.name}"
284
-
285
- gerenciador.adicionar(f, arquivo_id)
286
- ids_para_processar.append(arquivo_id)
287
-
288
- for arq_id in ids_para_processar:
289
- item = gerenciador.arquivos[arq_id]
290
- nome = item["nome"]
291
-
292
- # Cria um nome de cache consistente
293
- nome_cache = limpar_nome_arquivo(nome)
294
- caminho_cache = os.path.join(PASTA_TRANSCRICOES, nome_cache)
295
-
296
- # Lógica de Cache e Reprocessamento
297
- if os.path.exists(caminho_cache):
298
- try:
299
- with open(caminho_cache, "r", encoding="utf-8") as cache_file:
300
- dados_cache = json.load(cache_file)
301
- item["transcricao"] = dados_cache
302
- item["status"] = "processado"
303
- if nome.lower().endswith('.pdf') and "chunks_processados" in dados_cache:
304
- item["processado"] = {"tipo": "pdf", "chunks": []}
305
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
306
- history.append(["", f"♻️ **Cache Encontrado:** `{nome}` já foi processado. Carregando..."])
307
- yield history
308
- continue
309
- except Exception as e:
310
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
311
- history.append(["", f"⚠️ Erro cache `{nome}`: {e}. Reprocessando..."])
312
-
313
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
314
- history.append(["", f"⚙️ **Processando:** `{nome}`..."])
315
- yield history
316
-
317
- if nome.lower().endswith('.pdf'):
318
- if not PDF_SUPPORT:
319
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
320
- history.append(["", f"❌ Erro em `{nome}`: Biblioteca PDF ausente."])
321
- yield history
322
- continue
323
-
324
- pdf_proc, erro = processar_pdf_completo(item["arquivo"])
325
- if erro:
326
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
327
- history.append(["", f"❌ Erro em `{nome}`: {erro}"])
328
- yield history
329
- continue
330
-
331
- item["processado"] = pdf_proc
332
- chunks = pdf_proc["chunks"]
333
- total_chunks = len(chunks)
334
-
335
- chunks_ordenados = [None] * total_chunks
336
-
337
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
338
- history.append(["", f"📄 `{nome}` fragmentado em {total_chunks} partes. Iniciando IA (Paralelo: {MAX_WORKERS} threads)..."])
339
- yield history
340
-
341
- with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
342
- futures_map = {}
343
- for i, chunk in enumerate(chunks):
344
- future = executor.submit(transcrever_chunk, chunk, config_agentes)
345
- futures_map[future] = i
346
-
347
- concluidos = 0
348
- for future in as_completed(futures_map):
349
- index_original = futures_map[future]
350
- res, err = future.result()
351
-
352
- if err:
353
- chunks_ordenados[index_original] = {"status": "ERRO", "paginas": chunks[index_original]["paginas"]}
354
- else:
355
- chunks_ordenados[index_original] = {
356
- "status": "OK",
357
- "paginas": chunks[index_original]["paginas"],
358
- "transcricao": res.get("transcricao"),
359
- "resumo": res.get("resumo")
360
- }
361
-
362
- concluidos += 1
363
- if concluidos % 2 == 0 or concluidos == total_chunks:
364
- msg_base = f"📄 `{nome}`: Processando partes... ({concluidos}/{total_chunks})"
365
- history[-1][1] = msg_base
366
- yield history
367
-
368
- dados_finais = {
369
- "arquivo": nome,
370
- "data_processamento": str(datetime.now()),
371
- "chunks_processados": chunks_ordenados
372
- }
373
-
374
- item["transcricao"] = dados_finais
375
- item["status"] = "processado"
376
-
377
- try:
378
- with open(caminho_cache, "w", encoding="utf-8") as f_out:
379
- json.dump(dados_finais, f_out, indent=2, ensure_ascii=False)
380
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
381
- history.append(["", f"💾 `{nome}` processado e salvo no cache."])
382
- except Exception as e:
383
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
384
- history.append(["", f"⚠️ Erro ao salvar cache: {e}"])
385
-
386
- yield history
387
-
388
- else:
389
- res_content = ler_arquivo_texto(item["arquivo"].name)
390
- if res_content:
391
- item["processado"] = res_content
392
- dados_finais = {"conteudo": res_content, "data_processamento": str(datetime.now())}
393
- item["transcricao"] = dados_finais
394
- item["status"] = "processado"
395
-
396
- with open(caminho_cache, "w", encoding="utf-8") as f_out:
397
- json.dump(dados_finais, f_out, indent=2, ensure_ascii=False)
398
-
399
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
400
- history.append(["", f"✅ `{nome}` (Texto) lido e salvo."])
401
- else:
402
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
403
- history.append(["", f"❌ Falha ao ler `{nome}`."])
404
- yield history
405
-
406
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
407
- history.append(["", "🏁 **Processamento de lote finalizado.** Os arquivos estão prontos para análise."])
408
- yield history
409
-
410
-
411
- def chat_orquestrador(message, history, config_json, pipeline_state, incluir_passo_antecipatorio):
412
- if pipeline_state.get("is_paused"):
413
- # (Lógica de continuação após STOP)
414
- history.append([message, None])
415
-
416
- timeline_execucao = pipeline_state["timeline"]
417
- agentes_restantes = pipeline_state["remaining_agents"]
418
-
419
- timeline_execucao.append({
420
- "passo": len(timeline_execucao) + 1,
421
- "tipo": "resposta_usuario",
422
- "conteudo": message
423
- })
424
-
425
- pipeline_state["is_paused"] = False
426
-
427
- yield from executar_pipeline(history, timeline_execucao, agentes_restantes, pipeline_state)
428
- return
429
-
430
- # --- LÓGICA DE INÍCIO DE UMA NOVA CONVERSA ---
431
-
432
- # 1. Monta o Prompt de Contexto dos Arquivos
433
- try:
434
- prompt_contexto = gerenciador.gerar_prompt_com_transcricoes(message)
435
- except Exception as e:
436
- history.append([message, f"❌ Erro ao gerar contexto: {str(e)}"])
437
- yield history, [], pipeline_state
438
- return
439
-
440
- # 2. Carrega o Protocolo
441
- try:
442
- protocolo = json.loads(config_json)
443
- except:
444
- history.append([message, "❌ Erro no JSON de Configuração do Protocolo."])
445
- yield history, [], pipeline_state
446
- return
447
-
448
- # CORREÇÃO: Usar None é aceitável para a primeira parte de uma nova conversa.
449
- history.append([message, None])
450
-
451
- # 3. Lógica do Passo Antecipatório
452
- timeline_execucao = []
453
- agentes_a_executar = protocolo
454
-
455
- if incluir_passo_antecipatorio:
456
- conteudo_antecipatorio = ler_arquivo_texto(ARQUIVO_CONTEXTO_ANTECIPATORIO)
457
- if conteudo_antecipatorio:
458
- # Injeta o contexto na timeline como o primeiro passo
459
- timeline_execucao.append({
460
- "passo": 1,
461
- "tipo": "passo_antecipatorio_data",
462
- "agente": "GOVERNANCA_INICIAL",
463
- "conteudo": f"REGRAS E VERDADE FACTUAL INICIAL (data.txt):\n{conteudo_antecipatorio}"
464
- })
465
- # CORREÇÃO: [None, mensagem] -> ["", mensagem]
466
- history.append(["", f"ℹ️ **GOVERNANÇA INICIAL:** Contexto de `{ARQUIVO_CONTEXTO_ANTECIPATORIO}` injetado na cadeia cognitiva."])
467
- history[-1][1] += f"\n\nInstrução do usuário: {message}"
468
- else:
469
- # Se o arquivo não existe, apenas adiciona a instrução do usuário
470
- history[-1][1] = f"⚠️ Instrução do usuário: {message}"
471
- history[-1][1] += f"\n\n**Aviso:** Passo Antecipatório ativado, mas arquivo `{ARQUIVO_CONTEXTO_ANTECIPATORIO}` não encontrado."
472
-
473
- # Ajusta o número do passo inicial na timeline
474
- passo_inicial_prompt = len(timeline_execucao) + 1
475
- else:
476
- passo_inicial_prompt = 1
477
-
478
- # Adiciona a instrução do usuário (com contexto de arquivos) à trilha de auditoria
479
- timeline_execucao.append({"passo": passo_inicial_prompt, "tipo": "prompt_usuario_base", "conteudo": prompt_contexto})
480
-
481
- yield history, timeline_execucao, pipeline_state
482
-
483
- # Inicia a execução dos agentes
484
- yield from executar_pipeline(history, timeline_execucao, agentes_a_executar, pipeline_state)
485
-
486
-
487
- def executar_pipeline(history, timeline_execucao, agentes_a_executar, pipeline_state):
488
- # (Implementação de execução da pipeline e simulação de digitação)
489
- passo_atual = len(timeline_execucao) + 1
490
-
491
- for i, cfg in enumerate(agentes_a_executar):
492
- nome_agente = cfg.get("nome", "Agente")
493
- modelo_agente = model_pro if cfg.get("modelo") == "pro" else model_flash
494
- tipo_saida = cfg.get("tipo_saida", "json")
495
-
496
- msg_atual = history[-1][1] or ""
497
- history[-1][1] = msg_atual + f"⏳ **{nome_agente}** está analisando...\n"
498
- yield history, timeline_execucao, pipeline_state
499
-
500
- prompt_agente = f"""
501
- --- HISTÓRICO DA CONVERSA ATÉ AGORA ---
502
- {json.dumps(timeline_execucao, ensure_ascii=False, indent=2)}
503
- -----------------
504
- Sua Identidade: {nome_agente}
505
- Sua Missão Específica Agora: {cfg['missao']}
506
- Se o tipo de saída exigido for 'json', sua resposta DEVE ser APENAS o JSON.
507
- """
508
-
509
- try:
510
- inicio = time.time()
511
- resp = modelo_agente.generate_content(prompt_agente)
512
- texto_resp = resp.text
513
- duracao = time.time() - inicio
514
-
515
- # --- LÓGICA DE DETECÇÃO DE SAÍDA NÃO-JSON (STOP ou Relatório) ---
516
- if tipo_saida == "json":
517
- try:
518
- # Tenta interpretar a resposta como JSON
519
- texto_json_limpo = texto_resp.replace("```json", "").replace("```", "").strip()
520
- resposta_json = json.loads(texto_json_limpo)
521
-
522
- # 1. LÓGICA DE PAUSA (STOP)
523
- if resposta_json.get("PROXIMA_ACAO") == "PERGUNTAR_USUARIO" and resposta_json.get("DUVIDA_DETECTADA") == True:
524
-
525
- # VERIFICA REGRAS DE STOP - Fora de escopo?
526
- if "STOP:" in texto_resp:
527
- # Caso de STOP por fora do escopo
528
- 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."
529
- history[-1][1] = stop_message
530
- yield history, timeline_execucao, pipeline_state
531
- return
532
-
533
- # Caso de STOP por dúvida crítica (regra do protocolo da Fase 0)
534
- perguntas = resposta_json.get("TESTE_REFLEXAO", {}).get("perguntas", [])
535
-
536
- # Formata a resposta STOP
537
- stop_response = "STOP: preciso que você esclareça até 3 pontos antes de continuar:\n\n"
538
- for idx, p in enumerate(perguntas):
539
- if idx < 3:
540
- stop_response += f"{idx + 1}) {p}\n"
541
-
542
- # Salva o estado atual da pipeline
543
- pipeline_state["is_paused"] = True
544
- pipeline_state["timeline"] = timeline_execucao
545
- pipeline_state["remaining_agents"] = agentes_a_executar[i+1:]
546
-
547
- # Adiciona o passo à auditoria e ao chat
548
- timeline_execucao.append({"passo": passo_atual, "tipo": "STOP_USUARIO_REQUERIDO", "agente": nome_agente, "detalhes": stop_response})
549
-
550
- # Prepara a mensagem para o usuário no chat
551
- 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"
552
- for idx, p in enumerate(perguntas):
553
- if idx < 3:
554
- msg_para_usuario += f"**{idx + 1})** {p}\n"
555
-
556
- msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
557
- history[-1][1] = msg_atual + msg_para_usuario
558
-
559
- yield history, timeline_execucao, pipeline_state
560
- return # Sai da função para esperar o input do usuário
561
-
562
- # 2. Resposta JSON normal
563
- texto_para_auditoria = texto_json_limpo
564
- timeline_execucao.append({"passo": passo_atual, "tipo": "resposta_agente", "agente": nome_agente, "resposta": texto_para_auditoria})
565
-
566
- msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
567
- novo_trecho = f"✅ **[{nome_agente}]** concluiu sua análise em ({duracao:.1f}s). (JSON para Auditoria)\n"
568
- history[-1][1] = msg_atual + novo_trecho
569
- yield history, timeline_execucao, pipeline_state
570
-
571
- except json.JSONDecodeError:
572
- # Se deveria ser JSON e não é, trata como erro na auditoria
573
- 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."})
574
- msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
575
- history[-1][1] = msg_atual + f"❌ **[{nome_agente}]** falhou: Resposta não era JSON válido. ({duracao:.1f}s).\n"
576
- yield history, timeline_execucao, pipeline_state
577
-
578
- elif tipo_saida == "texto":
579
- # Lógica para a Fase 7 (RELATORIO_VALOR_VIDA)
580
-
581
- # O markdown será renderizado pelo Chatbot, não precisa de limpeza manual.
582
-
583
- # Adiciona o passo à auditoria
584
- timeline_execucao.append({"passo": passo_atual, "tipo": "relatorio_final", "agente": nome_agente, "relatorio": texto_resp})
585
-
586
- # Simula a digitação envolvente
587
- msg_final = f"**[RELATÓRIO FINAL DO {nome_agente}]**\n\n"
588
- history[-1][1] = msg_final
589
- yield history, timeline_execucao, pipeline_state
590
-
591
- # Divide o texto do relatório em blocos (parágrafos ou seções)
592
- blocos = texto_resp.split('\n\n')
593
-
594
- for bloco in blocos:
595
- if bloco.strip():
596
- # Simula a digitação bloco por bloco
597
- msg_final += f"{bloco}\n\n"
598
- history[-1][1] = msg_final
599
- # Pequena pausa para o efeito dinâmico
600
- time.sleep(0.5)
601
- yield history, timeline_execucao, pipeline_state
602
-
603
- # Finalização da digitação no chat
604
- msg_final += "\n\n--- FIM DO RELATÓRIO DE VALORAÇÃO ---"
605
- history[-1][1] = msg_final
606
- yield history, timeline_execucao, pipeline_state
607
-
608
- else:
609
- # Caso de tipo_saida não definido (erro)
610
- timeline_execucao.append({"passo": passo_atual, "tipo": "erro_config", "agente": nome_agente, "erro": "Tipo de saída não reconhecido ('json' ou 'texto')."})
611
- msg_atual = history[-1][1].replace(f"⏳ **{nome_agente}** está analisando...\n", "")
612
- history[-1][1] = msg_atual + f"\n❌ Erro de Configuração em {nome_agente}: Tipo de saída inválido. ({duracao:.1f}s).\n"
613
- yield history, timeline_execucao, pipeline_state
614
-
615
- except Exception as e:
616
- timeline_execucao.append({"passo": passo_atual, "tipo": "erro_agente", "agente": nome_agente, "erro": str(e)})
617
- msg_atual = history[-1][1]
618
- history[-1][1] = msg_atual.replace(f"⏳ **{nome_agente}** está analisando...\n", "") + f"\n❌ Erro em {nome_agente}: {str(e)}\n"
619
- yield history, timeline_execucao, pipeline_state
620
-
621
- passo_atual += 1
622
-
623
- # ==================== 6. UI (Gradio) ====================
624
-
625
- def ui_v29_stop_logic():
626
- css = """
627
- footer {display: none !important;}
628
- .contain {border: none !important;}
629
- """
630
-
631
- config_inicial = carregar_protocolo()
632
- documentacao = carregar_documentacao()
633
-
634
- # Define o objeto do tema
635
- app_theme = gr.themes.Soft()
636
-
637
- # CORREÇÃO: Removido 'css' e 'theme' do construtor gr.Blocks().
638
- with gr.Blocks(title="AI Forensics Auto") as app:
639
-
640
- # Estado da configuração dos agentes
641
- state_config = gr.State(config_inicial)
642
- # NOVO: Estado para controlar a pausa/continuação da pipeline
643
- pipeline_state = gr.State({"is_paused": False, "timeline": [], "remaining_agents": []})
644
-
645
- with gr.Tabs():
646
- with gr.Tab("💬 Investigação"):
647
-
648
- chatbot = gr.Chatbot(
649
- height=400,
650
- show_label=False,
651
- # CORREÇÃO: show_copy_button foi removido, render_markdown reativado com o formato de dados corrigido.
652
- render_markdown=True,
653
- label="Chat de Investigação"
654
- )
655
-
656
- with gr.Row():
657
- txt_input = gr.Textbox(
658
- scale=8,
659
- show_label=False,
660
- placeholder="Digite sua instrução ou responda à pergunta do agente...",
661
- lines=1
662
- )
663
- btn_enviar = gr.Button("Enviar 📨", variant="primary", scale=1)
664
-
665
- # Caixa de Opções Avançadas
666
- with gr.Accordion("⚙️ Opções Avançadas de Governança", open=False):
667
- chk_antecipatorio = gr.Checkbox(
668
- label=f"Incluir Contexto Antecipatório (`{ARQUIVO_CONTEXTO_ANTECIPATORIO}`) no início da análise (Recomendado).",
669
- value=True, # Ativado por padrão
670
- show_label=True
671
- )
672
-
673
- with gr.Accordion("📂 Adicionar Arquivos para Análise", open=False):
674
- gr.Markdown("Selecione arquivos (PDF, TXT). A transcrição iniciará **automaticamente**.")
675
- file_uploader = gr.File(
676
- file_count="multiple",
677
- file_types=[".pdf", ".txt", ".json", ".md"],
678
- label="Arraste arquivos aqui ou clique para selecionar"
679
- )
680
-
681
- with gr.Tab("🕵️ Auditoria"):
682
- gr.Markdown("### Trilha de Auditoria\nExibe o histórico completo de prompts e respostas de cada agente na última execução.")
683
- json_audit = gr.JSON(label="Timeline da Execução da Última Mensagem")
684
-
685
- with gr.Tab("⚙️ Protocolo & Autoria"):
686
- gr.Markdown(f"### Protocolo Causal (Diretrizes de Agentes)\nDesenvolvedor: **{DEVS_NAME}** ({DEVS_EMAIL}) | Licença: **{LICENSE_INFO}**")
687
- gr.Markdown("Visualize as missões dos agentes de IA. O número de fases é totalmente configurável na estrutura JSON abaixo.")
688
- code_config = gr.Code(value=config_inicial, language="json", label="protocolo.json (Visualização de Diretrizes)", interactive=False)
689
-
690
- with gr.Tab("📚 Documentação"):
691
- gr.Markdown("### Documentação da Aplicação (help.md)")
692
- gr.Markdown(documentacao)
693
-
694
-
695
- # Ação de clique agora passa o novo checkbox para o orquestrador
696
- btn_enviar.click(
697
- chat_orquestrador,
698
- inputs=[txt_input, chatbot, state_config, pipeline_state, chk_antecipatorio],
699
- outputs=[chatbot, json_audit, pipeline_state]
700
- ).then(
701
- lambda: "", outputs=[txt_input]
702
- )
703
-
704
- file_uploader.upload(
705
- automacao_upload_processamento,
706
- inputs=[file_uploader, chatbot, state_config],
707
- outputs=[chatbot]
708
- )
709
-
710
- # CORREÇÃO: Retorna o app, o css e o tema para serem passados para launch()
711
- return app, css, app_theme
712
-
713
- if __name__ == "__main__":
714
- # CORREÇÃO: Recebe o app, o css e o tema e os passa para app.launch()
715
- app, css_styles, app_theme = ui_v29_stop_logic()
716
- app.launch(css=css_styles, theme=app_theme)