GabrielGD commited on
Commit
bf9a754
·
verified ·
1 Parent(s): b464436

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +528 -150
app.py CHANGED
@@ -24,30 +24,24 @@ from html import escape
24
  import random
25
  import logging
26
  import traceback
27
- import re # Importado para regex
28
 
29
- # Configuração do Logging
30
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s')
31
  logger = logging.getLogger(__name__)
32
 
33
- # Carregar Variáveis de Ambiente
34
  load_dotenv()
35
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or os.environ.get("GEMINI_API_KEY")
36
  if not GEMINI_API_KEY:
37
  raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a no arquivo .env ou como Secret no Hugging Face.")
38
 
39
- # API URL (Ajuste conforme sua chave/acesso)
40
  GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}"
41
  logger.info(f"Usando API URL: ...{GEMINI_API_URL[-50:]}")
42
 
43
- # Configurações Globais
44
  CONFIG = {"max_retry_attempts": 3, "retry_delay": 5, "timeout": 120, "temperatura": 0.3, "max_tokens": 8192}
45
  rate_limiter = RateLimiter(max_calls=20, period=60)
46
 
47
- # Tipos de Petição
48
  TIPOS_DE_PETICAO = ["Petição Inicial", "Contestação", "Reconvenção", "Impugnação à Contestação", "Embargos de Declaração", "Recurso de Apelação", "Agravo de Instrumento", "Petição de Tutela Provisória"]
49
 
