victorafarias commited on
Commit
203fd05
·
1 Parent(s): f3be659

nova troca do GRONK pela OPEN AI

Browse files
Files changed (4) hide show
  1. app.py +69 -31
  2. llms.py +13 -5
  3. requirements.txt +0 -0
  4. templates/index.html +9 -9
app.py CHANGED
@@ -22,7 +22,7 @@ from langchain.prompts import PromptTemplate
22
  from langchain_core.output_parsers import StrOutputParser
23
 
24
  # Importa os LLMs
25
- from llms import claude_llm, grok_llm, gemini_llm
26
 
27
  # Importa os prompts
28
  from config import *
@@ -192,7 +192,8 @@ def process():
192
  if current_mode == 'test':
193
  log_print("=== MODO TESTE EXECUTADO ===")
194
  mock_text = form_data.get('mock_text', 'Este é um **texto** de `simulação`.')
195
- json_data = safe_json_dumps({'progress': 100, 'message': 'Simulação concluída!', 'partial_result': {'id': 'grok-output', 'content': mock_text}, 'done': True, 'mode': 'atomic' if processing_mode == 'atomic' else 'hierarchical'})
 
196
  yield f"data: {json_data}\n\n"
197
  if processing_mode == 'atomic':
198
  json_data = safe_json_dumps({'partial_result': {'id': 'sonnet-output', 'content': mock_text}})
@@ -242,8 +243,8 @@ def process():
242
  except Exception as e:
243
  results[key] = f"Erro ao processar {key.upper()}: {e}"
244
 
245
- claude_atomic_llm = claude_llm.bind(max_tokens=20000)
246
- models = {'grok': grok_llm, 'sonnet': claude_atomic_llm, 'gemini': gemini_llm}
247
 
248
  # Substituir os placeholders no template
