victorafarias commited on
Commit
2b7c617
·
1 Parent(s): 9301d0e

Correção do Processamento do Merge

Browse files
Files changed (2) hide show
  1. app.py +35 -46
  2. templates/index.html +68 -61
app.py CHANGED
@@ -6,7 +6,7 @@ import json
6
  import time
7
  import os
8
  import uuid
9
- import threading # Necessário para o processamento em paralelo
10
 
11
  # Importações do LangChain
12
  from langchain.prompts import PromptTemplate
@@ -52,11 +52,9 @@ def process():
52
  mock_text = form_data.get('mock_text', 'Este é um texto de simulação.')
53
  mock_html = markdown2.markdown(mock_text, extras=["fenced-code-blocks", "tables"])
54
  yield f"data: {json.dumps({'progress': 100, 'message': 'Simulação concluída!', 'partial_result': {'id': 'grok-output', 'content': mock_html}, 'done': True, 'mode': 'atomic' if processing_mode == 'atomic' else 'hierarchical'})}\n\n"
55
- # Simula o preenchimento de todas as caixas no modo teste
56
  if processing_mode == 'atomic':
57
  yield f"data: {json.dumps({'partial_result': {'id': 'sonnet-output', 'content': mock_html}})}\n\n"
58
  yield f"data: {json.dumps({'partial_result': {'id': 'gemini-output', 'content': mock_html}})}\n\n"
59
-
60
  else:
61
  if not solicitacao_usuario:
62
  yield f"data: {json.dumps({'error': 'Solicitação não fornecida.'})}\n\n"
@@ -67,48 +65,31 @@ def process():
67
  rag_context = get_relevant_context(file_paths, solicitacao_usuario)
68
 
69
  if processing_mode == 'atomic':
70
- # --- LÓGICA ATÔMICA (PARALELA) CORRIGIDA ---
71
  results = {}
72
  threads = []
73
-
74
  def run_chain(chain, inputs, key):
75
- try:
76
- results[key] = chain.invoke(inputs)['text']
77
- except Exception as e:
78
- results[key] = f"Erro ao processar {key}: {e}"
79
 
80
  models = {'grok': grok_llm, 'sonnet': claude_llm, 'gemini': gemini_llm}
81
  prompt = PromptTemplate(template=PROMPT_ATOMICO_INICIAL, input_variables=["solicitacao_usuario", "rag_context"])
82
-
83
  yield f"data: {json.dumps({'progress': 15, 'message': 'Iniciando processamento paralelo...'})}\n\n"
84
-
85
- # 1. Inicia todas as threads
86
  for name, llm in models.items():
87
  chain = LLMChain(llm=llm, prompt=prompt)
