GabrielGD commited on
Commit
eb320ef
·
verified ·
1 Parent(s): 5633276

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +510 -324
app.py CHANGED
@@ -7,6 +7,7 @@ import docx
7
  import io
8
  import time
9
  import tempfile
 
10
  from pdfplumber import open as pdfplumber_open
11
  from PIL import Image
12
  import pytesseract
@@ -18,65 +19,84 @@ import difflib
18
  from html import escape
19
  import random
20
  import logging
21
- import re
22
 
23
- # Configuração do Logging
24
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
25
  logger = logging.getLogger(__name__)
26
 
27
- # Leitura da API Key (usar Secrets no HF)
 
28
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
29
  if not GEMINI_API_KEY:
30
- raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a como Secret nas configurações do Space.")
 
 
 
 
 
 
31
 
32
- # API Endpoint
33
- GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={GEMINI_API_KEY}"
34
 
35
- # Configurações
36
  CONFIG = {
37
  "max_retry_attempts": 3,
38
  "retry_delay": 5,
39
- "timeout": 120,
40
- "temperatura": 0.3,
41
- "max_tokens": 8192
42
  }
43
 
44
- # Rate Limiter
45
- rate_limiter = RateLimiter(max_calls=30, period=60)
46
 
47
- # Constantes e Dicionários
48
  TIPOS_DE_PETICAO = [
49
- "Petição Inicial", "Contestação", "Reconvenção", "Impugnação à Contestação",
50
- "Embargos de Declaração", "Recurso de Apelação", "Agravo de Instrumento",
 
 
 
 
 
51
  "Petição de Tutela Provisória"
52
  ]
 
 
53
  DICAS_OAB_MELHORADO = {
 
54
  "Clareza e Concisão": "Seja direto e evite jargões excessivos. Facilite a leitura do juiz.",
55
  "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes ao seu argumento.",
56
  "Coerência Argumentativa": "Mantenha uma linha lógica de raciocínio do início ao fim da peça.",
57
  "Revisão Ortográfica e Gramatical": "Erros de português podem prejudicar a credibilidade da sua peça.",
58
  "Formatação": "Utilize parágrafos bem definidos, espaçamento adequado e destaque pontos importantes (negrito, itálico) com moderação.",
 
 
59
  "Endereçamento (Inicial)": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). Verifique a competência (material, territorial).",
60
  "Qualificação (Inicial)": "Inclua todos os dados do Art. 319, II, CPC. Use 'XXX' para dados não fornecidos.",
61
  "Fatos (Inicial)": "Narrativa cronológica, clara e objetiva, sem juízo de valor.",
62
  "Pedidos (Inicial)": "Certos, determinados e líquidos (sempre que possível). Cumule pedidos se necessário (Art. 327, CPC).",
63
  "Custas (Inicial)": "Sempre peça a juntada da guia ou a gratuidade de justiça (com declaração).",
64
  "Valor da Causa (Inicial)": "Deve corresponder ao benefício econômico pretendido (Art. 292, CPC).",
 
 
65
  "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes de discutir o mérito.",
66
  "Impugnação Específica (Contestação)": "Conteste cada fato alegado pelo autor, sob pena de presunção de veracidade (Art. 341, CPC).",
67
  "Mérito (Contestação)": "Apresente sua versão dos fatos e a fundamentação jurídica que ampara a improcedência dos pedidos do autor.",
68
  "Pedidos (Contestação)": "Requeira o acolhimento das preliminares e/ou a improcedência total dos pedidos do autor, além da condenação em custas e honorários.",
 
 
69
  "Pressupostos Recursais": "Verifique cabimento, tempestividade, preparo (custas recursais), regularidade formal e interesse/legitimidade.",
70
  "Dialeticidade (Recurso)": "Impugne especificamente os fundamentos da decisão recorrida (Art. 1010, II e III, CPC para Apelação).",
71
  "Delimitação da Matéria (Recurso)": "Indique claramente qual parte da decisão você está recorrendo.",
 
 
72
  "Fumus Boni Iuris": "Demonstre a plausibilidade do direito alegado com base em provas e argumentos sólidos.",
73
  "Periculum in Mora": "Evidencie o risco de dano irreparável ou de difícil reparação se a medida não for concedida imediatamente.",
74
  "Reversibilidade": "Se possível, argumente que a medida é reversível caso a decisão final seja diferente (Art. 300, §3º, CPC).",
75
  }
 
76
  HISTORICO = []
77
  CORRECAO_CACHE = {}
78
 
79
- # --- Funções de Extração de Texto ---
80
  def preprocess_image_for_ocr(img):
81
  try:
82
  gray = img.convert('L')
@@ -85,7 +105,7 @@ def preprocess_image_for_ocr(img):
85
  return Image.fromarray(thresh)
86
  except Exception as e:
87
  logger.error(f"Erro no pré-processamento da imagem: {str(e)}")
88
- return f"❌ Erro no pré-processamento: {str(e)}"
89
 
90
  def extract_text_from_pdf(file_path_or_obj):
91
  text = ""
@@ -96,30 +116,24 @@ def extract_text_from_pdf(file_path_or_obj):
96
  page_text = page.extract_text()
97
  if page_text:
98
  text += page_text + "\n"
99
- else:
100
  logger.warning(f"Página {i+1} sem texto extraível, tentando OCR.")
101
  try:
102
- img = page.to_image(resolution=300)
103
  preprocessed_img = preprocess_image_for_ocr(img.original)
104
- page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por')
105
  if page_ocr_text.strip():
106
  ocr_text += page_ocr_text + "\n"
107
  else:
108
  logger.warning(f"OCR falhou na página {i+1}.")
109
  ocr_text += f"[OCR falhou na página {i+1}]\n"
110
  except Exception as ocr_e:
111
- if "Tesseract is not installed or not in your PATH" in str(ocr_e):
112
- logger.error(f"Erro no OCR: Tesseract não encontrado ou não está no PATH. Verifique a instalação e o packages.txt no HF. {str(ocr_e)}")
113
- return "❌ Erro de OCR: Tesseract não encontrado. Verifique a configuração do ambiente."
114
- else:
115
- logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}")
116
- ocr_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n"
117
  pdf_reader.close()
118
 
119
- if "❌ Erro de OCR: Tesseract não encontrado." in ocr_text:
120
- return ocr_text
121
-
122
- if text.strip() and len(text) > len(ocr_text) * 0.5:
123
  logger.info("Usando texto extraído do PDF.")
124
  return text
125
  elif ocr_text.strip():
@@ -129,11 +143,9 @@ def extract_text_from_pdf(file_path_or_obj):
129
  logger.warning("Não foi possível extrair texto significativo do PDF.")
130
  return "⚠️ Não foi possível extrair texto do PDF (tentativa de OCR falhou ou texto inexistente)."
131
 
132
- except ImportError as ie:
133
- logger.error(f"Erro de importação ao processar PDF: {str(ie)}. Verifique se 'pdfplumber' está no requirements.txt.")
134
- return f"❌ Erro interno: Falta dependência para PDF ({str(ie)})."
135
  except Exception as e:
136
- logger.error(f"Erro geral ao processar o PDF: {str(e)}", exc_info=True)
 
137
  try:
138
  logger.info("Tentando fallback com PyPDF2.")
139
  text_fallback = ""
@@ -143,6 +155,7 @@ def extract_text_from_pdf(file_path_or_obj):
143
  for page in reader.pages:
144
  text_fallback += page.extract_text() or ""
145
  else:
 
146
  file_path_or_obj.seek(0)
147
  reader = PyPDF2.PdfReader(file_path_or_obj)
148
  for page in reader.pages:
@@ -157,33 +170,41 @@ def extract_text_from_pdf(file_path_or_obj):
157
  logger.error(f"Erro no fallback com PyPDF2: {str(fallback_e)}")
158
  return f"❌ Erro crítico ao processar o PDF: {str(e)}"
159
 
 
160
  def extract_text_from_docx(file_path_or_obj):
161
  try:
162
- doc = docx.Document(file_path_or_obj)
163
  text = ""
164
  for para in doc.paragraphs:
165
  text += para.text + "\n"
 
 
 
 
 
 
166
  return text if text.strip() else "⚠️ Documento DOCX vazio ou sem texto."
167
- except ImportError as ie:
168
- logger.error(f"Erro de importação ao processar DOCX: {str(ie)}. Verifique se 'python-docx' está no requirements.txt.")
169
- return f"❌ Erro interno: Falta dependência para DOCX ({str(ie)})."
170
  except Exception as e:
171
- logger.error(f"Erro ao processar o DOCX: {str(e)}", exc_info=True)
172
  return f"❌ Erro ao processar o DOCX: {str(e)}"
173
 
174
  def extract_text_from_txt(file_path_or_obj):
175
  try:
 
176
  if isinstance(file_path_or_obj, str):
177
  with open(file_path_or_obj, 'rb') as f:
178
  content = f.read()
 
179
  elif hasattr(file_path_or_obj, 'read'):
180
  content = file_path_or_obj.read()
 
181
  if hasattr(file_path_or_obj, 'seek'):
182
  try: file_path_or_obj.seek(0)
183
  except: pass
184
  else:
185
  return "❌ Tipo de entrada TXT inválido."
186
 
 
187
  encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
188
  for encoding in encodings:
189
  try:
@@ -195,51 +216,54 @@ def extract_text_from_txt(file_path_or_obj):
195
  logger.error("Não foi possível decodificar o arquivo TXT com encodings comuns.")
196
  return "❌ Não foi possível decodificar o arquivo TXT. Verifique o encoding."
197
  except Exception as e:
198
- logger.error(f"Erro ao processar o TXT: {str(e)}", exc_info=True)
199
  return f"❌ Erro ao processar o TXT: {str(e)}"
200
 
201
  def process_file(file_obj):
202
- logger.info(">>> PROCESS FILE INICIADO")
203
  if file_obj is None:
204
- logger.warning("--- Nenhum arquivo fornecido para processamento.")
205
  return "⚠️ Nenhum arquivo enviado."
206
  try:
207
- file_path = file_obj.name
 
 
 
 
 
 
208
  filename = os.path.basename(file_path).lower()
209
- logger.info(f"--- Processando arquivo: {filename} (path: {file_path})")
210
 
 
211
  try:
212
  file_size = os.path.getsize(file_path)
213
- logger.info(f"--- Tamanho do arquivo: {file_size} bytes")
214
- if file_size > 15 * 1024 * 1024:
215
- logger.warning(f"--- Arquivo {filename} excede o limite de tamanho.")
216
  return "⚠️ O arquivo é muito grande. Máx. 15 MB."
217
  except OSError as size_e:
218
- logger.warning(f"--- Não foi possível verificar o tamanho do arquivo: {size_e}")
 
219
 
220
- result_text = ""
221
  if filename.endswith('.pdf'):
222
- logger.info("--- Chamando extract_text_from_pdf")
223
- result_text = extract_text_from_pdf(file_path)
224
  elif filename.endswith('.docx'):
225
- logger.info("--- Chamando extract_text_from_docx")
226
- result_text = extract_text_from_docx(file_path)
227
  elif filename.endswith('.txt'):
228
- logger.info("--- Chamando extract_text_from_txt")
229
- result_text = extract_text_from_txt(file_path)
230
  else:
231
- logger.warning(f"--- Formato de arquivo não suportado: {filename}")
232
- result_text = "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
233
-
234
- logger.info(f"<<< PROCESS FILE FINALIZADO. Resultado: {result_text[:100]}...")
235
- return result_text
236
 
237
  except Exception as e:
238
  logger.error(f"Erro geral ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True)
239
- logger.info("<<< PROCESS FILE FINALIZADO COM ERRO GERAL")
240
  return f"❌ Erro inesperado ao processar o arquivo: {str(e)}"
 
241
 
242
- # --- Funções de Estruturação de Prompt ---
243
  def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
244
  base = """
245
  **Estrutura da Avaliação:**
@@ -294,6 +318,8 @@ def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=Fals
294
 
295
  **Requisitos Específicos para {tipo_peticao}:**
296
  """
 
 
297
  if tipo_peticao == "Petição Inicial":
298
  prompt_especifico += """
299
  - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I).
@@ -471,6 +497,7 @@ def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=Fals
471
  return prompt_especifico
472
 
473
  def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
 
474
  prompt = f"""
475
  **Caso/Contexto Fornecido:**
476
  ```
@@ -486,6 +513,8 @@ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
486
 
487
  **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):**
488
  """
 
 
489
  if tipo_peticao == "Petição Inicial":
490
  prompt += """
491
  1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável e justificado).
@@ -534,20 +563,8 @@ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
534
  10. **Continue com os Pedidos da Contestação** (improcedência da ação original, etc.).
535
  11. **Fechamento:** Padrão OAB.
536
  """
537
- elif tipo_peticao == "Impugnação à Contestação":
538
- prompt += """
539
- 1. **Endereçamento:** Ao juízo correto.
540
- 2. **Identificação:** Processo, Impugnante.
541
- 3. **Tempestividade:** Mencione o prazo de 15 dias.
542
- 4. **Estrutura:**
543
- * **Resposta às Preliminares:** Rebata cada preliminar arguida.
544
- * **Resposta aos Fatos Impeditivos/Modificativos/Extintivos:** Manifeste-se sobre fatos novos (Art. 350).
545
- * **Resposta aos Documentos:** Manifeste-se sobre documentos juntados (Art. 437).
546
- * **Ratificação da Inicial:** Reitere os termos e pedidos iniciais.
547
- 5. **Pedidos:** Rejeição das preliminares, refutação da defesa, procedência da ação.
548
- 6. **Provas:** Especifique provas para contrapor a defesa, se necessário.
549
- 7. **Fechamento:** Padrão OAB.
550
- """
551
  elif tipo_peticao == "Embargos de Declaração":
552
  prompt += """
553
  1. **Endereçamento:** Ao juízo que proferiu a decisão embargada.
@@ -560,45 +577,10 @@ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
560
  6. **Pedido:** Peça o conhecimento e ACOLHIMENTO dos embargos para sanar o(s) vício(s) apontado(s), especificando o que se espera (ex: "para que seja suprida a omissão quanto a...", "para que seja corrigida a contradição entre..."). Se cabível e fundamentado, pedir efeitos infringentes.
561
  7. **Fechamento:** Padrão OAB.
562
  """
563
- elif tipo_peticao == "Recurso de Apelação":
564
- prompt += """
565
- 1. **Peça de Interposição:** Ao juízo a quo (quem proferiu a sentença).
566
- 2. **Identificação:** Processo, Apelante, Apelado.
567
- 3. **Pressupostos:** Cabimento, tempestividade, preparo/JG.
568
- 4. **Pedido (Interposição):** Recebimento e remessa ao Tribunal.
569
- 5. **Razões Recursais:** Ao Tribunal (ad quem).
570
- 6. **Resumo:** Fatos, lide, sentença.
571
- 7. **Fundamentos (Art. 1010):** Error in procedendo/judicando. **Dialeticidade OBRIGATÓRIA**.
572
- 8. **Pedidos (Razões):** Conhecimento, PROVIMENTO para anular/reformar, inversão sucumbência.
573
- 9. **Fechamento:** Padrão OAB.
574
- """
575
- elif tipo_peticao == "Agravo de Instrumento":
576
- prompt += """
577
- 1. **Endereçamento:** Diretamente ao Tribunal.
578
- 2. **Identificação:** Processo de origem, Agravante, Agravado, Advogados (com endereços).
579
- 3. **Decisão Agravada:** Indicar e juntar cópia OBRIGATÓRIA.
580
- 4. **Cabimento:** Demonstrar hipótese do Art. 1015 ou mitigação.
581
- 5. **Pressupostos:** Tempestividade, preparo/JG.
582
- 6. **Peças Obrigatórias (Art. 1017):** Decisão, intimação, procurações. Facultativas: outras úteis.
583
- 7. **Razões:** Exposição do fato e direito para reforma/invalidação.
584
- 8. **Efeito Suspensivo/Tutela Antecipada:** Pedir fundamentadamente se necessário (Art. 1019, I).
585
- 9. **Pedidos Finais:** Conhecimento, efeito suspensivo/tutela (se houver), PROVIMENTO para reformar/invalidar.
586
- 10. **Fechamento:** Padrão OAB.
587
- """
588
- elif tipo_peticao == "Petição de Tutela Provisória":
589
- prompt += """
590
- 1. **Tipo:** Urgência ou Evidência? Antecedente ou Incidental?
591
- 2. **Endereçamento:** Juízo competente ou da causa.
592
- 3. **Identificação:** Partes, processo (se incidental).
593
- 4. **Requisitos:**
594
- * **Urgência (Art. 300):** Fumus Boni Iuris + Periculum in Mora (+ Reversibilidade, se possível).
595
- * **Evidência (Art. 311):** Uma das hipóteses legais (NÃO precisa periculum).
596
- 5. **Pedido Específico:** Qual medida liminar é solicitada?
597
- 6. **Se Antecedente:** Indicar pedido final ou aditar/completar no prazo.
598
- 7. **Pedidos:** Concessão da tutela, citação/intimação.
599
- 8. **Fechamento:** Padrão OAB.
600
- """
601
- else:
602
  prompt += """
603
  1. **Estrutura Lógica:** Organize a peça com começo, meio e fim.
604
  2. **Argumentos:** Apresente os argumentos de forma clara.
@@ -609,7 +591,48 @@ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
609
  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."
610
  return prompt
611
 
612
- # --- Função de Chamada da API ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
  def chamar_api(prompt):
614
  payload = {
615
  "contents": [{"parts": [{"text": prompt}]}],
@@ -617,10 +640,13 @@ def chamar_api(prompt):
617
  "temperature": CONFIG["temperatura"],
618
  "maxOutputTokens": CONFIG["max_tokens"],
619
  "topP": 0.95,
 
 
620
  }
621
  }
622
  headers = {"Content-Type": "application/json"}
623
  logger.info(f"Chamando API Gemini com {len(prompt)} caracteres de prompt. Temperatura: {CONFIG['temperatura']}, Max Tokens: {CONFIG['max_tokens']}")
 
624
 
625
  for tentativa in range(CONFIG["max_retry_attempts"]):
626
  try:
@@ -629,6 +655,7 @@ def chamar_api(prompt):
629
  response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"])
630
 
631
  logger.debug(f"Status Code recebido: {response.status_code}")
 
632
 
633
  if response.status_code == 429:
634
  retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1)))
@@ -640,13 +667,8 @@ def chamar_api(prompt):
640
  return "❌ Erro 429: Muitas requisições. Tente novamente mais tarde."
641
 
642
  if response.status_code != 200:
643
- error_info = {}
644
- error_msg = f"Erro desconhecido (Status {response.status_code})"
645
- try:
646
- error_info = response.json()
647
- error_msg = error_info.get("error", {}).get("message", error_msg)
648
- except json.JSONDecodeError:
649
- error_msg = f"{error_msg}. Resposta não é JSON: {response.text[:200]}"
650
  logger.error(f"Erro da API (Status {response.status_code}): {error_msg}")
651
  if tentativa < CONFIG["max_retry_attempts"] - 1:
652
  logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
@@ -656,6 +678,7 @@ def chamar_api(prompt):
656
 
657
  response_data = response.json()
658
 
 
659
  if not response_data.get("candidates"):
660
  prompt_feedback = response_data.get("promptFeedback", {})
661
  block_reason = prompt_feedback.get("blockReason")
@@ -666,10 +689,14 @@ def chamar_api(prompt):
666
  else:
667
  return "❌ Resposta vazia ou inválida da API (sem 'candidates'). Verifique o log para detalhes."
668
 
 
669
  candidate = response_data["candidates"][0]
 
 
670
  finish_reason = candidate.get("finishReason")
671
  if finish_reason not in [None, "STOP", "MAX_TOKENS"]:
672
  logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}")
 
673
  safety_ratings_candidate = candidate.get("safetyRatings", [])
674
  harm_categories = [r['category'] for r in safety_ratings_candidate if r['probability'] not in ['NEGLIGIBLE', 'LOW']]
675
  if harm_categories:
@@ -677,6 +704,7 @@ def chamar_api(prompt):
677
  else:
678
  return f"❌ A geração da resposta foi interrompida pela API. Motivo: {finish_reason}. Tente novamente ou simplifique a solicitação."
679
 
 
680
  if "content" not in candidate or "parts" not in candidate["content"] or not candidate["content"]["parts"]:
681
  logger.error("Estrutura de resposta inesperada da API: Faltando content/parts.")
682
  if tentativa < CONFIG["max_retry_attempts"] - 1:
@@ -687,6 +715,7 @@ def chamar_api(prompt):
687
 
688
  texto_gerado = candidate["content"]["parts"][0]["text"].strip()
689
  logger.info(f"API retornou com sucesso. Tamanho da resposta: {len(texto_gerado)} caracteres.")
 
690
  return texto_gerado
691
 
692
  except requests.Timeout:
@@ -705,10 +734,10 @@ def chamar_api(prompt):
705
  return f"❌ Erro na requisição: {e}"
706
  except json.JSONDecodeError as e:
707
  logger.error(f"Erro ao decodificar JSON da resposta da API: {e}")
708
- logger.debug(f"Resposta que causou erro de JSON: {response.text[:500]}")
709
  return f"❌ Erro ao processar a resposta da API (JSON inválido)."
710
  except Exception as e:
711
- logger.exception(f"Erro inesperado na chamada da API na tentativa {tentativa + 1}: {str(e)}")
712
  if tentativa < CONFIG["max_retry_attempts"] - 1:
713
  logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
714
  time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
@@ -717,34 +746,36 @@ def chamar_api(prompt):
717
 
718
  return "❌ Falha ao chamar a API após todas as tentativas."
719
 
720
- # --- Função Principal de Correção ---
721
  def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
722
- logger.info(f">>> CORRIGIR PETICAO INICIADO - Tipo: {tipo_peticao}, Treinamento: {modo_treinamento}")
723
  start_time = time.time()
724
 
725
  if not caso or not caso.strip():
726
- logger.warning("--- Caso não fornecido.")
727
- return "⚠️ Forneça o caso/contexto da peça.", "", "", "", "", ""
728
 
729
  if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO:
730
- logger.warning(f"--- Tipo de petição inválido ou não fornecido: {tipo_peticao}")
731
- return "⚠️ Selecione um tipo de petição válido.", "", "", "", "", ""
 
732
 
733
  peticao_conteudo = ""
734
  if peticao_arquivo is not None:
735
- logger.info("--- Processando arquivo de petição...")
736
  peticao_conteudo = process_file(peticao_arquivo)
737
  if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"):
738
- logger.error(f"--- Erro ao processar arquivo: {peticao_conteudo}")
739
- return peticao_conteudo, "", "", "", "", ""
740
- logger.info(f"--- Texto extraído do arquivo ({len(peticao_conteudo)} caracteres).")
741
  elif peticao_texto and peticao_texto.strip():
742
  peticao_conteudo = peticao_texto
743
- logger.info(f"--- Usando texto da petição fornecido ({len(peticao_conteudo)} caracteres).")
744
  else:
745
- logger.warning("--- Nenhuma petição fornecida (nem texto, nem arquivo).")
746
- return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", "", ""
747
 
 
748
  troll_triggers = [
749
  "escreva a petição", "faça a petição", "redija a petição",
750
  "elabore a petição", "crie a petição", "gere a petição",
@@ -763,41 +794,53 @@ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=Non
763
  "💡 Dica de ouro: Comece! Esboce a peça, mesmo que imperfeita. Depois a gente lapida juntos. Mande bala!",
764
  "😂 Pedir pra IA fazer a peça é tipo pedir pro GPS dirigir o carro. Ele te guia, mas quem tá no volante é você! Escreva aí!",
765
  ]
766
- logger.info("--- Detectado pedido para escrever petição. Retornando resposta troll.")
767
- return random.choice(respostas_troll), "", "", "", "", ""
768
 
 
 
769
  aviso_validacao = ""
 
 
 
 
770
 
 
771
  cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}"
772
  if cache_key in CORRECAO_CACHE:
773
- logger.info("--- Correção encontrada no cache.")
774
  cached_data = CORRECAO_CACHE[cache_key]
775
- diff_html_cache, _, _ = calcular_diff_interno(cached_data["peticao_original"], cached_data["peticao_corrigida"])
776
- logger.info("<<< CORRIGIR PETICAO FINALIZADO (CACHE HIT)")
777
- # Retorna 6 elementos para corresponder aos outputs
778
- return cached_data["analise_formatada"], cached_data["peticao_original"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"], diff_html_cache
779
 
780
- logger.info("--- Gerando prompt de análise...")
 
781
  prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento)
782
- logger.info("--- Enviando para análise da API...")
783
  analise = chamar_api(prompt_analise)
784
  if analise.startswith("❌") or analise.startswith("⏱️") or analise.startswith("⚠️"):
785
- logger.error(f"--- Erro na análise da petição: {analise}")
786
- return aviso_validacao + analise, peticao_conteudo, "", "", "", ""
787
- logger.info("--- Análise recebida da API.")
788
- analise_formatada = formatar_correcao(analise)
789
-
790
- logger.info("--- Gerando prompt para peça corrigida...")
 
 
791
  prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao)
792
- logger.info("--- Enviando para geração da peça corrigida API...")
793
  peticao_corrigida_bruta = chamar_api(prompt_peticao)
794
 
795
- peticao_corrigida_final = ""
796
  if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️") or peticao_corrigida_bruta.startswith("⚠️"):
797
- logger.error(f"--- Erro ao gerar peça corrigida: {peticao_corrigida_bruta}")
 
798
  peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}"
799
  else:
800
- logger.info("--- Peça corrigida recebida, aplicando pós-correção...")
 
 
 
801
  start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
802
  start_index = -1
803
  peticao_lower_corr = peticao_corrigida_bruta.lower()
@@ -810,51 +853,65 @@ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=Non
810
  if start_index != -1:
811
  peticao_extraida = peticao_corrigida_bruta[start_index:]
812
  else:
813
- peticao_extraida = peticao_corrigida_bruta
814
 
815
  peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao)
816
- logger.info("--- Pós-correção aplicada.")
 
817
 
818
- logger.info("--- Calculando diferenças...")
 
 
 
 
819
  diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final)
820
- logger.info("--- Diferenças calculadas.")
 
821
 
822
- correcao_completa_formatada = aviso_validacao + analise_formatada
 
823
  resultado_historico = {
824
  "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
825
  "tipo_peticao": tipo_peticao,
826
  "caso": caso,
827
  "peticao_original": peticao_conteudo,
828
- "analise_formatada": correcao_completa_formatada,
829
- "peticao_corrigida": peticao_corrigida_final,
830
  "resumo_diff": resumo_diff,
831
  "criticas_diff": criticas_diff
 
832
  }
833
  HISTORICO.append(resultado_historico)
834
- if len(HISTORICO) > 10:
835
  HISTORICO.pop(0)
836
 
 
837
  CORRECAO_CACHE[cache_key] = {
838
  "analise_formatada": correcao_completa_formatada,
839
  "peticao_original": peticao_conteudo,
840
  "peticao_corrigida": peticao_corrigida_final,
841
  "resumo_diff": resumo_diff,
842
  "criticas_diff": criticas_diff,
 
843
  }
844
 
845
  end_time = time.time()
846
- logger.info(f"<<< CORRIGIR PETICAO FINALIZADO em {end_time - start_time:.2f} segundos.")
847
- # Retorna 6 elementos para corresponder aos outputs
 
848
  return correcao_completa_formatada, peticao_conteudo, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html
849
 
850
- # --- Funções Auxiliares ---
851
  def formatar_correcao(texto):
 
 
852
  texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
853
  texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
854
  texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
855
  texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
856
  texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos")
857
 
 
858
  linhas = texto.split('\n')
859
  linhas_formatadas = []
860
  in_list = False
@@ -864,6 +921,7 @@ def formatar_correcao(texto):
864
  linhas_formatadas.append(f"\n**{linha_strip}**")
865
  in_list = False
866
  elif linha_strip.startswith("- ") or linha_strip.startswith("* "):
 
867
  prefix = " * " if in_list else "* "
868
  linhas_formatadas.append(prefix + linha_strip[2:])
869
  in_list = True
@@ -872,10 +930,13 @@ def formatar_correcao(texto):
872
  in_list = True
873
  else:
874
  linhas_formatadas.append(linha)
875
- if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")):
876
  in_list = False
877
 
 
 
878
  texto_final = "\n".join(linhas_formatadas)
 
879
  nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})"
880
  match = re.search(nota_pattern, texto_final, re.IGNORECASE)
881
  if match:
@@ -884,6 +945,7 @@ def formatar_correcao(texto):
884
 
885
  return texto_final
886
 
 
887
  def fornecer_dica_contextual(texto_peticao, tipo_peticao):
888
  if not texto_peticao or not texto_peticao.strip():
889
  return "Digite algo na petição para receber dicas..."
@@ -891,10 +953,12 @@ def fornecer_dica_contextual(texto_peticao, tipo_peticao):
891
  dicas_relevantes = []
892
  texto_lower = texto_peticao.lower()
893
 
 
894
  for chave, dica in DICAS_OAB_MELHORADO.items():
895
  if chave in ["Clareza e Concisão", "Fundamentação Legal", "Coerência Argumentativa", "Revisão Ortográfica e Gramatical", "Formatação"]:
896
  dicas_relevantes.append(f"💡 **{chave}:** {dica}")
897
 
 
898
  keywords_por_tipo = {
899
  "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor da causa", "provas", "audiência", "citação", "custas"],
900
  "Contestação": ["preliminar", "mérito", "impugnação", "fatos", "improcedência", "provas", "honorários"],
@@ -909,6 +973,7 @@ def fornecer_dica_contextual(texto_peticao, tipo_peticao):
909
  if tipo_peticao in keywords_por_tipo:
910
  for keyword in keywords_por_tipo[tipo_peticao]:
911
  if keyword in texto_lower:
 
912
  for chave, dica in DICAS_OAB_MELHORADO.items():
913
  chave_lower = chave.lower()
914
  if f"({tipo_peticao.split()[0].lower()})" in chave_lower or keyword in chave_lower:
@@ -919,11 +984,15 @@ def fornecer_dica_contextual(texto_peticao, tipo_peticao):
919
  if not dicas_relevantes:
920
  return "💡 Continue escrevendo ou selecione o tipo de peça para dicas mais específicas."
921
 
922
- return "\n\n".join(dicas_relevantes[:5])
 
 
 
923
 
924
  def calcular_diff_interno(texto_original, texto_corrigido):
 
925
  if not texto_original or not texto_corrigido or texto_corrigido.startswith("⚠️") or texto_corrigido.startswith("❌"):
926
- return "","",""
927
 
928
  orig_lines = texto_original.splitlines()
929
  corr_lines = texto_corrigido.splitlines()
@@ -953,6 +1022,8 @@ def calcular_diff_interno(texto_original, texto_corrigido):
953
  resultado_diff_html.append(f"<div class='linha-adicionada' title='Adicionado'><ins>{texto_escaped}</ins></div>")
954
  if any(kw in texto.lower() for kw in keywords_criticas_adicionadas):
955
  criticas.append(f"<span style='color: green;'>🟢 ADICIONADO (Potencialmente Essencial):</span> {texto_escaped}")
 
 
956
 
957
  resumo_estatistico = f"""
958
  **Resumo das Mudanças:**
@@ -963,6 +1034,7 @@ def calcular_diff_interno(texto_original, texto_corrigido):
963
 
964
  criticas_texto = "### 🚨 Mudanças Críticas / Essenciais Identificadas:\n" + "\n".join(criticas) if criticas else "Nenhuma mudança crítica/essencial automaticamente detectada."
965
 
 
966
  css = """
967
  <style>
968
  .diff-container { font-family: monospace; white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; }
@@ -979,6 +1051,9 @@ def calcular_diff_interno(texto_original, texto_corrigido):
979
 
980
  return visualizacao_detalhada_html, resumo_estatistico, criticas_texto
981
 
 
 
 
982
  def calcular_progresso():
983
  if not HISTORICO:
984
  return "📊 Nenhum progresso registrado ainda."
@@ -987,6 +1062,7 @@ def calcular_progresso():
987
  tipos_corrigidos = set()
988
  for entry in HISTORICO:
989
  tipos_corrigidos.add(entry["tipo_peticao"])
 
990
  nota_str = "N/A"
991
  match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"])
992
  if match:
@@ -995,7 +1071,7 @@ def calcular_progresso():
995
  notas.append(nota)
996
  nota_str = f"{nota:.1f}"
997
  except ValueError:
998
- pass
999
 
1000
  total_corridas = len(HISTORICO)
1001
  media = sum(notas) / len(notas) if notas else 0
@@ -1017,32 +1093,43 @@ def calcular_progresso():
1017
  """
1018
  return progresso_texto
1019
 
 
1020
  def mostrar_historico():
1021
  if not HISTORICO:
1022
  return "📜 Histórico vazio."
1023
 
1024
  historico_md = "### 📜 Histórico de Correções (Últimas 10)\n\n---\n"
 
1025
  for i, entry in enumerate(reversed(HISTORICO)):
1026
  caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso']
 
 
 
1027
  nota_str = "N/A"
1028
  match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"])
1029
  if match:
1030
  nota_str = f"{match.group(1).replace(',', '.')}/10"
1031
 
 
1032
  historico_md += f"**{len(HISTORICO)-i}. Data:** {entry['data']}\n"
1033
  historico_md += f"**Tipo:** {entry['tipo_peticao']}\n"
1034
  historico_md += f"**Nota:** {nota_str}\n"
1035
  historico_md += f"**Caso:** _{caso_resumo}_\n"
 
1036
  historico_md += "---\n"
1037
  return historico_md
1038
 
 
 
 
1039
  def salvar_progresso(caso, peticao, tipo_peticao):
1040
- logger.info(">>> SALVAR PROGRESSO INICIADO")
1041
  try:
1042
  if not caso or not peticao or not tipo_peticao:
1043
- logger.warning("--- Inputs insuficientes para salvar progresso.")
1044
  return "", "⚠️ Caso, Petição e Tipo de Petição são necessários para salvar.", None
1045
 
 
1046
  dados_progresso = {
1047
  "tipo_peticao": tipo_peticao,
1048
  "caso": caso,
@@ -1051,57 +1138,62 @@ def salvar_progresso(caso, peticao, tipo_peticao):
1051
  }
1052
 
1053
  json_content = json.dumps(dados_progresso, ensure_ascii=False, indent=4)
 
 
1054
  timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
1055
  filename = f"progresso_peticao_{timestamp_str}.json"
 
 
 
1056
  temp_dir = tempfile.gettempdir()
1057
  temp_file_path = os.path.join(temp_dir, filename)
1058
 
1059
  with open(temp_file_path, 'w', encoding='utf-8') as f:
1060
  f.write(json_content)
1061
 
1062
- logger.info(f"--- Progresso salvo com sucesso no arquivo temporário: {temp_file_path}")
 
1063
  success_message = (
1064
  f"✅ Progresso salvo! Baixe o arquivo `{filename}`. "
1065
  "Use o botão 'Carregar Progresso' e selecione este arquivo para restaurar."
1066
  )
1067
- logger.info("<<< SALVAR PROGRESSO FINALIZADO COM SUCESSO")
1068
- # Retorna o path para o componente gr.File poder oferecer o download
1069
  return json_content, success_message, temp_file_path
1070
 
1071
  except Exception as e:
1072
  logger.error(f"Erro ao salvar progresso: {str(e)}", exc_info=True)
1073
- logger.info("<<< SALVAR PROGRESSO FINALIZADO COM ERRO")
1074
- # Retorna None para o path do arquivo em caso de erro
1075
  return "", f"❌ Erro ao salvar o progresso: {str(e)}", None
1076
 
 
1077
  def carregar_progresso(arquivo_entrada):
1078
- logger.info(">>> CARREGAR PROGRESSO INICIADO")
1079
  if arquivo_entrada is None:
1080
- logger.warning("--- Nenhum arquivo selecionado para carregar.")
1081
  return "", "", "", "⚠️ Nenhum arquivo selecionado para carregar."
1082
 
1083
  try:
 
1084
  file_path = arquivo_entrada.name
1085
- logger.info(f"--- Carregando progresso do arquivo: {file_path}")
1086
 
1087
  with open(file_path, 'r', encoding='utf-8') as f:
1088
  dados = json.load(f)
1089
 
 
1090
  caso = dados.get("caso")
1091
  peticao = dados.get("peticao")
1092
  tipo_peticao = dados.get("tipo_peticao")
1093
 
1094
  if caso is None or peticao is None or tipo_peticao is None:
1095
- logger.warning(f"--- Arquivo JSON carregado não contém as chaves esperadas: {dados.keys()}")
1096
  return "", "", "", "❌ Erro: Arquivo de progresso inválido ou incompleto."
1097
 
1098
  if tipo_peticao not in TIPOS_DE_PETICAO:
1099
- logger.warning(f"--- Tipo de petição '{tipo_peticao}' do arquivo não é válido.")
1100
- # Retorna os dados carregados, mas deixa o dropdown vazio para seleção manual
1101
- return gr.update(value=None), caso, peticao, f"⚠️ Tipo de petição '{tipo_peticao}' do arquivo não é mais válido. Selecione manualmente."
1102
 
1103
- logger.info("<<< CARREGAR PROGRESSO FINALIZADO COM SUCESSO")
1104
- # Retorna os valores corretos para atualizar os componentes
1105
  return tipo_peticao, caso, peticao, "✅ Progresso carregado com sucesso!"
1106
 
1107
  except json.JSONDecodeError:
@@ -1109,69 +1201,119 @@ def carregar_progresso(arquivo_entrada):
1109
  return "", "", "", "❌ Erro: Arquivo selecionado não é um JSON válido."
1110
  except Exception as e:
1111
  logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True)
1112
- logger.info("<<< CARREGAR PROGRESSO FINALIZADO COM ERRO")
1113
  return "", "", "", f"❌ Erro inesperado ao carregar o progresso: {str(e)}"
1114
 
1115
- # --- Geração de Caso Fictício ---
1116
- def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
1117
- logger.info(f">>> GERAR CASO FICTICIO INICIADO para: {tipo_peticao}")
1118
- if not tipo_peticao:
1119
- logger.error("--- Tipo de petição não selecionado para gerar caso.")
1120
- return "Erro: Tipo de petição não selecionado para gerar caso."
1121
- prompt = estruturar_prompt_geracao_caso(tipo_peticao)
1122
- logger.info("--- Prompt de geração de caso estruturado.")
1123
- caso_gerado = chamar_api(prompt)
1124
- logger.info(f"--- Resultado da chamada API para gerar caso: {caso_gerado[:100]}...")
1125
 
1126
- if caso_gerado.startswith("❌") or caso_gerado.startswith("⏱️") or caso_gerado.startswith("⚠️"):
1127
- logger.error(f"--- Erro ao gerar caso fictício: {caso_gerado}")
1128
- return f"Falha ao gerar caso fictício.\n{caso_gerado}"
1129
 
1130
- frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de"
1131
- if frase_final_padrao.lower() not in caso_gerado.lower():
1132
- logger.warning("--- Frase 'Situação-Problema' não encontrada. Adicionando manualmente.")
1133
- nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado)
1134
- cliente_nome = nomes_possiveis[0] if nomes_possiveis else "[Cliente]"
1135
- 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})."
1136
 
1137
- logger.info("<<< GERAR CASO FICTICIO FINALIZADO COM SUCESSO")
1138
- return caso_gerado
 
 
 
1139
 
1140
- # --- Validação Básica ---
1141
- def validar_peticao_basica(peticao):
1142
- problemas = []
1143
- peticao_lower = peticao.lower()
1144
- if "excelentíssimo" not in peticao_lower and "eméritos" not in peticao_lower and "meritíssimo" not in peticao_lower :
1145
- problemas.append("Endereçamento inicial parece ausente ou incompleto.")
1146
- if "valor da causa" not in peticao_lower and "valor à causa" not in peticao_lower:
1147
- problemas.append("Atribuição de Valor da Causa não encontrada (essencial na Inicial).")
1148
- if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower :
1149
- problemas.append("Pedido de juntada de custas ou gratuidade de justiça não encontrado (essencial na Inicial).")
1150
- if "nestes termos" not in peticao_lower and "pede deferimento" not in peticao_lower:
1151
- problemas.append("Fechamento formal padrão ('Nestes termos pede deferimento') parece ausente.")
1152
- return problemas
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1153
 
1154
  # --- Interface Gradio ---
 
1155
  def interface_gradio():
1156
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
1157
  gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️")
1158
  gr.Markdown("Analise e aprimore suas peças processuais! Selecione o tipo, forneça o caso e sua peça (texto ou arquivo).")
1159
 
1160
- # Definir o Textbox oculto aqui para que as saídas dos botões possam referenciá-lo
1161
- peticao_original_hidden = gr.Textbox(visible=False, label="Original Hidden")
1162
-
1163
  with gr.Row():
1164
  with gr.Column(scale=1):
1165
- gr.Markdown("### Configurações")
1166
  tipo_peticao_input = gr.Dropdown(
1167
- label="Selecione o Tipo de Peça Processual",
1168
  choices=TIPOS_DE_PETICAO,
1169
- value="Petição Inicial"
1170
  )
1171
  modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento (Feedback Detalhado)", value=False)
1172
 
1173
  with gr.Accordion("⚙️ Configurações Avançadas", open=False):
1174
- temperatura_slider = gr.Slider(minimum=0.0, maximum=1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade (Temperatura)")
1175
  timeout_slider = gr.Slider(minimum=30, maximum=180, value=CONFIG["timeout"], step=10, label="Timeout da API (segundos)")
1176
  max_tokens_slider = gr.Slider(minimum=2048, maximum=8192, value=CONFIG["max_tokens"], step=256, label="Tamanho Máx. Resposta (Tokens)")
1177
  def atualizar_config(temp, timeout, tokens):
@@ -1196,8 +1338,11 @@ def interface_gradio():
1196
  salvar_btn = gr.Button("💾 Salvar Caso/Peça Atual")
1197
  carregar_btn = gr.Button("📂 Carregar Caso/Peça de Arquivo")
1198
  arquivo_progresso_upload = gr.File(label="Selecione o arquivo .json para carregar", file_types=[".json"], scale=2)
1199
- # Definido como interactive=False desde o início
1200
  progresso_download_file = gr.File(label="Baixar Progresso Salvo", interactive=False)
 
 
 
1201
 
1202
  with gr.Column(scale=3):
1203
  gr.Markdown("### 1. Forneça o Caso / Contexto")
@@ -1205,20 +1350,23 @@ def interface_gradio():
1205
  caso_input = gr.Textbox(label="📋 Caso ou Situação Processual", lines=10, placeholder="Insira o texto COMPLETO do caso ou da situação (ex: resumo da inicial para contestação, decisão para embargos/recurso).")
1206
  caso_btn = gr.Button("🎲 Gerar Caso Fictício", variant="secondary", scale=0)
1207
 
 
1208
  gr.Markdown("### 2. Insira Sua Peça Processual")
1209
  with gr.Tabs():
1210
  with gr.TabItem("✏️ Digitar Texto"):
1211
  peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=15, placeholder="Cole ou digite sua peça processual aqui...")
1212
- dica_contextual_output = gr.Markdown(value="*Dicas aparecerão aqui enquanto você digita...*")
1213
  btn_corrigir = gr.Button("🚀 CORRIGIR E ANALISAR!", variant="primary")
1214
 
1215
  with gr.TabItem("📎 Anexar Arquivo"):
1216
- peticao_arquivo_input = gr.File(label="Arquivo da Peça (.pdf, .docx, .txt - Máx 15MB)", file_types=[".pdf", ".docx", ".txt"], file_count="single")
1217
  arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Conteúdo do Arquivo (Texto Extraído)", lines=5, interactive=False)
1218
  btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary")
1219
 
1220
- processing_message = gr.Markdown("", visible=False)
1221
 
 
 
 
1222
  with gr.Tabs():
1223
  with gr.TabItem("📊 Análise e Correção"):
1224
  gr.Markdown("### 3. Resultados da Análise")
@@ -1235,123 +1383,99 @@ def interface_gradio():
1235
  criticas_output = gr.Markdown(label="Mudanças Críticas/Essenciais")
1236
  with gr.Column(scale=3):
1237
  visualizacao_detalhada_output = gr.HTML(label="📝 Alterações Detalhadas (Diff)")
 
 
 
1238
 
1239
- clear_btn = gr.Button("🗑️ Limpar Tudo")
1240
 
1241
- # --- FUNÇÕES WRAPPER (com try-except) ---
1242
- def corrigir_peticao_wrapper_texto(caso, tipo_peticao, peticao_texto, peticao_arquivo_state):
1243
- logger.info(">>> WRAPPER TEXTO INICIADO")
1244
- is_treinamento = modo_treinamento.value
1245
- logger.info(f"--- Modo Treinamento (Texto): {is_treinamento}")
1246
- try:
1247
- # A função corrigir_peticao retorna 6 valores
1248
- resultado = corrigir_peticao(caso, tipo_peticao, peticao_texto, peticao_arquivo_state, is_treinamento)
1249
- except Exception as e:
1250
- logger.error(f"Erro dentro do wrapper texto chamando corrigir_peticao: {e}", exc_info=True)
1251
- # Retornar tupla com erro para todos os outputs esperados (6 outputs)
1252
- return f"❌ Erro interno no processamento: {e}", "", "", "", "", ""
1253
- logger.info("<<< WRAPPER TEXTO FINALIZADO")
1254
- return resultado
1255
-
1256
- def corrigir_peticao_wrapper_arquivo(caso, tipo_peticao, peticao_texto_state, peticao_arquivo):
1257
- logger.info(">>> WRAPPER ARQUIVO INICIADO")
1258
- is_treinamento = modo_treinamento.value
1259
- logger.info(f"--- Modo Treinamento (Arquivo): {is_treinamento}")
1260
- try:
1261
- # A função corrigir_peticao retorna 6 valores
1262
- resultado = corrigir_peticao(caso, tipo_peticao, peticao_texto_state, peticao_arquivo, is_treinamento)
1263
- except Exception as e:
1264
- logger.error(f"Erro dentro do wrapper arquivo chamando corrigir_peticao: {e}", exc_info=True)
1265
- # Retornar tupla com erro para todos os outputs esperados (6 outputs)
1266
- return f"❌ Erro interno no processamento: {e}", "", "", "", "", ""
1267
- logger.info("<<< WRAPPER ARQUIVO FINALIZADO")
1268
- return resultado
1269
- # --- FIM DAS FUNÇÕES WRAPPER ---
1270
 
1271
 
1272
  # --- LÓGICA DOS BOTÕES E EVENTOS ---
1273
 
 
1274
  caso_btn.click(
1275
  fn=gerar_caso_ficticio,
1276
- inputs=[tipo_peticao_input],
1277
- outputs=[caso_input],
1278
- show_progress="full"
1279
  )
1280
 
 
1281
  peticao_texto_input.change(
1282
  fn=fornecer_dica_contextual,
1283
  inputs=[peticao_texto_input, tipo_peticao_input],
1284
  outputs=dica_contextual_output
1285
  )
 
1286
  tipo_peticao_input.change(
1287
  fn=fornecer_dica_contextual,
1288
  inputs=[peticao_texto_input, tipo_peticao_input],
1289
  outputs=dica_contextual_output
1290
  )
1291
 
 
 
1292
  peticao_arquivo_input.change(
1293
  fn=process_file,
1294
  inputs=[peticao_arquivo_input],
1295
- outputs=[arquivo_preview_output],
1296
- show_progress="minimal"
1297
  )
1298
 
1299
- # Botão Corrigir (Texto) - Usando wrapper e output corrigido
1300
  btn_corrigir.click(
1301
  fn=lambda: gr.update(value="⏳ Processando... Gerando análise e correção. Isso pode levar um minuto...", visible=True),
1302
  inputs=None,
1303
- outputs=[processing_message],
1304
- show_progress=False
1305
  ).then(
1306
- fn=corrigir_peticao_wrapper_texto,
1307
- inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None)],
1308
- # 6 outputs correspondendo aos 6 valores retornados por corrigir_peticao
1309
- outputs=[resultado_analise_output, peticao_original_hidden, resultado_peticao_corrigida_output,
1310
- resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
1311
- show_progress="full"
1312
  ).then(
1313
  fn=lambda: gr.update(value="", visible=False),
1314
  inputs=None,
1315
  outputs=[processing_message]
1316
  )
1317
 
1318
- # Botão Corrigir (Arquivo) - Usando wrapper e output corrigido
1319
  btn_corrigir_arquivo.click(
1320
  fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção. Isso pode levar um minuto...", visible=True),
1321
  inputs=None,
1322
- outputs=[processing_message],
1323
- show_progress=False
1324
  ).then(
1325
- fn=corrigir_peticao_wrapper_arquivo,
1326
- inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input],
1327
- # 6 outputs correspondendo aos 6 valores retornados por corrigir_peticao
1328
- outputs=[resultado_analise_output, peticao_original_hidden, resultado_peticao_corrigida_output,
1329
- resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
1330
- show_progress="full"
1331
  ).then(
1332
  fn=lambda: gr.update(value="", visible=False),
1333
  inputs=None,
1334
  outputs=[processing_message]
1335
  )
1336
 
1337
- # Botão Limpar - Usando gr.update()
1338
  clear_btn.click(
1339
  fn=lambda: [
1340
- gr.update(value="Petição Inicial"),
1341
- gr.update(value=False),
1342
- gr.update(value=None),
1343
- gr.update(value=None),
1344
- gr.update(value=None),
1345
- gr.update(value=""),
1346
- gr.update(value="", visible=False),
1347
- gr.update(value=""),
1348
- gr.update(value=""),
1349
- gr.update(value=""),
1350
- gr.update(value=""),
1351
- gr.update(value=""),
1352
- gr.update(value="*Dicas aparecerão aqui enquanto você digita...*"),
1353
- gr.update(value=None),
1354
- gr.update(value=None) # Limpa o valor do arquivo de download
1355
  ],
1356
  inputs=None,
1357
  outputs=[
@@ -1359,51 +1483,113 @@ def interface_gradio():
1359
  arquivo_preview_output, processing_message, resultado_analise_output,
1360
  resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output,
1361
  visualizacao_detalhada_output, dica_contextual_output, arquivo_progresso_upload,
1362
- progresso_download_file # Mantém o componente na lista de outputs
1363
  ]
1364
  )
1365
 
 
1366
  progresso_btn.click(fn=calcular_progresso, outputs=progresso_output)
1367
  historico_btn.click(fn=mostrar_historico, outputs=historico_output)
1368
 
1369
- # Salvar/Carregar Progresso - Lógica de visibilidade do botão de download removida
1370
  salvar_btn.click(
1371
  fn=lambda: gr.update(value="⏳ Salvando progresso atual...", visible=True),
1372
- inputs=None, outputs=processing_message, show_progress=False
1373
  ).then(
 
1374
  fn=salvar_progresso,
1375
  inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
1376
- # Saída para gr.File (terceiro output) atualiza o valor do componente progresso_download_file
1377
- outputs=[gr.Textbox(visible=False), config_status, progresso_download_file],
1378
- show_progress="minimal"
1379
- # REMOVIDO o .then() que mexia com a visibilidade
1380
  ).then(
1381
- lambda: gr.update(value="", visible=False), None, processing_message
 
 
 
 
1382
  )
1383
 
 
 
1384
  carregar_btn.click(
1385
  fn=lambda: gr.update(value="⏳ Carregando progresso do arquivo...", visible=True),
1386
- inputs=None, outputs=processing_message, show_progress=False
1387
  ).then(
1388
  fn=carregar_progresso,
1389
  inputs=[arquivo_progresso_upload],
1390
- outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status],
1391
- show_progress="minimal"
1392
  ).then(
1393
- lambda: gr.update(value="", visible=False), None, processing_message
1394
  )
1395
 
 
1396
  return demo
1397
 
1398
- # --- Bloco de Execução Principal ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1399
  if __name__ == "__main__":
1400
- logger.info("Iniciando a aplicação Gradio...")
1401
- try:
1402
- version = pytesseract.get_tesseract_version()
1403
- logger.info(f"Tesseract versão {version} detectado.")
1404
- except Exception as e:
1405
- logger.warning(f"Tesseract não encontrado ou erro ao verificar versão: {e}. OCR de PDFs pode falhar. Verifique packages.txt.")
 
 
 
 
 
 
 
1406
 
1407
  app = interface_gradio()
1408
- app.launch()
1409
- logger.info("Aplicação Gradio encerrada.")
 
 
7
  import io
8
  import time
9
  import tempfile
10
+ from dotenv import load_dotenv
11
  from pdfplumber import open as pdfplumber_open
12
  from PIL import Image
13
  import pytesseract
 
19
  from html import escape
20
  import random
21
  import logging
 
22
 
23
+ # Configura o logging para depuração
24
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
25
  logger = logging.getLogger(__name__)
26
 
27
+ load_dotenv()
28
+
29
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
30
  if not GEMINI_API_KEY:
31
+ raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a no arquivo .env ou como Secret.")
32
+
33
+ # Use um modelo mais recente se disponível e adequado, como gemini-1.5-flash
34
+ # GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={GEMINI_API_KEY}"
35
+ # Se gemini-1.5-flash não estiver disponível ou causar problemas, volte para gemini-pro ou o que funcionava
36
+ # GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={GEMINI_API_KEY}"
37
+ GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}"
38
 
 
 
39
 
 
40
  CONFIG = {
41
  "max_retry_attempts": 3,
42
  "retry_delay": 5,
43
+ "timeout": 120, # Aumentar timeout para prompts mais complexos
44
+ "temperatura": 0.3, # Um pouco mais de criatividade pode ser útil
45
+ "max_tokens": 8192 # Ajustar conforme necessidade e limites do modelo
46
  }
47
 
48
+ rate_limiter = RateLimiter(max_calls=30, period=60) # Reduzir um pouco a taxa para evitar 429
 
49
 
50
+ # --- TIPOS DE PEÇAS PROCESSUAIS ---
51
  TIPOS_DE_PETICAO = [
52
+ "Petição Inicial",
53
+ "Contestação",
54
+ "Reconvenção",
55
+ "Impugnação à Contestação",
56
+ "Embargos de Declaração",
57
+ "Recurso de Apelação",
58
+ "Agravo de Instrumento",
59
  "Petição de Tutela Provisória"
60
  ]
61
+
62
+ # --- DICIONÁRIO DE DICAS (Pode ser expandido por tipo) ---
63
  DICAS_OAB_MELHORADO = {
64
+ # Dicas Gerais (aplicáveis a várias peças)
65
  "Clareza e Concisão": "Seja direto e evite jargões excessivos. Facilite a leitura do juiz.",
66
  "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes ao seu argumento.",
67
  "Coerência Argumentativa": "Mantenha uma linha lógica de raciocínio do início ao fim da peça.",
68
  "Revisão Ortográfica e Gramatical": "Erros de português podem prejudicar a credibilidade da sua peça.",
69
  "Formatação": "Utilize parágrafos bem definidos, espaçamento adequado e destaque pontos importantes (negrito, itálico) com moderação.",
70
+
71
+ # Dicas Específicas (Exemplos para Petição Inicial)
72
  "Endereçamento (Inicial)": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). Verifique a competência (material, territorial).",
73
  "Qualificação (Inicial)": "Inclua todos os dados do Art. 319, II, CPC. Use 'XXX' para dados não fornecidos.",
74
  "Fatos (Inicial)": "Narrativa cronológica, clara e objetiva, sem juízo de valor.",
75
  "Pedidos (Inicial)": "Certos, determinados e líquidos (sempre que possível). Cumule pedidos se necessário (Art. 327, CPC).",
76
  "Custas (Inicial)": "Sempre peça a juntada da guia ou a gratuidade de justiça (com declaração).",
77
  "Valor da Causa (Inicial)": "Deve corresponder ao benefício econômico pretendido (Art. 292, CPC).",
78
+
79
+ # Dicas Específicas (Exemplos para Contestação)
80
  "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes de discutir o mérito.",
81
  "Impugnação Específica (Contestação)": "Conteste cada fato alegado pelo autor, sob pena de presunção de veracidade (Art. 341, CPC).",
82
  "Mérito (Contestação)": "Apresente sua versão dos fatos e a fundamentação jurídica que ampara a improcedência dos pedidos do autor.",
83
  "Pedidos (Contestação)": "Requeira o acolhimento das preliminares e/ou a improcedência total dos pedidos do autor, além da condenação em custas e honorários.",
84
+
85
+ # Dicas Específicas (Exemplo para Recurso)
86
  "Pressupostos Recursais": "Verifique cabimento, tempestividade, preparo (custas recursais), regularidade formal e interesse/legitimidade.",
87
  "Dialeticidade (Recurso)": "Impugne especificamente os fundamentos da decisão recorrida (Art. 1010, II e III, CPC para Apelação).",
88
  "Delimitação da Matéria (Recurso)": "Indique claramente qual parte da decisão você está recorrendo.",
89
+
90
+ # Dicas Específicas (Tutela Provisória)
91
  "Fumus Boni Iuris": "Demonstre a plausibilidade do direito alegado com base em provas e argumentos sólidos.",
92
  "Periculum in Mora": "Evidencie o risco de dano irreparável ou de difícil reparação se a medida não for concedida imediatamente.",
93
  "Reversibilidade": "Se possível, argumente que a medida é reversível caso a decisão final seja diferente (Art. 300, §3º, CPC).",
94
  }
95
+
96
  HISTORICO = []
97
  CORRECAO_CACHE = {}
98
 
99
+ # --- Funções de Extração de Texto (sem alterações significativas) ---
100
  def preprocess_image_for_ocr(img):
101
  try:
102
  gray = img.convert('L')
 
105
  return Image.fromarray(thresh)
106
  except Exception as e:
107
  logger.error(f"Erro no pré-processamento da imagem: {str(e)}")
108
+ return f"❌ Erro no pré-processamento: {str(e)}" # Retorna o erro
109
 
110
  def extract_text_from_pdf(file_path_or_obj):
111
  text = ""
 
116
  page_text = page.extract_text()
117
  if page_text:
118
  text += page_text + "\n"
119
+ else: # Tenta OCR se texto extraído for pobre/vazio
120
  logger.warning(f"Página {i+1} sem texto extraível, tentando OCR.")
121
  try:
122
+ img = page.to_image(resolution=300) # Aumenta resolução para OCR
123
  preprocessed_img = preprocess_image_for_ocr(img.original)
124
+ page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por') # Especifica português
125
  if page_ocr_text.strip():
126
  ocr_text += page_ocr_text + "\n"
127
  else:
128
  logger.warning(f"OCR falhou na página {i+1}.")
129
  ocr_text += f"[OCR falhou na página {i+1}]\n"
130
  except Exception as ocr_e:
131
+ logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}")
132
+ ocr_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n"
 
 
 
 
133
  pdf_reader.close()
134
 
135
+ # Decide qual texto retornar
136
+ if text.strip() and len(text) > len(ocr_text) * 0.5: # Se texto extraído parece razoável
 
 
137
  logger.info("Usando texto extraído do PDF.")
138
  return text
139
  elif ocr_text.strip():
 
143
  logger.warning("Não foi possível extrair texto significativo do PDF.")
144
  return "⚠️ Não foi possível extrair texto do PDF (tentativa de OCR falhou ou texto inexistente)."
145
 
 
 
 
146
  except Exception as e:
147
+ logger.error(f"Erro geral ao processar o PDF: {str(e)}")
148
+ # Tenta reabrir e usar PyPDF2 como fallback básico se pdfplumber falhar
149
  try:
150
  logger.info("Tentando fallback com PyPDF2.")
151
  text_fallback = ""
 
155
  for page in reader.pages:
156
  text_fallback += page.extract_text() or ""
157
  else:
158
+ # Precisa resetar o ponteiro se for objeto de arquivo
159
  file_path_or_obj.seek(0)
160
  reader = PyPDF2.PdfReader(file_path_or_obj)
161
  for page in reader.pages:
 
170
  logger.error(f"Erro no fallback com PyPDF2: {str(fallback_e)}")
171
  return f"❌ Erro crítico ao processar o PDF: {str(e)}"
172
 
173
+
174
  def extract_text_from_docx(file_path_or_obj):
175
  try:
176
+ doc = docx.Document(file_path_or_obj) # Funciona com path ou file object
177
  text = ""
178
  for para in doc.paragraphs:
179
  text += para.text + "\n"
180
+ # Opcional: Extrair texto de tabelas
181
+ # for table in doc.tables:
182
+ # for row in table.rows:
183
+ # for cell in row.cells:
184
+ # text += cell.text + "\t" # Separar células com tab
185
+ # text += "\n"
186
  return text if text.strip() else "⚠️ Documento DOCX vazio ou sem texto."
 
 
 
187
  except Exception as e:
188
+ logger.error(f"Erro ao processar o DOCX: {str(e)}")
189
  return f"❌ Erro ao processar o DOCX: {str(e)}"
190
 
191
  def extract_text_from_txt(file_path_or_obj):
192
  try:
193
+ # Se for um path, abre o arquivo
194
  if isinstance(file_path_or_obj, str):
195
  with open(file_path_or_obj, 'rb') as f:
196
  content = f.read()
197
+ # Se for um objeto de arquivo (como do Gradio), lê diretamente
198
  elif hasattr(file_path_or_obj, 'read'):
199
  content = file_path_or_obj.read()
200
+ # Tenta resetar o ponteiro se possível, para reutilização
201
  if hasattr(file_path_or_obj, 'seek'):
202
  try: file_path_or_obj.seek(0)
203
  except: pass
204
  else:
205
  return "❌ Tipo de entrada TXT inválido."
206
 
207
+ # Tenta decodificar com encodings comuns
208
  encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
209
  for encoding in encodings:
210
  try:
 
216
  logger.error("Não foi possível decodificar o arquivo TXT com encodings comuns.")
217
  return "❌ Não foi possível decodificar o arquivo TXT. Verifique o encoding."
218
  except Exception as e:
219
+ logger.error(f"Erro ao processar o TXT: {str(e)}")
220
  return f"❌ Erro ao processar o TXT: {str(e)}"
221
 
222
  def process_file(file_obj):
 
223
  if file_obj is None:
224
+ logger.warning("Nenhum arquivo fornecido para processamento.")
225
  return "⚠️ Nenhum arquivo enviado."
226
  try:
227
+ # Gradio > 3.0 usa tempfile._TemporaryFileWrapper que tem 'name'
228
+ # Gradio < 3.0 pode usar outros tipos, vamos checar a extensão pelo nome se disponível
229
+ file_path = getattr(file_obj, 'name', None)
230
+ if not file_path:
231
+ logger.error("Não foi possível obter o nome do arquivo do objeto.")
232
+ return "❌ Erro: Não foi possível identificar o tipo do arquivo."
233
+
234
  filename = os.path.basename(file_path).lower()
235
+ logger.info(f"Processando arquivo: {filename}")
236
 
237
+ # Verifica tamanho (usando o arquivo temporário diretamente se possível)
238
  try:
239
  file_size = os.path.getsize(file_path)
240
+ logger.info(f"Tamanho do arquivo: {file_size} bytes")
241
+ if file_size > 15 * 1024 * 1024: # Aumentado para 15MB
242
+ logger.warning(f"Arquivo {filename} excede o limite de tamanho.")
243
  return "⚠️ O arquivo é muito grande. Máx. 15 MB."
244
  except OSError as size_e:
245
+ logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}")
246
+ # Não impede o processamento, mas loga o aviso
247
 
248
+ # Determina o tipo e extrai o texto
249
  if filename.endswith('.pdf'):
250
+ # Passa o path do arquivo temporário
251
+ return extract_text_from_pdf(file_path)
252
  elif filename.endswith('.docx'):
253
+ # Passa o path do arquivo temporário
254
+ return extract_text_from_docx(file_path)
255
  elif filename.endswith('.txt'):
256
+ # Passa o path do arquivo temporário
257
+ return extract_text_from_txt(file_path)
258
  else:
259
+ logger.warning(f"Formato de arquivo não suportado: {filename}")
260
+ return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
 
 
 
261
 
262
  except Exception as e:
263
  logger.error(f"Erro geral ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True)
 
264
  return f"❌ Erro inesperado ao processar o arquivo: {str(e)}"
265
+ # --- ESTRUTURAÇÃO DE PROMPTS POR TIPO ---
266
 
 
267
  def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
268
  base = """
269
  **Estrutura da Avaliação:**
 
318
 
319
  **Requisitos Específicos para {tipo_peticao}:**
320
  """
321
+
322
+ # Adiciona requisitos específicos por tipo
323
  if tipo_peticao == "Petição Inicial":
324
  prompt_especifico += """
325
  - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I).
 
497
  return prompt_especifico
498
 
499
  def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
500
+ # Prompt base para gerar a versão corrigida
501
  prompt = f"""
502
  **Caso/Contexto Fornecido:**
503
  ```
 
513
 
514
  **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):**