249
  updated_prompt_template = PROMPT_ATOMICO_INICIAL.replace(
@@ -291,10 +292,15 @@ def process():
291
  json_data = safe_json_dumps({'progress': 80, 'message': 'Todos os modelos responderam. Formatando saídas...'})
292
  yield f"data: {json_data}\n\n"
293
 
294
- # MUDANÇA: Envia o texto bruto para cada modelo
295
- grok_text = results.get('grok', '')
296
- log_print(f"--- Resposta Bruta do GROK (Atômico) ---\n{grok_text[:200]}...\n--------------------------------------")
297
- json_data = safe_json_dumps({'partial_result': {'id': 'grok-output', 'content': grok_text}})
 
 
 
 
 
298
  yield f"data: {json_data}\n\n"
299
 
300
  sonnet_text = results.get('sonnet', '')
@@ -315,16 +321,21 @@ def process():
315
  # --- LÓGICA HIERÁRQUICA (SEQUENCIAL) ---
316
 
317
  # Atualizar prompts hierárquicos com parâmetros de tamanho
318
- updated_grok_template = PROMPT_HIERARQUICO_GROK.replace(
 
319
  "MIN_CHARS_PLACEHOLDER", str(min_chars)
320
  ).replace(
321
  "MAX_CHARS_PLACEHOLDER", str(max_chars)
322
  ).replace("<role>", f"<role>\n {contexto}") # injeta contexto
323
 
324
  # --- renderiza e loga o prompt final Hierárquico Grok ---
325
- log_print(f"[DEBUG] PROMPT HIERÁRQUICO GROK RENDERED:\n"
326
- f"{updated_grok_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, rag_context=rag_context)}\n""-"*80)
327
 
 
 
 
 
328
  updated_sonnet_template = PROMPT_HIERARQUICO_SONNET.replace(
329
  "MIN_CHARS_PLACEHOLDER", str(min_chars)
330
  ).replace(
@@ -332,9 +343,12 @@ def process():
332
  ).replace("<role>", f"<role>\n {contexto}") # injeta contexto
333
 
334
  # --- renderiza e loga o prompt final Hierárquico Sonnet ---
335
- log_print(f"[DEBUG] PROMPT HIERÁRQUICO SONNET RENDERED:\n"
336
- f"{updated_sonnet_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, texto_para_analise=resposta_grok)}\n""-"*80)
337
 
 
 
 
338
  updated_gemini_template = PROMPT_HIERARQUICO_GEMINI.replace(
339
  "MIN_CHARS_PLACEHOLDER", str(min_chars)
340
  ).replace(
@@ -345,7 +359,8 @@ def process():
345
  log_print(f"[DEBUG] PROMPT HIERÁRQUICO GEMINI RENDERED:\n"
346
  f"{updated_gemini_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, texto_para_analise=resposta_sonnet)}\n""-"*80)
347
 
348
- json_data = safe_json_dumps({'progress': 15, 'message': 'O GROK está processando sua solicitação...'})
 
349
  yield f"data: {json_data}\n\n"
350
 
351
  if processing_cancelled:
@@ -353,26 +368,43 @@ def process():
353
  yield f"data: {json_data}\n\n"
354
  return
355
 
356
- log_print("=== PROCESSANDO GROK ===")
357
- prompt_grok = PromptTemplate(template=updated_grok_template, input_variables=["contexto", "solicitacao_usuario", "rag_context"])
358
- chain_grok = prompt_grok | grok_llm | output_parser
359
- resposta_grok = chain_grok.invoke({"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context})
 
360
 
361
- log_print(f"=== GROK TERMINOU: {len(resposta_grok)} chars ===")
 
 
 
362
 
 
 
 
363
  if processing_cancelled:
364
  json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'})
365
  yield f"data: {json_data}\n\n"
366
  return
367
 
368
- if not resposta_grok or not resposta_grok.strip():
369
- log_print("=== ERRO: GROK VAZIO ===")
370
- json_data = safe_json_dumps({'error': 'Falha no serviço GROK: Sem resposta.'})
 
 
 
 
 
 
 
 
 
 
371
  yield f"data: {json_data}\n\n"
372
  return
373
 
374
- log_print("=== ENVIANDO RESPOSTA GROK PARA FRONTEND ===")
375
- json_data = safe_json_dumps({'progress': 33, 'message': 'Claude Sonnet está processando...', 'partial_result': {'id': 'grok-output', 'content': resposta_grok}})
376
  yield f"data: {json_data}\n\n"
377
 
378
  if processing_cancelled:
@@ -382,9 +414,9 @@ def process():
382
 
383
  log_print("=== PROCESSANDO SONNET ===")
384
  prompt_sonnet = PromptTemplate(template=updated_sonnet_template, input_variables=["contexto", "solicitacao_usuario", "texto_para_analise"])
385
- claude_with_max_tokens = claude_llm.bind(max_tokens=20000)
386
  chain_sonnet = prompt_sonnet | claude_with_max_tokens | output_parser
387
- resposta_sonnet = chain_sonnet.invoke({"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_grok})
388
 
389
  log_print(f"=== SONNET TERMINOU: {len(resposta_sonnet)} chars ===")
390
 
@@ -480,11 +512,16 @@ def merge():
480
  ).replace("<role>", f"<role>\n {contexto}") # injeta contexto
481
 
482
  # --- renderiza e loga o prompt final Atomico Merge --
483
- log_print(f"[DEBUG] PROMPT MERGE RENDERED:\n"
484
- f"{updated_merge_template.format(contexto=contexto, solicitacao_usuario=data.get('solicitacao_usuario'), texto_para_analise_grok=data.get('grok_text'), texto_para_analise_sonnet=data.get('sonnet_text'), texto_para_analise_gemini=data.get('gemini_text'))}\n""-"*80)
 
 
485
 
486
- prompt_merge = PromptTemplate(template=updated_merge_template, input_variables=["contexto", "solicitacao_usuario", "texto_para_analise_grok", "texto_para_analise_sonnet", "texto_para_analise_gemini"])
 
487
 
 
 
488
  # MUDANÇA: Usar Claude Sonnet para o merge
489
  claude_with_max_tokens = claude_llm.bind(max_tokens=64000)
490
  chain_merge = prompt_merge | claude_with_max_tokens | output_parser
@@ -501,7 +538,8 @@ def merge():
501
  resposta_merge = chain_merge.invoke({
502
  "contexto": data.get('contexto'),
503
  "solicitacao_usuario": data.get('solicitacao_usuario'),
504
- "texto_para_analise_grok": data.get('grok_text'),
 
505
  "texto_para_analise_sonnet": data.get('sonnet_text'),
506
  "texto_para_analise_gemini": data.get('gemini_text')
507
  })
@@ -549,4 +587,4 @@ def merge():
549
 
550
  if __name__ == '__main__':
551
  log_print("=== SERVIDOR FLASK INICIADO ===")
552
- app.run(debug=True)
 
22
  from langchain_core.output_parsers import StrOutputParser
23
 
24
  # Importa os LLMs
25
+ from llms import claude_llm, grok_llm, gemini_llm, openai_llm
26
 
27
  # Importa os prompts
28
  from config import *
 
192
  if current_mode == 'test':
193
  log_print("=== MODO TESTE EXECUTADO ===")
194
  mock_text = form_data.get('mock_text', 'Este é um **texto** de `simulação`.')
195
+ # json_data = safe_json_dumps({'progress': 100, 'message': 'Simulação concluída!', 'partial_result': {'id': 'grok-output', 'content': mock_text}, 'done': True, 'mode': 'atomic' if processing_mode == 'atomic' else 'hierarchical'})
196
+ json_data = safe_json_dumps({'progress': 100, 'message': 'Simulação concluída!', 'partial_result': {'id': 'openai-output', 'content': mock_text}, 'done': True, 'mode': 'atomic' if processing_mode == 'atomic' else 'hierarchical'})
197
  yield f"data: {json_data}\n\n"
198
  if processing_mode == 'atomic':
199
  json_data = safe_json_dumps({'partial_result': {'id': 'sonnet-output', 'content': mock_text}})
 
243
  except Exception as e:
244
  results[key] = f"Erro ao processar {key.upper()}: {e}"
245
 
246
+ claude_atomic_llm = claude_llm.bind(max_tokens=60000)
247
+ models = {'grok': grok_llm, 'sonnet': claude_atomic_llm, 'gemini': gemini_llm, 'openai': openai_llm}
248
 
249
  # Substituir os placeholders no template
250
  updated_prompt_template = PROMPT_ATOMICO_INICIAL.replace(
 
292
  json_data = safe_json_dumps({'progress': 80, 'message': 'Todos os modelos responderam. Formatando saídas...'})
293
  yield f"data: {json_data}\n\n"
294
 
295
+ # Envia o texto bruto para cada modelo
296
+ ##grok_text = results.get('grok', '')
297
+ ##log_print(f"--- Resposta Bruta do GROK (Atômico) ---\n{grok_text[:200]}...\n--------------------------------------")
298
+ ##json_data = safe_json_dumps({'partial_result': {'id': 'grok-output', 'content': grok_text}})
299
+ ##yield f"data: {json_data}\n\n"
300
+
301
+ openai_text = results.get('openai', '')
302
+ log_print(f"--- Resposta Bruta do OPEN AI (Atômico) ---\n{openai_text[:200]}...\n--------------------------------------")
303
+ json_data = safe_json_dumps({'partial_result': {'id': 'openai-output', 'content': openai_text}})
304
  yield f"data: {json_data}\n\n"
305
 
306
  sonnet_text = results.get('sonnet', '')
 
321
  # --- LÓGICA HIERÁRQUICA (SEQUENCIAL) ---
322
 
323
  # Atualizar prompts hierárquicos com parâmetros de tamanho
324
+ ##updated_grok_template = PROMPT_HIERARQUICO_GROK.replace(
325
+ updated_openai_template = PROMPT_HIERARQUICO_OPENAI.replace(
326
  "MIN_CHARS_PLACEHOLDER", str(min_chars)
327
  ).replace(
328
  "MAX_CHARS_PLACEHOLDER", str(max_chars)
329
  ).replace("<role>", f"<role>\n {contexto}") # injeta contexto
330
 
331
  # --- renderiza e loga o prompt final Hierárquico Grok ---
332
+ ##log_print(f"[DEBUG] PROMPT HIERÁRQUICO GROK RENDERED:\n"
333
+ ## f"{updated_grok_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, rag_context=rag_context)}\n""-"*80)
334
 
335
+ # --- renderiza e loga o prompt final Hierárquico OpenAI ---
336
+ log_print(f"[DEBUG] PROMPT HIERÁRQUICO OPEN AI RENDERED:\n"
337
+ f"{updated_openai_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, rag_context=rag_context)}\n""-"*80)
338
+
339
  updated_sonnet_template = PROMPT_HIERARQUICO_SONNET.replace(
340
  "MIN_CHARS_PLACEHOLDER", str(min_chars)
341
  ).replace(
 
343
  ).replace("<role>", f"<role>\n {contexto}") # injeta contexto
344
 
345
  # --- renderiza e loga o prompt final Hierárquico Sonnet ---
346
+ ##log_print(f"[DEBUG] PROMPT HIERÁRQUICO SONNET RENDERED:\n"
347
+ ## f"{updated_sonnet_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, texto_para_analise=resposta_grok)}\n""-"*80)
348
 
349
+ log_print(f"[DEBUG] PROMPT HIERÁRQUICO SONNET RENDERED:\n"
350
+ f"{updated_sonnet_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, texto_para_analise=resposta_openai)}\n""-"*80)
351
+
352
  updated_gemini_template = PROMPT_HIERARQUICO_GEMINI.replace(
353
  "MIN_CHARS_PLACEHOLDER", str(min_chars)
354
  ).replace(
 
359
  log_print(f"[DEBUG] PROMPT HIERÁRQUICO GEMINI RENDERED:\n"
360
  f"{updated_gemini_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, texto_para_analise=resposta_sonnet)}\n""-"*80)
361
 
362
+ ##json_data = safe_json_dumps({'progress': 15, 'message': 'O GROK está processando sua solicitação...'})
363
+ json_data = safe_json_dumps({'progress': 15, 'message': 'A OPEN AI está processando sua solicitação...'})
364
  yield f"data: {json_data}\n\n"
365
 
366
  if processing_cancelled:
 
368
  yield f"data: {json_data}\n\n"
369
  return
370
 
371
+ #log_print("=== PROCESSANDO GROK ===")
372
+ #prompt_grok = PromptTemplate(template=updated_grok_template, input_variables=["contexto", "solicitacao_usuario", "rag_context"])
373
+ #chain_grok = prompt_grok | grok_llm | output_parser
374
+ #resposta_grok = chain_grok.invoke({"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context})
375
+ #log_print(f"=== GROK TERMINOU: {len(resposta_grok)} chars ===")
376
 
377
+ log_print("=== PROCESSANDO OPEN AI ===")
378
+ prompt_openai = PromptTemplate(template=updated_openai_template, input_variables=["contexto", "solicitacao_usuario", "rag_context"])
379
+ chain_openai = prompt_openai | openai_llm | output_parser
380
+ resposta_openai = chain_openai.invoke({"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context})
381
 
382
+ log_print(f"=== OPEN AI TERMINOU: {len(resposta_openai)} chars ===")
383
+
384
+
385
  if processing_cancelled:
386
  json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'})
387
  yield f"data: {json_data}\n\n"
388
  return
389
 
390
+ ##if not resposta_grok or not resposta_grok.strip():
391
+ ## log_print("=== ERRO: GROK VAZIO ===")
392
+ ## json_data = safe_json_dumps({'error': 'Falha no serviço GROK: Sem resposta.'})
393
+ ## yield f"data: {json_data}\n\n"
394
+ ## return
395
+ ##
396
+ ##log_print("=== ENVIANDO RESPOSTA GROK PARA FRONTEND ===")
397
+ ##json_data = safe_json_dumps({'progress': 33, 'message': 'Claude Sonnet está processando...', 'partial_result': {'id': 'grok-output', 'content': resposta_grok}})
398
+ ##yield f"data: {json_data}\n\n"
399
+
400
+ if not resposta_openai or not resposta_openai.strip():
401
+ log_print("=== ERRO: OPEN AI VAZIO ===")
402
+ json_data = safe_json_dumps({'error': 'Falha no serviço OPEN AI: Sem resposta.'})
403
  yield f"data: {json_data}\n\n"
404
  return
405
 
406
+ log_print("=== ENVIANDO RESPOSTA OPEN AI PARA FRONTEND ===")
407
+ json_data = safe_json_dumps({'progress': 33, 'message': 'Claude Sonnet está processando...', 'partial_result': {'id': 'openai-output', 'content': resposta_openai}})
408
  yield f"data: {json_data}\n\n"
409
 
410
  if processing_cancelled:
 
414
 
415
  log_print("=== PROCESSANDO SONNET ===")
416
  prompt_sonnet = PromptTemplate(template=updated_sonnet_template, input_variables=["contexto", "solicitacao_usuario", "texto_para_analise"])
417
+ claude_with_max_tokens = claude_llm.bind(max_tokens=60000)
418
  chain_sonnet = prompt_sonnet | claude_with_max_tokens | output_parser
419
+ resposta_sonnet = chain_sonnet.invoke({"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_openai})
420
 
421
  log_print(f"=== SONNET TERMINOU: {len(resposta_sonnet)} chars ===")
422
 
 
512
  ).replace("<role>", f"<role>\n {contexto}") # injeta contexto
513
 
514
  # --- renderiza e loga o prompt final Atomico Merge --
515
+ ##log_print(f"[DEBUG] PROMPT MERGE RENDERED:\n"
516
+ ## f"{updated_merge_template.format(contexto=contexto, solicitacao_usuario=data.get('solicitacao_usuario'), texto_para_analise_grok=data.get('grok_text'), texto_para_analise_sonnet=data.get('sonnet_text'), texto_para_analise_gemini=data.get('gemini_text'))}\n""-"*80)
517
+ ##
518
+ ##prompt_merge = PromptTemplate(template=updated_merge_template, input_variables=["contexto", "solicitacao_usuario", "texto_para_analise_grok", "texto_para_analise_sonnet", "texto_para_analise_gemini"])
519
 
520
+ log_print(f"[DEBUG] PROMPT MERGE RENDERED:\n"
521
+ f"{updated_merge_template.format(contexto=contexto, solicitacao_usuario=data.get('solicitacao_usuario'), texto_para_analise_openai=data.get('openai_text'), texto_para_analise_sonnet=data.get('sonnet_text'), texto_para_analise_gemini=data.get('gemini_text'))}\n""-"*80)
522
 
523
+ prompt_merge = PromptTemplate(template=updated_merge_template, input_variables=["contexto", "solicitacao_usuario", "texto_para_analise_openai", "texto_para_analise_sonnet", "texto_para_analise_gemini"])
524
+
525
  # MUDANÇA: Usar Claude Sonnet para o merge
526
  claude_with_max_tokens = claude_llm.bind(max_tokens=64000)
527
  chain_merge = prompt_merge | claude_with_max_tokens | output_parser
 
538
  resposta_merge = chain_merge.invoke({
539
  "contexto": data.get('contexto'),
540
  "solicitacao_usuario": data.get('solicitacao_usuario'),
541
+ ##"texto_para_analise_grok": data.get('grok_text'),
542
+ "texto_para_analise_openai": data.get('openai_text'),
543
  "texto_para_analise_sonnet": data.get('sonnet_text'),
544
  "texto_para_analise_gemini": data.get('gemini_text')
545
  })
 
587
 
588
  if __name__ == '__main__':
589
  log_print("=== SERVIDOR FLASK INICIADO ===")
590
+ app.run(debug=True)
llms.py CHANGED
@@ -6,20 +6,28 @@ from dotenv import load_dotenv
6
  from custom_grok import GrokChatModel
7
  from langchain_google_genai import ChatGoogleGenerativeAI
8
  from langchain_anthropic import ChatAnthropic
 
 
9
 
10
  # Carrega as variáveis de ambiente do arquivo .env
11
  load_dotenv()
12
 
13
  # --- Inicialização dos LLMs ---
14
 
15
- # GROK da xAI
16
- grok_llm = GrokChatModel(
17
- api_key=os.getenv("X_API_KEY"),
18
- model=os.getenv("GROK_MODEL_ID"),
19
- base_url=os.getenv("X_API_BASE_URL"),
20
  timeout=900
21
  )
22
 
 
 
 
 
 
 
 
23
  # Claude Sonnet
24
  claude_llm = ChatAnthropic(
25
  api_key=os.getenv("ANTHROPIC_API_KEY"),
 
6
  from custom_grok import GrokChatModel
7
  from langchain_google_genai import ChatGoogleGenerativeAI
8
  from langchain_anthropic import ChatAnthropic
9
+ from langchain_openai import ChatOpenAI
10
+ from langchain_openai import OpenAIAssistantRunnable
11
 
12
  # Carrega as variáveis de ambiente do arquivo .env
13
  load_dotenv()
14
 
15
  # --- Inicialização dos LLMs ---
16
 
17
+ # OpenAI
18
+ openai_llm = OpenAIAssistantRunnable(
19
+ assistant_id=os.getenv("OPENAI_ASSISTANT_ID"),
20
+ as_agent=True, # O 'as_agent=True' garante o comportamento correto de entrada/saída
 
21
  timeout=900
22
  )
23
 
24
+ # GROK da xAI
25
+ ##grok_llm = GrokChatModel(
26
+ ## api_key=os.getenv("X_API_KEY"),
27
+ ## model=os.getenv("GROK_MODEL_ID"),
28
+ ## base_url=os.getenv("X_API_BASE_URL"),
29
+ ## timeout=900
30
+
31
  # Claude Sonnet
32
  claude_llm = ChatAnthropic(
33
  api_key=os.getenv("ANTHROPIC_API_KEY"),
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ
 
templates/index.html CHANGED
@@ -469,7 +469,7 @@
469
  <div class="header-container">
470
  <div>
471
  <h1>Sistema Multi-Agente IA</h1>
472
- <p id="flow-description">GROK ➔ Claude Sonnet ➔ Gemini</p>
473
  </div>
474
  <div class="controls-container">
475
  <div class="mode-toggle" title="A versão 'Hierárquica' gerará um único texto que passará por revisão em duas instâncias. Na versão 'Atômica', serão gerados 3 textos, um em cada modelo de IA; e depois um 4º texto será gerado fazendo um texto final consolidado dessas 3 versões.">
@@ -532,13 +532,13 @@ Use o botão `Converter para MD` para ver a mágica.</textarea>
532
  <div id="results-container" class="results-container">
533
  <div class="result-column">
534
  <div class="column-header">
535
- <h2>GROK</h2>
536
  <div>
537
- <button class="copy-btn" onclick="copyToClipboard('grok-output')">Copiar</button>
538
- <button class="convert-btn" onclick="convertToMarkdown('grok-output')">Converter para MD</button>
539
  </div>
540
  </div>
541
- <div class="output-box" id="grok-output"></div>
542
  </div>
543
  <div class="result-column">
544
  <div class="column-header">
@@ -613,8 +613,8 @@ Use o botão `Converter para MD` para ver a mágica.</textarea>
613
  processingModeSwitch.addEventListener('change', function() {
614
  const isAtomic = this.checked;
615
  document.getElementById('flow-description').textContent = isAtomic ?
616
- "GROK | Claude Sonnet | Gemini (Paralelo)" :
617
- "GROK ➔ Claude Sonnet ➔ Gemini";
618
  });
619
 
620
  // --- Lógica do botão de cancelar ---
@@ -825,11 +825,11 @@ Use o botão `Converter para MD` para ver a mágica.</textarea>
825
 
826
  const payload = {
827
  solicitacao_usuario: originalUserQuery,
828
- grok_text: rawTexts['grok-output'] || '',
829
  sonnet_text: rawTexts['sonnet-output'] || '',
830
  gemini_text: rawTexts['gemini-output'] || '',
831
  };
832
- debugLog(`Payload do merge preparado com textos de tamanho: G=${payload.grok_text.length}, S=${payload.sonnet_text.length}, G=${payload.gemini_text.length}`);
833
 
834
  try {
835
  const response = await fetch('/merge', {
 
469
  <div class="header-container">
470
  <div>
471
  <h1>Sistema Multi-Agente IA</h1>
472
+ <p id="flow-description">OPEN AI ➔ Claude Sonnet ➔ Gemini</p>
473
  </div>
474
  <div class="controls-container">
475
  <div class="mode-toggle" title="A versão 'Hierárquica' gerará um único texto que passará por revisão em duas instâncias. Na versão 'Atômica', serão gerados 3 textos, um em cada modelo de IA; e depois um 4º texto será gerado fazendo um texto final consolidado dessas 3 versões.">
 
532
  <div id="results-container" class="results-container">
533
  <div class="result-column">
534
  <div class="column-header">
535
+ <h2>OPEN AI</h2>
536
  <div>
537
+ <button class="copy-btn" onclick="copyToClipboard('openai-output')">Copiar</button>
538
+ <button class="convert-btn" onclick="convertToMarkdown('openai-output')">Converter para MD</button>
539
  </div>
540
  </div>
541
+ <div class="output-box" id="openai-output"></div>
542
  </div>
543
  <div class="result-column">
544
  <div class="column-header">
 
613
  processingModeSwitch.addEventListener('change', function() {
614
  const isAtomic = this.checked;
615
  document.getElementById('flow-description').textContent = isAtomic ?
616
+ "OPEN AI | Claude Sonnet | Gemini (Paralelo)" :
617
+ "OPEN AI ➔ Claude Sonnet ➔ Gemini";
618
  });
619
 
620
  // --- Lógica do botão de cancelar ---
 
825
 
826
  const payload = {
827
  solicitacao_usuario: originalUserQuery,
828
+ openai_text: rawTexts['openai-output'] || '',
829
  sonnet_text: rawTexts['sonnet-output'] || '',
830
  gemini_text: rawTexts['gemini-output'] || '',
831
  };
832
+ debugLog(`Payload do merge preparado com textos de tamanho: G=${payload.openai_text.length}, S=${payload.sonnet_text.length}, G=${payload.gemini_text.length}`);
833
 
834
  try {
835
  const response = await fetch('/merge', {