caarleexx commited on
Commit
1dacd2d
Β·
verified Β·
1 Parent(s): 3ba9006

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +158 -120
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
- # ║ PIPELINE V44: FRAG + VISÃO PAGINADA + PARALELISMO + CACHE + AUDITORIA ║
3
  # β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
4
 
5
  import os
@@ -11,7 +11,8 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
11
 
12
  import gradio as gr
13
  import google.generativeai as genai
14
- import pypdf # pip install pypdf
 
15
 
16
  # ==================== 1. CONFIGURAÇÃO ====================
17
 
@@ -19,12 +20,15 @@ api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
19
  if api_key and api_key != "SUA_API_KEY_AQUI":
20
  genai.configure(api_key=api_key)
21
 
 
 
22
  model_flash = genai.GenerativeModel("gemini-flash-latest")
 
23
  model_pro = genai.GenerativeModel("gemini-pro-latest")
24
 
25
  ARQUIVO_CONFIG = "protocolo_fragmentacao_visao-3.json"
26
  PASTA_CACHE = "cache_processamento"
27
- MAX_WORKERS = 5 # Paralelismo
28
 
29
  os.makedirs(PASTA_CACHE, exist_ok=True)
30
 
@@ -41,14 +45,13 @@ def carregar_protocolo():
41
  except:
42
  proto = [
43
  {
44
- "nome": "PAGINADOR_VISUAL",
45
  "missao": (
46
- "Transcreva"
47
- "Separe por pΓ‘gina e devolva uma lista JSON com objetos "
48
- "{'pagina','transcricao'}."
49
- "Retorne APENAS essa lista JSON, sem texto extra."
50
  ),
51
- "tipo_saida": "json",
52
  "modelo": "pro",
53
  }
54
  ]
@@ -78,10 +81,27 @@ def carregar_cache(hash_id):
78
  return json.load(f)
79
  return None
80
 
81
- # --------- DIVISÃO PDF ---------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5, logs=""):
84
- logs = log_point("ler_anexo_e_fragmentar() chamado", logs)
 
 
 
85
 
86
  if arquivo is None:
87
  return [], "", logs
@@ -91,50 +111,72 @@ def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5, logs=""):
91
  if not os.path.exists(filename):
92
  return [], f"[ERRO: Arquivo nΓ£o encontrado]", logs
93
 
94
- anexo_info = f"[PDF: {os.path.basename(filename)}]"
95
 
 
96
  if not filename.lower().endswith(".pdf"):
97
  logs = log_point("Arquivo texto simples detectado", logs)
98
  try:
99
  with open(filename, "r", encoding="utf-8") as f:
100
  texto = f.read()
101
- # Retorna como um ΓΊnico fragmento de texto
102
  return [texto], f"[TXT: {os.path.basename(filename)}]", logs
103
  except:
104
  return [], "[ERRO LEITURA TXT]", logs
105
 
 
106
  try:
107
- reader = pypdf.PdfReader(filename)
108
- total_pages = len(reader.pages)
109
- logs = log_point(f"PDF carregado: {total_pages} pΓ‘ginas", logs)
110
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  fragments = []
 
 
112
  for i in range(0, total_pages, paginas_por_fragmento):
113
- start = i + 1
114
  end = min(i + paginas_por_fragmento, total_pages)
115
-
116
- bloco_texto = ""
117
- for p in range(i, end):
118
- try:
119
- t = reader.pages[p].extract_text() or ""
120
- except Exception as e:
121
- t = f"\n[ERRO_EXTRACT_PAG_{p+1}: {e}]\n"
122
- bloco_texto += f"\n=== PAGINA {p+1}/{total_pages} ===\n{t}\n"
123
-
124
  fragment = (
125
  f"=== FRAG {i//paginas_por_fragmento + 1} "
126
- f"(PÁGS {start}-{end}/{total_pages}) ===\n"
127
- f"{bloco_texto.strip()}"
128
  )
129
  fragments.append(fragment)
130
 
131
  logs = log_point(f"Total de fragmentos criados: {len(fragments)}", logs)