515
  """
516
+
517
+ # Adiciona instruções específicas de CADA TIPO para a GERAÇÃO da peça corrigida
518
  if tipo_peticao == "Petição Inicial":
519
  prompt += """
520
  1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável e justificado).
 
563
  10. **Continue com os Pedidos da Contestação** (improcedência da ação original, etc.).
564
  11. **Fechamento:** Padrão OAB.
565
  """
566
+ # Adicionar instruções detalhadas para os outros tipos... (Impugnação, ED, Apelação, Agravo, Tutela)
567
+ # Exemplo para Embargos de Declaração:
 
 
 
 
 
 
 
 
 
 
 
 
568
  elif tipo_peticao == "Embargos de Declaração":
569
  prompt += """
570
  1. **Endereçamento:** Ao juízo que proferiu a decisão embargada.
 
577
  6. **Pedido:** Peça o conhecimento e ACOLHIMENTO dos embargos para sanar o(s) vício(s) apontado(s), especificando o que se espera (ex: "para que seja suprida a omissão quanto a...", "para que seja corrigida a contradição entre..."). Se cabível e fundamentado, pedir efeitos infringentes.
578
  7. **Fechamento:** Padrão OAB.
579
  """
580
+ # (Continuar para os outros tipos: Apelação, Agravo, Tutela, Impugnação)
581
+ # ... (Omitido por brevidade, mas seguir a mesma lógica dos prompts de análise)
582
+
583
+ else: # Fallback genérico
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
  prompt += """
