victorafarias commited on
Commit
17910ab
·
1 Parent(s): 5d14b13

Correçõe na conversão posterior do texto para md

Browse files
Files changed (1) hide show
  1. app.py +196 -141
app.py CHANGED
@@ -11,9 +11,12 @@ from html import escape, unescape
11
  import re
12
  from markdown_it import MarkdownIt
13
  from markdown2 import markdown as markdown2_render
14
- import traceback
15
  import sys
16
 
 
 
 
 
17
  # Importações do LangChain
18
  from langchain.prompts import PromptTemplate
19
  from langchain_core.output_parsers import StrOutputParser
@@ -38,6 +41,11 @@ app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
38
  # Instancia o conversor de Markdown
39
  md = MarkdownIt()
40
 
 
 
 
 
 
41
  # Função para renderização com fallback: tenta MarkdownIt, depois markdown2
42
  def render_markdown_cascata(texto: str) -> str:
43
  try:
@@ -45,14 +53,14 @@ def render_markdown_cascata(texto: str) -> str:
45
  if not is_html_empty(html_1):
46
  return html_1
47
  except Exception as e:
48
- print(f"MarkdownIt falhou: {e}")
49
 
50
  try:
51
  html_2 = markdown2_render(texto, extras=["fenced-code-blocks", "tables"])
52
  if not is_html_empty(html_2):
53
  return html_2
54
  except Exception as e:
55
- print(f"markdown2 falhou: {e}")
56
 
57
  return f"<pre>{escape(texto)}</pre>"
58
 
@@ -75,13 +83,11 @@ def is_html_empty(html: str) -> bool:
75
  @app.route('/')
76
  def index():
77
  """Renderiza a página inicial da aplicação."""
78
- print("=== ROTA INDEX ACESSADA ===")
79
  return render_template('index.html')
80
 
81
  # ROTA ATUALIZADA: Para converter texto em Markdown sob demanda
82
  @app.route('/convert', methods=['POST'])
83
  def convert():
84
- print("=== ROTA CONVERT ACESSADA ===")
85
  data = request.get_json()
86
  if not data or 'text' not in data:
87
  return jsonify({'error': 'Nenhum texto fornecido'}), 400
@@ -94,168 +100,216 @@ def convert():
94
  @app.route('/process', methods=['POST'])
95
  def process():
96
  """Processa a solicitação do usuário nos modos Hierárquico ou Atômico."""
97
- print("=== ROTA PROCESS ACESSADA ===")
98
- print(f"Method: {request.method}")
99
- print(f"Content-Type: {request.content_type}")
100
 
101
- try:
102
- form_data = request.form
103
- print(f"Form data keys: {list(form_data.keys())}")
104
- print(f"Form data: {dict(form_data)}")
105
-
106
- files = request.files.getlist('files')
107
- print(f"Files: {len(files)} arquivo(s)")
108
-
109
- mode = form_data.get('mode', 'real')
110
- processing_mode = form_data.get('processing_mode', 'hierarchical')
111
-
112
- print(f"Mode: {mode}")
113
- print(f"Processing mode: {processing_mode}")
 
 
 
 
 
 
114
 
115
- temp_file_paths = []
116
- if mode == 'real':
117
- for file in files:
118
- if file and file.filename:
119
- print(f"Processando arquivo: {file.filename}")
120
- unique_filename = str(uuid.uuid4()) + "_" + os.path.basename(file.filename)
121
- file_path = os.path.join('uploads', unique_filename)
122
- file.save(file_path)
123
- temp_file_paths.append(file_path)
124
- print(f"Arquivo salvo em: {file_path}")
125
-
126
- print("=== INICIANDO GENERATOR STREAM ===")
127
 
128
- def generate_stream(current_mode, form_data, file_paths):
129
- """Gera a resposta em streaming para o front-end."""
130
- print(f"=== DENTRO DO GENERATE_STREAM - Mode: {current_mode} ===")
131
-
 
 
 
 
 
 
 
 
 
132
  try:
133
- solicitacao_usuario = form_data.get('solicitacao', '')
134
- print(f"Solicitação do usuário: {solicitacao_usuario[:100]}...")
 
 
135
 
136
- if current_mode == 'test':
137
- print("=== MODO TESTE ===")
138
- mock_text = form_data.get('mock_text', 'Este é um **texto** de `simulação`.')
139
- yield f"data: {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'})}\n\n"
140
- if processing_mode == 'atomic':
141
- yield f"data: {json.dumps({'partial_result': {'id': 'sonnet-output', 'content': mock_text}})}\n\n"
142
- yield f"data: {json.dumps({'partial_result': {'id': 'gemini-output', 'content': mock_text}})}\n\n"
143
- else:
144
- print("=== MODO REAL ===")
145
- if not solicitacao_usuario:
146
- print("=== ERRO: SOLICITAÇÃO VAZIA ===")
147
- yield f"data: {json.dumps({'error': 'Solicitação não fornecida.'})}\n\n"
148
- return
149
 
150
- print("=== PROCESSANDO ARQUIVOS E CONTEXTO ===")
151
- yield f"data: {json.dumps({'progress': 0, 'message': 'Processando arquivos e extraindo contexto...'})}\n\n"
 
 
 
152
 
153
- rag_context = get_relevant_context(file_paths, solicitacao_usuario)
154
- print(f"RAG Context obtido: {len(rag_context)} caracteres")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
- output_parser = StrOutputParser()
 
 
 
 
157
 
158
- if processing_mode == 'atomic':
159
- print("=== MODO ATÔMICO ===")
160
- # Lógica atômica permanece a mesma...
161
- pass
162
 