50
- # Dicas (Amostra)
51
  DICAS_OAB_MELHORADO = {
52
  "Clareza e Concisão": "Seja direto e evite jargões excessivos.",
53
  "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes.",
@@ -56,14 +50,11 @@ DICAS_OAB_MELHORADO = {
56
  "Pedidos (Inicial)": "Certos, determinados e líquidos (se possível).",
57
  "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes do mérito.",
58
  "Impugnação Específica (Contestação)": "Conteste cada fato alegado (Art. 341, CPC)."
59
- # Adicionar mais dicas...
60
  }
61
 
62
- # Globais para Histórico e Cache
63
  HISTORICO = []
64
  CORRECAO_CACHE = {}
65
 
66
- # --- Funções de Extração de Texto (sem alterações) ---
67
  def preprocess_image_for_ocr(img_pil):
68
  if not CV2_AVAILABLE:
69
  logger.warning("OpenCV não disponível, usando conversão simples para OCR.")
@@ -190,10 +181,7 @@ def process_file(file_obj):
190
  logger.error(f"Erro GERAL ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True)
191
  return f"❌ Erro inesperado ao processar o arquivo: {str(e)}"
192
 
193
-
194
- # --- Estruturação de Prompts (sem alterações) ---
195
  def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
196
- # (Mantido como na versão anterior)
197
  base = """
198
  **Estrutura da Avaliação:**
199
  1. **Análise Geral:** Visão geral concisa (2-3 linhas) sobre a adequação da peça ao caso e tipo, consistência e clareza.
@@ -229,7 +217,6 @@ def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
229
  return base
230
 
231
  def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
232
- # (Mantido como na versão anterior)
233
  instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento)
234
  prompt_especifico = f"""
235
  ## Análise de Peça Processual: {tipo_peticao} (Critérios OAB Simulados)
@@ -244,16 +231,118 @@ def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=Fals
244
  {instrucoes_gerais}
245
  **Requisitos Específicos para {tipo_peticao}:**
246
  """
247
- # Adiciona requisitos específicos por tipo (mantidos)
248
- if tipo_peticao == "Petição Inicial": prompt_especifico += """...""" # Omitido para brevidade
249
- elif tipo_peticao == "Contestação": prompt_especifico += """...""" # Omitido para brevidade
250
- # ... (outros tipos) ...
251
- else: prompt_especifico += """...""" # Fallback genérico
252
- prompt_especifico += "\n\n**Instruções Finais:** Analise a peça do aluno estritamente com base no caso/contexto fornecido e nos critérios acima..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  return prompt_especifico
254
 
255
  def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
256
- # (Mantido como na versão anterior)
257
  prompt = f"""
258
  **Caso/Contexto Fornecido:**
259
  ```
@@ -263,24 +352,119 @@ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
263
  ```
264
  {peticao_original}
265
  ```
266
- **Tarefa:** Com base no caso e nos problemas identificados na peça original..., gere uma versão corrigida e aprimorada...
267
  **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):**
268
  """
269
- # Adiciona instruções específicas por tipo (mantidas)
270
- if tipo_peticao == "Petição Inicial": prompt += """...""" # Omitido para brevidade
271
- elif tipo_peticao == "Contestação": prompt += """...""" # Omitido para brevidade
272
- # ... (outros tipos) ...
273
- else: prompt += """...""" # Fallback genérico
274
- prompt += "\n\n**Instrução Final:** Gere **APENAS** o texto completo da peça corrigida abaixo..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  return prompt
276
 
277
  def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
278
- # (Mantido como na versão anterior)
279
  if "Nestes termos, pede deferimento" in peticao_corrigida:
280
  peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
281
  if tipo_peticao == "Petição Inicial":
282
  peticao_lower = peticao_corrigida.lower()
283
- # ... (lógica para adicionar custas e audiência mantida) ...
 
 
 
 
 
 
 
 
 
 
 
 
284
  linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
285
  fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."]
286
  while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]):
@@ -289,10 +473,7 @@ def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
289
  peticao_corrigida = "\n".join(linhas)
290
  return peticao_corrigida
291
 
292
-
293
- # --- Chamada à API (sem alterações) ---
294
  def chamar_api(prompt):
295
- # (Mantido como na versão anterior)
296
  payload = {"contents": [{"parts": [{"text": prompt}]}],"generationConfig": {"temperature": CONFIG["temperatura"],"maxOutputTokens": CONFIG["max_tokens"],"topP": 0.95,}}
297
  headers = {"Content-Type": "application/json"}
298
  logger.info(f"Chamando API Gemini. Temp={CONFIG['temperatura']}, MaxTokens={CONFIG['max_tokens']}")
@@ -301,76 +482,147 @@ def chamar_api(prompt):
301
  with rate_limiter:
302
  logger.debug(f"API Call - Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}")
303
  response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"])
304
- # ... (lógica de tratamento de resposta e erro mantida) ...
305
- if response.status_code == 200:
306
- response_data = response.json()
307
- # ... (verificações de candidates, finishReason, etc.) ...
308
- if response_data.get("candidates") and response_data["candidates"][0].get("content"):
309
- texto_gerado = response_data["candidates"][0]["content"]["parts"][0]["text"].strip()
310
- logger.info(f"API retornou com sucesso ({len(texto_gerado)} chars).")
311
- return texto_gerado
312
- else:
313
- # Tratar caso de resposta 200 mas sem conteúdo válido
314
- logger.error(f"API retornou 200 OK mas sem conteúdo válido: {response_data}")
315
- return "❌ Erro: Resposta da API recebida, mas sem conteúdo útil."
316
- # ... (outros tratamentos de erro: 429, != 200) ...
317
- # ... (tratamento de exceções: Timeout, RequestException, JSONDecodeError, Exception) ...
318
- except requests.Timeout: logger.warning(f"Timeout na API - Tentativa {tentativa + 1}.") # ... retry logic ...
319
- except requests.RequestException as e: logger.error(f"Erro de Rede/Requisição API - Tentativa {tentativa + 1}: {e}") # ... retry logic ...
320
- except json.JSONDecodeError as e: logger.error(f"Erro ao decodificar JSON da API: {e}") # ... return error ...
321
- except Exception as e: logger.exception(f"Erro inesperado na chamada da API - Tentativa {tentativa + 1}: {str(e)}") # ... retry logic ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  logger.error("Falha ao chamar a API após todas as tentativas.")
323
  return "❌ Falha ao comunicar com a API após múltiplas tentativas."
324
 
325
-
326
- # --- Função Principal de Correção (sem alterações na lógica interna) ---
327
  def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
328
- # (Mantida como na versão anterior, com try-except e retorno de 5 valores)
329
  logger.info(f"Iniciando correção. Tipo={tipo_peticao}, Treinamento={modo_treinamento}")
330
  start_time = time.time()
331
  analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html = "", "", "", "", ""
332
  peticao_conteudo = ""
333
  try:
334
- # ... (validações iniciais: caso, tipo_peticao) ...
335
- if not caso or not caso.strip(): return "⚠️ Forneça o caso...", "", "", "", ""
336
- if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione um tipo...", "", "", "", ""
337
-
338
- # ... (processa input: texto ou arquivo) ...
339
- if peticao_arquivo: peticao_conteudo = process_file(peticao_arquivo)
340
- elif peticao_texto: peticao_conteudo = peticao_texto
341
- else: return "⚠️ Forneça a petição...", "", "", "", ""
342
- if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"): return peticao_conteudo, "", "", "", ""
343
-
344
- # ... (anti-troll) ...
345
- troll_triggers = ["escreva a petição", "faça a petição", "redija a petição"]
346
- if any(t in peticao_conteudo.lower() for t in troll_triggers): return random.choice(["Hummm...", "Opa!"]), "", "", "", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
348
- # ... (cache check) ...
349
  cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}"
350
- if cache_key in CORRECAO_CACHE: # Retorna os 5 valores do cache
351
- cached = CORRECAO_CACHE[cache_key]
352
- return cached["analise_formatada"], cached["peticao_corrigida"], cached["resumo_diff"], cached["criticas_diff"], cached["diff_html"]
 
353
 
354
- # ... (chamada API para análise) ...
355
  prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento)
 
356
  analise_bruta = chamar_api(prompt_analise)
357
- if analise_bruta.startswith("❌") or analise_bruta.startswith("⏱️") or analise_bruta.startswith("⚠️"):
 
358
  return analise_bruta, "", "", "", ""
 
359
  analise_formatada = formatar_correcao(analise_bruta)
360
 
361
- # ... (chamada API para peça corrigida) ...
362
  prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao)
 
363
  peticao_corrigida_bruta = chamar_api(prompt_peticao)
364
- if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️") or peticao_corrigida_bruta.startswith("⚠️"):
 
 
365
  peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}"
366
  else:
367
- # ... (pós-correção) ...
368
- peticao_corrigida_final = pos_corrigir_peticao(peticao_corrigida_bruta, tipo_peticao)
369
-
370
- # ... (calcular diff) ...
 
 
 
 
 
 
 
 
371
  diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final)
 
372
 
373
- # ... (histórico e cache) ...
374
  resultado_cache = {"analise_formatada": analise_formatada,"peticao_corrigida": peticao_corrigida_final,"resumo_diff": resumo_diff,"criticas_diff": criticas_diff,"diff_html": diff_html}
375
  CORRECAO_CACHE[cache_key] = resultado_cache
376
  HISTORICO.append({"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"tipo_peticao": tipo_peticao,"caso": caso,"peticao_original": peticao_conteudo,**resultado_cache})
@@ -385,97 +637,241 @@ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=Non
385
  error_message = f"❌ Erro inesperado na função de correção: {str(e)}\nConsulte os logs para detalhes."
386
  return error_message, "", "", "", ""
387
 
388
-
389
- # --- Formatação e Dicas (sem alterações) ---
390
  def formatar_correcao(texto):
391
- # (Mantido como na versão anterior)
392
  texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
393
  texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
394
- # ... (outras formatações) ...
395
- return texto
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
  def fornecer_dica_contextual(texto_peticao, tipo_peticao):
398
- # (Mantido como na versão anterior)
399
- if not texto_peticao or not texto_peticao.strip(): return "*Digite algo na peça para receber dicas...*"
400
- # ... (lógica de dicas) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  return "\n\n".join(dicas_relevantes[:4])
402
 
403
-
404
- # --- Funções de Comparação (Diff) (sem alterações) ---
405
  def calcular_diff_interno(texto_original, texto_corrigido):
406
- # (Mantido como na versão anterior)
407
- if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")): return "","",""
408
- # ... (lógica do difflib) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  return visualizacao_detalhada_html, resumo_estatistico, criticas_texto
410
 
411
-
412
- # --- Funções de Progresso e Histórico (sem alterações) ---
413
  def calcular_progresso():
414
- # (Mantido como na versão anterior)
415
  if not HISTORICO: return "📊 Nenhum progresso registrado ainda."
416
- # ... (lógica de cálculo) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  return progresso_texto
418
 
419
  def mostrar_historico():
420
- # (Mantido como na versão anterior)
421
  if not HISTORICO: return "📜 Histórico vazio."
422
- # ... (lógica de formatação do histórico) ...
 
 
 
 
 
 
423
  return historico_md
424
 
425
-
426
- # --- Salvar/Carregar Progresso (sem alterações) ---
427
  def salvar_progresso(caso, peticao, tipo_peticao):
428
- # (Mantido como na versão anterior)
429
  logger.info("Iniciando salvamento de progresso...")
430
  try:
431
- # ... (lógica de salvar) ...
 
 
 
 
 
 
 
 
 
 
 
432
  return json_content, success_message, temp_file_path
433
- except Exception as e: # ... (tratamento de erro) ...
434
- return "", f"Erro ao salvar o progresso: {str(e)}", None
 
435
 
436
  def carregar_progresso(arquivo_entrada):
437
- # (Mantido como na versão anterior)
438
  logger.info("Iniciando carregamento de progresso...")
439
  if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo selecionado."
440
  try:
441
- # ... (lógica de carregar) ...
 
 
 
 
 
 
 
 
 
 
 
 
442
  return tipo_peticao, caso, peticao, "✅ Progresso carregado!"
443
- except Exception as e: # ... (tratamento de erro) ...
444
- return "", "", "", f"Erro ao carregar: {str(e)}"
445
-
 
 
 
446
 
447
- # --- Geração de Casos (sem alterações na lógica interna) ---
448
  def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
449
- # (Mantido como na versão anterior)
450
- prompt = f"""Crie um caso fictício **detalhado e realista** para um exercício prático de elaboração de uma peça do tipo **"{tipo_peticao}"**..."""
451
- # ... (instruções específicas por tipo mantidas) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  return prompt
453
 
454
  def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
455
- # (Mantido como na versão anterior, com try-except)
456
  logger.info(f"Solicitada gera��ão de caso para: {tipo_peticao}")
457
  try:
458
  if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO:
459
- return "⚠️ Erro: Selecione um tipo de petição válido."
 
 
460
  prompt = estruturar_prompt_geracao_caso(tipo_peticao)
 
461
  caso_gerado = chamar_api(prompt)
462
  if caso_gerado.startswith(("❌","⏱️","⚠️")):
 
463
  return f"⚠️ Falha ao gerar caso fictício:\n{caso_gerado}"
464
- # ... (pós-processamento da frase final mantido) ...
 
 
 
 
 
 
465
  return caso_gerado
466
  except Exception as e:
467
  logger.exception("Erro inesperado dentro de gerar_caso_ficticio:")
468
  return f"❌ Erro inesperado ao gerar o caso: {str(e)}"
469
 
470
-
471
- # --- Interface Gradio (com handlers .click() ajustados) ---
472
  def interface_gradio():
473
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
474
  gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️")
475
  gr.Markdown("Analise e aprimore suas peças processuais!")
476
-
477
  with gr.Row():
478
- with gr.Column(scale=1): # Coluna Configurações
479
  gr.Markdown("### ⚙️ Configurações")
480
  tipo_peticao_input = gr.Dropdown(label="1. Selecione o Tipo de Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial")
481
  modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento", value=False)
@@ -496,8 +892,7 @@ def interface_gradio():
496
  carregar_btn = gr.Button("📂 Carregar Caso/Peça")
497
  arquivo_progresso_upload = gr.File(label="Selecione .json", file_types=[".json"], scale=2)
498
  progresso_download_file = gr.File(label="Baixar Progresso", interactive=False)
499
-
500
- with gr.Column(scale=3): # Coluna Inputs
501
  gr.Markdown("### 1. Forneça o Caso / Contexto")
502
  with gr.Row():
503
  caso_input = gr.Textbox(label="📋 Caso ou Situação", lines=8, placeholder="Insira o texto COMPLETO do caso...")
@@ -513,7 +908,7 @@ def interface_gradio():
513
  arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Arquivo", lines=4, interactive=False)
514
  btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary")
515
 
516
- processing_message = gr.Markdown("", visible=False) # Movido para local acessível
517
 
518
  gr.Markdown("--- \n ### 3. Resultados da Correção")
519
  with gr.Tabs():
@@ -531,9 +926,6 @@ def interface_gradio():
531
 
532
  clear_btn = gr.Button("🗑️ Limpar Campos e Resultados")
533
 
534
- # --- LÓGICA DOS EVENTOS (Handlers .click() ajustados) ---
535
-
536
- # Gerar Caso
537
  caso_btn.click(
538
  fn=lambda: gr.update(value="⏳ Gerando caso...", visible=True), outputs=processing_message
539
  ).then(
@@ -542,37 +934,30 @@ def interface_gradio():
542
  fn=lambda: gr.update(value="", visible=False), outputs=processing_message
543
  )
544
 
545
- # Dicas Contextuais
546
  peticao_texto_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output)
547
  tipo_peticao_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output)
548
-
549
- # Preview Arquivo
550
  peticao_arquivo_input.change(fn=process_file, inputs=[peticao_arquivo_input], outputs=[arquivo_preview_output])
551
 
552
- # Botão Corrigir Texto
553
  btn_corrigir.click(
554
  fn=lambda: gr.update(value="⏳ Processando texto... Gerando análise e correção...", visible=True),
555
- outputs=processing_message # Mostra a mensagem imediatamente
556
  ).then(
557
- fn=corrigir_peticao, # Função principal
558
- inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento], # Inputs para corrigir_peticao
559
- # Outputs DEVE CORRESPONDER ao retorno de corrigir_peticao (5 valores)
560
  outputs=[resultado_analise_output, resultado_peticao_corrigida_output,
561
  resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
562
  concurrency_limit=1
563
  ).then(
564
  fn=lambda: gr.update(value="", visible=False),
565
- outputs=processing_message # Esconde a mensagem após a conclusão
566
  )
567
 
568
- # Botão Corrigir Arquivo
569
  btn_corrigir_arquivo.click(
570
  fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção...", visible=True),
571
  outputs=processing_message
572
  ).then(
573
  fn=corrigir_peticao,
574
  inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento],
575
- # Outputs DEVE CORRESPONDER ao retorno de corrigir_peticao (5 valores)
576
  outputs=[resultado_analise_output, resultado_peticao_corrigida_output,
577
  resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
578
  concurrency_limit=1
@@ -581,26 +966,21 @@ def interface_gradio():
581
  outputs=processing_message
582
  )
583
 
584
- # Configurações Avançadas
585
  config_update_btn.click(atualizar_config, inputs=[temperatura_slider, timeout_slider, max_tokens_slider], outputs=config_status)
586
-
587
- # Progresso e Histórico
588
  progresso_btn.click(fn=calcular_progresso, outputs=progresso_output)
589
  historico_btn.click(fn=mostrar_historico, outputs=historico_output)
590
 
591
- # Salvar Progresso
592
  salvar_btn.click(
593
  fn=lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message
594
  ).then(
595
  fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
596
- outputs=[gr.Textbox(visible=False), config_status, progresso_download_file] # O primeiro output (JSON) é oculto
597
  ).then(
598
- fn=lambda x: gr.update(visible=bool(x)), inputs=[progresso_download_file], outputs=[progresso_download_file] # Mostra/esconde botão download
599
  ).then(
600
- lambda: gr.update(value="", visible=False), None, processing_message # Limpa mensagem
601
  )
602
 
603
- # Carregar Progresso
604
  carregar_btn.click(
605
  fn=lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message
606
  ).then(
@@ -610,21 +990,19 @@ def interface_gradio():
610
  lambda: gr.update(value="", visible=False), None, processing_message
611
  )
612
 
613
- # Botão Limpar
614
  clear_btn.click(
615
- fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas aparecerão aqui...*", "", "", "", "", "", None, None, ""], # Adicionado estado inicial dica e config_status
616
  outputs=[
617
  tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input,
618
  peticao_arquivo_input, arquivo_preview_output, dica_contextual_output,
619
  resultado_analise_output, resultado_peticao_corrigida_output,
620
  resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output,
621
- arquivo_progresso_upload, progresso_download_file, config_status # Limpa status também
622
  ]
623
  )
624
 
625
  return demo
626
 
627
- # --- Execução Principal ---
628
  if __name__ == "__main__":
629
  logger.info("Iniciando a aplicação Gradio...")
630
  app = interface_gradio()
 
24
  import random
25
  import logging
26
  import traceback
27
+ import re
28
 
 
29
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s')
30
  logger = logging.getLogger(__name__)
31
 
 
32
  load_dotenv()
33
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or os.environ.get("GEMINI_API_KEY")
34
  if not GEMINI_API_KEY:
35
  raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a no arquivo .env ou como Secret no Hugging Face.")
36
 
 
37
  GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}"
38
  logger.info(f"Usando API URL: ...{GEMINI_API_URL[-50:]}")
39
 
 
40
  CONFIG = {"max_retry_attempts": 3, "retry_delay": 5, "timeout": 120, "temperatura": 0.3, "max_tokens": 8192}
41
  rate_limiter = RateLimiter(max_calls=20, period=60)
42
 
 
43
  TIPOS_DE_PETICAO = ["Petição Inicial", "Contestação", "Reconvenção", "Impugnação à Contestação", "Embargos de Declaração", "Recurso de Apelação", "Agravo de Instrumento", "Petição de Tutela Provisória"]
44
 
 
45
  DICAS_OAB_MELHORADO = {
46
  "Clareza e Concisão": "Seja direto e evite jargões excessivos.",
47
  "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes.",
 
50
  "Pedidos (Inicial)": "Certos, determinados e líquidos (se possível).",
51
  "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes do mérito.",
52
  "Impugnação Específica (Contestação)": "Conteste cada fato alegado (Art. 341, CPC)."
 
53
  }
54
 
 
55
  HISTORICO = []
56
  CORRECAO_CACHE = {}
57
 
 
58
  def preprocess_image_for_ocr(img_pil):
59
  if not CV2_AVAILABLE:
60
  logger.warning("OpenCV não disponível, usando conversão simples para OCR.")
 
181
  logger.error(f"Erro GERAL ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True)
182
  return f"❌ Erro inesperado ao processar o arquivo: {str(e)}"
183
 
 
 
184
  def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
 
185
  base = """
186
  **Estrutura da Avaliação:**
187
  1. **Análise Geral:** Visão geral concisa (2-3 linhas) sobre a adequação da peça ao caso e tipo, consistência e clareza.
 
217
  return base
218
 
219
  def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
 
220
  instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento)
221
  prompt_especifico = f"""
222
  ## Análise de Peça Processual: {tipo_peticao} (Critérios OAB Simulados)
 
231
  {instrucoes_gerais}
232
  **Requisitos Específicos para {tipo_peticao}:**
233
  """
234
+ if tipo_peticao == "Petição Inicial":
235
+ prompt_especifico += """
236
+ - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I).
237
+ - Qualificação completa das partes (Art. 319, II, CPC).
238
+ - Exposição clara e lógica dos fatos (causa de pedir remota).
239
+ - Fundamentação jurídica pertinente (causa de pedir próxima - Art. 319, III, CPC).
240
+ - Pedidos certos, determinados e líquidos (se possível) (Art. 319, IV, CPC).
241
+ - Valor da causa correto e justificado (Art. 291-292, 319, V, CPC).
242
+ - Indicação das provas pretendidas (Art. 319, VI, CPC).
243
+ - Opção pela audiência de conciliação/mediação (Art. 319, VII, CPC).
244
+ - Pedido de citação do réu (forma específica se necessário).
245
+ - Requerimento de juntada de documentos essenciais (procuração, custas/JG).
246
+ - Fechamento formal ("Nestes termos pede deferimento", Local, data, Advogado, OAB).
247
+ **Critérios Adicionais (Inicial):**
248
+ * Uso de "XXX" para dados ausentes. Penalizar invenção (-0.5 por ocorrência).
249
+ * Falta de pedido de juntada de custas/JG: -1.0 ponto.
250
+ * Erro em "Nestes termos, pede deferimento" (com vírgula): -0.2 ponto.
251
+ * Analisar foro (Art. 46 CPC vs. Art. 101 CDC): Escolha do foro do consumidor é válida se aplicável; não penalizar, apenas comentar se gerar dificuldades práticas.
252
+ """
253
+ elif tipo_peticao == "Contestação":
254
+ prompt_especifico += """
255
+ - Endereçamento ao juízo correto onde tramita a ação.
256
+ - Qualificação completa do réu (se já não constante nos autos, referenciar).
257
+ - Indicação do processo (número).
258
+ - Tempestividade (menção implícita ou explícita de que está no prazo).
259
+ - **Preliminares (Art. 337, CPC):** Alegar TODAS as aplicáveis (inexistência/nulidade citação, incompetência, inépcia, perempção, litispendência, coisa julgada, conexão, incapacidade, convenção de arbitragem, ausência de legitimidade/interesse, falta de caução, indevida concessão de JG).
260
+ - **Impugnação Específica dos Fatos:** Rebater PONTO A PONTO os fatos alegados na inicial (Art. 341, CPC). A falta de impugnação específica torna o fato incontroverso!
261
+ - **Mérito:** Apresentar a versão do réu sobre os fatos; apresentar a defesa de mérito direta (negando direito do autor) e/ou indireta (fatos impeditivos, modificativos, extintivos do direito do autor - ex: pagamento, prescrição, compensação).
262
+ - Fundamentação jurídica da defesa.
263
+ - **Pedidos:** Requerer acolhimento das preliminares (com extinção ou remessa); NO MÉRITO, requerer a IMPROCEDÊNCIA TOTAL dos pedidos do autor; Requerer a condenação do autor nas custas e honorários advocatícios (Art. 85, CPC).
264
+ - Indicação das provas que pretende produzir.
265
+ - Fechamento formal.
266
+ **Critérios Adicionais (Contestação):**
267
+ * A não alegação de preliminar arguível (exceto incompetência absoluta, conexão, convenção de arbitragem) preclui? Avaliar impacto.
268
+ * Falha grave na impugnação específica: -2.0 a -4.0 pontos.
269
+ * Ausência de pedido de improcedência: -1.5 pontos.
270
+ """
271
+ elif tipo_peticao == "Reconvenção":
272
+ prompt_especifico += """
273
+ - Apresentada NA MESMA PEÇA da Contestação (Art. 343, CPC).
274
+ - Endereçamento ao mesmo juízo.
275
+ - Indicação do processo.
276
+ - Qualificação completa do réu-reconvinte e do autor-reconvindo (podem ser referenciadas se já completas).
277
+ - **Conexão com a Ação Principal ou Fundamento de Defesa:** O pedido da reconvenção deve ser conexo (Art. 343, caput).
278
+ - **Estrutura Similar à Petição Inicial:** Fatos, Fundamentação, Pedidos específicos, Valor da causa, Provas.
279
+ - Pedido de intimação do autor-reconvindo para contestar a reconvenção (na pessoa de seu advogado - Art. 343, §1º).
280
+ - Fechamento formal (pode ser conjunto com a contestação).
281
+ **Critérios Adicionais (Reconvenção):**
282
+ * Ausência de conexão clara: -2.0 pontos. Falta de pedido/valor da causa/provas específicos: -1.0 por item. Apresentada em peça separada (erro formal grave): -3.0 pontos.
283
+ """
284
+ elif tipo_peticao == "Impugnação à Contestação":
285
+ prompt_especifico += """
286
+ - Endereçamento ao juízo correto. Indicação do processo. Qualificação do autor (impugnante). Tempestividade (15 dias - Art. 350/351 CPC).
287
+ - **Resposta às Preliminares:** Rebater cada preliminar arguida.
288
+ - **Resposta a Fatos Impeditivos/Modificativos/Extintivos:** Manifestar-se sobre fatos novos (Art. 350).
289
+ - **Resposta a Documentos Novos:** Manifestar-se sobre documentos juntados (Art. 437 CPC).
290
+ - **Ratificação da Inicial:** Reiterar os termos e pedidos da inicial.
291
+ - **Especificação de Provas:** Indicar provas para contrapor defesa indireta/preliminares (Art. 350/351).
292
+ - Pedidos: Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial. Fechamento formal.
293
+ **Critérios Adicionais (Impugnação):**
294
+ * Não responder a preliminar relevante: -1.5 pontos. Não se manifestar sobre fato novo: -2.0 pontos. Não ratificar a inicial: -0.5 pontos.
295
+ """
296
+ elif tipo_peticao == "Embargos de Declaração":
297
+ prompt_especifico += """
298
+ - **Contra Decisão Específica:** Indicar claramente qual decisão está sendo embargada. Endereçamento ao PRÓPRIO juízo prolator. Indicação do processo. Qualificação das partes. Tempestividade (5 dias - Art. 1023 CPC).
299
+ - **Cabimento (Art. 1022, CPC):** Apontar **PRECISAMENTE** e **FUNDAMENTADAMENTE** a ocorrência de: Omissão, Contradição, Obscuridade, Erro Material.
300
+ - **NÃO PODE REJULGAR A CAUSA:** Objetivo é sanar vício, não modificar o mérito (salvo efeitos infringentes excepcionais).
301
+ - **Pedido:** Requerer o conhecimento e ACOLHIMENTO para sanar o vício (com ou sem efeitos infringentes, se for o caso). Fechamento formal.
302
+ **Critérios Adicionais (Embargos):**
303
+ * Usar para rediscutir mérito (erro grave): -4.0 pontos. Não apontar vício claramente: -3.0 pontos. Endereçar ao tribunal (erro): -1.0 ponto.
304
+ """
305
+ elif tipo_peticao == "Recurso de Apelação":
306
+ prompt_especifico += """
307
+ - **Peça de Interposição:** Endereçamento ao juízo a quo. Indicação do processo. Qualificação das partes. Pressupostos (cabimento - Art. 1009; tempestividade - 15 dias Art. 1003, §5º; preparo/JG; regularidade). Pedido de recebimento e remessa ao Tribunal.
308
+ - **Razões Recursais:** Endereçamento ao TRIBUNAL competente. Exposição dos fatos e da sentença. **Fundamentos para Reforma/Anulação (Art. 1010, II e III):** Error in procedendo (anulação) ou Error in judicando (reforma). **Princípio da Dialeticidade:** Impugnar ESPECIFICAMENTE os fundamentos da sentença.
309
+ - **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão da sucumbência. Fechamento formal.
310
+ **Critérios Adicionais (Apelação):**
311
+ * Falta de interposição ou razões: Nota zero. Endereçamento incorreto das razões: -1.0. Ausência de dialeticidade: -3.0 a -5.0. Não pedir anulação/reforma: -1.5. Falta de preparo/JG: -2.0.
312
+ """
313
+ elif tipo_peticao == "Agravo de Instrumento":
314
+ prompt_especifico += """
315
+ - **Peça Única:** Endereçamento direto ao TRIBUNAL. Indicação processo origem. Qualificação partes e advogados (com endereços - Art. 1016, IV). Cópia da decisão agravada (OBRIGATÓRIA - Art. 1017, I).
316
+ - **Cabimento (Art. 1015 ou taxatividade mitigada):** Demonstrar hipótese legal ou urgência (Tema 988 STJ). Tempestividade (15 dias). Preparo/JG.
317
+ - **Peças Obrigatórias (Art. 1017, I):** Decisão agravada, certidão de intimação, procurações. Peças Facultativas (Art. 1017, III).
318
+ - **Razões (Art. 1016, II e III):** Exposição do fato e direito; razões para reforma/invalidação da decisão interlocutória.
319
+ - **Pedido de Efeito Suspensivo ou Tutela Antecipada Recursal (Art. 1019, I):** Se houver risco, pedir fundamentadamente.
320
+ - **Pedidos Finais:** Conhecimento; (se aplicável) efeito suspensivo/tutela antecipada; PROVIMENTO para reformar/invalidar; comunicação ao juiz (Art. 1018 - opcional se eletrônico). Fechamento formal.
321
+ **Critérios Adicionais (Agravo):**
322
+ * Ausência de peça obrigatória: Inadmissibilidade (-4.0). Não demonstrar cabimento: -3.0. Endereçar ao juiz a quo: -1.0. Não indicar advogados: -0.5.
323
+ """
324
+ elif tipo_peticao == "Petição de Tutela Provisória":
325
+ prompt_especifico += """
326
+ - **Tipo (Urgência ou Evidência):** Deixar claro. Antecedente ou incidental. Endereçamento (competente ou da causa). Qualificação partes. Indicação processo (se incidental).
327
+ - **Tutela de Urgência (Art. 300):** **Fumus Boni Iuris** (probabilidade do direito) E **Periculum in Mora** (perigo de dano/risco ao resultado útil). **Reversibilidade** (Art. 300, §3º - se possível).
328
+ - **Tutela de Evidência (Art. 311):** Demonstrar uma das hipóteses (I a IV). **NÃO precisa periculum in mora.**
329
+ - **Pedido Específico:** Indicar CLARAMENTE a medida liminar solicitada.
330
+ - **Se Antecedente (Art. 303 ou 305):** Indicar pedido final ou aditar/apresentar inicial no prazo. Valor da Causa.
331
+ - **Pedidos:** Concessão da tutela (liminar/justificação prévia); citação/intimação; (se antecedente) indicação da ação principal/aditamento. Fechamento formal.
332
+ **Critérios Adicionais (Tutela):**
333
+ * Não demonstrar Fumus/Periculum (Urgência): -3.0 a -5.0. Não enquadrar na Evidência: -3.0. Pedido genérico: -2.0. Confundir requisitos: -1.5.
334
+ """
335
+ else:
336
+ prompt_especifico += """
337
+ - **Adequação ao Tipo:** A peça corresponde à estrutura e finalidade do tipo?
338
+ - **Clareza:** É possível entender a pretensão?
339
+ - **Fundamentação Mínima:** Tentativa de basear em lei ou fatos?
340
+ - **Pedido:** Existe um pedido?
341
+ """
342
+ prompt_especifico += "\n\n**Instruções Finais:** Analise a peça do aluno estritamente com base no caso/contexto fornecido e nos critérios acima. Seja rigoroso como um examinador da OAB, mas forneça feedback construtivo e detalhado. Gere a resposta **exclusivamente** no formato solicitado."
343
  return prompt_especifico
344
 
345
  def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
 
346
  prompt = f"""
347
  **Caso/Contexto Fornecido:**
348
  ```
 
352
  ```
353
  {peticao_original}
354
  ```
355
+ **Tarefa:** Com base no caso e nos problemas da peça original, **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Siga **rigorosamente** as melhores práticas e requisitos formais/materiais para este tipo de peça no exame da OAB.
356
  **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):**
357
  """
358
+ if tipo_peticao == "Petição Inicial":
359
+ prompt += """
360
+ 1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável).
361
+ 2. **Qualificação:** Completa (Art. 319, II), usando "XXX" para dados ausentes.
362
+ 3. **Fatos:** Narre de forma cronológica, clara e fiel ao caso.
363
+ 4. **Fundamentação:** Cite artigos pertinentes (CC, CDC, CPC) e súmulas/jurisprudência, se aplicável.
364
+ 5. **Pedidos:** Claros, certos, determinados (quantificar danos se possível). Cumulados se cabível.
365
+ 6. **Valor da Causa:** Correto (soma dos benefícios - Art. 292).
366
+ 7. **Provas:** Indicar ("protesta provar o alegado por todos os meios...").
367
+ 8. **Audiência:** Indicar opção (Art. 319, VII).
368
+ 9. **Citação:** Pedido de citação (ex: "via postal, Art. 246, I, CPC").
369
+ 10. **Custas:** Requerimento expresso de juntada da guia OU pedido de gratuidade (com pedido de declaração). **NUNCA OMITIR.**
370
+ 11. **Fechamento:** "Nestes termos pede deferimento." (SEM vírgula). Seguido de "Local, data.", "ADVOGADO(A)", "OAB/...". **NUNCA identificar.**
371
+ """
372
+ elif tipo_peticao == "Contestação":
373
+ prompt += """
374
+ 1. **Endereçamento:** Ao juízo da ação.
375
+ 2. **Identificação:** Número do processo, qualificação do réu.
376
+ 3. **Tempestividade:** Mencionar ("A presente contestação é tempestiva...").
377
+ 4. **Estrutura:** Síntese da Inicial; Preliminares (Art. 337, fundamentadas); Mérito (Impugnação Específica + Defesa de Mérito Direta/Indireta com fundamentos); Reconvenção (Se houver, ANTES dos pedidos da contestação).
378
+ 5. **Pedidos da Contestação:** Acolhimento das preliminares (com consequência); **IMPROCEDÊNCIA TOTAL** dos pedidos do autor; Condenação em custas e honorários.
379
+ 6. **Provas:** Indicar as provas do réu.
380
+ 7. **Fechamento:** Padrão OAB.
381
+ """
382
+ elif tipo_peticao == "Reconvenção":
383
+ prompt += """
384
+ **IMPORTANTE:** Gere a Reconvenção como parte da Contestação.
385
+ 1. **Após a defesa de mérito da Contestação e ANTES dos pedidos da Contestação**, insira: "DA RECONVENÇÃO".
386
+ 2. **Qualificação:** Réu-Reconvinte e Autor-Reconvindo.
387
+ 3. **Conexão:** Justifique brevemente.
388
+ 4. **Fatos e Direito Reconvencionais:** Narre e fundamente o pedido do reconvinte.
389
+ 5. **Pedidos da Reconvenção:** Claros e específicos contra o autor-reconvindo.
390
+ 6. **Valor da Causa e Provas da Reconvenção:** Específicos.
391
+ 7. **Intimação:** Peça a intimação do autor-reconvindo para contestar.
392
+ 8. **Continue com os Pedidos da Contestação**.
393
+ 9. **Fechamento:** Padrão OAB.
394
+ """
395
+ elif tipo_peticao == "Impugnação à Contestação":
396
+ prompt += """
397
+ 1. Endereçamento, processo, qualificação autor, tempestividade.
398
+ 2. Tópico: **DA(S) PRELIMINAR(ES) ARGUIDA(S)** - Rebater cada uma.
399
+ 3. Tópico: **DO MÉRITO E DOS FATOS NOVOS** - Manifestar-se sobre fatos impeditivos/modificativos/extintivos e documentos.
400
+ 4. Tópico: **DA RATIFICAÇÃO DA INICIAL** - Reiterar termos e pedidos.
401
+ 5. Tópico: **DAS PROVAS** - Especificar provas contrapostas.
402
+ 6. **Pedidos:** Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial.
403
+ 7. Fechamento padrão OAB.
404
+ """
405
+ elif tipo_peticao == "Embargos de Declaração":
406
+ prompt += """
407
+ 1. Endereçamento ao juízo prolator. Processo, Embargante, Embargado. Decisão embargada identificada. Tempestividade (5 dias).
408
+ 2. Tópico para cada vício (Art. 1022): **DA OMISSÃO / DA CONTRADIÇÃO / DA OBSCURIDADE / DO ERRO MATERIAL**.
409
+ 3. Dentro de cada tópico: **Transcrever trecho relevante da decisão** e explicar **objetivamente** o vício. NÃO REJULGAR.
410
+ 4. **Pedido:** Conhecimento e ACOLHIMENTO para sanar o(s) vício(s) apontado(s), especificando o que se espera. Pedir efeitos infringentes se cabível e fundamentado.
411
+ 5. Fechamento padrão OAB.
412
+ """
413
+ elif tipo_peticao == "Recurso de Apelação":
414
+ prompt += """
415
+ 1. **Peça de Interposição:** Endereçamento ao juízo a quo. Processo, Apelante, Apelado. Pressupostos (cabimento, tempestividade, preparo/JG). Pedido de recebimento e remessa.
416
+ 2. **Razões Recursais:** Endereçamento ao Tribunal. Breve relato. **DOS FUNDAMENTOS PARA REFORMA/ANULAÇÃO:** Error in procedendo ou Error in judicando. Impugnar especificamente a sentença (dialeticidade).
417
+ 3. **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão sucumbência.
418
+ 4. Fechamento padrão OAB.
419
+ """
420
+ elif tipo_peticao == "Agravo de Instrumento":
421
+ prompt += """
422
+ 1. Peça única ao Tribunal. Processo origem, Agravante, Agravado, Advogados (com endereços). Decisão agravada identificada (juntar cópia).
423
+ 2. **DO CABIMENTO:** Demonstrar hipótese Art. 1015 ou mitigação. **DA TEMPESTIVIDADE.** **DO PREPARO/JG.** **DAS PEÇAS OBRIGATÓRIAS E FACULTATIVAS.**
424
+ 3. **DAS RAZÕES RECURSAIS:** Exposição fato/direito, motivos para reforma/invalidação da interlocutória.
425
+ 4. **DO PEDIDO DE EFEITO SUSPENSIVO / TUTELA ANTECIPADA RECURSAL** (se cabível e fundamentado).
426
+ 5. **Pedidos:** Conhecimento; (se aplicável) efeito suspensivo/tutela; PROVIMENTO para reformar/invalidar; comunicação ao juiz.
427
+ 6. Fechamento padrão OAB.
428
+ """
429
+ elif tipo_peticao == "Petição de Tutela Provisória":
430
+ prompt += """
431
+ 1. Indicar TIPO (Urgência/Evidência) e CARÁTER (Antecedente/Incidental). Endereçamento. Qualificação. Processo (se incidental).
432
+ 2. **DOS REQUISITOS:**
433
+ * **Urgência:** Tópico **DA PROBABILIDADE DO DIREITO (FUMUS BONI IURIS)** e tópico **DO PERIGO DE DANO OU RISCO AO RESULTADO ÚTIL (PERICULUM IN MORA)**. Se possível, **DA REVERSIBILIDADE**.
434
+ * **Evidência:** Tópico **DA HIPÓTESE DE EVIDÊNCIA (ART. 311, INCISO __)** - Demonstrar o enquadramento.
435
+ 3. **DO PEDIDO LIMINAR:** Descrever CLARAMENTE a medida solicitada.
436
+ 4. **Se Antecedente:** Indicar pedido final ou aditamento/inicial completa. Valor da Causa.
437
+ 5. **Pedidos:** Concessão da tutela; citação/intimação; (se antecedente) indicação ação principal/aditamento.
438
+ 6. Fechamento padrão OAB.
439
+ """
440
+ else:
441
+ prompt += """
442
+ 1. Estrutura Lógica: Começo, meio, fim.
443
+ 2. Argumentos Claros.
444
+ 3. Pedido Claro.
445
+ 4. Formalidades: Linguagem adequada, fechamento OAB.
446
+ """
447
+ prompt += "\n\n**Instrução Final:** Gere **APENAS** o texto completo da peça corrigida abaixo, começando pelo endereçamento e terminando com 'OAB/...'. Não inclua NENHUM comentário, introdução ou explicação ANTES ou DEPOIS do texto da peça."
448
  return prompt
449
 
450
  def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
 
451
  if "Nestes termos, pede deferimento" in peticao_corrigida:
452
  peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
453
  if tipo_peticao == "Petição Inicial":
454
  peticao_lower = peticao_corrigida.lower()
455
+ if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower:
456
+ partes = peticao_corrigida.split("Nestes termos pede deferimento")
457
+ if len(partes) == 2:
458
+ peticao_corrigida = partes[0].strip() + "\nRequer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento" + partes[1]
459
+ else:
460
+ peticao_corrigida = peticao_corrigida.replace("Local, data.", "Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.")
461
+ if "audiência de conciliação" not in peticao_lower and "audiência de mediação" not in peticao_lower:
462
+ partes = peticao_corrigida.split("Nestes termos pede deferimento")
463
+ if len(partes) == 2:
464
+ peticao_corrigida = partes[0].strip() + "\nOpta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\nNestes termos pede deferimento" + partes[1]
465
+ else:
466
+ peticao_corrigida = peticao_corrigida.replace("Local, data.", "Opta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\n\nLocal, data.")
467
+
468
  linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
469
  fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."]
470
  while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]):
 
473
  peticao_corrigida = "\n".join(linhas)
474
  return peticao_corrigida
475
 
 
 
476
  def chamar_api(prompt):
 
477
  payload = {"contents": [{"parts": [{"text": prompt}]}],"generationConfig": {"temperature": CONFIG["temperatura"],"maxOutputTokens": CONFIG["max_tokens"],"topP": 0.95,}}
478
  headers = {"Content-Type": "application/json"}
479
  logger.info(f"Chamando API Gemini. Temp={CONFIG['temperatura']}, MaxTokens={CONFIG['max_tokens']}")
 
482
  with rate_limiter:
483
  logger.debug(f"API Call - Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}")
484
  response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"])
485
+ logger.debug(f"API Response - Status: {response.status_code}")
486
+
487
+ if response.status_code == 429:
488
+ retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1)))
489
+ logger.warning(f"Erro 429 (Too Many Requests). Aguardando {retry_after}s...")
490
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(retry_after); continue
491
+ else: return " Erro 429: Muitas requisições. Tente novamente mais tarde."
492
+
493
+ if response.status_code != 200:
494
+ error_msg = "Erro desconhecido na API"
495
+ try:
496
+ error_info = response.json()
497
+ error_msg = error_info.get("error", {}).get("message", error_msg)
498
+ except json.JSONDecodeError:
499
+ error_msg = f"Erro {response.status_code}: {response.text[:200]}" # Mostra início do erro não-JSON
500
+ logger.error(f"API Error (Status {response.status_code}): {error_msg}")
501
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
502
+ return f"Erro da API ({response.status_code}): {error_msg}"
503
+
504
+ response_data = response.json()
505
+
506
+ if not response_data.get("candidates"):
507
+ prompt_feedback = response_data.get("promptFeedback", {})
508
+ block_reason = prompt_feedback.get("blockReason")
509
+ logger.error(f"API Response sem 'candidates'. Block Reason: {block_reason}")
510
+ if block_reason: return f"❌ A solicitação foi bloqueada pela API. Motivo: {block_reason}. Tente reformular."
511
+ else: return "❌ Resposta vazia ou inválida da API (sem 'candidates')."
512
+
513
+ candidate = response_data["candidates"][0]
514
+ finish_reason = candidate.get("finishReason")
515
+
516
+ if finish_reason and finish_reason not in ["STOP", "MAX_TOKENS", "FINISH_REASON_UNSPECIFIED"]:
517
+ logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}")
518
+ safety_ratings = candidate.get("safetyRatings", [])
519
+ harm_categories = [r['category'] for r in safety_ratings if r['probability'] not in ['NEGLIGIBLE', 'LOW']]
520
+ if harm_categories: return f"❌ Resposta bloqueada por segurança. Motivo: {finish_reason} ({', '.join(harm_categories)})."
521
+ else: return f"❌ Geração interrompida. Motivo: {finish_reason}."
522
+
523
+ if not candidate.get("content") or not candidate["content"].get("parts"):
524
+ logger.error("Estrutura de resposta inesperada: Faltando content/parts.")
525
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
526
+ return "❌ Resposta inválida da API (estrutura inesperada)."
527
+
528
+ texto_gerado = candidate["content"]["parts"][0]["text"].strip()
529
+ logger.info(f"API retornou com sucesso ({len(texto_gerado)} chars). Finish Reason: {finish_reason}")
530
+ return texto_gerado
531
+
532
+ except requests.Timeout:
533
+ logger.warning(f"Timeout na API - Tentativa {tentativa + 1}.")
534
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
535
+ return "⏱️ Tempo esgotado na comunicação com a API."
536
+ except requests.RequestException as e:
537
+ logger.error(f"Erro de Rede/Requisição API - Tentativa {tentativa + 1}: {e}")
538
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
539
+ return f"❌ Erro de conexão com a API: {e}"
540
+ except json.JSONDecodeError as e:
541
+ logger.error(f"Erro ao decodificar JSON da API: {e}")
542
+ logger.debug(f"Resposta não-JSON: {response.text[:500]}")
543
+ return f"❌ Erro ao processar a resposta da API (JSON inválido)."
544
+ except Exception as e:
545
+ logger.exception(f"Erro inesperado na chamada da API - Tentativa {tentativa + 1}: {str(e)}")
546
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
547
+ return f"❌ Erro inesperado no processamento da API: {str(e)}"
548
+
549
  logger.error("Falha ao chamar a API após todas as tentativas.")
550
  return "❌ Falha ao comunicar com a API após múltiplas tentativas."
551
 
 
 
552
  def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
 
553
  logger.info(f"Iniciando correção. Tipo={tipo_peticao}, Treinamento={modo_treinamento}")
554
  start_time = time.time()
555
  analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html = "", "", "", "", ""
556
  peticao_conteudo = ""
557
  try:
558
+ if not caso or not caso.strip():
559
+ logger.warning("Caso não fornecido.")
560
+ return "⚠️ Forneça o caso/contexto da peça.", "", "", "", ""
561
+ if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO:
562
+ logger.warning(f"Tipo de petição inválido ou não fornecido: {tipo_peticao}")
563
+ return "⚠️ Selecione um tipo de petição válido.", "", "", "", ""
564
+
565
+ if peticao_arquivo is not None:
566
+ logger.info("Processando arquivo de petição...")
567
+ peticao_conteudo = process_file(peticao_arquivo)
568
+ if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"):
569
+ logger.error(f"Erro ao processar arquivo: {peticao_conteudo}")
570
+ return peticao_conteudo, "", "", "", ""
571
+ elif peticao_texto and peticao_texto.strip():
572
+ peticao_conteudo = peticao_texto
573
+ else:
574
+ logger.warning("Nenhuma petição fornecida (texto ou arquivo).")
575
+ return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", ""
576
+ logger.info(f"Petição recebida ({len(peticao_conteudo)} chars).")
577
+
578
+ troll_triggers = ["escreva a petição", "faça a petição", "redija a petição", "elabore a petição", "crie a petição", "gere a petição", "faça para mim", "faça por mim"]
579
+ if any(trigger in peticao_conteudo.lower() for trigger in troll_triggers):
580
+ logger.info("Detectado pedido para escrever petição.")
581
+ return random.choice([
582
+ "Hummm... Parece que você quer que *eu* faça o trabalho, né? 😉 Que tal você tentar escrever a peça e *eu* te ajudo a corrigir?",
583
+ "🚫 Opa! Meu trabalho é *corrigir* e *ensinar*, não fazer a peça do zero. Mostre o que você fez e eu te dou o feedback!",
584
+ ]), "", "", "", ""
585
 
 
586
  cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}"
587
+ if cache_key in CORRECAO_CACHE:
588
+ logger.info("Correção encontrada no cache.")
589
+ cached_data = CORRECAO_CACHE[cache_key]
590
+ return cached_data["analise_formatada"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"], cached_data["diff_html"]
591
 
592
+ logger.info("Estruturando prompt de análise...")
593
  prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento)
594
+ logger.info("Chamando API para análise...")
595
  analise_bruta = chamar_api(prompt_analise)
596
+ if analise_bruta.startswith(("❌", "⏱️", "⚠️")):
597
+ logger.error(f"Erro retornado pela API (Análise): {analise_bruta}")
598
  return analise_bruta, "", "", "", ""
599
+ logger.info("Análise recebida. Formatando...")
600
  analise_formatada = formatar_correcao(analise_bruta)
601
 
602
+ logger.info("Estruturando prompt para peça corrigida...")
603
  prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao)
604
+ logger.info("Chamando API para gerar peça corrigida...")
605
  peticao_corrigida_bruta = chamar_api(prompt_peticao)
606
+
607
+ if peticao_corrigida_bruta.startswith(("❌", "⏱️", "⚠️")):
608
+ logger.error(f"Erro retornado pela API (Correção): {peticao_corrigida_bruta}")
609
  peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}"
610
  else:
611
+ logger.info("Peça corrigida recebida. Aplicando pós-correção...")
612
+ start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
613
+ start_index = -1
614
+ peticao_lower_corr = peticao_corrigida_bruta.lower()
615
+ for marker in start_markers:
616
+ idx = peticao_lower_corr.find(marker)
617
+ if idx != -1: start_index = idx; break
618
+ peticao_extraida = peticao_corrigida_bruta[start_index:] if start_index != -1 else peticao_corrigida_bruta
619
+ peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao)
620
+ logger.info("Pós-correção aplicada.")
621
+
622
+ logger.info("Calculando diferenças...")
623
  diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final)
624
+ logger.info("Diferenças calculadas.")
625
 
 
626
  resultado_cache = {"analise_formatada": analise_formatada,"peticao_corrigida": peticao_corrigida_final,"resumo_diff": resumo_diff,"criticas_diff": criticas_diff,"diff_html": diff_html}
627
  CORRECAO_CACHE[cache_key] = resultado_cache
628
  HISTORICO.append({"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"tipo_peticao": tipo_peticao,"caso": caso,"peticao_original": peticao_conteudo,**resultado_cache})
 
637
  error_message = f"❌ Erro inesperado na função de correção: {str(e)}\nConsulte os logs para detalhes."
638
  return error_message, "", "", "", ""
639
 
 
 
640
  def formatar_correcao(texto):
 
641
  texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
642
  texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
643
+ texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
644
+ texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
645
+ texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos")
646
+ linhas = texto.split('\n')
647
+ linhas_formatadas = []
648
+ in_list = False
649
+ for linha in linhas:
650
+ linha_strip = linha.strip()
651
+ if linha_strip.startswith(("1.","2.","3.","4.","5.")):
652
+ linhas_formatadas.append(f"\n**{linha_strip}**")
653
+ in_list = False
654
+ elif linha_strip.startswith(("- ","* ")):
655
+ prefix = " * " if in_list else "* "
656
+ linhas_formatadas.append(prefix + linha_strip[2:])
657
+ in_list = True
658
+ elif linha_strip.startswith(("a)","b)","c)","d)","e)")):
659
+ linhas_formatadas.append(f" * {linha_strip}")
660
+ in_list = True
661
+ else:
662
+ linhas_formatadas.append(linha)
663
+ if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")):
664
+ in_list = False
665
+ texto_final = "\n".join(linhas_formatadas)
666
+ nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})"
667
+ match = re.search(nota_pattern, texto_final, re.IGNORECASE)
668
+ if match:
669
+ nota = match.group(2).replace(",",".")
670
+ texto_final = re.sub(nota_pattern, f"\\1**{nota}/10**", texto_final, count=1, flags=re.IGNORECASE)
671
+ return texto_final
672
 
673
  def fornecer_dica_contextual(texto_peticao, tipo_peticao):
674
+ if not texto_peticao or not texto_peticao.strip():
675
+ return "*Digite algo na peça para receber dicas...*"
676
+ dicas_relevantes = []
677
+ texto_lower = texto_peticao.lower()
678
+ for chave in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]:
679
+ if chave in DICAS_OAB_MELHORADO:
680
+ dicas_relevantes.append(f"💡 **{chave}:** {DICAS_OAB_MELHORADO[chave]}")
681
+ keywords_por_tipo = {
682
+ "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor", "provas", "audiência", "citação", "custas"],
683
+ "Contestação": ["preliminar", "mérito", "impugnação", "improcedência", "honorários", "art. 337", "art. 341"],
684
+ "Reconvenção": ["reconvinte", "reconvindo", "conexão", "art. 343"],
685
+ "Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica", "art. 350"],
686
+ "Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "art. 1022"],
687
+ "Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "art. 1009"],
688
+ "Agravo de Instrumento": ["interlocutória", "cabimento", "urgência", "efeito suspensivo", "art. 1015", "art. 1017"],
689
+ "Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "art. 300", "art. 311"],
690
+ }
691
+ if tipo_peticao in keywords_por_tipo:
692
+ for chave, dica in DICAS_OAB_MELHORADO.items():
693
+ chave_lower = chave.lower()
694
+ if f"({tipo_peticao.split()[0].lower()})" in chave_lower or \
695
+ any(kw in chave_lower for kw in keywords_por_tipo[tipo_peticao] if kw in texto_lower):
696
+ dica_formatada = f"💡 **{chave}:** {dica}"
697
+ if dica_formatada not in dicas_relevantes:
698
+ dicas_relevantes.append(dica_formatada)
699
+ if not dicas_relevantes:
700
+ return "*Continue escrevendo ou selecione o tipo de peça para dicas mais específicas.*"
701
  return "\n\n".join(dicas_relevantes[:4])
702
 
 
 
703
  def calcular_diff_interno(texto_original, texto_corrigido):
704
+ if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")):
705
+ return "","",""
706
+ orig_lines = texto_original.splitlines()
707
+ corr_lines = texto_corrigido.splitlines()
708
+ diff = difflib.ndiff(orig_lines, corr_lines)
709
+ resultado_diff_html = []
710
+ criticas = []
711
+ linhas_removidas = 0
712
+ linhas_adicionadas = 0
713
+ keywords_criticas_removidas = ["invenção", "identificação", "erro crasso", "incorreto", "prazo"]
714
+ keywords_criticas_adicionadas = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "essencial", "preliminar", "tempestividade", "cabimento"]
715
+ for linha in diff:
716
+ texto = linha[2:]
717
+ texto_escaped = escape(texto)
718
+ if linha.startswith(' '): resultado_diff_html.append(f"<div class='linha-igual'>{texto_escaped}</div>")
719
+ elif linha.startswith('- '):
720
+ linhas_removidas += 1
721
+ resultado_diff_html.append(f"<div class='linha-removida' title='Removido'><del>{texto_escaped}</del></div>")
722
+ if texto.strip() and any(kw in texto.lower() for kw in keywords_criticas_removidas): criticas.append(f"<span style='color: red;'>🔴 REMOVIDO:</span> {texto_escaped}")
723
+ elif linha.startswith('+ '):
724
+ linhas_adicionadas += 1
725
+ resultado_diff_html.append(f"<div class='linha-adicionada' title='Adicionado'><ins>{texto_escaped}</ins></div>")
726
+ if texto.strip() and any(kw in texto.lower() for kw in keywords_criticas_adicionadas): criticas.append(f"<span style='color: green;'>🟢 ADICIONADO:</span> {texto_escaped}")
727
+ resumo_estatistico = f"**Resumo:**\n* Removidas: {linhas_removidas}\n* Adicionadas: {linhas_adicionadas}\n* **Saldo:** {linhas_adicionadas - linhas_removidas} linhas"
728
+ criticas_texto = "### 🚨 Mudanças Críticas / Essenciais:\n" + "\n".join(criticas) if criticas else "*Nenhuma mudança crítica/essencial detectada.*"
729
+ css = """<style>.diff-container { font-family: monospace; white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; max-height: 600px; overflow-y: auto;} .linha-igual { color: #555; margin: 1px 0; } .linha-removida { background-color: #ffebee; color: #c62828; margin: 1px 0; } .linha-adicionada { background-color: #e8f5e9; color: #2e7d32; margin: 1px 0; } .linha-removida del, .linha-adicionada ins { text-decoration: none; padding: 0 2px; border-radius: 2px; } .linha-removida del { background-color: #ffcdd2; } .linha-adicionada ins { background-color: #c8e6c9; }</style>"""
730
+ visualizacao_detalhada_html = f"{css}<div class='diff-container'>{''.join(resultado_diff_html)}</div>"
731
  return visualizacao_detalhada_html, resumo_estatistico, criticas_texto
732
 
 
 
733
  def calcular_progresso():
 
734
  if not HISTORICO: return "📊 Nenhum progresso registrado ainda."
735
+ notas = []
736
+ tipos_corrigidos = set()
737
+ for entry in HISTORICO:
738
+ tipos_corrigidos.add(entry["tipo_peticao"])
739
+ match = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.IGNORECASE)
740
+ if match:
741
+ try: notas.append(float(match.group(1).replace(",", ".")))
742
+ except ValueError: pass
743
+ total_corridas = len(HISTORICO)
744
+ media = sum(notas) / len(notas) if notas else 0
745
+ tendencia = ""
746
+ if len(notas) > 1:
747
+ if notas[-1] > notas[-2]: tendencia = "⬆️ Melhorando"
748
+ elif notas[-1] < notas[-2]: tendencia = "⬇️ Piorando"
749
+ else: tendencia = "↔️ Estável"
750
+ progresso_texto = f"**📊 Resumo:**\n* Correções: {total_corridas}\n* Tipos: {', '.join(sorted(list(tipos_corrigidos)))}\n* Média: {media:.1f}/10 ({len(notas)} válidas)\n* Tendência: {tendencia if tendencia else 'N/A'}"
751
  return progresso_texto
