caarleexx commited on
Commit
deadc30
·
verified ·
1 Parent(s): 8ef55f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -62
app.py CHANGED
@@ -1,6 +1,6 @@
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
- # ║ PIPELINE v31: FRAGMENTAÇÃO, TRANSCRIÇÃO (LOOP) E CATALOGAÇÃO
3
- # ║ Layout: Chat (Aba 1) | Debug (Aba 2) | Config (Aba 3)
4
  # ╚════════════════════════════════════════════════════════════════════════════╝
5
 
6
  import os
@@ -10,7 +10,7 @@ import time
10
  from datetime import datetime
11
  import gradio as gr
12
  import google.generativeai as genai
13
- import pypdf # Usando pypdf para a lógica de fragmentação real.
14
 
15
  # ==================== 1. CONFIGURAÇÃO ====================
16
  api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
@@ -19,14 +19,13 @@ if api_key: genai.configure(api_key=api_key)
19
  model_flash = genai.GenerativeModel("gemini-flash-latest")
20
  model_pro = genai.GenerativeModel("gemini-pro-latest")
21
 
22
- # **ATUALIZAÇÃO: Novo protocolo de fragmentação/catalogação**
23
  ARQUIVO_CONFIG = "protocolo_fragmentacao_transcricao.json"
24
 
25
  # ==================== 2. UTILIDADES ====================
26
 
27
  def carregar_protocolo():
28
  try:
29
- # Tenta carregar o protocolo que está sendo usado
30
  with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f: return f.read()
31
  except: return "[]"
32
 
@@ -37,13 +36,22 @@ def salvar_protocolo(conteudo):
37
  return "✅ Salvo"
38
  except: return "❌ Erro JSON"
39
 
40
- # **FUNÇÃO REAL: Lógica de Fragmentação de PDF com pypdf**
41
  def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5):
42
- """Lê o anexo. Se for PDF, usa pypdf para dividir em fragmentos de X páginas."""
43
- if arquivo is None: return [], ""
44
- filename = arquivo.name
 
 
 
 
 
 
 
 
45
  anexo_info = f"[ANEXO SISTEMA: {os.path.basename(filename)}]"
46
 
 
47
  if filename.lower().endswith(".pdf"):
48
  fragments = []
49
  try:
@@ -57,41 +65,42 @@ def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5):
57
 
58
  for j in range(start_page, end_page):
59
  try:
60
- # Extrai o texto; se vazio (OCR ou complexo), o LLM tentará limpá-lo
61
- text = reader.pages[j].extract_text() or f"[PAG {j+1}: EXTRAÇÃO VAZIA - OCR NECESSÁRIO]"
 
 
62
  fragment_text.append(text)
63
  except Exception as e:
64
- fragment_text.append(f"[PAG {j+1}: ERRO DE EXTRAÇÃO/ENCODING - {e}]")
65
 
66
- # Monta o input para o LLM: cabeçalho + texto extraído (mesmo que vazio)
67
- fragment_header = f"Fragmento {i//paginas_por_fragmento + 1} (Pgs {start_page+1}-{end_page} / Total {total_pages}):\n"
68
  fragments.append(fragment_header + "\n".join(fragment_text))
69
 
70
  return fragments, anexo_info
71
 
72
  except Exception as e:
73
- return [f"ERRO CRÍTICO NA LEITURA DE PDF: {e}"], anexo_info
74
 
75
- # Para arquivos não PDF, lê o conteúdo como um único fragmento.
76
  try:
77
  with open(filename, "r", encoding="utf-8") as f:
78
  return [f.read()], anexo_info
79
- except: return [], ""
 
80
 
81
 
82
  # ==================== 3. ENGINE DE EXECUÇÃO ====================
83
 
84
- # Modificada para aceitar um fragmento de texto como input (apenas para o Agente de Loop)
85
  def executar_no(timeline, config, fragmento_input=None):
86
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
87
 
 
88
  if fragmento_input is not None:
89
- # Se for o Agente de Loop (Passo 0), o prompt é apenas a missão + o fragmento
90
  input_para_prompt = fragmento_input
91
  else:
92
- # Para os agentes sequenciais, o prompt é a timeline completa (que inclui as transcrições)
93
- contexto = json.dumps(timeline, ensure_ascii=False, indent=2)
94
- input_para_prompt = contexto
95
 
96
  prompt = f"--- INPUT PARA O AGENTE ---\n{input_para_prompt}\n----------------\nAGENTE: {config['nome']}\nMISSÃO: {config['missao']}"
97
 
@@ -102,7 +111,7 @@ def executar_no(timeline, config, fragmento_input=None):
102
  out = resp.text