88
  thread = threading.Thread(target=run_chain, args=(chain, {"solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context}, name))
89
  threads.append(thread)
90
  thread.start()
91
-
92
- # 2. Aguarda a conclusão de todas as threads
93
  for thread in threads:
94
  thread.join()
95
-
96
  yield f"data: {json.dumps({'progress': 80, 'message': 'Todos os modelos responderam. Formatando saída...'})}\n\n"
97
-
98
- # 3. Envia todos os resultados de uma vez, agora que estão prontos
99
  grok_html = markdown2.markdown(results.get('grok', 'Falha ao obter resposta.'), extras=["fenced-code-blocks", "tables"])
100
  yield f"data: {json.dumps({'partial_result': {'id': 'grok-output', 'content': grok_html}})}\n\n"
101
-
102
  sonnet_html = markdown2.markdown(results.get('sonnet', 'Falha ao obter resposta.'), extras=["fenced-code-blocks", "tables"])
103
  yield f"data: {json.dumps({'partial_result': {'id': 'sonnet-output', 'content': sonnet_html}})}\n\n"
104
-
105
  gemini_html = markdown2.markdown(results.get('gemini', 'Falha ao obter resposta.'), extras=["fenced-code-blocks", "tables"])
106
  yield f"data: {json.dumps({'partial_result': {'id': 'gemini-output', 'content': gemini_html}})}\n\n"
107
-
108
  yield f"data: {json.dumps({'progress': 100, 'message': 'Processamento Atômico concluído!', 'done': True, 'mode': 'atomic'})}\n\n"
109
-
110
  else:
111
- # --- LÓGICA HIERÁRQUICA (SEQUENCIAL) ---
112
  yield f"data: {json.dumps({'progress': 15, 'message': 'O GROK está processando sua solicitação com os arquivos...'})}\n\n"
113
  prompt_grok = PromptTemplate(template=PROMPT_HIERARQUICO_GROK, input_variables=["solicitacao_usuario", "rag_context"])
114
  chain_grok = LLMChain(llm=grok_llm, prompt=prompt_grok)
@@ -131,39 +112,47 @@ def process():
131
  if not resposta_gemini or not resposta_gemini.strip(): raise ValueError("Falha no serviço Gemini: Sem resposta.")
132
  gemini_html = markdown2.markdown(resposta_gemini, extras=["fenced-code-blocks", "tables"])
133
  yield f"data: {json.dumps({'progress': 100, 'message': 'Processamento concluído!', 'partial_result': {'id': 'gemini-output', 'content': gemini_html}, 'done': True, 'mode': 'hierarchical'})}\n\n"
134
-
135
  except Exception as e:
136
  print(f"Ocorreu um erro durante o processamento: {e}")
137
  yield f"data: {json.dumps({'error': f'Ocorreu um erro inesperado na aplicação: {e}'})}\n\n"
138
 
139
  return Response(generate_stream(mode, form_data, temp_file_paths), mimetype='text/event-stream')
140
 
141
- # --- ROTA PARA O MERGE ---
142
  @app.route('/merge', methods=['POST'])
143
  def merge():
144
  data = request.get_json()
145
- try:
146
- prompt_merge = PromptTemplate(template=PROMPT_ATOMICO_MERGE, input_variables=["solicitacao_usuario", "texto_para_analise_grok", "texto_para_analise_sonnet", "texto_para_analise_gemini"])
147
-
148
- claude_with_max_tokens = claude_llm.bind(max_tokens=12000)
149
- chain_merge = LLMChain(llm=claude_with_max_tokens, prompt=prompt_merge)
150
-
151
- resposta_merge = chain_merge.invoke({
152
- "solicitacao_usuario": data.get('solicitacao_usuario'),
153
- "texto_para_analise_grok": data.get('grok_text'),
154
- "texto_para_analise_sonnet": data.get('sonnet_text'),
155
- "texto_para_analise_gemini": data.get('gemini_text')
156
- })['text']
157
-
158
- if not resposta_merge or not resposta_merge.strip():
159
- raise ValueError("Falha no serviço de Merge (Claude Sonnet): Sem resposta.")
160
-
161
- merge_html = markdown2.markdown(resposta_merge, extras=["fenced-code-blocks", "tables"])
162
- return jsonify({"success": True, "content": merge_html})
163
-
164
- except Exception as e:
165
- print(f"Erro no processo de merge: {e}")
166
- return jsonify({"success": False, "error": str(e)}), 500
 
 
 
 
 
 
 
 
 
167
 
168
  if __name__ == '__main__':
169
  app.run(debug=True)
 
6
  import time
7
  import os
8
  import uuid
9
+ import threading
10
 
11
  # Importações do LangChain
12
  from langchain.prompts import PromptTemplate
 
52
  mock_text = form_data.get('mock_text', 'Este é um texto de simulação.')
53
  mock_html = markdown2.markdown(mock_text, extras=["fenced-code-blocks", "tables"])
54
  yield f"data: {json.dumps({'progress': 100, 'message': 'Simulação concluída!', 'partial_result': {'id': 'grok-output', 'content': mock_html}, 'done': True, 'mode': 'atomic' if processing_mode == 'atomic' else 'hierarchical'})}\n\n"
 
55
  if processing_mode == 'atomic':
56
  yield f"data: {json.dumps({'partial_result': {'id': 'sonnet-output', 'content': mock_html}})}\n\n"
57
  yield f"data: {json.dumps({'partial_result': {'id': 'gemini-output', 'content': mock_html}})}\n\n"
 
58
  else:
59
  if not solicitacao_usuario:
60
  yield f"data: {json.dumps({'error': 'Solicitação não fornecida.'})}\n\n"
 
65
  rag_context = get_relevant_context(file_paths, solicitacao_usuario)
66
 
67
  if processing_mode == 'atomic':
 
68
  results = {}
69
  threads = []
 
70
  def run_chain(chain, inputs, key):
71
+ try: results[key] = chain.invoke(inputs)['text']
72
+ except Exception as e: results[key] = f"Erro ao processar {key}: {e}"
 
 
73
 
74
  models = {'grok': grok_llm, 'sonnet': claude_llm, 'gemini': gemini_llm}
75
  prompt = PromptTemplate(template=PROMPT_ATOMICO_INICIAL, input_variables=["solicitacao_usuario", "rag_context"])
 
76
  yield f"data: {json.dumps({'progress': 15, 'message': 'Iniciando processamento paralelo...'})}\n\n"
 
 
77
  for name, llm in models.items():
78
  chain = LLMChain(llm=llm, prompt=prompt)
79
  thread = threading.Thread(target=run_chain, args=(chain, {"solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context}, name))
80
  threads.append(thread)
81
  thread.start()
 
 
82
  for thread in threads:
83
  thread.join()
 
84
  yield f"data: {json.dumps({'progress': 80, 'message': 'Todos os modelos responderam. Formatando saída...'})}\n\n"
 
 
85
  grok_html = markdown2.markdown(results.get('grok', 'Falha ao obter resposta.'), extras=["fenced-code-blocks", "tables"])
86
  yield f"data: {json.dumps({'partial_result': {'id': 'grok-output', 'content': grok_html}})}\n\n"
 
87
  sonnet_html = markdown2.markdown(results.get('sonnet', 'Falha ao obter resposta.'), extras=["fenced-code-blocks", "tables"])
88
  yield f"data: {json.dumps({'partial_result': {'id': 'sonnet-output', 'content': sonnet_html}})}\n\n"
 
89
  gemini_html = markdown2.markdown(results.get('gemini', 'Falha ao obter resposta.'), extras=["fenced-code-blocks", "tables"])
90
  yield f"data: {json.dumps({'partial_result': {'id': 'gemini-output', 'content': gemini_html}})}\n\n"
 
91
  yield f"data: {json.dumps({'progress': 100, 'message': 'Processamento Atômico concluído!', 'done': True, 'mode': 'atomic'})}\n\n"
 
92
  else:
 
93
  yield f"data: {json.dumps({'progress': 15, 'message': 'O GROK está processando sua solicitação com os arquivos...'})}\n\n"
94
  prompt_grok = PromptTemplate(template=PROMPT_HIERARQUICO_GROK, input_variables=["solicitacao_usuario", "rag_context"])
95
  chain_grok = LLMChain(llm=grok_llm, prompt=prompt_grok)
 
112
  if not resposta_gemini or not resposta_gemini.strip(): raise ValueError("Falha no serviço Gemini: Sem resposta.")
113
  gemini_html = markdown2.markdown(resposta_gemini, extras=["fenced-code-blocks", "tables"])
114
  yield f"data: {json.dumps({'progress': 100, 'message': 'Processamento concluído!', 'partial_result': {'id': 'gemini-output', 'content': gemini_html}, 'done': True, 'mode': 'hierarchical'})}\n\n"
 
115
  except Exception as e:
116
  print(f"Ocorreu um erro durante o processamento: {e}")
117
  yield f"data: {json.dumps({'error': f'Ocorreu um erro inesperado na aplicação: {e}'})}\n\n"
118
 
119
  return Response(generate_stream(mode, form_data, temp_file_paths), mimetype='text/event-stream')
120
 
121
+ # --- ROTA DE MERGE ATUALIZADA PARA STREAMING ---
122
  @app.route('/merge', methods=['POST'])
123
  def merge():
124
  data = request.get_json()
125
+
126
+ def generate_merge_stream():
127
+ try:
128
+ yield f"data: {json.dumps({'progress': 0, 'message': 'Iniciando o processo de merge...'})}\n\n"
129
+
130
+ prompt_merge = PromptTemplate(template=PROMPT_ATOMICO_MERGE, input_variables=["solicitacao_usuario", "texto_para_analise_grok", "texto_para_analise_sonnet", "texto_para_analise_gemini"])
131
+
132
+ claude_with_max_tokens = claude_llm.bind(max_tokens=12000)
133
+ chain_merge = LLMChain(llm=claude_with_max_tokens, prompt=prompt_merge)
134
+
135
+ yield f"data: {json.dumps({'progress': 50, 'message': 'Enviando textos para o Claude Sonnet para consolidação...'})}\n\n"
136
+
137
+ resposta_merge = chain_merge.invoke({
138
+ "solicitacao_usuario": data.get('solicitacao_usuario'),
139
+ "texto_para_analise_grok": data.get('grok_text'),
140
+ "texto_para_analise_sonnet": data.get('sonnet_text'),
141
+ "texto_para_analise_gemini": data.get('gemini_text')
142
+ })['text']
143
+
144
+ if not resposta_merge or not resposta_merge.strip():
145
+ raise ValueError("Falha no serviço de Merge (Claude Sonnet): Sem resposta.")
146
+
147
+ merge_html = markdown2.markdown(resposta_merge, extras=["fenced-code-blocks", "tables"])
148
+
149
+ yield f"data: {json.dumps({'progress': 100, 'message': 'Merge concluído!', 'final_result': {'content': merge_html}, 'done': True})}\n\n"
150
+
151
+ except Exception as e:
152
+ print(f"Erro no processo de merge: {e}")
153
+ yield f"data: {json.dumps({'error': str(e)})}\n\n"
154
+
155
+ return Response(generate_merge_stream(), mimetype='text/event-stream')
156
 
157
  if __name__ == '__main__':
158
  app.run(debug=True)
templates/index.html CHANGED
@@ -3,19 +3,23 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Sistema Multi-Agente IA</title>
7
  <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
  </head>
9
  <body>
10
 
 
11
  <div id="loader-overlay" style="display: none;">
12
  <div class="loader-content">
13
  <div class="loader-spinner"></div>
14
  <p id="loader-message">Processando sua solicitação...</p>
15
- <div class="progress-bar-container"><div id="progress-bar" class="progress-bar"></div></div>
 
 
16
  </div>
17
  </div>
18
 
 
19
  <button id="merge-btn" class="floating-merge-btn" style="display: none;">Processar Merge</button>
20
 
21
  <div class="container">
@@ -25,6 +29,7 @@
25
  <p id="flow-description">GROK ➔ Claude Sonnet ➔ Gemini</p>
26
  </div>
27
  <div class="controls-container">
 
28
  <div class="mode-toggle" title="A versão 'Hierárquica' gerará um único no 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.">
29
  <span>Hierárquico</span>
30
  <label class="switch">
@@ -44,6 +49,7 @@
44
 
45
  <div id="error-box-container"></div>
46
 
 
47
  <div id="real-form-container">
48
  <form id="request-form-real">
49
  <label for="solicitacao_usuario">Digite sua solicitação (ou arraste arquivos aqui):</label>
@@ -60,12 +66,14 @@
60
  </form>
61
  </div>
62
 
 
63
  <div id="results-container" class="results-container" style="display: none;">
64
  <div class="result-column"><div class="column-header"><h2>GROK</h2><button class="copy-btn" onclick="copyToClipboard('grok-output')">Copiar</button></div><div class="output-box" id="grok-output"></div></div>
65
  <div class="result-column"><div class="column-header"><h2>Claude Sonnet</h2><button class="copy-btn" onclick="copyToClipboard('sonnet-output')">Copiar</button></div><div class="output-box" id="sonnet-output"></div></div>
66
  <div class="result-column"><div class="column-header"><h2>Gemini</h2><button class="copy-btn" onclick="copyToClipboard('gemini-output')">Copiar</button></div><div class="output-box" id="gemini-output"></div></div>
67
  </div>
68
 
 
69
  <div id="final-result-container" style="display: none;">
70
  <h2>Texto Final</h2>
71
  <div class="output-box" id="final-output"></div>
@@ -73,7 +81,7 @@
73
  </div>
74
 
75
  <script>
76
- // --- Variáveis Globais ---
77
  const processingModeSwitch = document.getElementById('processing-mode-switch');
78
  const modeSwitch = document.getElementById('mode-switch');
79
  const realContainer = document.getElementById('real-form-container');
@@ -89,30 +97,23 @@
89
  const finalResultContainer = document.getElementById('final-result-container');
90
  const finalOutput = document.getElementById('final-output');
91
  let attachedFiles = [];
92
- let originalUserQuery = ""; // Salvar a query original
93
 
94
- // --- Lógica de UI ---
95
  modeSwitch.addEventListener('change', function() {
96
  realContainer.style.display = this.checked ? 'none' : 'block';
97
  mockContainer.style.display = this.checked ? 'block' : 'none';
98
  });
99
-
100
  processingModeSwitch.addEventListener('change', function() {
101
- const description = document.getElementById('flow-description');
102
- description.textContent = this.checked ? "GROK | Claude Sonnet | Gemini (Paralelo)" : "GROK ➔ Claude Sonnet ➔ Gemini";
103
  });
104
 
105
- // --- Lógica de Upload (sem alterações) ---
106
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
107
  textarea.addEventListener(eventName, preventDefaults, false);
108
  document.body.addEventListener(eventName, preventDefaults, false);
109
  });
110
- ['dragenter', 'dragover'].forEach(eventName => {
111
- textarea.addEventListener(eventName, () => textarea.classList.add('drag-over'), false);
112
- });
113
- ['dragleave', 'drop'].forEach(eventName => {
114
- textarea.addEventListener(eventName, () => textarea.classList.remove('drag-over'), false);
115
- });
116
  textarea.addEventListener('drop', handleDrop, false);
117
  function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); }
118
  function handleDrop(e) { handleFiles(e.dataTransfer.files); }
@@ -146,22 +147,16 @@
146
 
147
  async function handleFormSubmit(event) {
148
  event.preventDefault();
149
-
150
- // Limpa o estado da UI
151
  errorContainer.innerHTML = '';
152
  resultsContainer.style.display = 'none';
153
  finalResultContainer.style.display = 'none';
154
  mergeBtn.style.display = 'none';
155
  document.querySelectorAll('.output-box').forEach(box => box.innerHTML = '');
156
-
157
- // Inicia o loader
158
  loaderMessage.textContent = 'Iniciando conexão...';
159
  progressBar.style.width = '0%';
160
  loader.style.display = 'flex';
161
-
162
  const formData = new FormData();
163
  formData.append('processing_mode', processingModeSwitch.checked ? 'atomic' : 'hierarchical');
164
-
165
  if (modeSwitch.checked) {
166
  formData.append('mode', 'test');
167
  formData.append('mock_text', document.getElementById('mock_text').value);
@@ -172,9 +167,6 @@
172
  formData.append('solicitacao', originalUserQuery);
173
  attachedFiles.forEach(file => { formData.append('files', file); });
174
  }
175
-
176
- // A lógica de streaming via fetch continua a mesma
177
- // ... (código do fetch e do reader, sem alterações)
178
  try {
179
  const response = await fetch('/process', { method: 'POST', body: formData });
180
  if (!response.ok || !response.body) throw new Error(`Erro na resposta do servidor: ${response.statusText}`);
@@ -191,7 +183,7 @@
191
  if (jsonData.trim()) {
192
  try {
193
  const data = JSON.parse(jsonData);
194
- processStreamData(data);
195
  } catch (e) { console.error("Erro ao parsear JSON:", jsonData); }
196
  }
197
  }
@@ -204,39 +196,12 @@
204
  }
205
  }
206
 
207
- function processStreamData(data) {
208
- if (data.error) {
209
- showError(data.error);
210
- loader.style.display = 'none';
211
- return;
212
- }
213
-
214
- loaderMessage.textContent = data.message;
215
- progressBar.style.width = data.progress + '%';
216
-
217
- if (data.partial_result) {
218
- resultsContainer.style.display = 'flex';
219
- const targetBox = document.getElementById(data.partial_result.id);
220
- if (targetBox) targetBox.innerHTML = data.partial_result.content;
221
- }
222
-
223
- if (data.done) {
224
- setTimeout(() => {
225
- loader.style.display = 'none';
226
- // Mostra o botão de merge apenas se o modo for Atômico
227
- if (data.mode === 'atomic') {
228
- mergeBtn.style.display = 'block';
229
- }
230
- }, 1000);
231
- }
232
- }
233
-
234
- // --- Lógica do Botão de Merge ---
235
  mergeBtn.addEventListener('click', async function() {
236
  loaderMessage.textContent = 'Processando o merge dos textos...';
237
  progressBar.style.width = '0%';
238
  loader.style.display = 'flex';
239
- this.style.display = 'none'; // Esconde o botão
240
 
241
  const payload = {
242
  solicitacao_usuario: originalUserQuery,
@@ -251,21 +216,63 @@
251
  headers: { 'Content-Type': 'application/json' },
252
  body: JSON.stringify(payload)
253
  });
254
- const result = await response.json();
255
 
256
- if (result.success) {
257
- finalOutput.innerHTML = result.content;
258
- finalResultContainer.style.display = 'block';
259
- } else {
260
- showError(result.error || "Ocorreu um erro desconhecido no merge.");
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  }
262
  } catch (error) {
263
  showError("A conexão falhou ao tentar processar o merge.");
264
- } finally {
265
  loader.style.display = 'none';
266
  }
267
  });
