GabrielGD commited on
Commit
c97e5bb
·
verified ·
1 Parent(s): 4d2751d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -277
app.py CHANGED
@@ -19,8 +19,8 @@ import difflib
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
 
@@ -30,24 +30,18 @@ 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",
@@ -59,35 +53,25 @@ TIPOS_DE_PETICAO = [
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).",
@@ -96,7 +80,6 @@ DICAS_OAB_MELHORADO = {
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,7 +88,7 @@ def preprocess_image_for_ocr(img):
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,12 +99,12 @@ def extract_text_from_pdf(file_path_or_obj):
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:
@@ -132,8 +115,7 @@ def extract_text_from_pdf(file_path_or_obj):
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():
@@ -145,7 +127,6 @@ def extract_text_from_pdf(file_path_or_obj):
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,7 +136,6 @@ def extract_text_from_pdf(file_path_or_obj):
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,19 +150,12 @@ def extract_text_from_pdf(file_path_or_obj):
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)}")
@@ -190,21 +163,17 @@ def extract_text_from_docx(file_path_or_obj):
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:
@@ -224,8 +193,6 @@ def process_file(file_obj):
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.")
@@ -234,26 +201,20 @@ def process_file(file_obj):
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}")
@@ -262,7 +223,6 @@ def process_file(file_obj):
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 = """
@@ -319,7 +279,6 @@ def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=Fals
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,7 +456,6 @@ def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=Fals
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
  ```
@@ -514,7 +472,6 @@ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
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,8 +520,6 @@ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
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,10 +532,7 @@ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
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,48 +543,34 @@ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
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,13 +578,10 @@ def chamar_api(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,7 +590,6 @@ def chamar_api(prompt):
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)))
@@ -678,7 +612,6 @@ def chamar_api(prompt):
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,14 +622,11 @@ def chamar_api(prompt):
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,7 +634,6 @@ def chamar_api(prompt):
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,7 +644,6 @@ def chamar_api(prompt):
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,10 +662,10 @@ def chamar_api(prompt):
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,19 +674,17 @@ def chamar_api(prompt):
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:
@@ -766,16 +692,15 @@ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=Non
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",
@@ -795,52 +720,37 @@ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=Non
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,65 +763,51 @@ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=Non
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,7 +817,6 @@ def formatar_correcao(texto):
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,13 +825,11 @@ def formatar_correcao(texto):
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,7 +838,6 @@ def formatar_correcao(texto):
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,12 +845,10 @@ def fornecer_dica_contextual(texto_peticao, tipo_peticao):
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,7 +863,6 @@ def fornecer_dica_contextual(texto_peticao, tipo_peticao):
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,15 +873,11 @@ def fornecer_dica_contextual(texto_peticao, tipo_peticao):
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,8 +907,6 @@ def calcular_diff_interno(texto_original, texto_corrigido):
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,7 +917,6 @@ def calcular_diff_interno(texto_original, texto_corrigido):
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,9 +933,6 @@ def calcular_diff_interno(texto_original, texto_corrigido):
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,7 +941,6 @@ def calcular_progresso():
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,7 +949,7 @@ def calcular_progresso():
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,35 +971,27 @@ def calcular_progresso():
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:
@@ -1129,7 +999,6 @@ def salvar_progresso(caso, peticao, 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,
@@ -1139,12 +1008,9 @@ def salvar_progresso(caso, peticao, tipo_peticao):
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
 
@@ -1158,28 +1024,24 @@ def salvar_progresso(caso, peticao, tipo_peticao):
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")
@@ -1192,7 +1054,6 @@ def carregar_progresso(arquivo_entrada):
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
 
@@ -1203,9 +1064,6 @@ def carregar_progresso(arquivo_entrada):
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).
@@ -1295,8 +1153,6 @@ def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
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 ⚖️")
@@ -1304,11 +1160,11 @@ def interface_gradio():
1304
 
1305
  with gr.Row():
1306
  with gr.Column(scale=1):
1307
- gr.Markdown("### Configurações (Settings)")
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
 
@@ -1338,11 +1194,7 @@ def interface_gradio():
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,12 +1202,11 @@ def interface_gradio():
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"):
@@ -1363,10 +1214,8 @@ def interface_gradio():
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,56 +1232,40 @@ def interface_gradio():
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),
@@ -1440,17 +1273,14 @@ def interface_gradio():
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),
@@ -1458,24 +1288,23 @@ def interface_gradio():
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=[
@@ -1487,56 +1316,42 @@ def interface_gradio():
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 :
@@ -1545,51 +1360,27 @@ def validar_peticao_basica(peticao):
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()
 
19
  from html import escape
20
  import random
21
  import logging
22
+ import re
23
 
 
24
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
25
  logger = logging.getLogger(__name__)
26
 
 
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
  GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}"
34
 
 
35
  CONFIG = {
36
  "max_retry_attempts": 3,
37
  "retry_delay": 5,
38
+ "timeout": 120,
39
+ "temperatura": 0.3,
40
+ "max_tokens": 8192
41
  }
42
 
43
+ rate_limiter = RateLimiter(max_calls=30, period=60)
44
 
 
45
  TIPOS_DE_PETICAO = [
46
  "Petição Inicial",
47
  "Contestação",
 
53
  "Petição de Tutela Provisória"
54
  ]
55
 
 
56
  DICAS_OAB_MELHORADO = {
 
57
  "Clareza e Concisão": "Seja direto e evite jargões excessivos. Facilite a leitura do juiz.",
58
  "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes ao seu argumento.",
59
  "Coerência Argumentativa": "Mantenha uma linha lógica de raciocínio do início ao fim da peça.",
60
  "Revisão Ortográfica e Gramatical": "Erros de português podem prejudicar a credibilidade da sua peça.",
61
  "Formatação": "Utilize parágrafos bem definidos, espaçamento adequado e destaque pontos importantes (negrito, itálico) com moderação.",
 
 
62
  "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).",
63
  "Qualificação (Inicial)": "Inclua todos os dados do Art. 319, II, CPC. Use 'XXX' para dados não fornecidos.",
64
  "Fatos (Inicial)": "Narrativa cronológica, clara e objetiva, sem juízo de valor.",
65
  "Pedidos (Inicial)": "Certos, determinados e líquidos (sempre que possível). Cumule pedidos se necessário (Art. 327, CPC).",
66
  "Custas (Inicial)": "Sempre peça a juntada da guia ou a gratuidade de justiça (com declaração).",
67
  "Valor da Causa (Inicial)": "Deve corresponder ao benefício econômico pretendido (Art. 292, CPC).",
 
 
68
  "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes de discutir o mérito.",
69
  "Impugnação Específica (Contestação)": "Conteste cada fato alegado pelo autor, sob pena de presunção de veracidade (Art. 341, CPC).",
70
  "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.",
71
  "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.",
 
 
72
  "Pressupostos Recursais": "Verifique cabimento, tempestividade, preparo (custas recursais), regularidade formal e interesse/legitimidade.",
73
  "Dialeticidade (Recurso)": "Impugne especificamente os fundamentos da decisão recorrida (Art. 1010, II e III, CPC para Apelação).",
74
  "Delimitação da Matéria (Recurso)": "Indique claramente qual parte da decisão você está recorrendo.",
 
 
75
  "Fumus Boni Iuris": "Demonstre a plausibilidade do direito alegado com base em provas e argumentos sólidos.",
76
  "Periculum in Mora": "Evidencie o risco de dano irreparável ou de difícil reparação se a medida não for concedida imediatamente.",
77
  "Reversibilidade": "Se possível, argumente que a medida é reversível caso a decisão final seja diferente (Art. 300, §3º, CPC).",
 
80
  HISTORICO = []
81
  CORRECAO_CACHE = {}
82
 
 
83
  def preprocess_image_for_ocr(img):
84
  try:
85
  gray = img.convert('L')
 
88
  return Image.fromarray(thresh)
89
  except Exception as e:
90
  logger.error(f"Erro no pré-processamento da imagem: {str(e)}")
91
+ return f"❌ Erro no pré-processamento: {str(e)}"
92
 
93
  def extract_text_from_pdf(file_path_or_obj):
94
  text = ""
 
99
  page_text = page.extract_text()
100
  if page_text:
101
  text += page_text + "\n"
102
+ else:
103
  logger.warning(f"Página {i+1} sem texto extraível, tentando OCR.")
104
  try:
105
+ img = page.to_image(resolution=300)
106
  preprocessed_img = preprocess_image_for_ocr(img.original)
107
+ page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por')
108
  if page_ocr_text.strip():
109
  ocr_text += page_ocr_text + "\n"
110
  else:
 
115
  ocr_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n"
116
  pdf_reader.close()
117
 
118
+ if text.strip() and len(text) > len(ocr_text) * 0.5:
 
119
  logger.info("Usando texto extraído do PDF.")
120
  return text
121
  elif ocr_text.strip():
 
127
 
128
  except Exception as e:
129
  logger.error(f"Erro geral ao processar o PDF: {str(e)}")
 
130
  try:
131
  logger.info("Tentando fallback com PyPDF2.")
132
  text_fallback = ""
 
136
  for page in reader.pages:
137
  text_fallback += page.extract_text() or ""
138
  else:
 
139
  file_path_or_obj.seek(0)
140
  reader = PyPDF2.PdfReader(file_path_or_obj)
141
  for page in reader.pages:
 
150
  logger.error(f"Erro no fallback com PyPDF2: {str(fallback_e)}")
151
  return f"❌ Erro crítico ao processar o PDF: {str(e)}"
152
 
 
153
  def extract_text_from_docx(file_path_or_obj):
154
  try:
155
+ doc = docx.Document(file_path_or_obj)
156
  text = ""
157
  for para in doc.paragraphs:
158
  text += para.text + "\n"
 
 
 
 
 
 
159
  return text if text.strip() else "⚠️ Documento DOCX vazio ou sem texto."
160
  except Exception as e:
161
  logger.error(f"Erro ao processar o DOCX: {str(e)}")
 
163
 
164
  def extract_text_from_txt(file_path_or_obj):
165
  try:
 
166
  if isinstance(file_path_or_obj, str):
167
  with open(file_path_or_obj, 'rb') as f:
168
  content = f.read()
 
169
  elif hasattr(file_path_or_obj, 'read'):
170
  content = file_path_or_obj.read()
 
171
  if hasattr(file_path_or_obj, 'seek'):
172
  try: file_path_or_obj.seek(0)
173
  except: pass
174
  else:
175
  return "❌ Tipo de entrada TXT inválido."
176
 
 
177
  encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
178
  for encoding in encodings:
179
  try:
 
193
  logger.warning("Nenhum arquivo fornecido para processamento.")
194
  return "⚠️ Nenhum arquivo enviado."
195
  try:
 
 
196
  file_path = getattr(file_obj, 'name', None)
197
  if not file_path:
198
  logger.error("Não foi possível obter o nome do arquivo do objeto.")
 
201
  filename = os.path.basename(file_path).lower()
202
  logger.info(f"Processando arquivo: {filename}")
203
 
 
204
  try:
205
  file_size = os.path.getsize(file_path)
206
  logger.info(f"Tamanho do arquivo: {file_size} bytes")
207
+ if file_size > 15 * 1024 * 1024:
208
  logger.warning(f"Arquivo {filename} excede o limite de tamanho.")
209
  return "⚠️ O arquivo é muito grande. Máx. 15 MB."
210
  except OSError as size_e:
211
  logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}")
 
212
 
 
213
  if filename.endswith('.pdf'):
 
214
  return extract_text_from_pdf(file_path)
215
  elif filename.endswith('.docx'):
 
216
  return extract_text_from_docx(file_path)
217
  elif filename.endswith('.txt'):
 
218
  return extract_text_from_txt(file_path)
219
  else:
220
  logger.warning(f"Formato de arquivo não suportado: {filename}")
 
223
  except Exception as e:
224
  logger.error(f"Erro geral ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True)
225
  return f"❌ Erro inesperado ao processar o arquivo: {str(e)}"
 
226
 
227
  def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
228
  base = """
 