103
  tempo = time.time() - inicio
104
 
105
- # O Agente de Transcrição retorna 'texto', os outros retornam 'json'
106
  content = json.loads(out.strip().replace('```json','').replace('```','')) if config['tipo_saida']=='json' else out
107
  log += f" (OK - {tempo:.2f}s)"
108
  return {"role": "assistant", "agent": config['nome'], "content": content}, log, out
@@ -110,17 +119,19 @@ def executar_no(timeline, config, fragmento_input=None):
110
  return {"role": "system", "error": str(e)}, f" (ERRO: {e})", str(e)
111
 
112
 
113
- # ==================== 4. ORQUESTRADOR (LÓGICA DO LOOP) ====================
114
 
115
  def orquestrador(texto, arquivo, history, json_config):
116
- # 1. Input Check e Fragmentação
117
  fragmentos, anexo_info = ler_anexo_e_fragmentar(arquivo)
118
 
 
119
  if not texto and not fragmentos:
120
- yield history, {}, "Sem input."
 
121
  return
122
 
123
- # 2. Setup
124
  history = history + [[texto + (" 📎" if arquivo else ""), None]]
125
  try: protocolo = json.loads(json_config)
126
  except:
@@ -128,58 +139,61 @@ def orquestrador(texto, arquivo, history, json_config):
128
  yield history, {}, "Erro JSON"
129
  return
130
 
131
- # A TIMELINE começa com o input do usuário (pergunta/instrução)
132
  timeline = [{"role": "user", "content": texto}]
133
  logs = f"🚀 START: {datetime.now().strftime('%H:%M:%S')}\n"
 
 
134
  history[-1][1] = "⏳ Iniciando análise..."
135
  yield history, timeline, logs
136
 
137
- # --- NOVO: Lógica de Loop/Transcrição (PASSO 0) ---
 
138
  concatenated_transcription = anexo_info + "\n\n"
139
 
140
  if protocolo and protocolo[0]['nome'] == 'TRANSCRITOR_FRAGMENTO (PASSO 0 - LOOP)' and len(fragmentos) > 0:
141
 
142
- cfg_transcricao = protocolo.pop(0) # Remove o Agente de Loop da lista principal
143
 
144
  for i, fragmento in enumerate(fragmentos):
145
- history[-1][1] = f"⚙️ {cfg_transcricao['nome']} trabalhando no fragmento {i+1}/{len(fragmentos)} (5 Pgs)..."
146
  yield history, timeline, logs
147
 
148
- # Executa o LLM no fragmento ATUAL, não na timeline
149
- # Isso força o Gemini a focar APENAS nas 5 páginas para transcrever/limpar
150
  res, log_add, raw = executar_no(timeline, cfg_transcricao, fragmento_input=fragmento)
151
  logs += log_add + "\n"
152
 
153
  if 'error' in res:
154
- timeline.append(res)
155
- yield history, timeline, logs
156
- return
157
 
158
- # Concatenamos o texto limpo retornado pelo LLM
159
- concatenated_transcription += res['content'] + "\n"
160
 
161
- logs += "\n✅ TRANSCRIÇÃO FRAGMENTADA E CONCATENADA CONCLUÍDA.\n"
162
 
163
- # Adiciona o resultado da transcrição concatenada à timeline para os próximos agentes
164
  timeline.append({
165
  "role": "system",
166
- "agent": "TEXTO_DOCUMENTO_COMPLETO",
167
  "content": concatenated_transcription
168
  })
169
- history[-1][1] = "✅ Transcrição completa. Iniciando Catalogação (Passo 1)..."
170
- yield history, timeline, logs
171
 
172
  elif len(fragmentos) > 0:
173
- # Se não Passo 0 (Transcrição), injeta o texto extraído cru como um bloco
174
- timeline.append({"role": "system", "agent": "TEXTO_DOCUMENTO_COMPLETO", "content": concatenated_transcription + "\n".join(fragmentos)})
 
 
 
 
175
 
176
- # 5. PASSOS SEGUINTES: EXECUÇÃO SEQUENCIAL (Catalogação)
177
  final_response = ""
178
- for cfg in protocolo: # Iteramos sobre a lista (agora começa do BIBLIOTECARIO_CATALOGADOR)
179
-
180
- history[-1][1] = f"⚙️ {cfg['nome']} trabalhando..."
181
  yield history, timeline, logs
182
 
 
183
  res, log_add, raw = executar_no(timeline, cfg)
184
  timeline.append(res)
185
  logs += log_add + "\n"