268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  function showError(message) {
270
  errorContainer.innerHTML = `<div class="error-box"><strong>Erro:</strong> ${message}<span class="close-btn-error" onclick="this.parentElement.style.display='none';" title="Fechar">&times;</span></div>`;
271
  }
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Sistema Multi-Agente de IA</title>
7
  <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
  </head>
9
  <body>
10
 
11
+ <!-- Loader Overlay com Barra de Progresso -->
12
  <div id="loader-overlay" style="display: none;">
13
  <div class="loader-content">
14
  <div class="loader-spinner"></div>
15
  <p id="loader-message">Processando sua solicitação...</p>
16
+ <div class="progress-bar-container">
17
+ <div id="progress-bar" class="progress-bar"></div>
18
+ </div>
19
  </div>
20
  </div>
21
 
22
+ <!-- Novo Botão Flutuante de Merge -->
23
  <button id="merge-btn" class="floating-merge-btn" style="display: none;">Processar Merge</button>
24
 
25
  <div class="container">
 
29
  <p id="flow-description">GROK ➔ Claude Sonnet ➔ Gemini</p>
30
  </div>
31
  <div class="controls-container">
32
+ <!-- Novo Seletor de Modo de Processamento -->
33
  <div class="mode-toggle" title="A versão 'Hierárquica' gerará um único no 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.">