279
  **Requisitos Específicos para {tipo_peticao}:**
280
  """
281
 
 
282
  if tipo_peticao == "Petição Inicial":
283
  prompt_especifico += """
284
  - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I).
 
456
  return prompt_especifico
457
 
458
  def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
 
459
  prompt = f"""
460
  **Caso/Contexto Fornecido:**
461
  ```
 
472
  **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):**
473
  """
474
 
 
475
  if tipo_peticao == "Petição Inicial":
476
  prompt += """
477
  1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável e justificado).
 
520
  10. **Continue com os Pedidos da Contestação** (improcedência da ação original, etc.).
521
  11. **Fechamento:** Padrão OAB.
522
  """
 
 
523
  elif tipo_peticao == "Embargos de Declaração":
524
  prompt += """
525
  1. **Endereçamento:** Ao juízo que proferiu a decisão embargada.
 
532
  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.
533
  7. **Fechamento:** Padrão OAB.
534
  """
535
+ else:
 
 
 
536
  prompt += """
537
  1. **Estrutura Lógica:** Organize a peça com começo, meio e fim.
538
  2. **Argumentos:** Apresente os argumentos de forma clara.
 
543
  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."
544
  return prompt
545
 
 
546
  def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
 
 
 
547
  if "Nestes termos, pede deferimento" in peticao_corrigida:
548
  peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
549
 
 
550
  if tipo_peticao == "Petição Inicial":
551
  if "juntada da guia de custas" not in peticao_corrigida.lower() and "gratuidade de justiça" not in peticao_corrigida.lower():
 
552
  partes = peticao_corrigida.split("Nestes termos pede deferimento")
553
  if len(partes) == 2:
554
  peticao_corrigida = partes[0] + "Requer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento" + partes[1]
555
+ else:
556
  peticao_corrigida = peticao_corrigida.replace("Local, data.", "Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.")
557
 
 
558
  if "audiência de conciliação" not in peticao_corrigida.lower() and "audiência de mediação" not in peticao_corrigida.lower():
 
559
  partes = peticao_corrigida.split("Nestes termos pede deferimento")
560
  if len(partes) == 2:
561
  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]
562
+ else:
563
  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.")
564
 
 
 
565
  linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
566
  fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."]
 
567
  while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]):
568
  linhas.pop()
 
569
  linhas.extend(fechamento_padrao)
570
  peticao_corrigida = "\n".join(linhas)
571
 
 
572
  return peticao_corrigida
573
 
 
574
  def chamar_api(prompt):
575
  payload = {
576
  "contents": [{"parts": [{"text": prompt}]}],
 
578
  "temperature": CONFIG["temperatura"],
579
  "maxOutputTokens": CONFIG["max_tokens"],
580
  "topP": 0.95,
 
 
581
  }
582
  }
583
  headers = {"Content-Type": "application/json"}
584
  logger.info(f"Chamando API Gemini com {len(prompt)} caracteres de prompt. Temperatura: {CONFIG['temperatura']}, Max Tokens: {CONFIG['max_tokens']}")
 
585
 
586
  for tentativa in range(CONFIG["max_retry_attempts"]):
587
  try:
 
590
  response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"])
591
 
592
  logger.debug(f"Status Code recebido: {response.status_code}")
 
593
 
594
  if response.status_code == 429:
595
  retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1)))
 
612
 
613
  response_data = response.json()
614
 
 
615
  if not response_data.get("candidates"):
616
  prompt_feedback = response_data.get("promptFeedback", {})
617
  block_reason = prompt_feedback.get("blockReason")
 
622
  else:
623
  return "❌ Resposta vazia ou inválida da API (sem 'candidates'). Verifique o log para detalhes."
624
 
 
625
  candidate = response_data["candidates"][0]
626
 
 
627
  finish_reason = candidate.get("finishReason")
628
  if finish_reason not in [None, "STOP", "MAX_TOKENS"]:
629
  logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}")
 
630
  safety_ratings_candidate = candidate.get("safetyRatings", [])
631
  harm_categories = [r['category'] for r in safety_ratings_candidate if r['probability'] not in ['NEGLIGIBLE', 'LOW']]
632
  if harm_categories:
 
634
  else:
635
  return f"❌ A geração da resposta foi interrompida pela API. Motivo: {finish_reason}. Tente novamente ou simplifique a solicitação."
636
 
 
637
  if "content" not in candidate or "parts" not in candidate["content"] or not candidate["content"]["parts"]:
638
  logger.error("Estrutura de resposta inesperada da API: Faltando content/parts.")
639
  if tentativa < CONFIG["max_retry_attempts"] - 1:
 
644
 
645
  texto_gerado = candidate["content"]["parts"][0]["text"].strip()
646
  logger.info(f"API retornou com sucesso. Tamanho da resposta: {len(texto_gerado)} caracteres.")
 
647
  return texto_gerado
648
 
649
  except requests.Timeout:
 
662
  return f"❌ Erro na requisição: {e}"
663
  except json.JSONDecodeError as e:
664
  logger.error(f"Erro ao decodificar JSON da resposta da API: {e}")
665
+ logger.debug(f"Resposta que causou erro de JSON: {response.text[:500]}")
666
  return f"❌ Erro ao processar a resposta da API (JSON inválido)."
667
  except Exception as e:
668
+ logger.exception(f"Erro inesperado na chamada da API na tentativa {tentativa + 1}: {str(e)}")
669
  if tentativa < CONFIG["max_retry_attempts"] - 1:
670
  logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
671
  time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
 
674
 
675
  return "❌ Falha ao chamar a API após todas as tentativas."
676
 
 
677
  def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
678
  logger.info(f"Iniciando correção para tipo: {tipo_peticao}")
679
  start_time = time.time()
680
 
681
  if not caso or not caso.strip():
682
  logger.warning("Caso não fornecido.")
683
+ return "⚠️ Forneça o caso/contexto da peça.", "", "", "", "", ""
684
 
685
  if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO:
686
  logger.warning(f"Tipo de petição inválido ou não fornecido: {tipo_peticao}")
687
+ return "⚠️ Selecione um tipo de petição válido.", "", "", "", "", ""
 
688
 
689
  peticao_conteudo = ""
690
  if peticao_arquivo is not None:
 
692
  peticao_conteudo = process_file(peticao_arquivo)
693
  if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"):
694
  logger.error(f"Erro ao processar arquivo: {peticao_conteudo}")
695
+ return peticao_conteudo, "", "", "", "", ""
696
  logger.info(f"Texto extraído do arquivo ({len(peticao_conteudo)} caracteres).")
697
  elif peticao_texto and peticao_texto.strip():
698
  peticao_conteudo = peticao_texto
699
  logger.info(f"Usando texto da petição fornecido ({len(peticao_conteudo)} caracteres).")
700
  else:
701
  logger.warning("Nenhuma petição fornecida (nem texto, nem arquivo).")
702
+ return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", "", ""
703
 
 
704
  troll_triggers = [
705
  "escreva a petição", "faça a petição", "redija a petição",
706
  "elabore a petição", "crie a petição", "gere a petição",
 
720
  "😂 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í!",
721
  ]
722
  logger.info("Detectado pedido para escrever petição. Retornando resposta troll.")
723
+ return random.choice(respostas_troll), "", "", "", "", ""
724
 
 
 
725
  aviso_validacao = ""
 
 
 
 
726
 
 
727
  cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}"
728
  if cache_key in CORRECAO_CACHE:
729
  logger.info("Correção encontrada no cache.")
730
  cached_data = CORRECAO_CACHE[cache_key]
731
+ return cached_data["analise_formatada"], cached_data["peticao_original"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"], cached_data["diff_html"]
 
732
 
 
733
  logger.info("Gerando prompt de análise...")
734
  prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento)
735
  logger.info("Enviando para análise da API...")
736
  analise = chamar_api(prompt_analise)
737
  if analise.startswith("❌") or analise.startswith("⏱️") or analise.startswith("⚠️"):
738
  logger.error(f"Erro na análise da petição: {analise}")
739
+ return aviso_validacao + analise, peticao_conteudo, "", "", "", ""
 
740
  logger.info("Análise recebida da API.")
741
+ analise_formatada = formatar_correcao(analise)
742
 
 
743
  logger.info("Gerando prompt para peça corrigida...")
744
  prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao)
745
  logger.info("Enviando para geração da peça corrigida API...")
746
  peticao_corrigida_bruta = chamar_api(prompt_peticao)
747
 
748
+ peticao_corrigida_final = ""
749
  if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️") or peticao_corrigida_bruta.startswith("⚠️"):
750
  logger.error(f"Erro ao gerar peça corrigida: {peticao_corrigida_bruta}")
 
751
  peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}"
752
  else:
753
  logger.info("Peça corrigida recebida, aplicando pós-correção...")
 
 
 
754
  start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
755
  start_index = -1
756
  peticao_lower_corr = peticao_corrigida_bruta.lower()
 
763
  if start_index != -1:
764
  peticao_extraida = peticao_corrigida_bruta[start_index:]
765
  else:
766
+ peticao_extraida = peticao_corrigida_bruta
767
 
768
  peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao)
769
  logger.info("Pós-correção aplicada.")
770
 
 
 
771
  logger.info("Calculando diferenças...")
772
+ visualizacao_detalhada_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final)
 
 
 
773
  logger.info("Diferenças calculadas.")
774
 
775
+ correcao_completa_formatada = aviso_validacao + analise_formatada
 
 
776
  resultado_historico = {
777
  "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
778
  "tipo_peticao": tipo_peticao,
779
  "caso": caso,
780
  "peticao_original": peticao_conteudo,
781
+ "analise_formatada": correcao_completa_formatada,
782
+ "peticao_corrigida": peticao_corrigida_final,
783
  "resumo_diff": resumo_diff,
784
  "criticas_diff": criticas_diff
 
785
  }
786
  HISTORICO.append(resultado_historico)
787
+ if len(HISTORICO) > 10:
788
  HISTORICO.pop(0)
789
 
 
790
  CORRECAO_CACHE[cache_key] = {
791
  "analise_formatada": correcao_completa_formatada,
792
  "peticao_original": peticao_conteudo,
793
  "peticao_corrigida": peticao_corrigida_final,
794
  "resumo_diff": resumo_diff,
795
  "criticas_diff": criticas_diff,
796
+ "diff_html": visualizacao_detalhada_html
797
  }
798
 
799
  end_time = time.time()
800
  logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.")
801
 
802
+ return correcao_completa_formatada, peticao_conteudo, peticao_corrigida_final, resumo_diff, criticas_diff, visualizacao_detalhada_html
 
 
803
 
804
  def formatar_correcao(texto):
 
 
805
  texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
806
  texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
807
  texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
808
  texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
809
  texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos")
810
 
 
811
  linhas = texto.split('\n')
812
  linhas_formatadas = []
813
  in_list = False
 
817
  linhas_formatadas.append(f"\n**{linha_strip}**")
818
  in_list = False
819
  elif linha_strip.startswith("- ") or linha_strip.startswith("* "):
 
820
  prefix = " * " if in_list else "* "
821
  linhas_formatadas.append(prefix + linha_strip[2:])
822
  in_list = True
 
825
  in_list = True
826
  else:
827
  linhas_formatadas.append(linha)
828
+ if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")):
829
  in_list = False
830
 
 
 
831
  texto_final = "\n".join(linhas_formatadas)
832
+
833
  nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})"
834
  match = re.search(nota_pattern, texto_final, re.IGNORECASE)
835
  if match:
 
838
 
839
  return texto_final
840
 
 
841
  def fornecer_dica_contextual(texto_peticao, tipo_peticao):
842
  if not texto_peticao or not texto_peticao.strip():
843
  return "Digite algo na petição para receber dicas..."
 
845
  dicas_relevantes = []
846
  texto_lower = texto_peticao.lower()
847
 
 
848
  for chave, dica in DICAS_OAB_MELHORADO.items():
849
  if chave in ["Clareza e Concisão", "Fundamentação Legal", "Coerência Argumentativa", "Revisão Ortográfica e Gramatical", "Formatação"]:
850
  dicas_relevantes.append(f"💡 **{chave}:** {dica}")
851
 
 
852
  keywords_por_tipo = {
853
  "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor da causa", "provas", "audiência", "citação", "custas"],
854
  "Contestação": ["preliminar", "mérito", "impugnação", "fatos", "improcedência", "provas", "honorários"],
 
863
  if tipo_peticao in keywords_por_tipo:
864
  for keyword in keywords_por_tipo[tipo_peticao]:
865
  if keyword in texto_lower:
 
866
  for chave, dica in DICAS_OAB_MELHORADO.items():
867
  chave_lower = chave.lower()
868
  if f"({tipo_peticao.split()[0].lower()})" in chave_lower or keyword in chave_lower:
 
873
  if not dicas_relevantes:
874
  return "💡 Continue escrevendo ou selecione o tipo de peça para dicas mais específicas."
875
 
876
+ return "\n\n".join(dicas_relevantes[:5])
 
 
 
877
 
878
  def calcular_diff_interno(texto_original, texto_corrigido):
 
879
  if not texto_original or not texto_corrigido or texto_corrigido.startswith("⚠️") or texto_corrigido.startswith("❌"):
880
+ return "","",""
881
 
882
  orig_lines = texto_original.splitlines()
883
  corr_lines = texto_corrigido.splitlines()
 
907
  resultado_diff_html.append(f"<div class='linha-adicionada' title='Adicionado'><ins>{texto_escaped}</ins></div>")
908
  if any(kw in texto.lower() for kw in keywords_criticas_adicionadas):
909
  criticas.append(f"<span style='color: green;'>🟢 ADICIONADO (Potencialmente Essencial):</span> {texto_escaped}")
 
 
910
 
911
  resumo_estatistico = f"""