585
  1. **Estrutura Lógica:** Organize a peça com começo, meio e fim.
586
  2. **Argumentos:** Apresente os argumentos de forma clara.
 
591
  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."
592
  return prompt
593
 
594
+
595
+ def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
596
+ # Aplica correções finais comuns ou específicas do tipo
597
+
598
+ # Correção geral de fechamento
599
+ if "Nestes termos, pede deferimento" in peticao_corrigida:
600
+ peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
601
+
602
+ # Adiciona elementos faltantes ESSENCIAIS (com base no tipo)
603
+ if tipo_peticao == "Petição Inicial":
604
+ if "juntada da guia de custas" not in peticao_corrigida.lower() and "gratuidade de justiça" not in peticao_corrigida.lower():
605
+ # Tenta inserir antes do fechamento final
606
+ partes = peticao_corrigida.split("Nestes termos pede deferimento")
607
+ if len(partes) == 2:
608
+ peticao_corrigida = partes[0] + "Requer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento" + partes[1]
609
+ else: # Adiciona no final antes do fechamento se a separação falhar
610
+ peticao_corrigida = peticao_corrigida.replace("Local, data.", "Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.")
611
+
612
+
613
+ if "audiência de conciliação" not in peticao_corrigida.lower() and "audiência de mediação" not in peticao_corrigida.lower():
614
+ # Tenta inserir antes do fechamento final
615
+ partes = peticao_corrigida.split("Nestes termos pede deferimento")
616
+ if len(partes) == 2:
617
+ peticao_corrigida = partes[0] + "Opta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\nNestes termos pede deferimento" + partes[1]
618
+ else: # Adiciona no final antes do fechamento
619
+ 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.")
620
+
621
+
622
+ # Garantir o fechamento padrão OAB no final
623
+ linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
624
+ fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."]
625
+ # Remove qualquer linha de fechamento existente que não seja a padrão
626
+ while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]):
627
+ linhas.pop()
628
+ # Adiciona o fechamento padrão
629
+ linhas.extend(fechamento_padrao)
630
+ peticao_corrigida = "\n".join(linhas)
631
+
632
+
633
+ return peticao_corrigida
634
+
635
+
636
  def chamar_api(prompt):