34
  <span>Hierárquico</span>
35
  <label class="switch">
 
49
 
50
  <div id="error-box-container"></div>
51
 
52
+ <!-- Formulários -->
53
  <div id="real-form-container">
54
  <form id="request-form-real">
55
  <label for="solicitacao_usuario">Digite sua solicitação (ou arraste arquivos aqui):</label>
 
66
  </form>
67
  </div>
68
 
69
+ <!-- Resultados -->
70
  <div id="results-container" class="results-container" style="display: none;">
71
  <div class="result-column"><div class="column-header"><h2>GROK</h2><button class="copy-btn" onclick="copyToClipboard('grok-output')">Copiar</button></div><div class="output-box" id="grok-output"></div></div>
72
  <div class="result-column"><div class="column-header"><h2>Claude Sonnet</h2><button class="copy-btn" onclick="copyToClipboard('sonnet-output')">Copiar</button></div><div class="output-box" id="sonnet-output"></div></div>
73
  <div class="result-column"><div class="column-header"><h2>Gemini</h2><button class="copy-btn" onclick="copyToClipboard('gemini-output')">Copiar</button></div><div class="output-box" id="gemini-output"></div></div>
74
  </div>
75
 
76
+ <!-- Novo Container para Resultado Final do Merge -->
77
  <div id="final-result-container" style="display: none;">