912
  **Resumo das Mudanças:**
 
917
 
918
  criticas_texto = "### 🚨 Mudanças Críticas / Essenciais Identificadas:\n" + "\n".join(criticas) if criticas else "Nenhuma mudança crítica/essencial automaticamente detectada."
919
 
 
920
  css = """
921
  <style>
922
  .diff-container { font-family: monospace; white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; }
 
933
 
934
  return visualizacao_detalhada_html, resumo_estatistico, criticas_texto
935
 
 
 
 
936
  def calcular_progresso():
937
  if not HISTORICO:
938
  return "📊 Nenhum progresso registrado ainda."
 
941
  tipos_corrigidos = set()
942
  for entry in HISTORICO:
943
  tipos_corrigidos.add(entry["tipo_peticao"])
 
944
  nota_str = "N/A"
945
  match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"])
946
  if match:
 
949
  notas.append(nota)
950
  nota_str = f"{nota:.1f}"
951
  except ValueError:
952
+ pass
953
 
954
  total_corridas = len(HISTORICO)
955
  media = sum(notas) / len(notas) if notas else 0
 
971
  """
972
  return progresso_texto
973
 
 
974
  def mostrar_historico():
975
  if not HISTORICO:
976
  return "📜 Histórico vazio."
977
 
978
  historico_md = "### 📜 Histórico de Correções (Últimas 10)\n\n---\n"
 
