caarleexx commited on
Commit
5c1a599
Β·
verified Β·
1 Parent(s): aa7d43a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +279 -226
app.py CHANGED
@@ -1,6 +1,6 @@
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
- # β•‘ PIPELINE v35: SΓ“CRATES v2 | BUGS FIXADOS | HF SPACES OK β•‘
3
- # β•‘ SEM Textbox extra | contexto_fallback fix | 100% input+history β•‘
4
  # β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
5
 
6
  import os
@@ -8,253 +8,306 @@ import json
8
  import re
9
  import time
10
  from datetime import datetime
 
11
  import gradio as gr
12
  import google.generativeai as genai
13
- import warnings
14
- warnings.filterwarnings("ignore")
15
 
16
  # ==================== 1. CONFIGURAÇÃO ====================
17
- api_key = os.getenv("GOOGLE_API_KEY")
18
- if api_key:
 
19
  genai.configure(api_key=api_key)
20
- model_flash = genai.GenerativeModel("gemini-flash-latest")
21
- model_pro = genai.GenerativeModel("gemini-pro-latest")
22
- else:
23
- model_flash = model_pro = None
24
-
25
- ARQUIVO_CONTEXT = "contexto_v35.json"
26
- ARQUIVO_HISTORY = "history_v35.json"
27
-
28
- # ==================== 2. ANALISADOR CONTEXTUAL (CORAÇÃO FIXADO) ====================
29
- class AnalisadorContextual:
30
- def __init__(self):
31
- self.contexto = self.carregar_contexto()
32
-
33
- def carregar_contexto(self):
34
- try:
35
- with open(ARQUIVO_CONTEXT, "r", encoding="utf-8") as f:
36
- return json.load(f)
37
- except:
38
- return {
39
- "classificacao": [],
40
- "fatos": [],
41
- "objetivo_usuario": "",
42
- "duvida_central": "",
43
- "timestamp": ""
44
- }
45
-
46
- def salvar_contexto(self):
47
- try:
48
- with open(ARQUIVO_CONTEXT, "w", encoding="utf-8") as f:
49
- json.dump(self.contexto, f, ensure_ascii=False, indent=2)
50
- except: pass
51
-
52
- def contexto_fallback(self, input_atual):
53
- """FIX: input_atual passado como parΓ’metro"""
54
- return {
55
- "classificacao": ["dΓΊvida"],
56
- "fatos": [],
57
- "objetivo_usuario": "AnΓ‘lise geral",
58
- "duvida_central": input_atual[:200],
59
- "timestamp": datetime.now().isoformat()
60
- }
61
-
62
- def analisar_input(self, input_atual, history):
63
- """TUDO do input + histΓ³rico - SEM outros textbox"""
64
- if not model_pro:
65
- return self.contexto_fallback(input_atual)
66
-
67
- history_resumo = "\n".join([f"πŸ‘€: {h[0][:80]}..." for h in history[-3:]])[:300]
68
-
69
- prompt = f"""CATALOGUE SEM RESPONDER:
70
-
71
- INPUT ATUAL: {input_atual[:400]}
72
- HISTΓ“RICO: {history_resumo}
73
-
74
- JSON:
75
- {{
76
- "classificacao": ["anexo","dΓΊvida","crΓ­tica","pesquisa"],
77
- "fatos": [["gpu",0.9,"input1","2025-12-05"]],
78
- "objetivo_usuario": "Meta da conversa",
79
- "duvida_central": "Foco atual"
80
- }}"""
81
-
82
- try:
83
- resp = model_pro.generate_content(prompt, temperature=0.1)
84
- raw = re.sub(r'``````|\n\s*\n', '', resp.text.strip())
85
- analise = json.loads(raw)
86
-
87
- # MERGE + UPDATE
88
- self.contexto.update(analise)
89
- self.aplicar_decainento()
90
- self.contexto["timestamp"] = datetime.now().isoformat()
91
- self.salvar_contexto()
92
- return self.contexto
93
- except:
94
- return self.contexto_fallback(input_atual)
95
-
96
- def aplicar_decainento(self):
97
- agora = time.time()
98
- novos_fatos = []
99
- for fato in self.contexto.get("fatos", []):
100
- peso = fato[1] * 0.95
101
- if peso > 0.1:
102
- novos_fatos.append([fato[0], peso, fato[2], datetime.now().isoformat()])
103
- self.contexto["fatos"] = novos_fatos[:15]
104
-
105
- # ==================== 3. SINGLETON GLOBAL ====================
106
- analisador = AnalisadorContextual()
107
-
108
- # ==================== 4. PLANEJADOR + EXECUTOR ====================
109
- def fallback_plano():
110
- return [
111
- {"nome": "Analisador", "missao": "Analise input+contexto", "modelo": "flash", "tipo_saida": "json"},
112
- {"nome": "RespostaFinal", "missao": "Resposta clara completa", "modelo": "pro", "tipo_saida": "texto"}
113
- ]
114
-
115
- def planejar_socrates(contexto, input_atual):
116
- if not model_pro: return fallback_plano()
117
-
118
- fatos = "\n".join([f"- {f[0]}({f[1]:.1f})" for f in contexto.get("fatos", [])[:4]])
119
- prompt = f"""CONTEXTO CATALOGADO: {contexto}
120
- INPUT: {input_atual[:200]}
121
-
122
- PLANO 3 agentes JSON:
123
- [{{"nome":"Explorador","missao":"Levante possibilidades","modelo":"flash","tipo_saida":"json"}},
124
- {{"nome":"Validador","missao":"Teste cenΓ‘rios","modelo":"pro","tipo_saida":"json"}},
125
- {{"nome":"Final","missao":"Resposta final","modelo":"pro","tipo_saida":"texto"}}]"""
126
-
127
  try:
128
- resp = model_pro.generate_content(prompt)
129
- plano_raw = re.sub(r'``````', '', resp.text.strip())
130
- return json.loads(plano_raw)
131
  except:
132
- return fallback_plano()
133
 
134
- def executar_agente(timeline, config):
135
- if not model_pro:
136
- return {"role": "system", "error": "Sem API"}, "ERRO", "Sem key"
137
-
138
- modelo = model_pro if config.get("modelo") == "pro" else model_flash
139
- contexto = json.dumps(timeline[-6:], ensure_ascii=False)
140
- prompt = f"CONTEXTO: {contexto}\nAGENTE: {config['nome']}\nMISSÃO: {config['missao']}"
141
-
142
  try:
143
- resp = modelo.generate_content(prompt)
144
- out = resp.text.strip()
145
- content = json.loads(re.sub(r'``````', '', out)) if config.get('tipo_saida') == 'json' else out
146
- return {"role": "assistant", "agent": config['nome'], "content": content}, "OK", out
147
  except:
148
- return {"role": "system", "error": "Erro"}, "ERRO", "Falha"
 
149
 
150
- # ==================== 5. ORQUESTRADOR v35 (SIMPLIFICADO) ====================
151
  def ler_anexo(arquivo):
152
- if not arquivo: return ""
 
153
  try:
154
  with open(arquivo.name, "r", encoding="utf-8") as f:
155
- return f"\nπŸ“Ž {os.path.basename(arquivo.name)}:\n{f.read()}\n"
156
- except: return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
- def orquestrador_v35(texto, arquivo, history, json_config=None):
159
- """SEM textbox extra - sΓ³ input+history"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  anexo = ler_anexo(arquivo)
161
- full_input = f"{texto}{anexo}".strip()
162
-
 
 
163
  if not full_input:
164
- yield history, {}, "Sem input"
 
 
 
 
 
 
 
 
 
 
 
165
  return
166
-
167
- # Adiciona input ao histΓ³rico
168
- history.append([full_input, "🧠 Catalogando contexto..."])
169
  timeline = [{"role": "user", "content": full_input}]
170
- logs = f"πŸš€ v35: {datetime.now().strftime('%H:%M:%S')}\n"
171
-
172
- yield history, timeline, logs
173
-
174
- # 1. ANALISADOR CONTEXTUAL (CORRIGIDO)
175
- contexto = analisador.analisar_input(full_input, history)
176
- logs += f"πŸ“Š {len(contexto.get('fatos',[]))} fatos | {contexto.get('classificacao',[])}\n"
177
- timeline.append({"role": "system", "contexto": contexto})
178
-
179
- history[-1][1] = f"βœ… {len(contexto.get('fatos',[]))} fatos | {contexto.get('classificacao', ['dΓΊvida'])[0]}"
180
- yield history, timeline, logs
181
-
182
- # 2. PLANEJADOR SΓ“CRATES
183
- plano = planejar_socrates(contexto, full_input)
184
- logs += f"🎯 Plano: {len(plano)} agentes\n"
185
- timeline.append({"role": "system", "plano": plano})
186
-
187
- history[-1][1] = f"🎯 {len(plano)} etapas planejadas"
188
- yield history, timeline, logs
189
-
190
- # 3. EXECUTA PLANO
191
- for i, agente in enumerate(plano):
192
- history[-1][1] = f"[{i+1}/{len(plano)}] {agente['nome']}..."
193
- yield history, timeline, logs
194
-
195
- res, status, raw = executar_agente(timeline, agente)
196
  timeline.append(res)
197
- logs += f" {status}\n"
198
-
199
- if agente.get('tipo_saida') == 'texto' and res.get('content'):
200
- history[-1][1] = str(res['content'])[:900]
201
- yield history, timeline, logs
202
-
203
- logs += "βœ… SΓ³crates v35 concluΓ­do"
204
- yield history, timeline, logs
205
-
206
- # ==================== 6. UI SIMPLIFICADA v35 ====================
207
- def ui_v35():
208
- css = "footer {display: none !important;} .contain {border: none !important;}"
209
-
210
- with gr.Blocks(title="πŸš€ PIPELINE v35 - SΓ“CRATES", css=css, theme=gr.themes.Soft()) as app:
211
- gr.Markdown("# 🧠 PIPELINE v35 - Input + Histórico = Tudo")
212
-
 
 
 
 
 
 
 
 
 
 
213
  with gr.Tabs():
214
- # ABA PRINCIPAL - SEM textbox extra
215
- with gr.Tab("πŸ’¬ Pipeline"):
216
- chatbot = gr.Chatbot(height=600, show_copy_button=True, type="tuples")
217
-
 
 
 
 
 
 
 
218
  with gr.Row():
219
- txt_in = gr.Textbox(placeholder="Digite input... (contexto automΓ‘tico)",
220
- lines=3, container=False)
221
- file_in = gr.UploadButton("πŸ“Ž", file_types=[".txt", ".py", ".json"])
222
- btn_send = gr.Button("▢️ Executar", variant="primary")
223
-
224
- file_status = gr.Markdown("")
225
- file_in.upload(lambda x: f"πŸ“Ž {os.path.basename(x.name) if x else ''}",
226
- file_in, file_status)
227
-
228
- # DEBUG
229
- with gr.Tab("πŸ” Debug"):
230
- out_dna = gr.JSON(label="Timeline")
231
- out_logs = gr.Textbox(label="Logs", lines=15)
232
-
233
- # CONTEXTO PERSISTENTE
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  with gr.Tab("🧠 Contexto"):
235
- contexto_display = gr.JSON(label="contexto_v35.json", value={})
236
- gr.Button("πŸ”„ Refresh").click(lambda: analisador.contexto, outputs=contexto_display)
237
-
238
- # TRIGGERS SIMPLIFICADOS (sem 4ΒΊ input)
239
- btn_send.click(
240
- orquestrador_v35,
241
- inputs=[txt_in, file_in, chatbot],
242
- outputs=[chatbot, out_dna, out_logs]
243
- ).then(lambda: "", outputs=txt_in)
244
-
245
- txt_in.submit(
246
- orquestrador_v35,
247
- inputs=[txt_in, file_in, chatbot],
248
- outputs=[chatbot, out_dna, out_logs]
249
- ).then(lambda: "", outputs=txt_in)
250
-
 
 
 
 
 
 
 
 
 
 
 
 
251
  return app
252
 
 
253
  if __name__ == "__main__":
254
- print("πŸš€ v35 SΓ“CRATES - SEM ERROS")
255
- print("βœ… contexto_fallback fixado")
256
- print("βœ… Sem textbox extra - sΓ³ input+history")
257
- print("βœ… HF Spaces 100% OK")
258
-
259
- app = ui_v35()
260
- app.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
+ # β•‘ PIPELINE v28: UI + CONTEXTO GLOBAL β•‘
3
+ # β•‘ Layout: Chat | DepuraΓ§Γ£o | Contexto | Config β•‘
4
  # β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
5
 
6
  import os
 
8
  import re
9
  import time
10
  from datetime import datetime
11
+
12
  import gradio as gr
13
  import google.generativeai as genai
 
 
14
 
15
  # ==================== 1. CONFIGURAÇÃO ====================
16
+
17
+ api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
18
+ if api_key:
19
  genai.configure(api_key=api_key)
20
+
21
+ model_flash = genai.GenerativeModel("gemini-flash-latest")
22
+ model_pro = genai.GenerativeModel("gemini-pro-latest")
23
+
24
+ ARQUIVO_CONFIG = "protocolo.json"
25
+ ARQUIVO_CONTEXTO = "contexto.json"
26
+
27
+
28
+ # ==================== 2. UTILIDADES ====================
29
+
30
+ def carregar_protocolo():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  try:
32
+ with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f:
33
+ return f.read()
 
34
  except:
35
+ return "[]"
36
 
37
+
38
+ def salvar_protocolo(conteudo):
 
 
 
 
 
 
39
  try:
40
+ json.loads(conteudo)
41
+ with open(ARQUIVO_CONFIG, "w", encoding="utf-8") as f:
42
+ f.write(conteudo)
43
+ return "βœ… Salvo"
44
  except:
45
+ return "❌ Erro JSON"
46
+
47
 
 
48
  def ler_anexo(arquivo):
49
+ if arquivo is None:
50
+ return ""
51
  try:
52
  with open(arquivo.name, "r", encoding="utf-8") as f:
53
+ return (
54
+ f"\n\n[ANEXO SISTEMA: {os.path.basename(arquivo.name)}]\n"
55
+ f"{f.read()}\n[FIM ANEXO]\n"
56
+ )
57
+ except:
58
+ return ""
59
+
60
+
61
+ # -------- CONTEXTO GLOBAL --------
62
+
63
+ def contexto_vazio():
64
+ return {
65
+ "classificacao": "", # "duvida" ou "critica"
66
+ "fatos": [], # lista de {descricao, peso, ultima_mencao}
67
+ "objetivo_usuario": "",
68
+ "duvida_central_ultimos_input": "",
69
+ "timestamp": ""
70
+ }
71
+
72
+
73
+ def carregar_contexto():
74
+ try:
75
+ with open(ARQUIVO_CONTEXTO, "r", encoding="utf-8") as f:
76
+ return json.load(f)
77
+ except:
78
+ return contexto_vazio()
79
+
80
+
81
+ def salvar_contexto(ctx):
82
+ try:
83
+ with open(ARQUIVO_CONTEXTO, "w", encoding="utf-8") as f:
84
+ json.dump(ctx, f, ensure_ascii=False, indent=2)
85
+ return ctx
86
+ except:
87
+ return ctx
88
+
89
+
90
+ def atualizar_contexto(full_input, timeline, contexto_antigo):
91
+ """
92
+ VersΓ£o mΓ­nima: atualiza timestamp e ΓΊltima dΓΊvida central
93
+ a partir do ΓΊltimo input do usuΓ‘rio. Pode ser refinado depois
94
+ com agentes especΓ­ficos de metacogniΓ§Γ£o.
95
+ """
96
+ ctx = contexto_antigo.copy() if contexto_antigo else contexto_vazio()
97
+
98
+ # Timestamp sempre atualizado
99
+ ctx["timestamp"] = datetime.now().isoformat()
100
+
101
+ # HeurΓ­stica simples de classificaΓ§Γ£o inicial
102
+ texto_lower = full_input.lower()
103
+ if "?" in full_input:
104
+ ctx["classificacao"] = "duvida"
105
+ elif "reclama" in texto_lower or "problema" in texto_lower or "erro" in texto_lower:
106
+ ctx["classificacao"] = "critica"
107
+
108
+ # Último input como "dúvida central" bruta
109
+ ctx["duvida_central_ultimos_input"] = full_input.strip()
110
+
111
+ # Objetivo do usuΓ‘rio (placeholder: pode ser enriquecido por agente)
112
+ if not ctx.get("objetivo_usuario"):
113
+ ctx["objetivo_usuario"] = "Inferir objetivo do usuΓ‘rio com base na conversa."
114
+
115
+ # TODO futuro: atualizar lista de fatos com pesos e decay
116
+ # por enquanto apenas preserva ctx["fatos"]
117
+
118
+ return ctx
119
+
120
+
121
+ # ==================== 3. ENGINE DE EXECUÇÃO ====================
122
+
123
+ def executar_no(timeline, config, contexto):
124
+ modelo = model_pro if config.get("modelo") == "pro" else model_flash
125
+
126
+ contexto_timeline = json.dumps(timeline, ensure_ascii=False, indent=2)
127
+ contexto_global = json.dumps(contexto, ensure_ascii=False, indent=2)
128
+
129
+ prompt = (
130
+ f"--- CONTEXTO GLOBAL ---\n{contexto_global}\n"
131
+ f"--- TIMELINE ---\n{contexto_timeline}\n"
132
+ f"----------------\n"
133
+ f"AGENTE: {config['nome']}\n"
134
+ f"MISSÃO: {config['missao']}"
135
+ )
136
+
137
+ log = f"\nπŸ”Έ {config['nome']}..."
138
+
139
+ try:
140
+ inicio = time.time()
141
+ resp = modelo.generate_content(prompt)
142
+ out = resp.text
143
+ tempo = time.time() - inicio
144
 
145
+ if config.get("tipo_saida") == "json":
146
+ # Tenta normalizar saΓ­da JSON simples
147
+ cleaned = out.strip().replace("``````", "")
148
+ content = json.loads(cleaned)
149
+ else:
150
+ content = out
151
+
152
+ log += f" (OK - {tempo:.2f}s)"
153
+
154
+ return {"role": "assistant", "agent": config["nome"], "content": content}, log, out
155
+
156
+ except Exception as e:
157
+ # Em caso de erro, registra no timeline como system
158
+ return {"role": "system", "error": str(e)}, f" (ERRO: {e})", str(e)
159
+
160
+
161
+ # ==================== 4. ORQUESTRADOR ====================
162
+
163
+ def orquestrador(texto, arquivo, history, json_config):
164
+ # 1. Input Check
165
  anexo = ler_anexo(arquivo)
166
+ full_input = f"{texto}\n{anexo}".strip()
167
+
168
+ contexto = carregar_contexto()
169
+
170
  if not full_input:
171
+ # Sem input, apenas retorna contexto atual
172
+ yield history, {}, "Sem input.", contexto
173
+ return
174
+
175
+ # 2. Setup histΓ³rico
176
+ history = history + [[texto + (" πŸ“Ž" if arquivo else ""), None]]
177
+
178
+ try:
179
+ protocolo = json.loads(json_config)
180
+ except:
181
+ history[-1][1] = "❌ Erro no JSON de Configuração."
182
+ yield history, {}, "Erro JSON", contexto
183
  return
184
+
 
 
185
  timeline = [{"role": "user", "content": full_input}]
186
+
187
+ # Atualiza contexto com o novo input inicial
188
+ contexto = atualizar_contexto(full_input, timeline, contexto)
189
+ salvar_contexto(contexto)
190
+
191
+ logs = f"πŸš€ START: {datetime.now().strftime('%H:%M:%S')}\n"
192
+
193
+ history[-1][1] = "⏳ Iniciando anÑlise..."
194
+ yield history, timeline, logs, contexto
195
+
196
+ # 3. Loop de nΓ³s
197
+ final_response = ""
198
+
199
+ for cfg in protocolo:
200
+ history[-1][1] = f"βš™οΈ {cfg['nome']} trabalhando..."
201
+ yield history, timeline, logs, contexto
202
+
203
+ res, log_add, raw = executar_no(timeline, cfg, contexto)
 
 
 
 
 
 
 
 
204
  timeline.append(res)
205
+ logs += log_add + "\n"
206
+
207
+ # Atualiza contexto apΓ³s cada nΓ³ com a nova timeline completa
208
+ contexto = atualizar_contexto(full_input, timeline, contexto)
209
+ salvar_contexto(contexto)
210
+
211
+ if cfg.get("tipo_saida") == "texto" and res.get("role") == "assistant":
212
+ final_response = res.get("content", "")
213
+ history[-1][1] = final_response
214
+ yield history, timeline, logs, contexto
215
+
216
+ logs += "βœ… FIM."
217
+ yield history, timeline, logs, contexto
218
+
219
+
220
+ # ==================== 5. UI LIMPA (v28) ====================
221
+
222
+ def ui_clean():
223
+ css = """
224
+ footer {display: none !important;}
225
+ .contain {border: none !important;}
226
+ """
227
+
228
+ config_init = carregar_protocolo()
229
+
230
+ with gr.Blocks(title="AI Forensics", css=css, theme=gr.themes.Soft()) as app:
231
  with gr.Tabs():
232
+
233
+ # === ABA 1: CHAT (LIMPO) ===
234
+ with gr.Tab("πŸ’¬ Investigador"):
235
+ chatbot = gr.Chatbot(
236
+ label="",
237
+ show_label=False,
238
+ height=600,
239
+ show_copy_button=True,
240
+ render_markdown=True
241
+ )
242
+
243
  with gr.Row():
244
+ with gr.Column(scale=10):
245
+ txt_in = gr.Textbox(
246
+ show_label=False,
247
+ placeholder="Descreva o caso ou instruΓ§Γ£o...",
248
+ lines=1,
249
+ max_lines=5,
250
+ container=False,
251
+ )
252
+ with gr.Column(scale=1, min_width=50):
253
+ file_in = gr.UploadButton(
254
+ "πŸ“Ž",
255
+ file_types=[".txt", ".md", ".csv", ".json"],
256
+ size="sm",
257
+ )
258
+ with gr.Column(scale=1, min_width=80):
259
+ btn_send = gr.Button(
260
+ "Enviar",
261
+ variant="primary",
262
+ size="sm",
263
+ )
264
+
265
+ # Feedback visual sutil do arquivo
266
+ file_status = gr.Markdown("", visible=True)
267
+ file_in.upload(
268
+ lambda x: f"πŸ“Ž Anexo: {os.path.basename(x.name)}",
269
+ file_in,
270
+ file_status,
271
+ )
272
+
273
+ # === ABA 2: DEPURAÇÃO ===
274
+ with gr.Tab("πŸ•΅οΈ DepuraΓ§Γ£o"):
275
+ with gr.Row():
276
+ out_dna = gr.JSON(label="DNA (Timeline)")
277
+ out_logs = gr.Textbox(label="Logs do Sistema", lines=20)
278
+
279
+ # === ABA 3: CONTEXTO GLOBAL ===
280
  with gr.Tab("🧠 Contexto"):
281
+ contexto_json = gr.JSON(label="Contexto Global (contexto.json)")
282
+
283
+ # === ABA 4: CONFIG (TÉCNICO) ===
284
+ with gr.Tab("βš™οΈ Config"):
285
+ with gr.Row():
286
+ btn_save = gr.Button("Salvar Config")
287
+ lbl_save = gr.Label(show_label=False)
288
+ code_json = gr.Code(
289
+ value=config_init,
290
+ language="json",
291
+ label="protocolo.json",
292
+ )
293
+ btn_save.click(salvar_protocolo, code_json, lbl_save)
294
+
295
+ # === TRIGGERS ===
296
+ # Enter ou BotΓ£o Enviar
297
+ triggers = [btn_send.click, txt_in.submit]
298
+
299
+ for trig in triggers:
300
+ trig(
301
+ orquestrador,
302
+ inputs=[txt_in, file_in, chatbot, code_json],
303
+ outputs=[chatbot, out_dna, out_logs, contexto_json],
304
+ ).then(
305
+ lambda: (None, ""),
306
+ outputs=[txt_in, file_status],
307
+ )
308
+
309
  return app
310
 
311
+
312
  if __name__ == "__main__":
313
+ ui_clean().launch()