752
 
753
  def mostrar_historico():
 
754
  if not HISTORICO: return "📜 Histórico vazio."
755
+ historico_md = "### 📜 Histórico (Últimas 10)\n---\n"
756
+ for i, entry in enumerate(reversed(HISTORICO)):
757
+ caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso']
758
+ nota_str = "N/A"
759
+ match = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.IGNORECASE)
760
+ if match: nota_str = f"{match.group(1).replace(',', '.')}/10"
761
+ historico_md += f"**{len(HISTORICO)-i}.** {entry['data']} | **Tipo:** {entry['tipo_peticao']} | **Nota:** {nota_str}\n **Caso:** _{caso_resumo}_\n---\n"
762
  return historico_md
763
 
 
 
764
  def salvar_progresso(caso, peticao, tipo_peticao):
 
765
  logger.info("Iniciando salvamento de progresso...")
766
  try:
767
+ if not caso or not peticao or not tipo_peticao:
768
+ logger.warning("Inputs insuficientes para salvar progresso.")
769
+ return "", "⚠️ Caso, Petição e Tipo são necessários para salvar.", None
770
+ dados_progresso = {"tipo_peticao": tipo_peticao, "caso": caso, "peticao": peticao, "timestamp": datetime.now().isoformat()}
771
+ json_content = json.dumps(dados_progresso, ensure_ascii=False, indent=4)
772
+ timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
773
+ filename = f"progresso_peticao_{timestamp_str}.json"
774
+ temp_dir = tempfile.gettempdir()
775
+ temp_file_path = os.path.join(temp_dir, filename)
776
+ with open(temp_file_path, 'w', encoding='utf-8') as f: f.write(json_content)
777
+ logger.info(f"Progresso salvo com sucesso em: {temp_file_path}")
778
+ success_message = f"✅ Progresso salvo! Baixe `{filename}`."
779
  return json_content, success_message, temp_file_path