979
  for i, entry in enumerate(reversed(HISTORICO)):
980
  caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso']
981
  peticao_resumo = (entry['peticao_original'][:80] + '...') if len(entry['peticao_original']) > 80 else entry['peticao_original']
982
 
 
983
  nota_str = "N/A"
984
  match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"])
985
  if match:
986
  nota_str = f"{match.group(1).replace(',', '.')}/10"
987
 
 
988
  historico_md += f"**{len(HISTORICO)-i}. Data:** {entry['data']}\n"
989
  historico_md += f"**Tipo:** {entry['tipo_peticao']}\n"
990
  historico_md += f"**Nota:** {nota_str}\n"
991
  historico_md += f"**Caso:** _{caso_resumo}_\n"
 
992
  historico_md += "---\n"
993
  return historico_md
994
 
 
 
 
995
  def salvar_progresso(caso, peticao, tipo_peticao):
996
  logger.info("Iniciando salvamento de progresso...")
997
  try:
 
999
  logger.warning("Inputs insuficientes para salvar progresso.")
1000
  return "", "⚠️ Caso, Petição e Tipo de Petição são necessários para salvar.", None
1001
 
 
1002
  dados_progresso = {
1003
  "tipo_peticao": tipo_peticao,
1004
  "caso": caso,
 
1008
 
1009
  json_content = json.dumps(dados_progresso, ensure_ascii=False, indent=4)
1010
 
 
1011
  timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
1012
  filename = f"progresso_peticao_{timestamp_str}.json"
1013
 
 
 
1014
  temp_dir = tempfile.gettempdir()
1015
  temp_file_path = os.path.join(temp_dir, filename)
1016
 
 
1024
  "Use o botão 'Carregar Progresso' e selecione este arquivo para restaurar."
1025
  )
