caarleexx commited on
Commit
1b52886
Β·
verified Β·
1 Parent(s): 7bed317

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -145
app.py CHANGED
@@ -1,6 +1,6 @@
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,100 +10,94 @@ import time
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")
17
  if api_key: genai.configure(api_key=api_key)
18
 
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
 
32
  def salvar_protocolo(conteudo):
33
  try:
34
  json.loads(conteudo)
35
- with open("protocolo.json", "w", encoding="utf-8") as f: f.write(conteudo)
 
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:
58
- reader = pypdf.PdfReader(filename)
59
- total_pages = len(reader.pages)
60
-
61
- for i in range(0, total_pages, paginas_por_fragmento):
62
- fragment_text = []
63
- start_page = i
64
- end_page = min(i + paginas_por_fragmento, total_pages)
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
 
 
 
 
 
 
 
107
  log = f"\nπŸ”Έ {config['nome']}..."
108
  try:
109
  inicio = time.time()
@@ -111,178 +105,155 @@ def executar_no(timeline, config, fragmento_input=None):
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
118
  except Exception as e:
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:
 
138
  history[-1][1] = "❌ Erro no JSON de ConfiguraΓ§Γ£o."
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"
200
 
201
  if cfg['tipo_saida'] == 'texto':
202
  final_response = res['content']
203
- history[-1][1] = final_response
204
-
205
- yield history, timeline, logs
206
-
207
  logs += "βœ… FIM."
208
  yield history, timeline, logs
209
 
210
-
211
- # ==================== 5. UI LIMPA (v32) ====================
212
-
213
  def ui_clean():
214
  css = """
215
- footer {display: none !important;}
216
  .contain {border: none !important;}
217
  """
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="",
229
- show_label=False,
230
- height=600,
231
- show_copy_button=True,
232
- render_markdown=True
233
  )
234
 
235
  with gr.Row():
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
243
  )
244
  with gr.Column(scale=1, min_width=50):
245
  file_in = gr.UploadButton(
246
- "πŸ“Ž",
247
- file_types=[".txt", ".md", ".csv", ".json", ".pdf"],
248
- size="sm"
249
  )
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 ===
272
  triggers = [btn_send.click, txt_in.submit]
273
-
274
  for trig in triggers:
275
  trig(
276
  orquestrador,
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
286
 
287
  if __name__ == "__main__":
288
- ui_clean().launch()
 
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
+ # β•‘ PIPELINE v33: DIVISΓƒO BURRA + VISΓƒO PURA + CONCATENAÇÃO FIEL β•‘
3
+ # β•‘ SEM OCR/EXTRAÇÃO - APENAS METADADOS + OLHOS DO GEMINI β•‘
4
  # β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
5
 
6
  import os
 
10
  from datetime import datetime
11
  import gradio as gr
12
  import google.generativeai as genai
13
+ import pypdf # pip install pypdf
14
 
15
  # ==================== 1. CONFIGURAÇÃO ====================
16
  api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
17
  if api_key: genai.configure(api_key=api_key)
18
 
19
  model_flash = genai.GenerativeModel("gemini-flash-latest")
20
+ model_pro = genai.GenerativeModel("gemini-pro-latest")
21
 
22
+ ARQUIVO_CONFIG = "protocolo_fragmentacao_visao.json"
 
23
 
24
  # ==================== 2. UTILIDADES ====================
 
25
  def carregar_protocolo():
26
  try:
27
+ with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f:
28
+ return f.read()
29
+ except:
30
+ return """[
31
+ {
32
+ "nome": "VISÃO_FRAGMENTO (PASSO 0)",
33
+ "missao": "VocΓͺ Γ© OLHO MECΓ‚NICO. Descreva APENAS o que vΓͺ nestas pΓ‘ginas: imagens, texto visΓ­vel, layout, objetos, cores, diagramas. SEM interpretaΓ§Γ΅es, julgamentos, omissΓ΅es ou criaΓ§Γ΅es. Liste tudo fielmente.",
34
+ "tipo_saida": "texto",
35
+ "modelo": "flash"
36
+ },
37
+ {
38
+ "nome": "CATALOGADOR",
39
+ "missao": "Analise todas as descriΓ§Γ΅es de visΓ£o acumuladas. Crie Γ­ndices temΓ‘ticos e estrutura sem alterar fatos visuais.",
40
+ "tipo_saida": "json",
41
+ "modelo": "pro"
42
+ },
43
+ {
44
+ "nome": "CONCATENADOR_FINAL",
45
+ "missao": "UNA TODAS descriçáes de visão em TEXTO ÚNICO CONTÍNUO. Mantenha ordem original. Sem resumos, só o retrato fiel completo do documento.",
46
+ "tipo_saida": "texto",
47
+ "modelo": "pro"
48
+ }
49
+ ]"""
50
 
51
  def salvar_protocolo(conteudo):
52
  try:
53
  json.loads(conteudo)
54
+ with open("protocolo.json", "w", encoding="utf-8") as f:
55
+ f.write(conteudo)
56
  return "βœ… Salvo"
57
+ except:
58
+ return "❌ Erro JSON"
59
 
60
+ # DIVISÃO BURRA: SEM TEXTO/OCR - APENAS METADADOS
61
+ def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=3):
62
+ """DIVISÃO BURRA: APENAS METADADOS. LLM faz visão nativa."""
63
+ if arquivo is None: return [], ""
 