637
  payload = {
638
  "contents": [{"parts": [{"text": prompt}]}],
 
640
  "temperature": CONFIG["temperatura"],
641
  "maxOutputTokens": CONFIG["max_tokens"],
642
  "topP": 0.95,
643
+ # Stop sequences podem ajudar a evitar texto indesejado após a peça
644
+ # "stopSequences": ["```", "---"]
645
  }
646
  }
647
  headers = {"Content-Type": "application/json"}
648
  logger.info(f"Chamando API Gemini com {len(prompt)} caracteres de prompt. Temperatura: {CONFIG['temperatura']}, Max Tokens: {CONFIG['max_tokens']}")
649
+ # logger.debug(f"Prompt enviado (primeiros 500 chars): {prompt[:500]}")
650
 
651
  for tentativa in range(CONFIG["max_retry_attempts"]):
652
  try:
 
655
  response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"])
656
 
657
  logger.debug(f"Status Code recebido: {response.status_code}")
658
+ # logger.debug(f"Resposta da API (raw): {response.text[:500]}") # Log da resposta crua (cuidado com dados sensíveis)
659
 
660
  if response.status_code == 429:
661
  retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1)))
 
667
  return "❌ Erro 429: Muitas requisições. Tente novamente mais tarde."
668
 
669
  if response.status_code != 200:
670
+ error_info = response.json()
671
+ error_msg = error_info.get("error", {}).get("message", "Erro desconhecido na API")
 
 
 
 
 
672
  logger.error(f"Erro da API (Status {response.status_code}): {error_msg}")
673
  if tentativa < CONFIG["max_retry_attempts"] - 1:
674
  logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
 
678
 
679
  response_data = response.json()
680
 
681
+ # Verifica bloqueio por segurança ou conteúdo
682
  if not response_data.get("candidates"):
683
  prompt_feedback = response_data.get("promptFeedback", {})
684
  block_reason = prompt_feedback.get("blockReason")
 
689
  else:
690
  return "❌ Resposta vazia ou inválida da API (sem 'candidates'). Verifique o log para detalhes."
691
 