132
  return fragments, anexo_info, logs
 
133
  except Exception as e:
134
- logs = log_point(f"ERRO PDF: {e}", logs)
135
- return [], f"[ERRO PDF: {str(e)}]", logs
 
 
 
136
 
137
- # ==================== 3. ENGINE DE EXECUÇÃO ====================
138
 
139
  def _extrair_json_possivel(out_raw: str) -> str:
140
  cleaned = out_raw.strip()
@@ -151,7 +193,7 @@ def _extrair_json_possivel(out_raw: str) -> str:
151
 
152
  def executar_no(timeline, config, fragmento_input=None):
153
  """
154
- FunΓ§Γ£o Worker que serΓ‘ chamada tanto sequencialmente quanto em paralelo.
155
  """
156
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
157
 
@@ -176,56 +218,67 @@ def executar_no(timeline, config, fragmento_input=None):
176
  out = resp.text or ""
177
  break
178
  except Exception as e:
179
- if "429" in str(e):
180
  time.sleep(2 * (tentativa + 1))
181
  continue
182
  raise e
183
 
184
  content = out
185
- if config["tipo_saida"] == "json":
186
  cleaned = _extrair_json_possivel(out)
187
  try:
188
  content = json.loads(cleaned)
189
  except:
190
- content = [] # Fallback em caso de erro de parse
191
-
192
  return {"role": "assistant", "agent": config["nome"], "content": content}, None
193
  except Exception as e:
194
  return {"role": "system", "error": str(e)}, str(e)
195
 
196
- # ==================== 4. ORQUESTRADOR ====================
197
 
198
  def orquestrador(texto, arquivo, history, json_config, confext_state):
199
- logs = f"πŸš€ START: {datetime.now().strftime('%H:%M:%S')}\n"
200
- logs = log_point("Orquestrador V44 iniciado", logs)
201
 
202
  # 1. PreparaΓ§Γ£o
203
  if history is None: history = []
204
 
205
  nome_arquivo = os.path.basename(getattr(arquivo, "name", "sem_arquivo")) if arquivo else "sem_arquivo"
206
- hash_op = gerar_hash_arquivo(nome_arquivo + json_config) # Hash baseado no arquivo + protocolo
 
207
 
208
  # 2. Verifica Cache
209
  cache_existente = carregar_cache(hash_op) if arquivo else None
210
 
 
 
211
  if cache_existente:
212
  logs = log_point(f"♻️ Cache encontrado para {nome_arquivo}", logs)
213
  confext_upload = cache_existente["confext_upload"]
214
  timeline = cache_existente.get("timeline", [])
215
- history.append([texto, "βœ… Arquivo carregado do cache! AnΓ‘lise pronta."])
 
 
216
  yield history, timeline, logs, confext_upload
217
 
218
- # Se houver texto novo do usuΓ‘rio, seguimos para anΓ‘lise final, senΓ£o paramos
219
  if not texto:
220
  return
221
  else:
222
- # 3. Processamento Normal
 
 
 
223
  fragmentos, anexo_info, logs = ler_anexo_e_fragmentar(
224
  arquivo, paginas_por_fragmento=5, logs=logs
225
  )
226
 
227
- history.append([texto + (" πŸ“Ž" if arquivo else ""), None])
228
- yield history, {}, logs, confext_state
 
 
 
 
229
 
230
  try:
231
  protocolo = json.loads(json_config)
@@ -235,87 +288,73 @@ def orquestrador(texto, arquivo, history, json_config, confext_state):
235
  return
236
 
237
  timeline = [{"role": "user", "content": texto}]
 
 
238
  confext_upload = {
239
  "arquivo": nome_arquivo,
240
  "meta": anexo_info,
241
- "paginas": []
242
  }
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- # 4. ExecuΓ§Γ£o Paginador (Paralela)
245
- if protocolo and fragmentos:
246
- cfg_visao = protocolo[0] # Assume que o primeiro Γ© o leitor
247
- logs = log_point(f"Iniciando Leitura Paralela ({MAX_WORKERS} workers) com {cfg_visao['nome']}", logs)
248
- history[-1][1] = f"⏳ Fragmentando e lendo {len(fragmentos)} partes em paralelo..."
249
- yield history, timeline, logs, confext_upload
 