64
 
 
 
65
  filename = getattr(arquivo, 'name', arquivo)
66
+ if not os.path.exists(filename):
67
+ return [], f"[ERRO: Arquivo nΓ£o encontrado: {filename}]"
68
 
69
+ anexo_info = f"[PDF: {os.path.basename(filename)}]"
70
+ if not filename.lower().endswith('.pdf'):
71
+ return [f"[ARQUIVO_TEXTO: {os.path.basename(filename)}]"], anexo_info
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
 
73
  try:
74
+ reader = pypdf.PdfReader(filename)
75
+ total_pages = len(reader.pages)
76
+ fragments = []
77
+ for i in range(0, total_pages, paginas_por_fragmento):
78
+ start = i + 1
79
+ end = min(i + paginas_por_fragmento, total_pages)
80
+ fragment = f"=== FRAG {i//paginas_por_fragmento+1} (PÁGS {start}-{end}/{total_pages}) ===\n[VISΓƒO: Descreva IMAGENS/TEXTO_VISUAL/LAYOUT. SEM julgar/omitir/criar.]"
81
+ fragments.append(fragment)
82
+ return fragments, anexo_info
83
+ except Exception as e:
84
+ return [f"[ERRO PDF: {str(e)}]"], anexo_info
85
 
86
  # ==================== 3. ENGINE DE EXECUÇÃO ====================
 
87
  def executar_no(timeline, config, fragmento_input=None):
88
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
89
 
 
90
  if fragmento_input is not None:
91
  input_para_prompt = fragmento_input
92
  else:
 
93
  input_para_prompt = json.dumps(timeline, ensure_ascii=False, indent=2)
 
 
94
 
95
+ prompt = f"""--- INPUT PARA O AGENTE ---
96
+ {input_para_prompt}
97
+ ----------------
98
+ AGENTE: {config['nome']}
99
+ MISSÃO: {config['missao']}"""
100
+
101
  log = f"\nπŸ”Έ {config['nome']}..."
102
  try:
103
  inicio = time.time()
 
105
  out = resp.text
106
  tempo = time.time() - inicio
107
 
108
+ content = (json.loads(out.strip().replace('``````',''))
109
+ if config['tipo_saida']=='json' else out)
110
+
111
  log += f" (OK - {tempo:.2f}s)"
112
  return {"role": "assistant", "agent": config['nome'], "content": content}, log, out
113
  except Exception as e:
114
  return {"role": "system", "error": str(e)}, f" (ERRO: {e})", str(e)
115
 
 
116
  # ==================== 4. ORQUESTRADOR ====================
 
117
  def orquestrador(texto, arquivo, history, json_config):
118
+ # 1. Leitura e FragmentaΓ§Γ£o BURRA
119
  fragmentos, anexo_info = ler_anexo_e_fragmentar(arquivo)
120
 
 
121
  if not texto and not fragmentos:
 
122
  yield history, {}, "⚠️ Sem input ou erro ao ler arquivo."
123
  return
124
+
125
  # 2. Setup Inicial
126
  history = history + [[texto + (" πŸ“Ž" if arquivo else ""), None]]
127
+ try:
128
+ protocolo = json.loads(json_config)
129
+ except:
130
  history[-1][1] = "❌ Erro no JSON de ConfiguraΓ§Γ£o."
131
  yield history, {}, "Erro JSON"
132
  return
133
+
 
134
  timeline = [{"role": "user", "content": texto}]
135
  logs = f"πŸš€ START: {datetime.now().strftime('%H:%M:%S')}\n"
136
+ if fragmentos:
137
+ logs += f"πŸ“Ž PDF dividido: {len(fragmentos)} fragments burros.\n"
138
 
139
+ history[-1][1] = "⏳ Dividindo + Visão..."
140
  yield history, timeline, logs
 
 
 
 
141
 
142
+ # --- PASSO 0: LOOP DE VISÃO ---
143
+ concatenated_vision = anexo_info + "\n\n"
144
+ if protocolo and protocolo[0]['nome'] == 'VISÃO_FRAGMENTO (PASSO 0)' and len(fragmentos) > 0:
145
+ cfg_visao = protocolo.pop(0) # Remove PASSO 0
146
 
147
  for i, fragmento in enumerate(fragmentos):