780
+ except Exception as e:
781
+ logger.error(f"Erro ao salvar progresso: {str(e)}", exc_info=True)
782
+ return "", f"❌ Erro ao salvar: {str(e)}", None
783
 
784
  def carregar_progresso(arquivo_entrada):
 
785
  logger.info("Iniciando carregamento de progresso...")
786
  if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo selecionado."
787
  try:
788
+ file_path = arquivo_entrada.name
789
+ logger.info(f"Carregando progresso do arquivo: {file_path}")
790
+ with open(file_path, 'r', encoding='utf-8') as f: dados = json.load(f)
791
+ caso = dados.get("caso")
792
+ peticao = dados.get("peticao")
793
+ tipo_peticao = dados.get("tipo_peticao")
794
+ if caso is None or peticao is None or tipo_peticao is None:
795
+ logger.warning(f"Arquivo JSON carregado não contém chaves esperadas: {dados.keys()}")
796
+ return "", "", "", "❌ Erro: Arquivo de progresso inválido."
797
+ if tipo_peticao not in TIPOS_DE_PETICAO:
798
+ logger.warning(f"Tipo de petição '{tipo_peticao}' do arquivo não é válido.")
799
+ return caso, peticao, "", f"⚠️ Tipo '{tipo_peticao}' inválido. Selecione manualmente."
800
+ logger.info("Progresso carregado com sucesso.")
801
  return tipo_peticao, caso, peticao, "✅ Progresso carregado!"