78
  <h2>Texto Final</h2>
79
  <div class="output-box" id="final-output"></div>
 
81
  </div>
82
 
83
  <script>
84
+ // --- Variáveis Globais e Lógica de UI ---
85
  const processingModeSwitch = document.getElementById('processing-mode-switch');
86
  const modeSwitch = document.getElementById('mode-switch');
87
  const realContainer = document.getElementById('real-form-container');
 
97
  const finalResultContainer = document.getElementById('final-result-container');
98
  const finalOutput = document.getElementById('final-output');
99
  let attachedFiles = [];
100
+ let originalUserQuery = "";
101
 
 
102
  modeSwitch.addEventListener('change', function() {
103
  realContainer.style.display = this.checked ? 'none' : 'block';
104
  mockContainer.style.display = this.checked ? 'block' : 'none';
105
  });
 
106
  processingModeSwitch.addEventListener('change', function() {
107
+ document.getElementById('flow-description').textContent = this.checked ? "GROK | Claude Sonnet | Gemini (Paralelo)" : "GROK ➔ Claude Sonnet ➔ Gemini";
 
108
  });
109
 
110
+ // --- Lógica de Upload ---
111
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
112
  textarea.addEventListener(eventName, preventDefaults, false);
113
  document.body.addEventListener(eventName, preventDefaults, false);