250
 
251
- resultados_ordenados = [None] * len(fragmentos)
 
252
 
253
- with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
254
- futures_map = {executor.submit(executar_no, [], cfg_visao, frag): i for i, frag in enumerate(fragmentos)}
255
-
256
- concluidos = 0
257
- for future in as_completed(futures_map):
258
- idx = futures_map[future]
259
- res, erro = future.result()
260
-
261
- if erro:
262
- logs = log_point(f"Erro no frag {idx}: {erro}", logs)
263
- else:
264
- resultados_ordenados[idx] = res["content"]
265
-
266
- concluidos += 1
267
- history[-1][1] = f"⏳ Leitura: {concluidos}/{len(fragmentos)} partes processadas..."
268
- yield history, timeline, logs, confext_upload
269
-
270
- # Consolidar resultados ordenados
271
- for pags in resultados_ordenados:
272
- if pags:
273
- if isinstance(pags, list):
274
- confext_upload["paginas"].extend(pags)
275
- elif isinstance(pags, dict):
276
- confext_upload["paginas"].append(pags)
277
-
278
- logs = log_point(f"Leitura concluΓ­da. Total pΓ‘ginas extraΓ­das: {len(confext_upload['paginas'])}", logs)
279
-
280
- # Salvar Cache apΓ³s a leitura pesada
281
- if arquivo:
282
- salvar_cache(hash_op, {"confext_upload": confext_upload, "timeline": timeline})
283
- logs = log_point("Estado salvo em Cache", logs)
284
-
285
- # Injeta contexto no timeline
286
- timeline.append({
287
- "role": "system",
288
- "agent": "CONFEXT_UPLOAD",
289
- "content": confext_upload
290
- })
291
-
292
- # 5. ExecuΓ§Γ£o dos Agentes de AnΓ‘lise (Sequencial)
293
- restante = protocolo[1:] if protocolo else []
294
-
295
- for cfg in restante:
296
- history[-1][1] = f"βš™οΈ {cfg['nome']} analisando..."
297
- logs = log_point(f"Iniciando agente: {cfg['nome']}", logs)
298
- yield history, timeline, logs, confext_upload
299
 
300
- # Passa timeline atualizada
301
- res, erro = executar_no(timeline, cfg, fragmento_input=None)
302
-
303
- if erro:
304
- logs = log_point(f"Erro agente {cfg['nome']}: {erro}", logs)
305
- else:
306
- timeline.append(res)
307
- if cfg.get("tipo_saida") == "texto":
308
- history[-1][1] = res["content"]
 
 
 
 
 
 
309
 
310
- yield history, timeline, logs, confext_upload
 
 
 
 
 
 
311
 
312
  if not texto and arquivo:
313
- history[-1][1] = "βœ… Documento processado e indexado. Pode fazer perguntas."
314
 
315
  logs = log_point("Processo Finalizado", logs)
316
  yield history, timeline, logs, confext_upload
317
 
318
- # ==================== 5. UI ====================
319
 
320
  def ui_clean():