802
+ except json.JSONDecodeError:
803
+ logger.error(f"Erro ao decodificar JSON do arquivo: {file_path}")
804
+ return "", "", "", "❌ Erro: Arquivo não é JSON válido."
805
+ except Exception as e:
806
+ logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True)
807
+ return "", "", "", f"❌ Erro inesperado ao carregar: {str(e)}"
808
 
 
809
  def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
810
+ prompt = f"""
811
+ Crie um caso fictício **detalhado e realista** para um exercício prático de elaboração de uma peça do tipo **"{tipo_peticao}"**, simulando um cenário de Exame de Ordem (OAB).
812
+ **Instruções Gerais:**
813
+ 1. Complexidade Adequada (15-25 linhas).
814
+ 2. Dados Essenciais: Nomes, locais, datas, valores, relação jurídica, conflito.
815
+ 3. Coerência: Fatos lógicos justificando a peça.
816
+ 4. Foco no Tipo de Peça: Cenário deve exigir a **{tipo_peticao}**.
817
+ **Instruções Específicas para "{tipo_peticao}":**
818
+ """
819
+ if tipo_peticao == "Petição Inicial":
820
+ prompt += """* Descreva violação de direitos (contrato descumprido, dano, etc.) onde cliente busca tutela inicial. * Inclua elementos para danos materiais/morais. * Forneça dados para qualificação das partes. * **Exemplo Tema:** Consumo, inadimplemento, cobrança indevida, despejo."""
821
+ elif tipo_peticao == "Contestação":
822
+ prompt += """* **PRIMEIRO:** Resumo da Petição Inicial recebida pelo réu (autor, alegações, pedidos). * **DEPOIS:** Versão dos fatos do RÉU, incluindo informações para: Alegar preliminares; Impugnar fatos; Apresentar defesa de mérito (pagamento, prescrição, etc.). * Forneça dados qualificação réu."""
823
+ elif tipo_peticao == "Reconvenção":
824
+ prompt += """* Siga estrutura **Contestação**: Resumo Petição Inicial + Versão Réu. * **ADICIONE fatos/fundamentos** para RÉU fazer pedido PRÓPRIO contra AUTOR ORIGINAL (CONEXO com ação/defesa). * **Exemplo:** Cobrança -> Réu contesta e reconvém pedindo indenização por danos no mesmo negócio."""
825
+ elif tipo_peticao == "Impugnação à Contestação":
826
+ prompt += """* Resumo Petição Inicial do Autor. * **Resumo Contestação** do réu, destacando: Preliminares arguidas; Fatos novos (impeditivos/modificativos/extintivos); Documentos juntados. * Objetivo: fornecer elementos para Autor rebater contestação."""
827
+ elif tipo_peticao == "Embargos de Declaração":
828
+ prompt += """* Breve descrição caso original. * **Apresente DISPOSITIVO ou TRECHO FUNDAMENTAL de DECISÃO JUDICIAL** (sentença/acórdão). * **INDIQUE CLARAMENTE vício** (omissão, contradição, obscuridade, erro material) NESSA DECISÃO, dando contexto. * **Exemplo:** "Sentença julgou X, mas omissa quanto a Y." OU "Acórdão afirmou X e concluiu Y (contradição)." """
829
+ elif tipo_peticao == "Recurso de Apelação":
830
+ prompt += """* Descrição caso original. * **Resumo da SENTENÇA** (procedente/improcedente/parcial, fundamentos juiz). * **FORNEÇA ELEMENTOS indicando ERRO na sentença** (in judicando/in procedendo) que justifiquem reforma/anulação. * **Exemplo:** "Sentença ignorou prova X." OU "Juiz indeferiu testemunha chave Y (cerceamento)." * Indique cliente (apelante)."""
831
+ elif tipo_peticao == "Agravo de Instrumento":
832
+ prompt += """* Descrição caso em andamento. * **Apresente DECISÃO INTERLOCUTÓRIA específica**. * Decisão DEVE se enquadrar no Art. 1015 ou justificar mitigação. * **FORNEÇA ELEMENTOS indicando ERRO** na decisão E, se possível, **URGÊNCIA**. * **Exemplo:** "Juiz indeferiu JG apesar de prova." OU "Deferiu liminar sem comprovação mora." * Indique cliente (agravante)."""
833
+ elif tipo_peticao == "Petição de Tutela Provisória":
834
+ prompt += """* Situação de conflito com **urgência** (risco iminente) ou direito **evidente**. * **FORNEÇA ELEMENTOS claros demonstrando:** **Urgência:** Probabilidade do direito (fumus) E Perigo de dano (periculum). **Evidência:** Hipótese do Art. 311 (abuso defesa, repetitivo, etc.). * Indique medida específica desejada. * Especifique se antecedente ou incidental."""
835
+ else:
836
+ prompt += "* Crie cenário jurídico cível com conflito claro."
837
+ prompt += """
838
+ **Finalize o caso com:**
839
+ "**Situação-Problema:** Na qualidade de advogado(a) de [Nome do Cliente], elabore a peça processual cabível ({tipo_peticao})."
840
+ **Importante:** Evite casos muito específicos. Foque em temas comuns. Seja criativo!
841
+ """
842
  return prompt