114
  });
115
+ ['dragenter', 'dragover'].forEach(eventName => textarea.addEventListener(eventName, () => textarea.classList.add('drag-over'), false));
116
+ ['dragleave', 'drop'].forEach(eventName => textarea.addEventListener(eventName, () => textarea.classList.remove('drag-over'), false));
 
 
 
 
117
  textarea.addEventListener('drop', handleDrop, false);
118
  function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); }
119
  function handleDrop(e) { handleFiles(e.dataTransfer.files); }
 
147
 
148
  async function handleFormSubmit(event) {
149
  event.preventDefault();
 
 
150
  errorContainer.innerHTML = '';
151
  resultsContainer.style.display = 'none';
152
  finalResultContainer.style.display = 'none';
153
  mergeBtn.style.display = 'none';
154
  document.querySelectorAll('.output-box').forEach(box => box.innerHTML = '');
 
 
155
  loaderMessage.textContent = 'Iniciando conexão...';
156
  progressBar.style.width = '0%';
157
  loader.style.display = 'flex';
 
158
  const formData = new FormData();
159
  formData.append('processing_mode', processingModeSwitch.checked ? 'atomic' : 'hierarchical');
 
160
  if (modeSwitch.checked) {
161
  formData.append('mode', 'test');
162
  formData.append('mock_text', document.getElementById('mock_text').value);
 
167
  formData.append('solicitacao', originalUserQuery);
168
  attachedFiles.forEach(file => { formData.append('files', file); });
169
  }
 
 
 
170
  try {
171
  const response = await fetch('/process', { method: 'POST', body: formData });
172
  if (!response.ok || !response.body) throw new Error(`Erro na resposta do servidor: ${response.statusText}`);
 
183
  if (jsonData.trim()) {
184
  try {
185
  const data = JSON.parse(jsonData);
186
+ processStreamData(data, false);
187
  } catch (e) { console.error("Erro ao parsear JSON:", jsonData); }
188
  }
189
  }
 
196
  }