1026
 
 
1027
  return json_content, success_message, temp_file_path
1028
 
1029
  except Exception as e:
1030
  logger.error(f"Erro ao salvar progresso: {str(e)}", exc_info=True)
1031
  return "", f"❌ Erro ao salvar o progresso: {str(e)}", None
1032
 
 
1033
  def carregar_progresso(arquivo_entrada):
1034
  logger.info("Iniciando carregamento de progresso...")
1035
  if arquivo_entrada is None:
1036
  return "", "", "", "⚠️ Nenhum arquivo selecionado para carregar."
1037
 
1038
  try:
 
1039
  file_path = arquivo_entrada.name
1040
  logger.info(f"Carregando progresso do arquivo: {file_path}")
1041
 
1042
  with open(file_path, 'r', encoding='utf-8') as f:
1043
  dados = json.load(f)
1044
 
 
1045
  caso = dados.get("caso")
1046
  peticao = dados.get("peticao")
1047
  tipo_peticao = dados.get("tipo_peticao")
 
1054
  logger.warning(f"Tipo de petição '{tipo_peticao}' do arquivo não é válido.")
1055
  return caso, peticao, "", f"⚠️ Tipo de petição '{tipo_peticao}' do arquivo não é mais válido. Selecione manualmente."
1056
 
 
1057
  logger.info("Progresso carregado com sucesso.")