843
 
844
  def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
 
845
  logger.info(f"Solicitada gera��ão de caso para: {tipo_peticao}")
846
  try:
847
  if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO:
848
+ logger.warning(f"Tentativa de gerar caso com tipo inválido: {tipo_peticao}")
849
+ return "⚠️ Erro: Selecione um tipo de petição válido antes de gerar o caso."
850
+ logger.info("Estruturando prompt para geração de caso...")
851
  prompt = estruturar_prompt_geracao_caso(tipo_peticao)
852
+ logger.info("Chamando API para gerar caso...")
853
  caso_gerado = chamar_api(prompt)
854
  if caso_gerado.startswith(("❌","⏱️","⚠️")):
855
+ logger.error(f"Erro da API ao gerar caso: {caso_gerado}")
856
  return f"⚠️ Falha ao gerar caso fictício:\n{caso_gerado}"
857
+ frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de"
858
+ if frase_final_padrao.lower() not in caso_gerado.lower():
859
+ logger.warning("Frase 'Situação-Problema' não encontrada. Adicionando manualmente.")
860
+ nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado)
861
+ cliente_nome = nomes_possiveis[0] if nomes_possiveis else "[Cliente]"
862
+ caso_gerado += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente_nome}, elabore a peça processual cabível ({tipo_peticao})."
863
+ logger.info("Caso fictício gerado com sucesso.")
864
  return caso_gerado