197
  }
198
 
199
+ // --- Lógica do Botão de Merge com Streaming ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  mergeBtn.addEventListener('click', async function() {
201
  loaderMessage.textContent = 'Processando o merge dos textos...';
202
  progressBar.style.width = '0%';
203
  loader.style.display = 'flex';
204
+ this.style.display = 'none';
205
 
206
  const payload = {
207
  solicitacao_usuario: originalUserQuery,
 
216
  headers: { 'Content-Type': 'application/json' },
217
  body: JSON.stringify(payload)
218
  });
219
+ if (!response.ok || !response.body) throw new Error(`Erro na resposta do servidor: ${response.statusText}`);
220
 
221
+ const reader = response.body.getReader();
222
+ const decoder = new TextDecoder();
223
+ while (true) {
224
+ const { done, value } = await reader.read();
225
+ if (done) break;
226
+ const chunk = decoder.decode(value, { stream: true });
227
+ const lines = chunk.split('\n\n');
228
+ lines.forEach(line => {
229
+ if (line.startsWith('data: ')) {
230
+ const jsonData = line.substring(6);
231
+ if (jsonData.trim()) {
232
+ try {
233
+ const data = JSON.parse(jsonData);
234
+ processStreamData(data, true); // true = é merge
235
+ } catch (e) { console.error("Erro ao parsear JSON do merge:", jsonData); }
236
+ }
237
+ }
238
+ });
239
  }