1058
  return tipo_peticao, caso, peticao, "✅ Progresso carregado com sucesso!"
1059
 
 
1064
  logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True)
1065
  return "", "", "", f"❌ Erro inesperado ao carregar o progresso: {str(e)}"
1066
 
 
 
 
1067
  def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
1068
  prompt = f"""
1069
  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).
 
1153
  """
1154
  return prompt
1155
 
 
 
1156
  def interface_gradio():
1157
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
1158
  gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️")
 
1160
 
1161
  with gr.Row():
1162
  with gr.Column(scale=1):
1163
+ gr.Markdown("### Configurações (Settings)")
1164
  tipo_peticao_input = gr.Dropdown(
1165
  label=" Selecione o Tipo de Peça Processual",
1166
  choices=TIPOS_DE_PETICAO,
1167
+ value="Petição Inicial"
1168
  )
1169
  modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento (Feedback Detalhado)", value=False)
1170
 
 
1194
  salvar_btn = gr.Button("💾 Salvar Caso/Peça Atual")
1195
  carregar_btn = gr.Button("📂 Carregar Caso/Peça de Arquivo")
1196
  arquivo_progresso_upload = gr.File(label="Selecione o arquivo .json para carregar", file_types=[".json"], scale=2)
 
1197
  progresso_download_file = gr.File(label="Baixar Progresso Salvo", interactive=False)
 
 
 