865
  except Exception as e:
866
  logger.exception("Erro inesperado dentro de gerar_caso_ficticio:")
867
  return f"❌ Erro inesperado ao gerar o caso: {str(e)}"
868
 
 
 
869
  def interface_gradio():
870
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
871
  gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️")
872
  gr.Markdown("Analise e aprimore suas peças processuais!")
 
873
  with gr.Row():
874
+ with gr.Column(scale=1):
875
  gr.Markdown("### ⚙️ Configurações")
876
  tipo_peticao_input = gr.Dropdown(label="1. Selecione o Tipo de Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial")
877
  modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento", value=False)
 
892
  carregar_btn = gr.Button("📂 Carregar Caso/Peça")
893
  arquivo_progresso_upload = gr.File(label="Selecione .json", file_types=[".json"], scale=2)
894
  progresso_download_file = gr.File(label="Baixar Progresso", interactive=False)
895
+ with gr.Column(scale=3):
 
896
  gr.Markdown("### 1. Forneça o Caso / Contexto")
897
  with gr.Row():
898
  caso_input = gr.Textbox(label="📋 Caso ou Situação", lines=8, placeholder="Insira o texto COMPLETO do caso...")
 
908
  arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Arquivo", lines=4, interactive=False)
909
  btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary")