@@ -193,8 +207,8 @@ def orquestrador(texto, arquivo, history, json_config):
193
  logs += "✅ FIM."
194
  yield history, timeline, logs
195
 
196
- # ==================== 5. UI LIMPA (v31) ====================
197
- # (A UI permanece a mesma)
198
 
199
  def ui_clean():
200
  css = """
@@ -204,11 +218,11 @@ def ui_clean():
204
 
205
  config_init = carregar_protocolo()
206
 
207
- with gr.Blocks(title="Protocolo Fragmentação/Transcrição", css=css, theme=gr.themes.Soft()) as app:
208
 
209
  with gr.Tabs():
210
 
211
- # === ABA 1: CHAT (LIMPO) ===
212
  with gr.Tab("💬 Investigador"):
213
  chatbot = gr.Chatbot(
214
  label="",
@@ -222,7 +236,7 @@ def ui_clean():
222
  with gr.Column(scale=10):
223
  txt_in = gr.Textbox(
224
  show_label=False,
225
- placeholder="Descreva o caso ou instrução...",
226
  lines=1,
227
  max_lines=5,
228
  container=False
@@ -236,23 +250,22 @@ def ui_clean():
236
  with gr.Column(scale=1, min_width=80):
237
  btn_send = gr.Button("Enviar", variant="primary", size="sm")
238
 
239
- # Feedback visual sutil do arquivo
240
  file_status = gr.Markdown("", visible=True)
241
- file_in.upload(lambda x: f"📎 Anexo: {os.path.basename(x.name)}", file_in, file_status)
242
 
243
- # === ABA 2: DEPURAÇÃO (ESCONDIDO) ===
244
  with gr.Tab("🕵️ Depuração"):
245
  with gr.Row():
246
  out_dna = gr.JSON(label="DNA (Timeline)")
247
  out_logs = gr.Textbox(label="Logs do Sistema", lines=20)
248
 
249
- # === ABA 3: CONFIG (TÉCNICO) ===
250
  with gr.Tab("⚙️ Config"):
251
  with gr.Row():
252
  btn_save = gr.Button("Salvar Config")
253
  lbl_save = gr.Label(show_label=False)
254
- # Label para refletir o novo protocolo
255
- code_json = gr.Code(value=config_init, language="json", label="protocolo.json")
256
  btn_save.click(salvar_protocolo, code_json, lbl_save)
257
 
258
  # === TRIGGERS ===
@@ -264,8 +277,9 @@ def ui_clean():
264
  inputs=[txt_in, file_in, chatbot, code_json],
265
  outputs=[chatbot, out_dna, out_logs]
266
  ).then(
267
- lambda: (None, ""),
268
- outputs=[txt_in, file_status]
 
269
  )
270
 
271
  return app
 
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
+ # ║ PIPELINE v32: FRAGMENTAÇÃO, TRANSCRIÇÃO (LOOP) & CATALOGAÇÃO
3
+ # ║ CORREÇÃO: MANIPULAÇÃO ROBUSTA DE ANEXOS E LIMPEZA DE BUFFER
4
  # ╚════════════════════════════════════════════════════════════════════════════╝
5
 
6
  import os
 
10
  from datetime import datetime
11
  import gradio as gr
12
  import google.generativeai as genai
13
+ import pypdf # Certifique-se de que 'pip install pypdf' foi executado
14
 
15
  # ==================== 1. CONFIGURAÇÃO ====================
16
  api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
 
19
  model_flash = genai.GenerativeModel("gemini-flash-latest")
20
  model_pro = genai.GenerativeModel("gemini-pro-latest")
21
 
22
+ # Configuração Padrão do Protocolo
23
  ARQUIVO_CONFIG = "protocolo_fragmentacao_transcricao.json"
24
 
25
  # ==================== 2. UTILIDADES ====================
26
 
27
  def carregar_protocolo():
28
  try:
 
29
  with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f: return f.read()
30
  except: return "[]"
31
 
 
36
  return "✅ Salvo"
37
  except: return "❌ Erro JSON"
38
 
39
+ # **CORREÇÃO: Função de Leitura Robusta**
40
  def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5):
41
+ """Lê o anexo com segurança, tratando objetos do Gradio."""
42
+ if arquivo is None:
43
+ return [], ""
44
+
45
+ # Tratamento seguro do caminho do arquivo
46
+ # O Gradio pode enviar um objeto NamedString ou apenas o caminho
47
+ filename = getattr(arquivo, 'name', arquivo)
48
+
49
+ if not os.path.exists(filename):
50
+ return [], f"[ERRO SISTEMA: Arquivo temporário não encontrado: {filename}]"
51
+
52
  anexo_info = f"[ANEXO SISTEMA: {os.path.basename(filename)}]"
53
 
54
+ # Lógica PDF
55
  if filename.lower().endswith(".pdf"):
56
  fragments = []
57
  try:
 
65
 
66
  for j in range(start_page, end_page):
67
  try:
68
+ # Tenta extrair. Se falhar ou vier vazio, coloca um placeholder
69
+ text = reader.pages[j].extract_text()
70
+ if not text or text.strip() == "":
71
+ text = f"[PAG {j+1}: (Texto Vazio/Imagem) - Necessário OCR via LLM]"
72
  fragment_text.append(text)
73
  except Exception as e:
74
+ fragment_text.append(f"[PAG {j+1}: Erro de Leitura: {e}]")
75
 
76
+ # Cabeçalho claro para o LLM saber onde está
77
+ fragment_header = f"=== FRAGMENTO {i//paginas_por_fragmento + 1} (Páginas {start_page+1} a {end_page} de {total_pages}) ===\n"
78
  fragments.append(fragment_header + "\n".join(fragment_text))
79
 
80
  return fragments, anexo_info
81
 
82
  except Exception as e:
83
+ return [f"ERRO CRÍTICO NA LEITURA DO PDF: {str(e)}"], anexo_info
84
 
85
+ # Lógica para TXT/MD/JSON (Lê como um único fragmento)
86
  try:
87
  with open(filename, "r", encoding="utf-8") as f:
88
  return [f.read()], anexo_info
89
+ except:
90
+ return [f"Erro ao ler arquivo de texto."], anexo_info
91
 
92
 
93
  # ==================== 3. ENGINE DE EXECUÇÃO ====================
94
 
 
95
  def executar_no(timeline, config, fragmento_input=None):
96
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
97
 
98
+ # Se receber um fragmento direto (Passo 0), usa apenas ele como input
99
  if fragmento_input is not None:
 
100
  input_para_prompt = fragmento_input
101
  else:
102
+ # Caso contrário, usa toda a timeline acumulada
103
+ input_para_prompt = json.dumps(timeline, ensure_ascii=False, indent=2)
 
104
 
105
  prompt = f"--- INPUT PARA O AGENTE ---\n{input_para_prompt}\n----------------\nAGENTE: {config['nome']}\nMISSÃO: {config['missao']}"
106
 
 
111
  out = resp.text
112
  tempo = time.time() - inicio
113
 
114
+ # Se for JSON, faz o parse. Se for texto (Transcrição), retorna puro.
115
  content = json.loads(out.strip().replace('```json','').replace('```','')) if config['tipo_saida']=='json' else out
116
  log += f" (OK - {tempo:.2f}s)"
117
  return {"role": "assistant", "agent": config['nome'], "content": content}, log, out
 
119
  return {"role": "system", "error": str(e)}, f" (ERRO: {e})", str(e)
120
 
121
 
122
+ # ==================== 4. ORQUESTRADOR ====================
123
 
124
  def orquestrador(texto, arquivo, history, json_config):
125
+ # 1. Leitura e Fragmentação
126
  fragmentos, anexo_info = ler_anexo_e_fragmentar(arquivo)
127
 
128
+ # Validação básica
129
  if not texto and not fragmentos:
130
+ # Se não tem texto E não tem fragmentos válidos
131
+ yield history, {}, "⚠️ Sem input ou erro ao ler arquivo."
132
  return
133
 
134
+ # 2. Setup Inicial
135
  history = history + [[texto + (" 📎" if arquivo else ""), None]]
136
  try: protocolo = json.loads(json_config)
137
  except:
 
139
  yield history, {}, "Erro JSON"
140
  return
141
 
142
+ # Timeline inicial
143
  timeline = [{"role": "user", "content": texto}]
144
  logs = f"🚀 START: {datetime.now().strftime('%H:%M:%S')}\n"
145
+ if fragmentos: logs += f"📎 Arquivo processado: {len(fragmentos)} fragmentos gerados.\n"
146
+
147
  history[-1][1] = "⏳ Iniciando análise..."
148
  yield history, timeline, logs
149
 
150
+ # --- PASSO 0: LOOP DE TRANSCRIÇÃO ---
151
+ # Verifica se o primeiro passo é o transcritor e se temos arquivo
152
  concatenated_transcription = anexo_info + "\n\n"
153
 
154
  if protocolo and protocolo[0]['nome'] == 'TRANSCRITOR_FRAGMENTO (PASSO 0 - LOOP)' and len(fragmentos) > 0:
155
 
156
+ cfg_transcricao = protocolo.pop(0) # Remove o passo 0 da fila
157
 
158
  for i, fragmento in enumerate(fragmentos):
159
+ history[-1][1] = f"⚙️ {cfg_transcricao['nome']} processando parte {i+1} de {len(fragmentos)}..."
160
  yield history, timeline, logs
161
 
162
+ # Chama o LLM passando APENAS o fragmento atual
 
163
  res, log_add, raw = executar_no(timeline, cfg_transcricao, fragmento_input=fragmento)
164
  logs += log_add + "\n"
165
 
166
  if 'error' in res:
167
+ logs += f"❌ Erro no fragmento {i+1}: {res['error']}\n"
168
+ # Continua mesmo com erro, ou para? Vamos continuar concatenando o erro para registro.
 
169
 
170
+ # Acumula o resultado
171
+ concatenated_transcription += f"\n--- TRANSCRIÇÃO PARTE {i+1} ---\n{res['content']}\n"
172
 
173
+ logs += "\n✅ Transcrição completa.\n"
174
 
175
+ # Adiciona o texto COMPLETO transcrito à timeline para o Bibliotecário ler
176
  timeline.append({
177
  "role": "system",
178
+ "agent": "DOCUMENTO_COMPLETO_TRANSCRITO",
179
  "content": concatenated_transcription
180
  })
 
 
181
 
182
  elif len(fragmentos) > 0:
183
+ # Se não tinha o passo de transcrição configurado, injeta o texto cru
184
+ timeline.append({
185
+ "role": "system",
186
+ "agent": "DOCUMENTO_COMPLETO_RAW",
187
+ "content": concatenated_transcription + "\n".join(fragmentos)
188
+ })
189
 
190
+ # --- PASSO 1 e SEGUINTES: CATALOGAÇÃO SEQUENCIAL ---
191
  final_response = ""
192
+ for cfg in protocolo:
193
+ history[-1][1] = f"⚙️ {cfg['nome']} analisando..."
 
194
  yield history, timeline, logs
195
 
196
+ # Executa com a timeline completa
197
  res, log_add, raw = executar_no(timeline, cfg)
198
  timeline.append(res)
199
  logs += log_add + "\n"
 
207
  logs += "✅ FIM."
208
  yield history, timeline, logs
209
 
210
+
211
+ # ==================== 5. UI LIMPA (v32) ====================
212
 
213
  def ui_clean():
214
  css = """
 