321
  css = """
@@ -325,7 +364,7 @@ def ui_clean():
325
 
326
  config_init = carregar_protocolo()
327
 
328
- with gr.Blocks(title="AI Forensics Auto V44", css=css, theme=gr.themes.Soft()) as app:
329
  confext_state = gr.State(value=None)
330
 
331
  with gr.Tabs():
@@ -360,18 +399,17 @@ def ui_clean():
360
 
361
  def _on_upload(x):
362
  nome = os.path.basename(getattr(x, "name", x))
363
- return f"πŸ“Ž Anexo pronto para anΓ‘lise: {nome}"
364
 
365
  file_in.upload(_on_upload, inputs=file_in, outputs=file_status)
366
 
367
- # --- AQUI ESTÁ A ABA SOLICITADA ---
368
  with gr.Tab("πŸ•΅οΈ Auditoria & Debug"):
369
- gr.Markdown("### 🧠 Processo Interno de Pensamento")
370
  with gr.Row():
371
- out_dna = gr.JSON(label="Timeline da IA (Contexto)")
372
  out_logs = gr.Textbox(label="Logs do Sistema", lines=20)
373
 
374
- gr.Markdown("### πŸ“‚ Dados Estruturados (Confext)")
375
  confext_view = gr.JSON(label="ConteΓΊdo ExtraΓ­do")
376
 
377
  with gr.Tab("βš™οΈ Config"):
@@ -393,7 +431,7 @@ def ui_clean():
393
  trig(
394
  _orq_wrapper,
395
  inputs=[txt_in, file_in, chatbot, code_json, confext_state],
396
- outputs=[chatbot, out_dna, out_logs, confext_state], # Atualiza aba Debug
397
  ).then(
398
  lambda c: (None, None, "", c)[1:],
399
  inputs=confext_state,
@@ -401,7 +439,7 @@ def ui_clean():
401
  ).then(
402
  lambda c: c,
403
  inputs=confext_state,
404
- outputs=confext_view, # Atualiza visualizador JSON
405
  )
406
 
407
  return app
 
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
+ # β•‘ PIPELINE V45: FRAG + OCR VISUAL (GEMINI) + AUDITORIA (HF READY) β•‘
3
  # β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
4
 
5
  import os
 
11
 
12
  import gradio as gr
13
  import google.generativeai as genai
14
+ from pdf2image import convert_from_path # Requer poppler-utils no packages.txt
15
+ from PIL import Image
16
 
17
  # ==================== 1. CONFIGURAÇÃO ====================
18
 
 
20
  if api_key and api_key != "SUA_API_KEY_AQUI":
21
  genai.configure(api_key=api_key)
22
 
23
+ # Modelos
24
+ # Flash: Usado para OCR rΓ‘pido (Visual) e tarefas simples
25
  model_flash = genai.GenerativeModel("gemini-flash-latest")
26
+ # Pro: Usado para raciocΓ­nio complexo no pipeline
27
  model_pro = genai.GenerativeModel("gemini-pro-latest")
28
 
29
  ARQUIVO_CONFIG = "protocolo_fragmentacao_visao-3.json"
30
  PASTA_CACHE = "cache_processamento"
31
+ MAX_WORKERS = 4 # Ajustado para evitar Rate Limit do Gemini no OCR
32
 
33
  os.makedirs(PASTA_CACHE, exist_ok=True)
34
 
 
45
  except:
46
  proto = [
47
  {
48
+ "nome": "ANALISTA_PRINCIPAL",
49
  "missao": (
50
+ "Analise o conteΓΊdo transcrito. "
51
+ "Identifique datas, nomes e o objetivo do documento. "
52
+ "Retorne um resumo estruturado."
 
53
  ),
54
+ "tipo_saida": "texto",
55
  "modelo": "pro",
56
  }
57
  ]
 
81
  return json.load(f)
82
  return None
83
 
84
+ # ==================== 3. ENGINE OCR (SUBSTITUI PYPDF) ====================
85
+
86
+ def transcrever_pagina_imagem(imagem, indice):
87
+ """FunΓ§Γ£o auxiliar para transcrever uma ΓΊnica imagem via Gemini Vision"""
88
+ try:
89
+ prompt_ocr = (
90
+ "Atue como um sistema OCR. Transcreva fielmente todo o texto desta imagem. "
91
+ "Se houver tabelas, represente-as em Markdown. "
92
+ "NΓ£o faΓ§a comentΓ‘rios, apenas retorne o texto."
93
+ )
94
+ response = model_flash.generate_content([prompt_ocr, imagem])
95
+ texto = response.text if response.text else "[PΓ‘gina vazia ou ilegΓ­vel]"
96
+ return indice, f"=== PÁGINA {indice} ===\n{texto}\n"
97
+ except Exception as e:
98
+ return indice, f"=== PÁGINA {indice} (ERRO OCR) ===\nErro: {str(e)}\n"
99
 
100
  def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5, logs=""):
101
+ """
102
+ V45: Converte PDF em Imagens -> Gemini Vision OCR -> Fragmentos de Texto.
103
+ """
104
+ logs = log_point("ler_anexo_e_fragmentar (OCR V45) chamado", logs)
105
 
106
  if arquivo is None:
107
  return [], "", logs
 
111
  if not os.path.exists(filename):
112
  return [], f"[ERRO: Arquivo nΓ£o encontrado]", logs
113
 
114
+ anexo_info = f"[DOC: {os.path.basename(filename)}]"
115
 
116
+ # 1. Se for TXT/MD simples
117
  if not filename.lower().endswith(".pdf"):
118
  logs = log_point("Arquivo texto simples detectado", logs)
119
  try:
120
  with open(filename, "r", encoding="utf-8") as f:
121
  texto = f.read()
 
122
  return [texto], f"[TXT: {os.path.basename(filename)}]", logs
123
  except:
124
  return [], "[ERRO LEITURA TXT]", logs
125
 
126
+ # 2. Processamento PDF com OCR (Vision)
127
  try:
128
+ logs = log_point("Convertendo PDF em imagens (pdf2image)...", logs)
129
+ # Importante: No HuggingFace, o poppler deve estar instalado via packages.txt
130
+ imagens = convert_from_path(filename)
131
+ total_pages = len(imagens)
132
+ logs = log_point(f"PDF convertido: {total_pages} pΓ‘ginas (imagens). Iniciando OCR...", logs)
133
+
134
+ # TranscriΓ§οΏ½οΏ½o Paralela das Imagens
135
+ textos_paginas = [""] * total_pages
136
+
137
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
138
+ # Submete tarefas
139
+ futuros = {executor.submit(transcrever_pagina_imagem, img, i+1): i for i, img in enumerate(imagens)}
140
+
141
+ completed_count = 0
142
+ for future in as_completed(futuros):
143
+ idx, texto_transcrito = future.result()
144
+ # O Γ­ndice retornado Γ© baseado em 1, ajustamos para lista 0-based
145
+ textos_paginas[idx-1] = texto_transcrito
146
+ completed_count += 1
147
+ if completed_count % 2 == 0:
148
+ print(f"OCR Progresso: {completed_count}/{total_pages}")
149
+
150
+ logs = log_point("OCR concluΓ­do. Agrupando fragmentos...", logs)
151
+
152
+ # 3. Agrupar pΓ‘ginas transcritas em fragmentos
153
  fragments = []
154
+ full_ocr_text = "" # Opcional: manter tudo junto se precisar
155
+
156
  for i in range(0, total_pages, paginas_por_fragmento):
157
+ start = i
158
  end = min(i + paginas_por_fragmento, total_pages)
159
+
160
+ bloco_texto = "\n".join(textos_paginas[start:end])
161
+
 
 
 
 
 
 
162
  fragment = (
163
  f"=== FRAG {i//paginas_por_fragmento + 1} "
164
+ f"(PÁGS {start+1}-{end}/{total_pages}) ===\n"
165
+ f"{bloco_texto}"
166
  )
167
  fragments.append(fragment)
168
 
169
  logs = log_point(f"Total de fragmentos criados: {len(fragments)}", logs)
170
  return fragments, anexo_info, logs
171
+
172
  except Exception as e:
173
+ err_msg = f"ERRO CRÍTICO OCR: {str(e)}"
174
+ if "poppler" in str(e).lower():
175
+ err_msg += " (DICA: Verifique se poppler-utils estΓ‘ instalado no sistema/packages.txt)"
176
+ logs = log_point(err_msg, logs)
177
+ return [], f"[ERRO: {err_msg}]", logs
178
 
179
+ # ==================== 4. ENGINE DE EXECUÇÃO (PIPELINE) ====================
180
 
181
  def _extrair_json_possivel(out_raw: str) -> str:
182
  cleaned = out_raw.strip()
 
193
 
194
  def executar_no(timeline, config, fragmento_input=None):
195
  """