163
- else:
164
- print("=== MODO HIERÁRQUICO INICIADO ===")
165
-
166
- # GROK
167
- print("=== PROCESSANDO GROK ===")
168
- yield f"data: {json.dumps({'progress': 15, 'message': 'O GROK está processando sua solicitação...'})}\n\n"
169
-
170
- prompt_grok = PromptTemplate(template=PROMPT_HIERARQUICO_GROK, input_variables=["solicitacao_usuario", "rag_context"])
171
- chain_grok = prompt_grok | grok_llm | output_parser
172
-
173
- print("=== INVOCANDO GROK ===")
174
- resposta_grok = chain_grok.invoke({"solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context})
175
- print(f"=== GROK RESPONDEU: {len(resposta_grok)} caracteres ===")
176
-
177
- if not resposta_grok or not resposta_grok.strip():
178
- print("=== ERRO: GROK SEM RESPOSTA ===")
179
- yield f"data: {json.dumps({'error': 'Falha no serviço GROK: Sem resposta.'})}\n\n"
180
  return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
 
182
- print("=== ENVIANDO RESPOSTA DO GROK ===")
183
- yield f"data: {json.dumps({'progress': 33, 'message': 'Claude Sonnet está processando...', 'partial_result': {'id': 'grok-output', 'content': resposta_grok}})}\n\n"
184
-
185
- # SONNET
186
- print("=== PROCESSANDO SONNET ===")
187
- prompt_sonnet = PromptTemplate(template=PROMPT_HIERARQUICO_SONNET, input_variables=["solicitacao_usuario", "texto_para_analise"])
188
- claude_with_max_tokens = claude_llm.bind(max_tokens=20000)
189
- chain_sonnet = prompt_sonnet | claude_with_max_tokens | output_parser
190
 
191
- print("=== INVOCANDO SONNET ===")
192
- resposta_sonnet = chain_sonnet.invoke({"solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_grok})
193
- print(f"=== SONNET RESPONDEU: {len(resposta_sonnet)} caracteres ===")
 
194
 
195
- if not resposta_sonnet or not resposta_sonnet.strip():
196
- print("=== ERRO: SONNET SEM RESPOSTA ===")
197
- yield f"data: {json.dumps({'error': 'Falha no serviço Claude Sonnet: Sem resposta.'})}\n\n"
198
- return
199
-
200
- print("=== ENVIANDO RESPOSTA DO SONNET ===")
201
  try:
202
- sonnet_response = {
 
 
203
  'progress': 66,
204
  'message': 'Gemini está processando...',
205
  'partial_result': {
206
  'id': 'sonnet-output',
207
- 'content': resposta_sonnet
208
  }
209
  }
210
- yield f"data: {json.dumps(sonnet_response)}\n\n"
211
- print("=== SONNET JSON ENVIADO COM SUCESSO ===")
212
- except Exception as json_error:
213
- print(f"=== ERRO NO JSON DO SONNET: {json_error} ===")
214
- # Fallback
215
- fallback_response = {
216
- 'progress': 66,
217
- 'message': 'Gemini está processando...',
218
- 'partial_result': {
219
- 'id': 'sonnet-output',
220
- 'content': f"ERRO DE ENCODING: {str(json_error)}"
221
- }
222
- }
223
- yield f"data: {json.dumps(fallback_response)}\n\n"
224
-
225
- # GEMINI
226
- print("=== PROCESSANDO GEMINI ===")
227
- prompt_gemini = PromptTemplate(template=PROMPT_HIERARQUICO_GEMINI, input_variables=["solicitacao_usuario", "texto_para_analise"])
228
- chain_gemini = prompt_gemini | gemini_llm | output_parser
229
-
230
- print("=== INVOCANDO GEMINI ===")
231
- resposta_gemini = chain_gemini.invoke({"solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_sonnet})
232
- print(f"=== GEMINI RESPONDEU: {len(resposta_gemini)} caracteres ===")
233
-
234
- if not resposta_gemini or not resposta_gemini.strip():
235
- print("=== ERRO: GEMINI SEM RESPOSTA ===")
236
- yield f"data: {json.dumps({'error': 'Falha no serviço Gemini: Sem resposta.'})}\n\n"
237
  return
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
- print("=== ENVIANDO RESPOSTA DO GEMINI ===")
240
- yield f"data: {json.dumps({'progress': 100, 'message': 'Processamento concluído!', 'partial_result': {'id': 'gemini-output', 'content': resposta_gemini}, 'done': True, 'mode': 'hierarchical'})}\n\n"
241
- print("=== PROCESSAMENTO HIERÁRQUICO CONCLUÍDO ===")
242
 
243
  except Exception as e:
244
- print(f"=== ERRO GERAL NO GENERATE_STREAM: {e} ===")
245
- print(f"=== TRACEBACK: {traceback.format_exc()} ===")
246
- yield f"data: {json.dumps({'error': f'Erro inesperado: {str(e)}'})}\n\n"
 
247
 
248
- return Response(generate_stream(mode, form_data, temp_file_paths), mimetype='text/event-stream')
249
-
250
- except Exception as e:
251
- print(f"=== ERRO NA ROTA PROCESS: {e} ===")
252
- print(f"=== TRACEBACK: {traceback.format_exc()} ===")
253
- return Response(f"data: {json.dumps({'error': f'Erro na rota process: {str(e)}'})}\n\n", mimetype='text/event-stream')
254
 
255
  @app.route('/merge', methods=['POST'])
256
  def merge():
257
  """Recebe os textos do modo Atômico e os consolida usando um LLM."""
258
- print("=== ROTA MERGE ACESSADA ===")
259
  data = request.get_json()
260
 
261
  def generate_merge_stream():
@@ -282,17 +336,18 @@ def merge():
282
  yield f"data: {json.dumps({'error': 'Falha no serviço de Merge (GROK): Sem resposta.'})}\n\n"
283
  return
284
 
285
- print(f"--- Resposta Bruta do Merge (GROK) ---\n{resposta_merge}\n------------------------------------")
286
  word_count = len(resposta_merge.split())
287
 
 
288
  yield f"data: {json.dumps({'progress': 100, 'message': 'Merge concluído!', 'final_result': {'content': resposta_merge, 'word_count': word_count}, 'done': True})}\n\n"
289
 
290
  except Exception as e:
291
- print(f"Erro no processo de merge: {e}")
292
  yield f"data: {json.dumps({'error': str(e)})}\n\n"
293
 
294
  return Response(generate_merge_stream(), mimetype='text/event-stream')
295
 
296
  if __name__ == '__main__':
297
- print("=== INICIANDO SERVIDOR FLASK ===")
298
- app.run(debug=True)
 
11
  import re
12
  from markdown_it import MarkdownIt
13
  from markdown2 import markdown as markdown2_render
 
14
  import sys
15
 
16
+ # Força o flush dos prints para aparecer nos logs do container
17
+ sys.stdout.reconfigure(line_buffering=True)
18
+ sys.stderr.reconfigure(line_buffering=True)
19
+
20
  # Importações do LangChain
21
  from langchain.prompts import PromptTemplate
22
  from langchain_core.output_parsers import StrOutputParser
 
41
  # Instancia o conversor de Markdown
42
  md = MarkdownIt()
43
 
44
+ def log_print(message):
45
+ """Função para garantir que os logs apareçam no container"""
46
+ print(f"[DEBUG] {message}", flush=True)
47
+ sys.stdout.flush()
48
+
49
  # Função para renderização com fallback: tenta MarkdownIt, depois markdown2
50
  def render_markdown_cascata(texto: str) -> str:
51
  try:
 
53
  if not is_html_empty(html_1):
54
  return html_1
55
  except Exception as e:
56
+ log_print(f"MarkdownIt falhou: {e}")
57
 
58
  try:
59
  html_2 = markdown2_render(texto, extras=["fenced-code-blocks", "tables"])
60
  if not is_html_empty(html_2):
61
  return html_2
62
  except Exception as e:
63
+ log_print(f"markdown2 falhou: {e}")
64
 
65
  return f"<pre>{escape(texto)}</pre>"
66
 
 
83
  @app.route('/')
84
  def index():
85
  """Renderiza a página inicial da aplicação."""
 
86
  return render_template('index.html')
87
 
88
  # ROTA ATUALIZADA: Para converter texto em Markdown sob demanda
89
  @app.route('/convert', methods=['POST'])
90
  def convert():
 
91
  data = request.get_json()
92
  if not data or 'text' not in data:
93
  return jsonify({'error': 'Nenhum texto fornecido'}), 400
 
100
  @app.route('/process', methods=['POST'])
101
  def process():
102
  """Processa a solicitação do usuário nos modos Hierárquico ou Atômico."""
103
+ log_print("=== ROTA PROCESS ACESSADA ===")
 
 
104
 
105
+ form_data = request.form
106
+ files = request.files.getlist('files')
107
+ mode = form_data.get('mode', 'real')
108
+ processing_mode = form_data.get('processing_mode', 'hierarchical')
109
+
110
+ log_print(f"Mode: {mode}, Processing: {processing_mode}")
111
+
112
+ temp_file_paths = []
113
+ if mode == 'real':
114
+ for file in files:
115
+ if file and file.filename:
116
+ unique_filename = str(uuid.uuid4()) + "_" + os.path.basename(file.filename)
117
+ file_path = os.path.join('uploads', unique_filename)
118
+ file.save(file_path)
119
+ temp_file_paths.append(file_path)
120
+
121
+ def generate_stream(current_mode, form_data, file_paths):
122
+ """Gera a resposta em streaming para o front-end."""
123
+ log_print(f"=== GENERATE_STREAM INICIADO - Mode: {current_mode} ===")
124
 
125
+ solicitacao_usuario = form_data.get('solicitacao', '')
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ if current_mode == 'test':
128
+ log_print("=== MODO TESTE EXECUTADO ===")
129
+ mock_text = form_data.get('mock_text', 'Este é um **texto** de `simulação`.')
130
+ yield f"data: {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'})}\n\n"
131
+ if processing_mode == 'atomic':
132
+ yield f"data: {json.dumps({'partial_result': {'id': 'sonnet-output', 'content': mock_text}})}\n\n"
133
+ yield f"data: {json.dumps({'partial_result': {'id': 'gemini-output', 'content': mock_text}})}\n\n"
134
+ else:
135
+ if not solicitacao_usuario:
136
+ log_print("=== ERRO: SOLICITAÇÃO VAZIA ===")
137
+ yield f"data: {json.dumps({'error': 'Solicitação não fornecida.'})}\n\n"
138
+ return
139
+
140
  try:
141
+ log_print("=== INICIANDO PROCESSAMENTO REAL ===")
142
+ yield f"data: {json.dumps({'progress': 0, 'message': 'Processando arquivos e extraindo contexto...'})}\n\n"
143
+ rag_context = get_relevant_context(file_paths, solicitacao_usuario)
144
+ log_print(f"=== RAG CONTEXT OBTIDO: {len(rag_context)} chars ===")
145
 
146
+ output_parser = StrOutputParser()
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
+ if processing_mode == 'atomic':
149
+ log_print("=== MODO ATÔMICO SELECIONADO ===")
150
+ # --- LÓGICA ATÔMICA (PARALELA) ---
151
+ results = {}
152
+ threads = []
153
 
154
+ def run_chain_with_timeout(chain, inputs, key, timeout=300):
155
+ def task():
156
+ return chain.invoke(inputs)
157
+
158
+ with concurrent.futures.ThreadPoolExecutor() as executor:
159
+ future = executor.submit(task)
160
+ try:
161
+ result = future.result(timeout=timeout)
162
+ if not result or not result.strip():
163
+ results[key] = "Error:EmptyResponse"
164
+ else:
165
+ results[key] = result
166
+ except concurrent.futures.TimeoutError:
167
+ results[key] = f"Erro ao processar {key.upper()}: Tempo limite excedido."
168
+ except Exception as e:
169
+ results[key] = f"Erro ao processar {key.upper()}: {e}"
170
+
171
+ claude_atomic_llm = claude_llm.bind(max_tokens=20000)
172
+ models = {'grok': grok_llm, 'sonnet': claude_atomic_llm, 'gemini': gemini_llm}
173
+
174
+ prompt = PromptTemplate(template=PROMPT_ATOMICO_INICIAL, input_variables=["solicitacao_usuario", "rag_context"])
175
+ yield f"data: {json.dumps({'progress': 15, 'message': 'Iniciando processamento paralelo...'})}\n\n"
176
 
177
+ for name, llm in models.items():
178
+ chain = prompt | llm | output_parser
179
+ thread = threading.Thread(target=run_chain_with_timeout, args=(chain, {"solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context}, name))
180
+ threads.append(thread)
181
+ thread.start()
182
 
183
+ for thread in threads:
184
+ thread.join()
 
 
185
 
186
+ for key, result in results.items():
187
+ if result == "Error:EmptyResponse" or "Erro ao processar" in result:
188
+ error_msg = result if "Erro ao processar" in result else f"Falha no serviço {key.upper()}: Sem resposta."
189
+ yield f"data: {json.dumps({'error': error_msg})}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  return
191
+
192
+ yield f"data: {json.dumps({'progress': 80, 'message': 'Todos os modelos responderam. Formatando saídas...'})}\n\n"
193
+
194
+ # MUDANÇA: Envia o texto bruto para cada modelo
195
+ grok_text = results.get('grok', '')
196
+ log_print(f"--- Resposta Bruta do GROK (Atômico) ---\n{grok_text[:200]}...\n--------------------------------------")
197
+ yield f"data: {json.dumps({'partial_result': {'id': 'grok-output', 'content': grok_text}})}\n\n"
198
+
199
+ sonnet_text = results.get('sonnet', '')
200
+ log_print(f"--- Resposta Bruta do Sonnet (Atômico) ---\n{sonnet_text[:200]}...\n----------------------------------------")
201
+ yield f"data: {json.dumps({'partial_result': {'id': 'sonnet-output', 'content': sonnet_text}})}\n\n"
202
+
203
+ gemini_text = results.get('gemini', '')
204
+ log_print(f"--- Resposta Bruta do Gemini (Atômico) ---\n{gemini_text[:200]}...\n----------------------------------------")
205
+ yield f"data: {json.dumps({'partial_result': {'id': 'gemini-output', 'content': gemini_text}})}\n\n"
206
+
207
+ yield f"data: {json.dumps({'progress': 100, 'message': 'Processamento Atômico concluído!', 'done': True, 'mode': 'atomic'})}\n\n"
208
+
209
+ else:
210
+ log_print("=== MODO HIERÁRQUICO SELECIONADO ===")
211
+ # --- LÓGICA HIERÁRQUICA (SEQUENCIAL) ---
212
+ yield f"data: {json.dumps({'progress': 15, 'message': 'O GROK está processando sua solicitação...'})}\n\n"
213
+
214
+ log_print("=== PROCESSANDO GROK ===")
215
+ prompt_grok = PromptTemplate(template=PROMPT_HIERARQUICO_GROK, input_variables=["solicitacao_usuario", "rag_context"])
216
+ chain_grok = prompt_grok | grok_llm | output_parser
217
+ resposta_grok = chain_grok.invoke({"solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context})
218
+
219
+ log_print(f"=== GROK TERMINOU: {len(resposta_grok)} chars ===")
220
+
221
+ if not resposta_grok or not resposta_grok.strip():
222
+ log_print("=== ERRO: GROK VAZIO ===")
223
+ yield f"data: {json.dumps({'error': 'Falha no serviço GROK: Sem resposta.'})}\n\n"
224
+ return
225
+
226
+ log_print("=== ENVIANDO RESPOSTA GROK PARA FRONTEND ===")
227
+ yield f"data: {json.dumps({'progress': 33, 'message': 'Claude Sonnet está processando...', 'partial_result': {'id': 'grok-output', 'content': resposta_grok}})}\n\n"
228
+
229
+ log_print("=== PROCESSANDO SONNET ===")
230
+ prompt_sonnet = PromptTemplate(template=PROMPT_HIERARQUICO_SONNET, input_variables=["solicitacao_usuario", "texto_para_analise"])
231
+ claude_with_max_tokens = claude_llm.bind(max_tokens=20000)
232
+ chain_sonnet = prompt_sonnet | claude_with_max_tokens | output_parser
233
+ resposta_sonnet = chain_sonnet.invoke({"solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_grok})
234
+
235
+ log_print(f"=== SONNET TERMINOU: {len(resposta_sonnet)} chars ===")
236
+
237
+ if not resposta_sonnet or not resposta_sonnet.strip():
238
+ log_print("=== ERRO: SONNET VAZIO ===")
239
+ yield f"data: {json.dumps({'error': 'Falha no serviço Claude Sonnet: Sem resposta.'})}\n\n"
240
+ return
241
+
242
+ log_print("=== TENTANDO ENVIAR RESPOSTA SONNET ===")
243
+
244
+ # Aqui vamos testar se o problema é no JSON
245
+ try:
246
+ # Primeiro, vamos testar criar o JSON sem enviar
247
+ test_data = {
248
+ 'progress': 66,
249
+ 'message': 'Gemini está processando...',
250
+ 'partial_result': {
251
+ 'id': 'sonnet-output',
252
+ 'content': resposta_sonnet
253
+ }
254
+ }
255
+ test_json = json.dumps(test_data)
256
+ log_print(f"=== JSON SONNET CRIADO COM SUCESSO: {len(test_json)} chars ===")
257
 
258
+ # Agora vamos enviar
259
+ yield f"data: {test_json}\n\n"
260
+ log_print("=== JSON SONNET ENVIADO COM SUCESSO ===")
 
 
 
 
 
261
 
262
+ except Exception as json_error:
263
+ log_print(f"=== ERRO JSON SONNET: {json_error} ===")
264
+ log_print(f"=== TIPO DA RESPOSTA SONNET: {type(resposta_sonnet)} ===")
265
+ log_print(f"=== PRIMEIROS 500 CHARS SONNET: {repr(resposta_sonnet[:500])} ===")
266
 
267
+ # Vamos tentar limpar a string
 
 
 
 
 
268
  try:
269
+ # Remove caracteres problemáticos
270
+ cleaned_response = resposta_sonnet.encode('utf-8', errors='ignore').decode('utf-8')
271
+ fallback_data = {
272
  'progress': 66,
273
  'message': 'Gemini está processando...',
274
  'partial_result': {
275
  'id': 'sonnet-output',
276
+ 'content': cleaned_response
277
  }
278
  }
279
+ yield f"data: {json.dumps(fallback_data)}\n\n"
280
+ log_print("=== FALLBACK SONNET ENVIADO ===")
281
+ except Exception as fallback_error:
282
+ log_print(f"=== FALLBACK TAMBÉM FALHOU: {fallback_error} ===")
283
+ yield f"data: {json.dumps({'error': f'Erro ao processar resposta do Sonnet: {str(json_error)}'})}\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  return
285
+
286
+ log_print("=== PROCESSANDO GEMINI ===")
287
+ prompt_gemini = PromptTemplate(template=PROMPT_HIERARQUICO_GEMINI, input_variables=["solicitacao_usuario", "texto_para_analise"])
288
+ chain_gemini = prompt_gemini | gemini_llm | output_parser
289
+ resposta_gemini = chain_gemini.invoke({"solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_sonnet})
290
+
291
+ log_print(f"=== GEMINI TERMINOU: {len(resposta_gemini)} chars ===")
292
+
293
+ if not resposta_gemini or not resposta_gemini.strip():
294
+ log_print("=== ERRO: GEMINI VAZIO ===")
295
+ yield f"data: {json.dumps({'error': 'Falha no serviço Gemini: Sem resposta.'})}\n\n"
296
+ return
297
 
298
+ log_print("=== ENVIANDO RESPOSTA GEMINI ===")
299
+ yield f"data: {json.dumps({'progress': 100, 'message': 'Processamento concluído!', 'partial_result': {'id': 'gemini-output', 'content': resposta_gemini}, 'done': True, 'mode': 'hierarchical'})}\n\n"
300
+ log_print("=== PROCESSAMENTO COMPLETO ===")
301
 
302
  except Exception as e:
303
+ log_print(f"Ocorreu um erro durante o processamento: {e}")
304
+ import traceback
305
+ log_print(f"Traceback: {traceback.format_exc()}")
306
+ yield f"data: {json.dumps({'error': f'Ocorreu um erro inesperado na aplicação: {e}'})}\n\n"
307
 
308
+ return Response(generate_stream(mode, form_data, temp_file_paths), mimetype='text/event-stream')
 
 
 
 
 
309
 
310
  @app.route('/merge', methods=['POST'])
311
  def merge():
312
  """Recebe os textos do modo Atômico e os consolida usando um LLM."""
 
313
  data = request.get_json()
314
 
315
  def generate_merge_stream():
 
336
  yield f"data: {json.dumps({'error': 'Falha no serviço de Merge (GROK): Sem resposta.'})}\n\n"
337
  return
338
 
339
+ log_print(f"--- Resposta Bruta do Merge (GROK) ---\n{resposta_merge}\n------------------------------------")
340
  word_count = len(resposta_merge.split())
341
 
342
+ # MUDANÇA: Envia o texto bruto do merge em vez de HTML
343
  yield f"data: {json.dumps({'progress': 100, 'message': 'Merge concluído!', 'final_result': {'content': resposta_merge, 'word_count': word_count}, 'done': True})}\n\n"
344
 
345
  except Exception as e:
346
+ log_print(f"Erro no processo de merge: {e}")
347
  yield f"data: {json.dumps({'error': str(e)})}\n\n"
348
 
349
  return Response(generate_merge_stream(), mimetype='text/event-stream')
350
 
351
  if __name__ == '__main__':
352
+ log_print("=== SERVIDOR FLASK INICIADO ===")
353
+ app.run(debug=True)