caarleexx commited on
Commit
de54042
·
verified ·
1 Parent(s): 22d6f06

Upload app (11).py

Browse files
Files changed (1) hide show
  1. app (11).py +486 -0
app (11).py ADDED
@@ -0,0 +1,486 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ # Dependências para PDF
12
+ try:
13
+ import PyPDF2
14
+ PDF_SUPPORT = True
15
+ except ImportError:
16
+ PDF_SUPPORT = False
17
+ print("⚠️ PyPDF2 não instalado. Install: pip install PyPDF2")
18
+
19
+ # ==================== 1. CONFIGURAÇÃO ====================
20
+
21
+ api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
22
+ if api_key and api_key != "SUA_API_KEY_AQUI":
23
+ genai.configure(api_key=api_key)
24
+
25
+ model_flash = genai.GenerativeModel("gemini-flash-latest")
26
+ model_pro = genai.GenerativeModel("gemini-pro-latest")
27
+
28
+ ARQUIVO_CONFIG = "protocolo.json"
29
+ PASTA_TRANSCRICOES = "transcricoes"
30
+ PAGES_PER_CHUNK = 10
31
+ MAX_WORKERS = 5 # Limite de chamadas paralelas
32
+
33
+ os.makedirs(PASTA_TRANSCRICOES, exist_ok=True)
34
+
35
+ # ==================== 2. UTILIDADES ====================
36
+
37
+ def carregar_protocolo():
38
+ try:
39
+ with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f:
40
+ return f.read()
41
+ except:
42
+ return json.dumps([
43
+ {"nome": "Leitor", "modelo": "flash", "missao": "Resumir o documento"},
44
+ {"nome": "Investigador", "modelo": "pro", "missao": "Encontrar inconsistências"}
45
+ ], indent=2)
46
+
47
+ def salvar_protocolo(conteudo):
48
+ try:
49
+ json.loads(conteudo)
50
+ with open(ARQUIVO_CONFIG, "w", encoding="utf-8") as f:
51
+ f.write(conteudo)
52
+ return "✅ Salvo"
53
+ except:
54
+ return "❌ Erro JSON"
55
+
56
+ def limpar_nome_arquivo(nome):
57
+ nome_base = os.path.basename(nome)
58
+ nome_limpo = "".join([c for c in nome_base if c.isalnum() or c in (' ', '.', '_', '-')]).strip()
59
+ return nome_limpo + ".json"
60
+
61
+ def extrair_texto_pdf(caminho_pdf):
62
+ try:
63
+ with open(caminho_pdf, 'rb') as f:
64
+ reader = PyPDF2.PdfReader(f)
65
+ paginas = []
66
+ for i, page in enumerate(reader.pages):
67
+ texto = page.extract_text()
68
+ paginas.append({
69
+ "numero": i + 1,
70
+ "texto": texto,
71
+ "metadata": str(page)[:200]
72
+ })
73
+ return paginas, None
74
+ except Exception as e:
75
+ return None, str(e)
76
+
77
+ def fragmentar_pdf(paginas, tamanho_chunk=PAGES_PER_CHUNK):
78
+ chunks = []
79
+ for i in range(0, len(paginas), tamanho_chunk):
80
+ chunk = paginas[i:i + tamanho_chunk]
81
+ num_inicio = chunk[0]["numero"]
82
+ num_fim = chunk[-1]["numero"]
83
+
84
+ texto_consolidado = "\n---QUEBRA DE PÁGINA---\n".join(
85
+ [f"[PÁGINA {p['numero']}]\n{p['texto']}" for p in chunk]
86
+ )
87
+
88
+ chunks.append({
89
+ "id": f"chunk_{num_inicio}_{num_fim}",
90
+ "paginas": f"{num_inicio}-{num_fim}",
91
+ "num_paginas": len(chunk),
92
+ "texto": texto_consolidado,
93
+ "metadata": [p["metadata"] for p in chunk]
94
+ })
95
+ return chunks
96
+
97
+ def processar_pdf_completo(arquivo_pdf):
98
+ if not PDF_SUPPORT:
99
+ return None, "❌ PyPDF2 não disponível"
100
+
101
+ try:
102
+ paginas, erro = extrair_texto_pdf(arquivo_pdf.name if hasattr(arquivo_pdf, 'name') else arquivo_pdf)
103
+ if erro:
104
+ return None, f"❌ Erro ao ler PDF: {erro}"
105
+
106
+ chunks = fragmentar_pdf(paginas)
107
+ nome_arquivo = os.path.basename(arquivo_pdf.name if hasattr(arquivo_pdf, 'name') else arquivo_pdf)
108
+
109
+ return {
110
+ "arquivo": nome_arquivo,
111
+ "total_paginas": len(paginas),
112
+ "total_chunks": len(chunks),
113
+ "chunks": chunks,
114
+ "tipo": "pdf"
115
+ }, None
116
+ except Exception as e:
117
+ return None, f"❌ Erro no processamento: {str(e)}"
118
+
119
+ def ler_arquivo_texto(arquivo):
120
+ if arquivo is None: return None
121
+ try:
122
+ with open(arquivo.name, "r", encoding="utf-8") as f:
123
+ conteudo = f.read()
124
+ return {
125
+ "arquivo": os.path.basename(arquivo.name),
126
+ "conteudo": conteudo,
127
+ "tipo": "texto"
128
+ }
129
+ except: return None
130
+
131
+ # ==================== 3. PIPELINE DE IA ====================
132
+
133
+ def transcrever_chunk(chunk_data, config_agentes):
134
+ # Função auxiliar para ser executada na thread
135
+ modelo = model_flash
136
+ try:
137
+ if config_agentes and isinstance(config_agentes, list):
138
+ if config_agentes[0].get("modelo") == "pro":
139
+ modelo = model_pro
140
+ except:
141
+ pass
142
+
143
+ prompt = f"""
144
+ ANÁLISE DE DOCUMENTO (OCR/LEITURA):
145
+ Transcreva e estruture o conteúdo das páginas {chunk_data['paginas']}.
146
+ Texto extraído:
147
+ {chunk_data['texto']}
148
+
149
+ Retorne JSON: {{ "transcricao": "...", "objetos": ["..."], "resumo": "..." }}
150
+ """
151
+ try:
152
+ # Retry simples em caso de erro 429 (rate limit)
153
+ for tentativa in range(3):
154
+ try:
155
+ resposta = modelo.generate_content(prompt)
156
+ texto_resp = resposta.text.replace("```json", "").replace("```", "")
157
+ return json.loads(texto_resp.strip()), None
158
+ except Exception as inner_e:
159
+ if "429" in str(inner_e):
160
+ time.sleep(2 * (tentativa + 1))
161
+ continue
162
+ raise inner_e
163
+ except Exception as e:
164
+ return None, str(e)
165
+
166
+ # ==================== 4. GERENCIADOR DE ARQUIVOS ====================
167
+
168
+ class GerenciadorArquivos:
169
+ def __init__(self):
170
+ self.arquivos = {}
171
+
172
+ def adicionar(self, arquivo, arquivo_id):
173
+ self.arquivos[arquivo_id] = {
174
+ "arquivo": arquivo,
175
+ "nome": os.path.basename(arquivo.name),
176
+ "status": "adicionado",
177
+ "processado": None,
178
+ "transcricao": None
179
+ }
180
+
181
+ def gerar_prompt_com_transcricoes(self, texto_usuario):
182
+ prompt = texto_usuario + "\n\n--- CONTEXTO DOS ARQUIVOS ---\n"
183
+ count = 0
184
+ for _, item in self.arquivos.items():
185
+ if item["status"] == "processado" and item["transcricao"]:
186
+ count += 1
187
+ trans = item["transcricao"]
188
+ nome = item["nome"]
189
+ prompt += f"\n[ARQUIVO: {nome}]\n"
190
+
191
+ if isinstance(trans, dict) and "chunks_processados" in trans:
192
+ # Como garantimos a ordem na lista chunks_processados, iteramos normalmente
193
+ for chunk in trans["chunks_processados"]:
194
+ if chunk.get("status") == "OK":
195
+ resumo = chunk.get('resumo', '')
196
+ resumo = str(resumo) if resumo else ""
197
+ prompt += f"Páginas {chunk['paginas']}: {resumo}\n"
198
+
199
+ texto_full = chunk.get('transcricao', '')
200
+ if texto_full:
201
+ texto_seguro = str(texto_full)
202
+ prompt += f"Trecho: {texto_seguro[:400]}...\n"
203
+ else:
204
+ prompt += "Trecho: (vazio)\n"
205
+
206
+ elif isinstance(trans, dict) and "conteudo" in trans:
207
+ conteudo = str(trans['conteudo'])
208
+ prompt += f"Conteúdo: {conteudo[:1000]}...\n"
209
+
210
+ if count == 0:
211
+ prompt += "(Nenhum arquivo processado ainda)"
212
+ return prompt
213
+
214
+ # Instância Global
215
+ gerenciador = GerenciadorArquivos()
216
+
217
+ # ==================== 5. FUNÇÕES DE ORQUESTRAÇÃO ====================
218
+
219
+ def automacao_upload_processamento(files, history, config_json):
220
+ if not files:
221
+ return history
222
+
223
+ try:
224
+ config_agentes = json.loads(config_json)
225
+ except:
226
+ config_agentes = []
227
+
228
+ if history is None:
229
+ history = []
230
+
231
+ history.append([None, f"📂 **SISTEMA:** Recebi {len(files)} arquivo(s). Verificando cache e processando..."])
232
+ yield history
233
+
234
+ ids_para_processar = []
235
+
236
+ for f in files:
237
+ arquivo_id = f"arq_{int(time.time()*1000)}_{f.name}"
238
+ gerenciador.adicionar(f, arquivo_id)
239
+ ids_para_processar.append(arquivo_id)
240
+
241
+ for arq_id in ids_para_processar:
242
+ item = gerenciador.arquivos[arq_id]
243
+ nome = item["nome"]
244
+
245
+ # --- VERIFICAÇÃO DE CACHE ---
246
+ nome_cache = limpar_nome_arquivo(nome)
247
+ caminho_cache = os.path.join(PASTA_TRANSCRICOES, nome_cache)
248
+
249
+ if os.path.exists(caminho_cache):
250
+ try:
251
+ with open(caminho_cache, "r", encoding="utf-8") as cache_file:
252
+ dados_cache = json.load(cache_file)
253
+ item["transcricao"] = dados_cache
254
+ item["status"] = "processado"
255
+ if nome.lower().endswith('.pdf') and "chunks_processados" in dados_cache:
256
+ item["processado"] = {"tipo": "pdf", "chunks": []}
257
+ history.append([None, f"♻️ **Cache Encontrado:** `{nome}` já foi processado. Carregando..."])
258
+ yield history
259
+ continue
260
+ except Exception as e:
261
+ history.append([None, f"⚠️ Erro cache `{nome}`: {e}. Reprocessando..."])
262
+ # ---------------------------
263
+
264
+ history.append([None, f"⚙️ **Processando:** `{nome}`..."])
265
+ yield history
266
+
267
+ if nome.lower().endswith('.pdf'):
268
+ if not PDF_SUPPORT:
269
+ history.append([None, f"❌ Erro em `{nome}`: Biblioteca PDF ausente."])
270
+ yield history
271
+ continue
272
+
273
+ pdf_proc, erro = processar_pdf_completo(item["arquivo"])
274
+ if erro:
275
+ history.append([None, f"❌ Erro em `{nome}`: {erro}"])
276
+ yield history
277
+ continue
278
+
279
+ item["processado"] = pdf_proc
280
+ chunks = pdf_proc["chunks"]
281
+ total_chunks = len(chunks)
282
+
283
+ # Inicializa lista com o tamanho exato para garantir a ordem
284
+ chunks_ordenados = [None] * total_chunks
285
+
286
+ history.append([None, f"📄 `{nome}` fragmentado em {total_chunks} partes. Iniciando IA (Paralelo: {MAX_WORKERS} threads)..."])
287
+ yield history
288
+
289
+ # --- PROCESSAMENTO PARALELO ---
290
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
291
+ # Dicionário para mapear Future -> Índice Original
292
+ futures_map = {}
293
+
294
+ # Submeter todas as tarefas
295
+ for i, chunk in enumerate(chunks):
296
+ future = executor.submit(transcrever_chunk, chunk, config_agentes)
297
+ futures_map[future] = i
298
+
299
+ # Coletar resultados conforme ficam prontos
300
+ concluidos = 0
301
+ for future in as_completed(futures_map):
302
+ index_original = futures_map[future]
303
+ res, err = future.result()
304
+
305
+ if err:
306
+ chunks_ordenados[index_original] = {"status": "ERRO", "paginas": chunks[index_original]["paginas"]}
307
+ else:
308
+ chunks_ordenados[index_original] = {
309
+ "status": "OK",
310
+ "paginas": chunks[index_original]["paginas"],
311
+ "transcricao": res.get("transcricao"),
312
+ "resumo": res.get("resumo")
313
+ }
314
+
315
+ concluidos += 1
316
+ # Atualiza a UI a cada 2 chunks ou no final para não flodar
317
+ if concluidos % 2 == 0 or concluidos == total_chunks:
318
+ msg_base = f"📄 `{nome}`: Processando partes... ({concluidos}/{total_chunks})"
319
+ history[-1][1] = msg_base
320
+ yield history
321
+ # ------------------------------
322
+
323
+ dados_finais = {
324
+ "arquivo": nome,
325
+ "data_processamento": str(datetime.now()),
326
+ "chunks_processados": chunks_ordenados # Agora contém a lista na ordem correta
327
+ }
328
+
329
+ item["transcricao"] = dados_finais
330
+ item["status"] = "processado"
331
+
332
+ try:
333
+ with open(caminho_cache, "w", encoding="utf-8") as f_out:
334
+ json.dump(dados_finais, f_out, indent=2, ensure_ascii=False)
335
+ history.append([None, f"💾 `{nome}` processado e salvo no cache."])
336
+ except Exception as e:
337
+ history.append([None, f"⚠️ Erro ao salvar cache: {e}"])
338
+
339
+ yield history
340
+
341
+ else:
342
+ # Processamento de Texto Simples (não precisa de paralelismo pois é 1 chunk)
343
+ res = ler_arquivo_texto(item["arquivo"])
344
+ if res:
345
+ item["processado"] = res
346
+ dados_finais = {"conteudo": res["conteudo"], "data_processamento": str(datetime.now())}
347
+ item["transcricao"] = dados_finais
348
+ item["status"] = "processado"
349
+
350
+ with open(caminho_cache, "w", encoding="utf-8") as f_out:
351
+ json.dump(dados_finais, f_out, indent=2, ensure_ascii=False)
352
+
353
+ history.append([None, f"✅ `{nome}` (Texto) lido e salvo."])
354
+ else:
355
+ history.append([None, f"❌ Falha ao ler `{nome}`."])
356
+ yield history
357
+
358
+ history.append([None, "🏁 **Processamento de lote finalizado.** Os arquivos estão prontos para análise."])
359
+ yield history
360
+
361
+
362
+ def chat_orquestrador(message, history, config_json):
363
+ try:
364
+ prompt_contexto = gerenciador.gerar_prompt_com_transcricoes(message)
365
+ except Exception as e:
366
+ history.append([message, f"❌ Erro ao gerar contexto: {str(e)}"])
367
+ yield history
368
+ return
369
+
370
+ try:
371
+ protocolo = json.loads(config_json)
372
+ except:
373
+ history.append([message, "❌ Erro no JSON de Configuração."])
374
+ yield history
375
+ return
376
+
377
+ history.append([message, None])
378
+ yield history
379
+
380
+ timeline_execucao = [{"role": "user", "content": prompt_contexto}]
381
+
382
+ for cfg in protocolo:
383
+ nome_agente = cfg.get("nome", "Agente")
384
+ modelo_agente = model_pro if cfg.get("modelo") == "pro" else model_flash
385
+
386
+ history[-1][1] = f"⏳ **{nome_agente}** está analisando..."
387
+ yield history
388
+
389
+ prompt_agente = f"""
390
+ --- HISTÓRICO ---
391
+ {json.dumps(timeline_execucao, ensure_ascii=False)}
392
+ -----------------
393
+ Você é: {nome_agente}
394
+ Sua Missão: {cfg['missao']}
395
+ Responda de forma concisa e direta.
396
+ """
397
+ try:
398
+ inicio = time.time()
399
+ resp = modelo_agente.generate_content(prompt_agente)
400
+ texto_resp = resp.text
401
+ duracao = time.time() - inicio
402
+
403
+ timeline_execucao.append({"role": "model", "content": f"[{nome_agente}]: {texto_resp}"})
404
+
405
+ msg_atual = history[-1][1]
406
+ if "⏳" in msg_atual: msg_atual = ""
407
+
408
+ novo_trecho = f"**[{nome_agente}]** ({duracao:.1f}s):\n{texto_resp}\n\n"
409
+ history[-1][1] = msg_atual + novo_trecho
410
+ yield history
411
+
412
+ except Exception as e:
413
+ msg_atual = history[-1][1]
414
+ history[-1][1] = msg_atual + f"\n❌ Erro em {nome_agente}: {str(e)}\n"
415
+ yield history
416
+
417
+ # ==================== 6. UI (Gradio) ====================
418
+
419
+ def ui_v28_corrected():
420
+ css = """
421
+ footer {display: none !important;}
422
+ .contain {border: none !important;}
423
+ """
424
+
425
+ config_inicial = carregar_protocolo()
426
+
427
+ with gr.Blocks(title="AI Forensics Auto", css=css, theme=gr.themes.Soft()) as app:
428
+
429
+ state_config = gr.State(config_inicial)
430
+
431
+ with gr.Tabs():
432
+ with gr.Tab("💬 Investigação"):
433
+
434
+ chatbot = gr.Chatbot(
435
+ height=550,
436
+ show_label=False,
437
+ show_copy_button=True,
438
+ render_markdown=True
439
+ )
440
+
441
+ with gr.Row():
442
+ txt_input = gr.Textbox(
443
+ scale=8,
444
+ show_label=False,
445
+ placeholder="Digite sua instrução ou pergunta sobre o caso...",
446
+ lines=1
447
+ )
448
+ btn_enviar = gr.Button("Enviar 📨", variant="primary", scale=1)
449
+
450
+ with gr.Accordion("📂 Adicionar Arquivos para Análise", open=False):
451
+ gr.Markdown("Selecione arquivos (PDF, TXT). A transcrição iniciará **automaticamente** e os logs aparecerão no chat acima.")
452
+ file_uploader = gr.File(
453
+ file_count="multiple",
454
+ file_types=[".pdf", ".txt", ".json", ".md"],
455
+ label="Arraste arquivos aqui ou clique para selecionar"
456
+ )
457
+
458
+ with gr.Tab("⚙️ Contexto & Config"):
459
+ gr.Markdown("### Protocolo dos Agentes")
460
+ with gr.Row():
461
+ btn_save_cfg = gr.Button("💾 Salvar Alterações")
462
+ lbl_cfg_status = gr.Label(show_label=False)
463
+
464
+ code_config = gr.Code(value=config_inicial, language="json", label="protocolo.json")
465
+
466
+ btn_save_cfg.click(salvar_protocolo, inputs=[code_config], outputs=[lbl_cfg_status])
467
+ btn_save_cfg.click(lambda x: x, inputs=[code_config], outputs=[state_config])
468
+
469
+ btn_enviar.click(
470
+ chat_orquestrador,
471
+ inputs=[txt_input, chatbot, state_config],
472
+ outputs=[chatbot]
473
+ ).then(
474
+ lambda: "", outputs=[txt_input]
475
+ )
476
+
477
+ file_uploader.upload(
478
+ automacao_upload_processamento,
479
+ inputs=[file_uploader, chatbot, state_config],
480
+ outputs=[chatbot]
481
+ )
482
+
483
+ return app
484
+
485
+ if __name__ == "__main__":
486
+ ui_v28_corrected().launch()