218
 
219
  config_init = carregar_protocolo()
220
 
221
+ with gr.Blocks(title="AI Bibliotecário", css=css, theme=gr.themes.Soft()) as app:
222
 
223
  with gr.Tabs():
224
 
225
+ # === ABA 1: CHAT ===
226
  with gr.Tab("💬 Investigador"):
227
  chatbot = gr.Chatbot(
228
  label="",
 
236
  with gr.Column(scale=10):
237
  txt_in = gr.Textbox(
238
  show_label=False,
239
+ placeholder="Instruções opcionais...",
240
  lines=1,
241
  max_lines=5,
242
  container=False
 
250
  with gr.Column(scale=1, min_width=80):
251
  btn_send = gr.Button("Enviar", variant="primary", size="sm")
252
 
253
+ # Feedback do arquivo
254
  file_status = gr.Markdown("", visible=True)
255
+ file_in.upload(lambda x: f"📎 Anexo pronto: {os.path.basename(getattr(x, 'name', x))}", file_in, file_status)
256
 
257
+ # === ABA 2: LOGS ===
258
  with gr.Tab("🕵️ Depuração"):
259
  with gr.Row():
260
  out_dna = gr.JSON(label="DNA (Timeline)")
261
  out_logs = gr.Textbox(label="Logs do Sistema", lines=20)
262
 
263
+ # === ABA 3: CONFIG ===
264
  with gr.Tab("⚙️ Config"):
265
  with gr.Row():
266
  btn_save = gr.Button("Salvar Config")
267
  lbl_save = gr.Label(show_label=False)
268
+ code_json = gr.Code(value=config_init, language="json", label="protocolo.json")
 
269
  btn_save.click(salvar_protocolo, code_json, lbl_save)
270
 
271
  # === TRIGGERS ===
 
277
  inputs=[txt_in, file_in, chatbot, code_json],
278
  outputs=[chatbot, out_dna, out_logs]
279
  ).then(
280
+ # CORREÇÃO: Limpa txt_in E file_in E file_status após o envio
281
+ lambda: (None, None, ""),
282
+ outputs=[txt_in, file_in, file_status]
283
  )
284
 
285
  return app