1198
 
1199
  with gr.Column(scale=3):
1200
  gr.Markdown("### 1. Forneça o Caso / Contexto")
 
1202
  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).")
1203
  caso_btn = gr.Button("🎲 Gerar Caso Fictício", variant="secondary", scale=0)
1204
 
 
1205
  gr.Markdown("### 2. Insira Sua Peça Processual")
1206
  with gr.Tabs():
1207
  with gr.TabItem("✏️ Digitar Texto"):
1208
  peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=15, placeholder="Cole ou digite sua peça processual aqui...")
1209
+ dica_contextual_output = gr.Markdown(value="*Dicas aparecerão aqui enquanto você digita...*")
1210
  btn_corrigir = gr.Button("🚀 CORRIGIR E ANALISAR!", variant="primary")
1211
 
1212
  with gr.TabItem("📎 Anexar Arquivo"):
 
1214
  arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Conteúdo do Arquivo (Texto Extraído)", lines=5, interactive=False)
1215
  btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary")
1216
 
1217
+ processing_message = gr.Markdown("", visible=False)
1218
 
 
 
 
1219
  with gr.Tabs():
1220
  with gr.TabItem("📊 Análise e Correção"):
1221
  gr.Markdown("### 3. Resultados da Análise")
 
1232
  criticas_output = gr.Markdown(label="Mudanças Críticas/Essenciais")
1233
  with gr.Column(scale=3):
1234
  visualizacao_detalhada_output = gr.HTML(label="📝 Alterações Detalhadas (Diff)")
 
 
 
 
1235
 
 
1236
  clear_btn = gr.Button("🗑️ Limpar Tudo")
1237
 
 
 
 
 
1238
  caso_btn.click(
1239
  fn=gerar_caso_ficticio,
1240
+ inputs=[tipo_peticao_input],
1241
  outputs=[caso_input]
1242
  )
1243
 
 
1244
  peticao_texto_input.change(
1245
  fn=fornecer_dica_contextual,
1246
  inputs=[peticao_texto_input, tipo_peticao_input],
1247
  outputs=dica_contextual_output
1248
  )
 
1249
  tipo_peticao_input.change(
1250
  fn=fornecer_dica_contextual,
1251
  inputs=[peticao_texto_input, tipo_peticao_input],
1252
  outputs=dica_contextual_output
1253
  )
1254
 
 
 
1255
  peticao_arquivo_input.change(
1256
  fn=process_file,
1257
  inputs=[peticao_arquivo_input],
1258
  outputs=[arquivo_preview_output]
1259
  )