196
+ FunΓ§Γ£o Worker que executa a anΓ‘lise lΓ³gica sobre o texto jΓ‘ extraΓ­do.
197
  """
198
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
199
 
 
218
  out = resp.text or ""
219
  break
220
  except Exception as e:
221
+ if "429" in str(e): # Rate limit
222
  time.sleep(2 * (tentativa + 1))
223
  continue
224
  raise e
225
 
226
  content = out
227
+ if config.get("tipo_saida") == "json":
228
  cleaned = _extrair_json_possivel(out)
229
  try:
230
  content = json.loads(cleaned)
231
  except:
232
+ content = [] # Fallback
233
+
234
  return {"role": "assistant", "agent": config["nome"], "content": content}, None
235
  except Exception as e:
236
  return {"role": "system", "error": str(e)}, str(e)
237
 
238
+ # ==================== 5. ORQUESTRADOR ====================
239
 
240
  def orquestrador(texto, arquivo, history, json_config, confext_state):
241
+ logs = f"πŸš€ START V45 (OCR): {datetime.now().strftime('%H:%M:%S')}\n"
242
+ logs = log_point("Orquestrador iniciado", logs)
243
 
244
  # 1. PreparaΓ§Γ£o
245
  if history is None: history = []
246
 
247
  nome_arquivo = os.path.basename(getattr(arquivo, "name", "sem_arquivo")) if arquivo else "sem_arquivo"
248
+ # O hash agora considera o arquivo fΓ­sico para evitar refazer OCR caro
249
+ hash_op = gerar_hash_arquivo(nome_arquivo + "V45_OCR")
250
 
251
  # 2. Verifica Cache
252
  cache_existente = carregar_cache(hash_op) if arquivo else None
253
 
254
+ fragmentos = []
255
+
256
  if cache_existente:
257
  logs = log_point(f"♻️ Cache encontrado para {nome_arquivo}", logs)
258
  confext_upload = cache_existente["confext_upload"]
259
  timeline = cache_existente.get("timeline", [])
260
+ fragmentos = cache_existente.get("fragmentos_cached", [])
261
+
262
+ history.append([texto, "βœ… Arquivo carregado do cache! OCR jΓ‘ realizado."])
263
  yield history, timeline, logs, confext_upload
264
 
 
265
  if not texto:
266
  return
267
  else:
268
+ # 3. Processamento: OCR via Gemini Vision
269
+ history.append([texto + (" πŸ“Ž" if arquivo else ""), "⏳ Lendo documento (OCR com Gemini Vision)... isso pode levar alguns segundos."])
270
+ yield history, {}, logs, confext_state
271
+
272
  fragmentos, anexo_info, logs = ler_anexo_e_fragmentar(
273
  arquivo, paginas_por_fragmento=5, logs=logs
274
  )
275
 
276
+ if not fragmentos and arquivo:
277
+ history[-1][1] = "❌ Falha ao ler o arquivo. Verifique se é um PDF vÑlido."
278
+ yield history, {}, logs, confext_state
279
+ return
280
+
281
+ logs = log_point("Texto extraΓ­do via OCR com sucesso.", logs)
282
 
283
  try:
284
  protocolo = json.loads(json_config)
 
288
  return
289
 
290
  timeline = [{"role": "user", "content": texto}]
291
+
292
+ # Cria estrutura inicial de dados
293
  confext_upload = {
294
  "arquivo": nome_arquivo,
295
  "meta": anexo_info,
296
+ "conteudo_ocr": fragmentos # Salva o texto bruto aqui
297
  }
298
+
299
+ # Salva Cache logo apΓ³s o OCR (que Γ© a parte cara/demorada)
300
+ if arquivo:
301
+ salvar_cache(hash_op, {
302
+ "confext_upload": confext_upload,
303
+ "timeline": timeline,
304
+ "fragmentos_cached": fragmentos
305
+ })
306
+ logs = log_point("OCR salvo em Cache", logs)
307
+
308
+ history[-1][1] = f"βœ… OCR ConcluΓ­do. Texto extraΓ­do. Iniciando anΓ‘lise..."
309
+ yield history, timeline, logs, confext_upload
310
 
311
+ # 4. Injeta contexto extraΓ­do no timeline para os agentes lerem
312
+ timeline_context = timeline.copy()
313
+ timeline_context.append({
314
+ "role": "system",
315
+ "agent": "SYSTEM_OCR",
316
+ "content": f"ConteΓΊdo do Documento (ExtraΓ­do via OCR):\n{json.dumps(fragmentos, ensure_ascii=False)}"
317
+ })
318
 
319
+ # 5. ExecuΓ§Γ£o dos Agentes de AnΓ‘lise (Baseado no Protocolo)
320
+ if not json_config: return
321
 
322
+ try:
323
+ protocolo = json.loads(json_config)
324
+ except:
325
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
+ for cfg in protocolo:
328
+ history[-1][1] = f"βš™οΈ {cfg['nome']} analisando..."
329
+ logs = log_point(f"Iniciando agente: {cfg['nome']}", logs)
330
+ yield history, timeline, logs, confext_upload
331
+
332
+ # O agente recebe o timeline com o contexto do documento
333
+ res, erro = executar_no(timeline_context, cfg, fragmento_input=None)
334
+
335
+ if erro:
336
+ logs = log_point(f"Erro agente {cfg['nome']}: {erro}", logs)
337
+ history[-1][1] = f"❌ Erro em {cfg['nome']}: {erro}"
338
+ else:
339
+ timeline.append(res)
340
+ # Atualiza contexto para o prΓ³ximo agente
341
+ timeline_context.append(res)
342
 
343
+ if cfg.get("tipo_saida") == "texto":
344
+ history[-1][1] = res["content"]
345
+ elif cfg.get("tipo_saida") == "json":
346
+ # Se for JSON, mostra bonitinho ou apenas avisa
347
+ history[-1][1] = f"βœ… {cfg['nome']} finalizou anΓ‘lise estruturada."
348
+
349
+ yield history, timeline, logs, confext_upload
350
 
351
  if not texto and arquivo:
352
+ history[-1][1] = "βœ… Documento digitalizado e analisado."
353
 
354
  logs = log_point("Processo Finalizado", logs)
355
  yield history, timeline, logs, confext_upload
356
 
357
+ # ==================== 6. UI ====================
358
 
359
  def ui_clean():
360
  css = """
 