692
+
693
  candidate = response_data["candidates"][0]
694
+
695
+ # Verifica se a GERAÇÃO foi bloqueada
696
  finish_reason = candidate.get("finishReason")
697
  if finish_reason not in [None, "STOP", "MAX_TOKENS"]:
698
  logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}")
699
+ # Tenta obter a razão específica do bloqueio de segurança se houver
700
  safety_ratings_candidate = candidate.get("safetyRatings", [])
701
  harm_categories = [r['category'] for r in safety_ratings_candidate if r['probability'] not in ['NEGLIGIBLE', 'LOW']]
702
  if harm_categories:
 
704
  else:
705
  return f"❌ A geração da resposta foi interrompida pela API. Motivo: {finish_reason}. Tente novamente ou simplifique a solicitação."
706
 
707
+
708
  if "content" not in candidate or "parts" not in candidate["content"] or not candidate["content"]["parts"]:
709
  logger.error("Estrutura de resposta inesperada da API: Faltando content/parts.")
710
  if tentativa < CONFIG["max_retry_attempts"] - 1:
 
715
 
716
  texto_gerado = candidate["content"]["parts"][0]["text"].strip()
717
  logger.info(f"API retornou com sucesso. Tamanho da resposta: {len(texto_gerado)} caracteres.")
718
+ # logger.debug(f"Texto gerado (primeiros 500 chars): {texto_gerado[:500]}")
719
  return texto_gerado
720
 
721
  except requests.Timeout:
 
734
  return f"❌ Erro na requisição: {e}"
735
  except json.JSONDecodeError as e:
736
  logger.error(f"Erro ao decodificar JSON da resposta da API: {e}")
737
+ logger.debug(f"Resposta que causou erro de JSON: {response.text[:500]}") # Log da resposta problemática
738
  return f"❌ Erro ao processar a resposta da API (JSON inválido)."
739
  except Exception as e:
740
+ logger.exception(f"Erro inesperado na chamada da API na tentativa {tentativa + 1}: {str(e)}") # Loga o traceback completo
741
  if tentativa < CONFIG["max_retry_attempts"] - 1:
742
  logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
743
  time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
 
746
 
747
  return "❌ Falha ao chamar a API após todas as tentativas."
748
 
749
+
750
  def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
751
+ logger.info(f"Iniciando correção para tipo: {tipo_peticao}")
752
  start_time = time.time()
753
 
754
  if not caso or not caso.strip():
755
+ logger.warning("Caso não fornecido.")
756
+ return "⚠️ Forneça o caso/contexto da peça.", "", "", "", "" # Retorna tupla vazia para diff
757
 
758
  if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO:
759
+ logger.warning(f"Tipo de petição inválido ou não fornecido: {tipo_peticao}")
760
+ return "⚠️ Selecione um tipo de petição válido.", "", "", "", ""
761
+
762
 
763
  peticao_conteudo = ""
764
  if peticao_arquivo is not None:
765
+ logger.info("Processando arquivo de petição...")
766
  peticao_conteudo = process_file(peticao_arquivo)
767
  if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"):
768
+ logger.error(f"Erro ao processar arquivo: {peticao_conteudo}")
769
+ return peticao_conteudo, "", "", "", ""
770
+ logger.info(f"Texto extraído do arquivo ({len(peticao_conteudo)} caracteres).")
771
  elif peticao_texto and peticao_texto.strip():
772
  peticao_conteudo = peticao_texto
773
+ logger.info(f"Usando texto da petição fornecido ({len(peticao_conteudo)} caracteres).")
774
  else:
775
+ logger.warning("Nenhuma petição fornecida (nem texto, nem arquivo).")
776
+ return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", ""
777
 
778
+ # --- TROLL DETECTION ---
779
  troll_triggers = [
780
  "escreva a petição", "faça a petição", "redija a petição",
781
  "elabore a petição", "crie a petição", "gere a petição",
 
794
  "💡 Dica de ouro: Comece! Esboce a peça, mesmo que imperfeita. Depois a gente lapida juntos. Mande bala!",
795
  "😂 Pedir pra IA fazer a peça é tipo pedir pro GPS dirigir o carro. Ele te guia, mas quem tá no volante é você! Escreva aí!",
796
  ]
797
+ logger.info("Detectado pedido para escrever petição. Retornando resposta troll.")
798
+ return random.choice(respostas_troll), "", "", "", ""
799
 
800
+ # --- VALIDAÇÃO BÁSICA (Opcional e específica) ---
801
+ # Pode ser desativada ou adaptada, pois é muito focada na Inicial
802
  aviso_validacao = ""
803
+ # if tipo_peticao == "Petição Inicial":
804
+ # problemas = validar_peticao_basica(peticao_conteudo)
805
+ # if problemas:
806
+ # aviso_validacao = f"### ⚠️ Alertas Rápidos (Validação Inicial)\n" + "\n".join([f"- {p}" for p in problemas]) + "\n\n---\n\n"
807
 
808
+ # --- CACHE CHECK ---
809
  cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}"
810
  if cache_key in CORRECAO_CACHE:
811
+ logger.info("Correção encontrada no cache.")
812
  cached_data = CORRECAO_CACHE[cache_key]
813
+ # Retorna a tupla completa do cache
814
+ return cached_data["analise_formatada"], cached_data["peticao_original"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"]
 
 
815
 
816
+ # --- CHAMADA PARA ANÁLISE ---
817
+ logger.info("Gerando prompt de análise...")
818
  prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento)
819
+ logger.info("Enviando para análise da API...")
820
  analise = chamar_api(prompt_analise)
821
  if analise.startswith("❌") or analise.startswith("⏱️") or analise.startswith("⚠️"):
822
+ logger.error(f"Erro na análise da petição: {analise}")
823
+ # Retorna o erro e strings vazias para os outros campos
824
+ return aviso_validacao + analise, peticao_conteudo, "", "", ""
825
+ logger.info("Análise recebida da API.")
826
+ analise_formatada = formatar_correcao(analise) # Formata a análise para melhor visualização
827
+
828
+ # --- CHAMADA PARA GERAR PETIÇÃO CORRIGIDA ---
829
+ logger.info("Gerando prompt para peça corrigida...")
830
  prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao)
831
+ logger.info("Enviando para geração da peça corrigida API...")
832
  peticao_corrigida_bruta = chamar_api(prompt_peticao)
833
 
834
+ peticao_corrigida_final = "" # Inicializa
835
  if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️") or peticao_corrigida_bruta.startswith("⚠️"):
836
+ logger.error(f"Erro ao gerar peça corrigida: {peticao_corrigida_bruta}")
837
+ # Retorna a análise formatada e o erro da peça corrigida
838
  peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}"
839
  else:
840
+ logger.info("Peça corrigida recebida, aplicando pós-correção...")
841
+ # Tenta extrair APENAS a peça, caso a IA adicione texto extra
842
+ # Isso pode ser ajustado conforme o comportamento da IA
843
+ # Exemplo: procurar por "Excelentíssimo" ou similar e pegar dali pra frente
844
  start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
845
  start_index = -1
846
  peticao_lower_corr = peticao_corrigida_bruta.lower()
 
853
  if start_index != -1:
854
  peticao_extraida = peticao_corrigida_bruta[start_index:]
855
  else:
856
+ peticao_extraida = peticao_corrigida_bruta # Usa como está se não achar marcador
857
 
858
  peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao)
859
+ logger.info("Pós-correção aplicada.")
860
+
861
 
862
+ # --- DIFERENÇA ---
863
+ logger.info("Calculando diferenças...")
864
+ # Usar peticao_conteudo (original) e peticao_corrigida_final
865
+ # A função comparar_peticao_melhorada agora será chamada internamente
866
+ # para gerar o diff e os resumos, retornando tudo junto.
867
  diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final)
868
+ logger.info("Diferenças calculadas.")
869
+
870
 
871
+ # --- HISTÓRICO E CACHE ---
872
+ correcao_completa_formatada = aviso_validacao + analise_formatada # Não inclui a peça corrigida aqui
873
  resultado_historico = {
874
  "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
875
  "tipo_peticao": tipo_peticao,
876
  "caso": caso,
877
  "peticao_original": peticao_conteudo,
878
+ "analise_formatada": correcao_completa_formatada, # Salva análise formatada
879
+ "peticao_corrigida": peticao_corrigida_final, # Salva peça corrigida
880
  "resumo_diff": resumo_diff,
881
  "criticas_diff": criticas_diff
882
+ # Não salva o diff_html no histórico/cache por ser grande
883
  }
884
  HISTORICO.append(resultado_historico)
885
+ if len(HISTORICO) > 10: # Limita o histórico
886
  HISTORICO.pop(0)
887
 
888
+ # Salva no cache os dados relevantes para exibição
889
  CORRECAO_CACHE[cache_key] = {
890
  "analise_formatada": correcao_completa_formatada,
891
  "peticao_original": peticao_conteudo,
892
  "peticao_corrigida": peticao_corrigida_final,
893
  "resumo_diff": resumo_diff,
894
  "criticas_diff": criticas_diff,
895
+ # "diff_html": diff_html # Opcional: salvar se o cache puder lidar
896
  }
897
 
898
  end_time = time.time()
899
+ logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.")
900
+
901
+ # Retorna todos os resultados necessários para a interface
902
  return correcao_completa_formatada, peticao_conteudo, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html
903
 
904
+
905
  def formatar_correcao(texto):
906
+ # Esta função pode ser melhorada para usar Markdown de forma mais eficaz
907
+ # Ex: usar ** para títulos, * para ênfase, etc.
908
  texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
909
  texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
910
  texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
911
  texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
912
  texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos")
913
 
914
+ # Adiciona um pouco mais de formatação básica
915
  linhas = texto.split('\n')
916
  linhas_formatadas = []
917
  in_list = False
 
921
  linhas_formatadas.append(f"\n**{linha_strip}**")
922
  in_list = False
923
  elif linha_strip.startswith("- ") or linha_strip.startswith("* "):
924
+ # Melhora listas dentro de pontos a melhorar
925
  prefix = " * " if in_list else "* "
926
  linhas_formatadas.append(prefix + linha_strip[2:])
927
  in_list = True
 
930
  in_list = True
931
  else:
932
  linhas_formatadas.append(linha)
933
+ if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")): # Reseta se não for item de lista
934
  in_list = False
935
 
936
+
937
+ # Tenta formatar a nota
938
  texto_final = "\n".join(linhas_formatadas)
939
+ import re
940
  nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})"
941
  match = re.search(nota_pattern, texto_final, re.IGNORECASE)
942
  if match:
 
945
 
946
  return texto_final
947
 
948
+
949
  def fornecer_dica_contextual(texto_peticao, tipo_peticao):
950
  if not texto_peticao or not texto_peticao.strip():
951
  return "Digite algo na petição para receber dicas..."
 
953
  dicas_relevantes = []
954
  texto_lower = texto_peticao.lower()
955
 
956
+ # Adiciona dicas gerais primeiro
957
  for chave, dica in DICAS_OAB_MELHORADO.items():
958
  if chave in ["Clareza e Concisão", "Fundamentação Legal", "Coerência Argumentativa", "Revisão Ortográfica e Gramatical", "Formatação"]:
959
  dicas_relevantes.append(f"💡 **{chave}:** {dica}")
960
 
961
+ # Adiciona dicas específicas do tipo (se houver palavras-chave)
962
  keywords_por_tipo = {
963
  "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor da causa", "provas", "audiência", "citação", "custas"],
964
  "Contestação": ["preliminar", "mérito", "impugnação", "fatos", "improcedência", "provas", "honorários"],
 
973
  if tipo_peticao in keywords_por_tipo:
974
  for keyword in keywords_por_tipo[tipo_peticao]:
975
  if keyword in texto_lower:
976
+ # Busca dicas que contenham a keyword ou o tipo no nome
977
  for chave, dica in DICAS_OAB_MELHORADO.items():
978
  chave_lower = chave.lower()
979
  if f"({tipo_peticao.split()[0].lower()})" in chave_lower or keyword in chave_lower:
 
984
  if not dicas_relevantes:
985
  return "💡 Continue escrevendo ou selecione o tipo de peça para dicas mais específicas."
986
 
987
+ # Limita o número de dicas exibidas
988
+ return "\n\n".join(dicas_relevantes[:5]) # Mostra no máximo 5 dicas
989
+
990
+ # --- Funções de Comparação (Diff) ---
991
 
992
  def calcular_diff_interno(texto_original, texto_corrigido):
993
+ """Calcula a diferença e retorna HTML, resumo e críticas."""
994
  if not texto_original or not texto_corrigido or texto_corrigido.startswith("⚠️") or texto_corrigido.startswith("❌"):
995
+ return "","","" # Retorna vazio se não houver o que comparar
996
 
997
  orig_lines = texto_original.splitlines()
998
  corr_lines = texto_corrigido.splitlines()
 
1022
  resultado_diff_html.append(f"<div class='linha-adicionada' title='Adicionado'><ins>{texto_escaped}</ins></div>")
1023
  if any(kw in texto.lower() for kw in keywords_criticas_adicionadas):
1024
  criticas.append(f"<span style='color: green;'>🟢 ADICIONADO (Potencialmente Essencial):</span> {texto_escaped}")
1025
+ #elif linha.startswith('? '): # Linha de informação do diff, pode ser ignorada ou estilizada
1026
+ # resultado_diff_html.append(f"<div class='linha-info'>{texto_escaped}</div>")
1027
 
1028
  resumo_estatistico = f"""
1029
  **Resumo das Mudanças:**
 
1034
 
1035
  criticas_texto = "### 🚨 Mudanças Críticas / Essenciais Identificadas:\n" + "\n".join(criticas) if criticas else "Nenhuma mudança crítica/essencial automaticamente detectada."
1036
 
1037
+ # Estilo CSS para o HTML do Diff
1038
  css = """
1039
  <style>
1040
  .diff-container { font-family: monospace; white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; }
 
1051
 
1052
  return visualizacao_detalhada_html, resumo_estatistico, criticas_texto
1053
 
1054
+
1055
+ # --- Funções de Progresso e Histórico ---
1056
+
1057
  def calcular_progresso():
1058
  if not HISTORICO:
1059
  return "📊 Nenhum progresso registrado ainda."
 
1062
  tipos_corrigidos = set()
1063
  for entry in HISTORICO:
1064
  tipos_corrigidos.add(entry["tipo_peticao"])
1065
+ # Extrai a nota da análise formatada
1066
  nota_str = "N/A"
1067
  match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"])
1068
  if match:
 
1071
  notas.append(nota)
1072
  nota_str = f"{nota:.1f}"
1073
  except ValueError:
1074
+ pass # Ignora se não conseguir converter
1075
 
1076
  total_corridas = len(HISTORICO)
1077
  media = sum(notas) / len(notas) if notas else 0
 
1093
  """
1094
  return progresso_texto
1095
 
1096
+
1097
  def mostrar_historico():
1098
  if not HISTORICO:
1099
  return "📜 Histórico vazio."
1100
 
1101
  historico_md = "### 📜 Histórico de Correções (Últimas 10)\n\n---\n"
1102
+ # Mostra do mais recente para o mais antigo
1103
  for i, entry in enumerate(reversed(HISTORICO)):
1104
  caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso']
1105
+ peticao_resumo = (entry['peticao_original'][:80] + '...') if len(entry['peticao_original']) > 80 else entry['peticao_original']
1106
+
1107
+ # Tenta pegar a nota
1108
  nota_str = "N/A"
1109
  match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"])
1110
  if match:
1111
  nota_str = f"{match.group(1).replace(',', '.')}/10"
1112
 
1113
+
1114
  historico_md += f"**{len(HISTORICO)-i}. Data:** {entry['data']}\n"
1115
  historico_md += f"**Tipo:** {entry['tipo_peticao']}\n"
1116
  historico_md += f"**Nota:** {nota_str}\n"
1117
  historico_md += f"**Caso:** _{caso_resumo}_\n"
1118
+ # historico_md += f"**Petição Original:** _{peticao_resumo}_\n" # Pode poluir muito
1119
  historico_md += "---\n"
1120
  return historico_md
1121
 
1122
+
1123
+ # --- Funções de Salvar/Carregar Progresso ---
1124
+
1125
  def salvar_progresso(caso, peticao, tipo_peticao):
1126
+ logger.info("Iniciando salvamento de progresso...")
1127
  try:
1128
  if not caso or not peticao or not tipo_peticao:
1129
+ logger.warning("Inputs insuficientes para salvar progresso.")
1130
  return "", "⚠️ Caso, Petição e Tipo de Petição são necessários para salvar.", None
1131
 
1132
+ # Salva apenas os inputs atuais, não o histórico inteiro
1133
  dados_progresso = {
1134
  "tipo_peticao": tipo_peticao,
1135
  "caso": caso,
 
1138
  }
1139
 
1140
  json_content = json.dumps(dados_progresso, ensure_ascii=False, indent=4)
1141
+
1142
+ # Cria nome de arquivo com data/hora para evitar conflitos
1143
  timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
1144
  filename = f"progresso_peticao_{timestamp_str}.json"
1145
+
1146
+ # Usa NamedTemporaryFile para segurança e limpeza automática se algo der errado
1147
+ # Mas precisamos do path para o Gradio File output, então gerenciamos manualmente
1148
  temp_dir = tempfile.gettempdir()
1149
  temp_file_path = os.path.join(temp_dir, filename)
1150
 
1151
  with open(temp_file_path, 'w', encoding='utf-8') as f:
1152
  f.write(json_content)
1153
 
1154
+ logger.info(f"Progresso salvo com sucesso no arquivo temporário: {temp_file_path}")
1155
+
1156
  success_message = (
1157
  f"✅ Progresso salvo! Baixe o arquivo `{filename}`. "
1158
  "Use o botão 'Carregar Progresso' e selecione este arquivo para restaurar."
1159
  )
1160
+
1161
+ # Retorna o conteúdo (para visualização opcional), a mensagem e o path do arquivo para download
1162
  return json_content, success_message, temp_file_path
1163
 
1164
  except Exception as e:
1165
  logger.error(f"Erro ao salvar progresso: {str(e)}", exc_info=True)
 
 
1166
  return "", f"❌ Erro ao salvar o progresso: {str(e)}", None
1167
 
1168
+
1169
  def carregar_progresso(arquivo_entrada):
1170
+ logger.info("Iniciando carregamento de progresso...")
1171
  if arquivo_entrada is None:
 
1172
  return "", "", "", "⚠️ Nenhum arquivo selecionado para carregar."
1173
 
1174
  try:
1175
+ # Gradio File object tem o atributo 'name' com o path do arquivo temporário
1176
  file_path = arquivo_entrada.name
1177
+ logger.info(f"Carregando progresso do arquivo: {file_path}")
1178
 
1179
  with open(file_path, 'r', encoding='utf-8') as f:
1180
  dados = json.load(f)
1181
 
1182
+ # Verifica se as chaves esperadas existem
1183
  caso = dados.get("caso")
1184
  peticao = dados.get("peticao")
1185
  tipo_peticao = dados.get("tipo_peticao")
1186
 
1187
  if caso is None or peticao is None or tipo_peticao is None:
1188
+ logger.warning(f"Arquivo JSON carregado não contém as chaves esperadas: {dados.keys()}")
1189
  return "", "", "", "❌ Erro: Arquivo de progresso inválido ou incompleto."
1190
 
1191
  if tipo_peticao not in TIPOS_DE_PETICAO:
1192
+ logger.warning(f"Tipo de petição '{tipo_peticao}' do arquivo não é válido.")
1193
+ return caso, peticao, "", f"⚠️ Tipo de petição '{tipo_peticao}' do arquivo não é mais válido. Selecione manualmente."
 
1194
 
1195
+
1196
+ logger.info("Progresso carregado com sucesso.")
1197
  return tipo_peticao, caso, peticao, "✅ Progresso carregado com sucesso!"
1198
 
1199
  except json.JSONDecodeError:
 
1201
  return "", "", "", "❌ Erro: Arquivo selecionado não é um JSON válido."
1202
  except Exception as e:
1203
  logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True)
 
1204
  return "", "", "", f"❌ Erro inesperado ao carregar o progresso: {str(e)}"
1205
 
 
 
 
 
 
 
 
 
 
 
1206
 
1207
+ # --- GERAÇÃO DE CASOS ---
 
 
1208
 
1209
+ def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
1210
+ prompt = f"""
1211
+ 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).
 
 
 
1212
 
1213
+ **Instruções Gerais para o Caso:**
1214
+ 1. **Complexidade Adequada:** Deve ter informações suficientes para redigir a peça solicitada, mas sem ser excessivamente longo ou complicado. (Ideal: 15-25 linhas).
1215
+ 2. **Dados Essenciais:** Inclua nomes completos (fictícios) das partes envolvidas, locais (cidades/estados fictícios ou genéricos), datas relevantes, valores monetários (se aplicável), e uma descrição clara da relação jurídica e do conflito.
1216
+ 3. **Coerência:** Os fatos devem ser lógicos e justificar a peça processual indicada.
1217
+ 4. **Foco no Tipo de Peça:** O cenário deve ser construído para exigir especificamente a elaboração de um(a) **{tipo_peticao}**.
1218
 