1260
 
 
1261
  btn_corrigir.click(
1262
  fn=lambda: gr.update(value="⏳ Processando... Gerando análise e correção. Isso pode levar um minuto...", visible=True),
1263
  inputs=None,
1264
  outputs=[processing_message]
1265
  ).then(
1266
  fn=corrigir_peticao,
 
1267
  inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento],
1268
+ outputs=[resultado_analise_output, gr.Textbox(visible=False), resultado_peticao_corrigida_output,
 
1269
  resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output]
1270
  ).then(
1271
  fn=lambda: gr.update(value="", visible=False),
 
1273
  outputs=[processing_message]
1274
  )
1275
 
 
1276
  btn_corrigir_arquivo.click(
1277
  fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção. Isso pode levar um minuto...", visible=True),
1278
  inputs=None,
1279
  outputs=[processing_message]
1280
  ).then(
1281
  fn=corrigir_peticao,
 
1282
  inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento],
1283
+ outputs=[resultado_analise_output, gr.Textbox(visible=False), resultado_peticao_corrigida_output,
 
1284
  resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output]
1285
  ).then(
1286
  fn=lambda: gr.update(value="", visible=False),
 
1288
  outputs=[processing_message]
1289
  )
1290
 
 
1291
  clear_btn.click(
1292
  fn=lambda: [
1293
+ "Petição Inicial",
1294
+ False,
1295
+ None,
1296
+ None,
1297
+ None,
1298
+ None,
1299
+ "",
1300
+ "",
1301
+ "",
1302
+ "",
1303
+ "",
1304
+ "",
1305
+ "",
1306
+ None,
1307
+ None,
1308
  ],
1309
  inputs=None,
1310
  outputs=[
 
1316
  ]
1317
  )
1318
 
 
1319
  progresso_btn.click(fn=calcular_progresso, outputs=progresso_output)
1320
  historico_btn.click(fn=mostrar_historico, outputs=historico_output)
1321
 
 
1322
  salvar_btn.click(
1323
  fn=lambda: gr.update(value="⏳ Salvando progresso atual...", visible=True),
1324
  inputs=None, outputs=processing_message
1325
  ).then(
 
1326
  fn=salvar_progresso,
1327
  inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
1328
+ outputs=[gr.Textbox(visible=False), config_status, progresso_download_file]
 
1329
  ).then(
1330
+ fn=lambda x: gr.update(visible=bool(x)),
1331
  inputs=[progresso_download_file],
1332
  outputs=[progresso_download_file]
1333
  ).then(
1334
+ lambda: gr.update(value="", visible=False), None, processing_message
1335
  )
1336
 
 
 
1337
  carregar_btn.click(
1338
  fn=lambda: gr.update(value="⏳ Carregando progresso do arquivo...", visible=True),
1339
  inputs=None, outputs=processing_message
1340
  ).then(
1341
  fn=carregar_progresso,
1342
  inputs=[arquivo_progresso_upload],
1343
+ outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status]
 
1344
  ).then(
1345
+ lambda: gr.update(value="", visible=False), None, processing_message
1346
  )
1347
 
 
1348
  return demo
1349
 
 
1350
  def validar_peticao_basica(peticao):
 
 
 
1351
  problemas = []
1352
  peticao_lower = peticao.lower()
1353
  if "excelentíssimo" not in peticao_lower and "eméritos" not in peticao_lower and "meritíssimo" not in peticao_lower :
1354
  problemas.append("Endereçamento inicial parece ausente ou incompleto.")
 
 
1355
  if "valor da causa" not in peticao_lower and "valor à causa" not in peticao_lower:
1356
  problemas.append("Atribuição de Valor da Causa não encontrada (essencial na Inicial).")
1357
  if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower :
 
1360
  problemas.append("Fechamento formal padrão ('Nestes termos pede deferimento') parece ausente.")
1361
  return problemas
1362
 
 
 
1363
  def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
 
1364
  if not tipo_peticao:
1365
  return "Erro: Tipo de petição não selecionado para gerar caso."
1366
  logger.info(f"Gerando caso fictício para: {tipo_peticao}")
1367
  prompt = estruturar_prompt_geracao_caso(tipo_peticao)
1368
  caso_gerado = chamar_api(prompt)
1369
 
 
1370
  if caso_gerado.startswith("❌") or caso_gerado.startswith("⏱️") or caso_gerado.startswith("⚠️"):
1371
  logger.error(f"Erro ao gerar caso fictício: {caso_gerado}")
1372
+ return f"Falha ao gerar caso fictício.\n{caso_gerado}"
1373
 
 
1374
  frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de"
1375
  if frase_final_padrao.lower() not in caso_gerado.lower():
 
1376
  logger.warning("Frase 'Situação-Problema' não encontrada na geração do caso. Adicionando manualmente.")
1377
+ nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado)
 
1378
  cliente_nome = nomes_possiveis[0] if nomes_possiveis else "[Cliente]"
1379
  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})."
1380
 
1381
  logger.info("Caso fictício gerado com sucesso.")
1382
  return caso_gerado
1383
 
 
1384
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1385
  app = interface_gradio()
 
 
1386
  app.launch()