364
 
365
  config_init = carregar_protocolo()
366
 
367
+ with gr.Blocks(title="AI Forensics V45 (OCR Edition)", css=css, theme=gr.themes.Soft()) as app:
368
  confext_state = gr.State(value=None)
369
 
370
  with gr.Tabs():
 
399
 
400
  def _on_upload(x):
401
  nome = os.path.basename(getattr(x, "name", x))
402
+ return f"πŸ“Ž Anexo pronto para OCR: {nome}"
403
 
404
  file_in.upload(_on_upload, inputs=file_in, outputs=file_status)
405
 
 
406
  with gr.Tab("πŸ•΅οΈ Auditoria & Debug"):
407
+ gr.Markdown("### 🧠 Processo Interno")
408
  with gr.Row():
409
+ out_dna = gr.JSON(label="Timeline da IA")
410
  out_logs = gr.Textbox(label="Logs do Sistema", lines=20)
411
 
412
+ gr.Markdown("### πŸ“‚ Dados Estruturados")
413
  confext_view = gr.JSON(label="ConteΓΊdo ExtraΓ­do")
414
 
415
  with gr.Tab("βš™οΈ Config"):
 
431
  trig(
432
  _orq_wrapper,
433
  inputs=[txt_in, file_in, chatbot, code_json, confext_state],
434
+ outputs=[chatbot, out_dna, out_logs, confext_state],
435
  ).then(
436
  lambda c: (None, None, "", c)[1:],
437
  inputs=confext_state,
 
439
  ).then(
440
  lambda c: c,
441
  inputs=confext_state,
442
+ outputs=confext_view,
443
  )
444
 
445
  return app