148
+ history[-1][1] = f"πŸ‘οΈ {cfg_visao['nome']} frag {i+1}/{len(fragmentos)}..."
149
  yield history, timeline, logs
150
 
151
+ res, log_add, raw = executar_no(timeline, cfg_visao, fragmento_input=fragmento)
 
152
  logs += log_add + "\n"
153
 
154
  if 'error' in res:
155
+ logs += f"❌ Erro frag {i+1}: {res['error']}\n"
 
156
 
157
+ concatenated_vision += f"\n--- VISÃO FRAG {i+1} ---\n{res['content']}\n"
 
 
 
158
 
159
+ logs += "\nβœ… VisΓ΅es completas.\n"
160
  timeline.append({
161
+ "role": "system",
162
+ "agent": "DOCUMENTO_VISAO_COMPLETA",
163
+ "content": concatenated_vision
164
  })
 
165
  elif len(fragmentos) > 0:
 
166
  timeline.append({
167
+ "role": "system",
168
+ "agent": "FRAGMENTS_RAW",
169
+ "content": concatenated_vision + "\n".join(fragmentos)
170
  })
171
+
172
+ # --- PASSOS 1+: ANÁLISE SEQUENCIAL ---
173
  final_response = ""
174
+ for cfg in protocolo:
175
+ history[-1][1] = f"βš™οΈ {cfg['nome']}..."
176
  yield history, timeline, logs
177
 
 
178
  res, log_add, raw = executar_no(timeline, cfg)
179
  timeline.append(res)
180
  logs += log_add + "\n"
181
 
182
  if cfg['tipo_saida'] == 'texto':
183
  final_response = res['content']
184
+ history[-1][1] = final_response
185
+ yield history, timeline, logs
186
+
 
187
  logs += "βœ… FIM."
188
  yield history, timeline, logs
189
 
190
+ # ==================== 5. UI LIMPA (v33) ====================
 
 
191
  def ui_clean():
192
  css = """
193
+ footer {display: none !important;}
194
  .contain {border: none !important;}
195
  """
196
 
197
  config_init = carregar_protocolo()
198
+
199
+ with gr.Blocks(title="πŸ‘οΈ AI VisΓ£o Pura", css=css, theme=gr.themes.Soft()) as app:
 
200
  with gr.Tabs():
 
201
  # === ABA 1: CHAT ===
202
  with gr.Tab("πŸ’¬ Investigador"):
203
  chatbot = gr.Chatbot(
204
+ label="", show_label=False, height=600,
205
+ show_copy_button=True, render_markdown=True
 
 
 
206
  )
207
 
208
  with gr.Row():
209
  with gr.Column(scale=10):
210
  txt_in = gr.Textbox(
211
+ show_label=False,
212
+ placeholder="InstruΓ§Γ΅es opcionais...",
213
+ lines=1, max_lines=5, container=False
 
 
214
  )
215
  with gr.Column(scale=1, min_width=50):
216
  file_in = gr.UploadButton(
217
+ "πŸ“Ž", file_types=[".txt", ".md", ".csv", ".json", ".pdf"], size="sm"
 
 
218
  )
219
  with gr.Column(scale=1, min_width=80):
220
  btn_send = gr.Button("Enviar", variant="primary", size="sm")
221
 
 
222
  file_status = gr.Markdown("", visible=True)
223
+ file_in.upload(
224
+ lambda x: f"πŸ“Ž PDF pronto: {os.path.basename(getattr(x, 'name', x))}",
225
+ file_in, file_status
226
+ )
227
+
228
  # === ABA 2: LOGS ===
229
  with gr.Tab("πŸ•΅οΈ DepuraΓ§Γ£o"):
230
  with gr.Row():
231
  out_dna = gr.JSON(label="DNA (Timeline)")
232
  out_logs = gr.Textbox(label="Logs do Sistema", lines=20)
233
+
234
  # === ABA 3: CONFIG ===
235
  with gr.Tab("βš™οΈ Config"):
236
  with gr.Row():
237
  btn_save = gr.Button("Salvar Config")
238
  lbl_save = gr.Label(show_label=False)
239
+ code_json = gr.Code(
240
+ value=config_init, language="json", label="protocolo.json"
241
+ )
242
  btn_save.click(salvar_protocolo, code_json, lbl_save)
243
+
244
  # === TRIGGERS ===
245
  triggers = [btn_send.click, txt_in.submit]
 
246
  for trig in triggers:
247
  trig(
248
  orquestrador,
249
  inputs=[txt_in, file_in, chatbot, code_json],
250
  outputs=[chatbot, out_dna, out_logs]
251
  ).then(
252
+ lambda: (None, None, ""),
253
+ outputs=[txt_in, file_in, file_status]
 
254
  )
255
+
256
  return app
257
 
258
  if __name__ == "__main__":
259
+ ui_clean().launch()