240
  } catch (error) {
241
  showError("A conexão falhou ao tentar processar o merge.");
 
242
  loader.style.display = 'none';
243
  }
244
  });
245
 
246
+ // --- Função de Processamento de Stream Unificada ---
247
+ function processStreamData(data, isMerge) {
248
+ if (data.error) {
249
+ showError(data.error);
250
+ loader.style.display = 'none';
251
+ return;
252
+ }
253
+
254
+ loaderMessage.textContent = data.message;
255
+ progressBar.style.width = data.progress + '%';
256
+
257
+ if (isMerge && data.final_result) {
258
+ finalOutput.innerHTML = data.final_result.content;
259
+ finalResultContainer.style.display = 'block';
260
+ } else if (data.partial_result) {
261
+ resultsContainer.style.display = 'flex';
262
+ const targetBox = document.getElementById(data.partial_result.id);
263
+ if (targetBox) targetBox.innerHTML = data.partial_result.content;
264
+ }
265
+
266
+ if (data.done) {
267
+ setTimeout(() => {
268
+ loader.style.display = 'none';
269
+ if (data.mode === 'atomic' && !isMerge) {
270
+ mergeBtn.style.display = 'block';
271
+ }
272
+ }, 1000);
273
+ }
274
+ }
275
+
276
  function showError(message) {
277
  errorContainer.innerHTML = `<div class="error-box"><strong>Erro:</strong> ${message}<span class="close-btn-error" onclick="this.parentElement.style.display='none';" title="Fechar">&times;</span></div>`;
278
  }