910
 
911
+ processing_message = gr.Markdown("", visible=False)
912
 
913
  gr.Markdown("--- \n ### 3. Resultados da Correção")
914
  with gr.Tabs():
 
926
 
927
  clear_btn = gr.Button("🗑️ Limpar Campos e Resultados")
928
 
 
 
 
929
  caso_btn.click(
930
  fn=lambda: gr.update(value="⏳ Gerando caso...", visible=True), outputs=processing_message
931
  ).then(
 
934
  fn=lambda: gr.update(value="", visible=False), outputs=processing_message
935
  )
936
 
 
937
  peticao_texto_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output)
938
  tipo_peticao_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output)
 
 
939
  peticao_arquivo_input.change(fn=process_file, inputs=[peticao_arquivo_input], outputs=[arquivo_preview_output])
940
 
 
941
  btn_corrigir.click(
942
  fn=lambda: gr.update(value="⏳ Processando texto... Gerando análise e correção...", visible=True),
943
+ outputs=processing_message
944
  ).then(
945
+ fn=corrigir_peticao,
946
+ inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento],
 
947
  outputs=[resultado_analise_output, resultado_peticao_corrigida_output,
948
  resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
949
  concurrency_limit=1
950
  ).then(
951
  fn=lambda: gr.update(value="", visible=False),
952
+ outputs=processing_message
953
  )
954
 
 
955
  btn_corrigir_arquivo.click(
956
  fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção...", visible=True),
957
  outputs=processing_message
958
  ).then(
959
  fn=corrigir_peticao,
960
  inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento],
 
961
  outputs=[resultado_analise_output, resultado_peticao_corrigida_output,
962
  resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
963
  concurrency_limit=1
 
966
  outputs=processing_message
967
  )
968
 
 
969
  config_update_btn.click(atualizar_config, inputs=[temperatura_slider, timeout_slider, max_tokens_slider], outputs=config_status)
 
 
970
  progresso_btn.click(fn=calcular_progresso, outputs=progresso_output)
971
  historico_btn.click(fn=mostrar_historico, outputs=historico_output)
972
 
 
973
  salvar_btn.click(
974
  fn=lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message
975
  ).then(
976
  fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
977
+ outputs=[gr.Textbox(visible=False), config_status, progresso_download_file]
978
  ).then(
979
+ fn=lambda x: gr.update(visible=bool(x)), inputs=[progresso_download_file], outputs=[progresso_download_file]
980
  ).then(
981
+ lambda: gr.update(value="", visible=False), None, processing_message
982
  )
983
 
 
984
  carregar_btn.click(
985
  fn=lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message
986
  ).then(
 
990
  lambda: gr.update(value="", visible=False), None, processing_message
991
  )
992
 
 
993
  clear_btn.click(
994
+ fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas aparecerão aqui...*", "", "", "", "", "", None, None, ""],
995
  outputs=[
996
  tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input,
997
  peticao_arquivo_input, arquivo_preview_output, dica_contextual_output,
998
  resultado_analise_output, resultado_peticao_corrigida_output,
999
  resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output,
1000
+ arquivo_progresso_upload, progresso_download_file, config_status
1001
  ]
1002
  )
1003
 
1004
  return demo
1005
 
 
1006
  if __name__ == "__main__":
1007
  logger.info("Iniciando a aplicação Gradio...")
1008
  app = interface_gradio()