1219
+ **Instruções Específicas para Gerar Caso para "{tipo_peticao}":**
1220
+ """
1221
+
1222
+ if tipo_peticao == "Petição Inicial":
1223
+ prompt += """
1224
+ * Descreva uma situação onde direitos foram violados (contrato descumprido, dano sofrido, etc.) e o cliente (autor) busca a tutela jurisdicional pela primeira vez.
1225
+ * Inclua elementos que permitam calcular danos materiais e/ou justificar danos morais.
1226
+ * Forneça dados para qualificação das partes (autor e réu).
1227
+ * **Exemplo de Tema:** Acidente de consumo, inadimplemento contratual, cobrança indevida, ação de despejo, etc.
1228
+ """
1229
+ elif tipo_peticao == "Contestação":
1230
+ prompt += """
1231
+ * **Apresente PRIMEIRO um resumo da Petição Inicial** que o réu (seu cliente) recebeu (quem processou, o que alegou, o que pediu).
1232
+ * **Depois, apresente a versão dos fatos do RÉU**, incluindo informações que permitam:
1233
+ * Alegar preliminares (ex: incompetência do juízo, ilegitimidade da parte, etc.).
1234
+ * Impugnar os fatos da inicial.
1235
+ * Apresentar defesa de mérito (ex: pagamento já realizado, culpa exclusiva da vítima, prescrição, etc.).
1236
+ * Forneça dados para qualificação do réu.
1237
+ """
1238
+ elif tipo_peticao == "Reconvenção":
1239
+ prompt += """
1240
+ * Siga a estrutura da **Contestação**: Apresente o resumo da Petição Inicial do autor original.
1241
+ * Apresente a versão dos fatos do Réu (seu cliente) para a defesa.
1242
+ * **ADICIONE fatos e fundamentos que permitam ao RÉU fazer um pedido PRÓPRIO contra o AUTOR ORIGINAL**, que seja CONEXO com a ação principal ou com a defesa.
1243
+ * **Exemplo:** Numa ação de cobrança, o réu contesta e reconvém pedindo indenização por danos que o autor lhe causou no mesmo negócio.
1244
+ """
1245
+ elif tipo_peticao == "Impugnação à Contestação":
1246
+ prompt += """
1247
+ * Apresente um resumo da Petição Inicial original do seu cliente (Autor).
1248
+ * **Apresente um resumo da Contestação** apresentada pelo réu, destacando:
1249
+ * As preliminares arguidas pelo réu (se houver).
1250
+ * Os fatos novos (impeditivos, modificativos, extintivos) alegados pelo réu.
1251
+ * Os documentos juntados pelo réu (se houver menção a algum relevante).
1252
+ * O objetivo é fornecer elementos para o Autor rebater a contestação.
1253
+ """
1254
+ elif tipo_peticao == "Embargos de Declaração":
1255
+ prompt += """
1256
+ * Descreva brevemente o caso original (quem processou quem e por quê).
1257
+ * **Apresente o DISPOSITIVO ou um TRECHO FUNDAMENTAL de uma DECISÃO JUDICIAL (sentença, acórdão, etc.)** proferida no caso.
1258
+ * **INDIQUE CLARAMENTE qual o vício (omissão, contradição, obscuridade ou erro material) presente NESSA DECISÃO**, fornecendo contexto para que o aluno possa identificar e fundamentar os Embargos.
1259
+ * **Exemplo:** "A sentença julgou procedente o pedido X, mas foi omissa quanto ao pedido Y que também constava na inicial." OU "O acórdão afirmou X na fundamentação, mas concluiu Y no dispositivo, gerando contradição."
1260
+ """
1261
+ elif tipo_peticao == "Recurso de Apelação":
1262
+ prompt += """
1263
+ * Descreva o caso original (Autor vs. Réu, causa de pedir, pedidos).
1264
+ * **Apresente um resumo da SENTENÇA proferida pelo juiz de primeira instância**, indicando se foi procedente, improcedente, parcialmente procedente, e os principais fundamentos do juiz.
1265
+ * **FORNEÇA ELEMENTOS que indiquem um ERRO na sentença (error in judicando ou in procedendo)**, que justifiquem a reforma ou anulação pelo Tribunal.
1266
+ * **Exemplo:** "A sentença julgou improcedente o pedido por falta de provas, ignorando o documento X juntado aos autos." OU "O juiz indeferiu a oitiva da testemunha chave Y, cerceando a defesa do réu, e julgou procedente."
1267
+ * Indique quem é o cliente (apelante).
1268
+ """
1269
+ elif tipo_peticao == "Agravo de Instrumento":
1270
+ prompt += """
1271
+ * Descreva o caso original em andamento.
1272
+ * **Apresente uma DECISÃO INTERLOCUTÓRIA específica** proferida pelo juiz (NÃO a sentença final).
1273
+ * **A decisão DEVE se enquadrar numa das hipóteses do Art. 1015 do CPC** (ou justificar a taxatividade mitigada).
1274
+ * **FORNEÇA ELEMENTOS que indiquem um ERRO nessa decisão interlocutória** E, se possível, a **URGÊNCIA** em revertê-la (risco de dano).
1275
+ * **Exemplo:** "O juiz indeferiu o pedido de gratuidade de justiça, embora o autor tenha comprovado baixa renda." OU "O juiz deferiu a liminar de busca e apreensão do bem X sem a devida comprovação da mora."
1276
+ * Indique quem é o cliente (agravante).
1277
+ """
1278
+ elif tipo_peticao == "Petição de Tutela Provisória":
1279
+ prompt += """
1280
+ * Descreva uma situação de conflito onde uma das partes precisa de uma medida **urgente** (risco iminente) ou onde seu direito é **evidente**.
1281
+ * **FORNEÇA ELEMENTOS claros que demonstrem:**
1282
+ * **Para Urgência:** A probabilidade do direito (fumus boni iuris) E o perigo de dano ou risco ao resultado útil do processo (periculum in mora).
1283
+ * **Para Evidência:** Uma das hipóteses do Art. 311 do CPC (ex: defesa protelatória, tese firmada em repetitivo, etc.).
1284
+ * Indique qual medida específica o cliente deseja (ex: bloqueio de bens, retirada de nome do SPC, etc.).
1285
+ * Especifique se a tutela é pedida ANTES do processo principal (antecedente) ou DENTRO de um processo já existente (incidental).
1286
+ """
1287
+ else:
1288
+ prompt += "* Crie um cenário jurídico cível com um conflito claro entre duas ou mais partes."
1289
+
1290
+ prompt += """
1291
+ **Finalize o caso com:**
1292
+ "**Situação-Problema:** Na qualidade de advogado(a) de [Nome do Cliente], elabore a peça processual cabível ({tipo_peticao})."
1293
+
1294
+ **Importante:** Evite criar casos que dependam de conhecimento jurídico EXTREMAMENTE específico ou de nicho. Foque em temas comuns do direito civil e processual civil. Seja criativo!
1295
+ """
1296
+ return prompt
1297
 
1298
  # --- Interface Gradio ---
1299
+
1300
  def interface_gradio():
1301
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
1302
  gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️")
1303
  gr.Markdown("Analise e aprimore suas peças processuais! Selecione o tipo, forneça o caso e sua peça (texto ou arquivo).")
1304
 
 
 
 
1305
  with gr.Row():
1306
  with gr.Column(scale=1):
1307
+ gr.Markdown("### настроики (Settings)") # Configurações em Russo para brincar ;)
1308
  tipo_peticao_input = gr.Dropdown(
1309
+ label=" Selecione o Tipo de Peça Processual",
1310
  choices=TIPOS_DE_PETICAO,
1311
+ value="Petição Inicial" # Default
1312
  )
1313
  modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento (Feedback Detalhado)", value=False)
1314
 
1315
  with gr.Accordion("⚙️ Configurações Avançadas", open=False):
1316
+ temperatura_slider = gr.Slider(minimum=0.0, maximum=1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade (Temperatura)", info="Valores maiores geram respostas mais criativas, porém menos previsíveis.")
1317
  timeout_slider = gr.Slider(minimum=30, maximum=180, value=CONFIG["timeout"], step=10, label="Timeout da API (segundos)")
1318
  max_tokens_slider = gr.Slider(minimum=2048, maximum=8192, value=CONFIG["max_tokens"], step=256, label="Tamanho Máx. Resposta (Tokens)")
1319
  def atualizar_config(temp, timeout, tokens):
 
1338
  salvar_btn = gr.Button("💾 Salvar Caso/Peça Atual")
1339
  carregar_btn = gr.Button("📂 Carregar Caso/Peça de Arquivo")
1340
  arquivo_progresso_upload = gr.File(label="Selecione o arquivo .json para carregar", file_types=[".json"], scale=2)
1341
+ # Para o download
1342
  progresso_download_file = gr.File(label="Baixar Progresso Salvo", interactive=False)
1343
+ # Para visualização opcional do JSON salvo
1344
+ # progresso_json_output = gr.Textbox(label="Progresso Salvo (JSON)", lines=3, interactive=False, visible=False)
1345
+
1346
 
1347
  with gr.Column(scale=3):
1348
  gr.Markdown("### 1. Forneça o Caso / Contexto")
 
1350
  caso_input = gr.Textbox(label="📋 Caso ou Situação Processual", lines=10, placeholder="Insira o texto COMPLETO do caso ou da situação (ex: resumo da inicial para contestação, decisão para embargos/recurso).")
1351
  caso_btn = gr.Button("🎲 Gerar Caso Fictício", variant="secondary", scale=0)
1352
 
1353
+
1354
  gr.Markdown("### 2. Insira Sua Peça Processual")
1355
  with gr.Tabs():
1356
  with gr.TabItem("✏️ Digitar Texto"):
1357
  peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=15, placeholder="Cole ou digite sua peça processual aqui...")
1358
+ dica_contextual_output = gr.Markdown(value="*Dicas aparecerão aqui enquanto você digita...*") # Usar Markdown
1359
  btn_corrigir = gr.Button("🚀 CORRIGIR E ANALISAR!", variant="primary")
1360
 
1361
  with gr.TabItem("📎 Anexar Arquivo"):
1362
+ peticao_arquivo_input = gr.File(label="Arquivo da Peça (.pdf, .docx, .txt - Máx 15MB)", file_types=[".pdf", ".docx", ".txt"])
1363
  arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Conteúdo do Arquivo (Texto Extraído)", lines=5, interactive=False)
1364
  btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary")
1365
 
 
1366
 
1367
+ processing_message = gr.Markdown("", visible=False) # Para feedback de processamento
1368
+
1369
+ # --- ABA DE RESULTADOS ---
1370
  with gr.Tabs():
1371
  with gr.TabItem("📊 Análise e Correção"):
1372
  gr.Markdown("### 3. Resultados da Análise")
 
1383
  criticas_output = gr.Markdown(label="Mudanças Críticas/Essenciais")
1384
  with gr.Column(scale=3):
1385
  visualizacao_detalhada_output = gr.HTML(label="📝 Alterações Detalhadas (Diff)")
1386
+ # Saídas ocultas para guardar os textos comparados
1387
+ # peticao_aluno_output_diff = gr.Textbox(visible=False)
1388
+ # peticao_corrigida_output_diff = gr.Textbox(visible=False)
1389
 
 
1390
 
1391
+ # --- Botão Limpar ---
1392
+ clear_btn = gr.Button("🗑️ Limpar Tudo")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1393
 
1394
 
1395
  # --- LÓGICA DOS BOTÕES E EVENTOS ---
1396
 
1397
+ # Gerar Caso Fictício
1398
  caso_btn.click(
1399
  fn=gerar_caso_ficticio,
1400
+ inputs=[tipo_peticao_input], # Passa o tipo selecionado
1401
+ outputs=[caso_input]
 
1402
  )
1403
 
1404
+ # Atualizar dicas contextuais
1405
  peticao_texto_input.change(
1406
  fn=fornecer_dica_contextual,
1407
  inputs=[peticao_texto_input, tipo_peticao_input],
1408
  outputs=dica_contextual_output
1409
  )
1410
+ # Atualizar dicas se o TIPO mudar e já houver texto
1411
  tipo_peticao_input.change(
1412
  fn=fornecer_dica_contextual,
1413
  inputs=[peticao_texto_input, tipo_peticao_input],
1414
  outputs=dica_contextual_output
1415
  )
1416
 
1417
+
1418
+ # Processar arquivo para preview
1419
  peticao_arquivo_input.change(
1420
  fn=process_file,
1421
  inputs=[peticao_arquivo_input],
1422
+ outputs=[arquivo_preview_output]
 
1423
  )
1424
 
1425
+ # Botão Corrigir (Texto)
1426
  btn_corrigir.click(
1427
  fn=lambda: gr.update(value="⏳ Processando... Gerando análise e correção. Isso pode levar um minuto...", visible=True),
1428
  inputs=None,
1429
+ outputs=[processing_message]
 
1430
  ).then(
1431
+ fn=corrigir_peticao,
1432
+ # Inputs: caso, tipo_peticao, peticao_texto, peticao_arquivo=None, modo_treinamento
1433
+ inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento],
1434
+ # Outputs: analise_fmt, peticao_orig, peticao_corr, resumo_diff, criticas_diff, diff_html
1435
+ outputs=[resultado_analise_output, gr.Textbox(visible=False), resultado_peticao_corrigida_output, # peticao_aluno_output_diff (oculto)
1436
+ resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output]
1437
  ).then(
1438
  fn=lambda: gr.update(value="", visible=False),
1439
  inputs=None,
1440
  outputs=[processing_message]
1441
  )
1442
 
1443
+ # Botão Corrigir (Arquivo)
1444
  btn_corrigir_arquivo.click(
1445
  fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção. Isso pode levar um minuto...", visible=True),
1446
  inputs=None,
1447
+ outputs=[processing_message]
 
1448
  ).then(
1449
+ fn=corrigir_peticao,
1450
+ # Inputs: caso, tipo_peticao, peticao_texto=None, peticao_arquivo, modo_treinamento
1451
+ inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento],
1452
+ # Outputs: analise_fmt, peticao_orig, peticao_corr, resumo_diff, criticas_diff, diff_html
1453
+ outputs=[resultado_analise_output, gr.Textbox(visible=False), resultado_peticao_corrigida_output, # peticao_aluno_output_diff (oculto)
1454
+ resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output]
1455
  ).then(
1456
  fn=lambda: gr.update(value="", visible=False),
1457
  inputs=None,
1458
  outputs=[processing_message]
1459
  )
1460
 
1461
+ # Botão Limpar
1462
  clear_btn.click(
1463
  fn=lambda: [
1464
+ "Petição Inicial", # Reseta Dropdown
1465
+ False, # Reseta Modo Treinamento
1466
+ None, # Caso
1467
+ None, # Petição Texto
1468
+ None, # Petição Arquivo
1469
+ None, # Arquivo Preview
1470
+ "", # Mensagem Processamento
1471
+ "", # Análise
1472
+ "", # Petição Corrigida
1473
+ "", # Resumo Diff
1474
+ "", # Críticas Diff
1475
+ "", # HTML Diff
1476
+ "", # Dicas Contextuais
1477
+ None, # Arquivo Progresso Upload
1478
+ None, # Arquivo Progresso Download
1479
  ],
1480
  inputs=None,
1481
  outputs=[
 
1483
  arquivo_preview_output, processing_message, resultado_analise_output,
1484
  resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output,
1485
  visualizacao_detalhada_output, dica_contextual_output, arquivo_progresso_upload,
1486
+ progresso_download_file
1487
  ]
1488
  )
1489
 
1490
+ # Botões de Progresso e Histórico
1491
  progresso_btn.click(fn=calcular_progresso, outputs=progresso_output)
1492
  historico_btn.click(fn=mostrar_historico, outputs=historico_output)
1493
 
1494
+ # Salvar Progresso
1495
  salvar_btn.click(
1496
  fn=lambda: gr.update(value="⏳ Salvando progresso atual...", visible=True),
1497
+ inputs=None, outputs=processing_message
1498
  ).then(
1499
+ # Passa os campos relevantes para salvar
1500
  fn=salvar_progresso,
1501
  inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
1502
+ # Retorna: json_content (não usado direto), success_message, temp_file_path
1503
+ outputs=[gr.Textbox(visible=False), config_status, progresso_download_file] # Usa config_status para msg, e file para download
 
 
1504
  ).then(
1505
+ fn=lambda x: gr.update(visible=bool(x)), # Mostra o botão de download se path for retornado
1506
+ inputs=[progresso_download_file],
1507
+ outputs=[progresso_download_file]
1508
+ ).then(
1509
+ lambda: gr.update(value="", visible=False), None, processing_message # Limpa msg processamento
1510
  )
1511
 
1512
+
1513
+ # Carregar Progresso
1514
  carregar_btn.click(
1515
  fn=lambda: gr.update(value="⏳ Carregando progresso do arquivo...", visible=True),
1516
+ inputs=None, outputs=processing_message
1517
  ).then(
1518
  fn=carregar_progresso,
1519
  inputs=[arquivo_progresso_upload],
1520
+ # Retorna: tipo_peticao, caso, peticao, status_message
1521
+ outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status] # Atualiza os campos e mostra status
1522
  ).then(
1523
+ lambda: gr.update(value="", visible=False), None, processing_message # Limpa msg processamento
1524
  )
1525
 
1526
+
1527
  return demo
1528
 
1529
+ # --- Funções de Validação (Manter simples ou remover/adaptar) ---
1530
+ def validar_peticao_basica(peticao):
1531
+ # Esta validação é MUITO focada em Petição Inicial.
1532
+ # Poderia ser desativada ou ter lógica condicional por tipo.
1533
+ # Por enquanto, vamos mantê-la simples e avisar que é básica.
1534
+ problemas = []
1535
+ peticao_lower = peticao.lower()
1536
+ if "excelentíssimo" not in peticao_lower and "eméritos" not in peticao_lower and "meritíssimo" not in peticao_lower :
1537
+ problemas.append("Endereçamento inicial parece ausente ou incompleto.")
1538
+ #if "art. 319" not in peticao and "cpc" not in peticao_lower:
1539
+ # problemas.append("Menção ao Art. 319 do CPC não encontrada (comum na Inicial).")
1540
+ if "valor da causa" not in peticao_lower and "valor à causa" not in peticao_lower:
1541
+ problemas.append("Atribuição de Valor da Causa não encontrada (essencial na Inicial).")
1542
+ if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower :
1543
+ problemas.append("Pedido de juntada de custas ou gratuidade de justiça não encontrado (essencial na Inicial).")
1544
+ if "nestes termos" not in peticao_lower and "pede deferimento" not in peticao_lower:
1545
+ problemas.append("Fechamento formal padrão ('Nestes termos pede deferimento') parece ausente.")
1546
+ return problemas
1547
+
1548
+
1549
+ # --- Geração de Caso Fictício (Chamada Principal) ---
1550
+ def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
1551
+ """Função principal para gerar caso, chamando a API com o prompt estruturado."""
1552
+ if not tipo_peticao:
1553
+ return "Erro: Tipo de petição não selecionado para gerar caso."
1554
+ logger.info(f"Gerando caso fictício para: {tipo_peticao}")
1555
+ prompt = estruturar_prompt_geracao_caso(tipo_peticao)
1556
+ caso_gerado = chamar_api(prompt)
1557
+
1558
+ # Verifica se a resposta da API foi um erro
1559
+ if caso_gerado.startswith("❌") or caso_gerado.startswith("⏱️") or caso_gerado.startswith("⚠️"):
1560
+ logger.error(f"Erro ao gerar caso fictício: {caso_gerado}")
1561
+ return f"Falha ao gerar caso fictício.\n{caso_gerado}" # Retorna o erro
1562
+
1563
+ # Pequeno pós-processamento para garantir que a frase final esteja lá
1564
+ frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de"
1565
+ if frase_final_padrao.lower() not in caso_gerado.lower():
1566
+ # Tenta adicionar uma frase genérica se a específica falhar
1567
+ logger.warning("Frase 'Situação-Problema' não encontrada na geração do caso. Adicionando manualmente.")
1568
+ # Extrai um nome de cliente potencial do texto gerado
1569
+ nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado) # Tenta achar nomes compostos
1570
+ cliente_nome = nomes_possiveis[0] if nomes_possiveis else "[Cliente]"
1571
+ 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})."
1572
+
1573
+ logger.info("Caso fictício gerado com sucesso.")
1574
+ return caso_gerado
1575
+
1576
+ # --- Execução ---
1577
  if __name__ == "__main__":
1578
+ # Configuração de Tesseract (Ajuste o caminho se necessário)
1579
+ # try:
1580
+ # # Exemplo para Windows:
1581
+ # # pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
1582
+ # # Exemplo para Linux (geralmente já está no PATH se instalado via apt):
1583
+ # # Se não estiver no PATH, especifique o caminho:
1584
+ # # pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract'
1585
+ # # Apenas para verificar se o comando é encontrado:
1586
+ # version = pytesseract.get_tesseract_version()
1587
+ # logger.info(f"Tesseract versão {version} encontrado.")
1588
+ # except Exception as e:
1589
+ # logger.warning(f"Tesseract não encontrado ou erro ao verificar versão: {e}. OCR de PDFs escaneados pode falhar.")
1590
+ # # Considerar desabilitar OCR ou avisar o usuário mais explicitamente
1591
 
1592
  app = interface_gradio()
1593
+ # Use share=True apenas se precisar de link público temporário
1594
+ # app.launch(share=True)
1595
+ app.launch()