GabrielGD commited on
Commit
58c871a
·
verified ·
1 Parent(s): 051d48f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +343 -490
app.py CHANGED
@@ -1,9 +1,8 @@
1
- # -*- coding: utf-8 -*-
2
  import gradio as gr
3
- import google.generativeai as genai
4
- from google.api_core import exceptions as google_exceptions
5
  import os
6
  import json
 
7
  import docx
8
  import io
9
  import time
@@ -12,534 +11,388 @@ from dotenv import load_dotenv
12
  from pdfplumber import open as pdfplumber_open
13
  from PIL import Image
14
  import pytesseract
15
- try:
16
- import cv2
17
- import numpy as np
18
- CV2_AVAILABLE = True
19
- except ImportError:
20
- CV2_AVAILABLE = False
21
- print("Aviso: OpenCV (cv2) não encontrado.")
22
  from datetime import datetime
23
  from ratelimiter import RateLimiter
24
  import difflib
25
  from html import escape
26
  import random
27
  import logging
28
- import traceback
29
- import re
30
 
31
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s')
 
32
  logger = logging.getLogger(__name__)
33
 
34
  load_dotenv()
35
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or os.environ.get("GEMINI_API_KEY")
36
  if not GEMINI_API_KEY:
37
- raise ValueError("❌ Chave GEMINI_API_KEY não encontrada.")
38
 
39
- try:
40
- genai.configure(api_key=GEMINI_API_KEY)
41
- logger.info("Biblioteca google-generativeai configurada.")
42
- except Exception as e:
43
- raise ValueError(f"❌ Erro config google-generativeai: {e}")
44
 
45
- GEMINI_MODEL_NAME = "gemini-1.5-flash-latest"
46
- CONFIG = {"max_retry_attempts": 3, "retry_delay": 5, "timeout_api": 120, "temperatura": 0.3, "max_tokens": 8192}
47
- rate_limiter = RateLimiter(max_calls=15, period=60)
48
-
49
- SAFETY_SETTINGS = [
50
- {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
51
- {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
52
- {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
53
- {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
54
- ]
55
 
56
- TIPOS_DE_PETICAO = ["Petição Inicial", "Contestação", "Reconvenção", "Impugnação à Contestação", "Embargos de Declaração", "Recurso de Apelação", "Agravo de Instrumento", "Petição de Tutela Provisória"]
57
 
 
58
  DICAS_OAB_MELHORADO = {
59
- "Clareza e Concisão": "Direto, evite jargões.",
60
- "Fundamentação Legal": "Cite leis, súmulas, jurisprudência.",
61
- "Revisão Ortográfica": "Evite erros de português.",
62
- "Endereçamento (Inicial)": "Verifique competência.",
63
- "Pedidos (Inicial)": "Certos, determinados, líquidos.",
64
- "Preliminares (Contestação)": "Art. 337 CPC antes do mérito.",
65
- "Impugnação Específica (Contestação)": "Conteste cada fato (Art. 341)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
 
68
  HISTORICO = []
69
  CORRECAO_CACHE = {}
70
 
71
- def preprocess_image_for_ocr(img_pil):
72
- if not CV2_AVAILABLE:
73
- logger.warning("OpenCV indisponível, usando conversão simples.")
74
- return img_pil.convert('L')
75
  try:
76
- img_cv = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2GRAY)
77
- _, thresh = cv2.threshold(img_cv, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
 
78
  return Image.fromarray(thresh)
79
  except Exception as e:
80
- logger.error(f"Erro pré-processamento OpenCV: {e}")
81
- return img_pil.convert('L')
82
 
83
  def extract_text_from_pdf(file_path_or_obj):
84
- text = ""; ocr_text_parts = []
85
  try:
86
- logger.info(f"Abrindo PDF: {getattr(file_path_or_obj, 'name', file_path_or_obj)}")
87
- with pdfplumber_open(file_path_or_obj) as pdf:
88
- logger.info(f"PDF com {len(pdf.pages)} páginas.")
89
- for i, page in enumerate(pdf.pages):
90
- page_text = page.extract_text(x_tolerance=2, y_tolerance=2)
91
- if page_text and len(page_text.strip()) > 20: text += page_text + "\n"
92
- else:
93
- logger.warning(f"Pág {i+1} sem texto, tentando OCR.")
94
- try:
95
- img = page.to_image(resolution=200)
96
- preprocessed_img = preprocess_image_for_ocr(img.original)
97
- page_ocr = pytesseract.image_to_string(preprocessed_img, lang='por', config='--psm 6')
98
- if page_ocr and page_ocr.strip(): ocr_text_parts.append(page_ocr)
99
- else: logger.warning(f"OCR vazio pág {i+1}."); ocr_text_parts.append(f"[OCR P{i+1}: Vazio]")
100
- except Exception as e: logger.error(f"Erro OCR pág {i+1}: {e}"); ocr_text_parts.append(f"[Erro OCR P{i+1}: {e}]")
101
- ocr_text = "\n".join(ocr_text_parts)
102
- len_text, len_ocr = len(text.strip()), len(ocr_text.strip())
103
- if len_text > len_ocr * 0.7: logger.info(f"Usando texto PDF ({len_text} chars)."); return text
104
- elif len_ocr > 0: logger.info(f"Usando OCR PDF ({len_ocr} chars)."); return ocr_text
105
- elif len_text > 0: logger.info("Usando texto curto PDF."); return text
106
- else: logger.warning("PDF sem texto."); return "⚠️ PDF sem texto (extração/OCR falharam)."
107
- except Exception as e: logger.error(f"Erro PDF: {e}", exc_info=True); return f"❌ Erro PDF: {e}"
 
 
 
 
 
 
 
 
 
 
 
108
 
109
  def extract_text_from_docx(file_path_or_obj):
110
  try:
111
- doc = docx.Document(file_path_or_obj)
112
- text = "\n".join([p.text for p in doc.paragraphs if p.text])
113
- return text if text.strip() else "⚠️ DOCX vazio."
114
- except Exception as e: logger.error(f"Erro DOCX: {e}", exc_info=True); return f"❌ Erro DOCX: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  def extract_text_from_txt(file_path_or_obj):
117
  try:
118
- content = b''; encodings = ['utf-8', 'latin-1', 'cp1252']
119
  if isinstance(file_path_or_obj, str):
120
- with open(file_path_or_obj, 'rb') as f: content = f.read()
121
- elif hasattr(file_path_or_obj, 'read'):
122
- if hasattr(file_path_or_obj, 'seek') and callable(file_path_or_obj.seek):
123
- try: file_path_or_obj.seek(0)
124
- except Exception as seek_e: logger.warning(f"Não foi possível resetar ponteiro TXT: {seek_e}")
 
 
 
 
 
125
  content = file_path_or_obj.read()
126
- else: return "❌ Input TXT inválido."
127
- for enc in encodings:
128
- try:
129
- decoded = content.decode(enc)
130
- logger.info(f"TXT decodificado: {enc}.")
131
- return decoded if decoded.strip() else "⚠️ TXT vazio."
132
- except UnicodeDecodeError: continue
133
- return "❌ Não foi possível decodificar TXT."
134
- except Exception as e: logger.error(f"Erro TXT: {e}", exc_info=True); return f"❌ Erro TXT: {e}"
 
135
 
136
  def process_file(file_obj):
137
- if file_obj is None: return "⚠️ Nenhum arquivo."
 
138
  try:
139
- fpath = getattr(file_obj, 'name', None);
140
- if not fpath: return "❌ Erro: Não identificou arquivo."
141
- fname = os.path.basename(fpath).lower(); logger.info(f"Processando: {fname}")
142
- try:
143
- fsize = os.path.getsize(fpath); logger.info(f"Tamanho: {fsize} bytes")
144
- if fsize > 15*1024*1024: return "⚠️ Arquivo grande (Máx 15MB)."
145
- except OSError as e: logger.warning(f"Erro tamanho arquivo: {e}")
146
- if fname.endswith('.pdf'): return extract_text_from_pdf(fpath)
147
- elif fname.endswith('.docx'): return extract_text_from_docx(fpath)
148
- elif fname.endswith('.txt'): return extract_text_from_txt(fpath)
149
- else: return "⚠️ Formato não suportado (PDF, DOCX, TXT)."
150
- except Exception as e: logger.error(f"Erro process_file: {e}", exc_info=True); return f"❌ Erro processar arquivo: {e}"
151
-
152
- def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
153
- base = """
154
- **Estrutura:** 1.Análise Geral. 2.Nota Final(0-10). 3.Pontos Positivos. 4.Pontos a Melhorar(Desc, Fund., Impacto, Sug.). 5.Análise Requisitos.
155
- **Critérios:** Coerência, Fundamentação, Linguagem, Pedidos. NÃO INVENTAR FATOS(-3a-5). NÃO SE IDENTIFICAR(Nota ZERO)."""
156
- if modo_treinamento: base += "\n**Treinamento:** Detalhar Pontos a Melhorar(a-e). Destacar inconsistências. Sugerir estratégias."
157
- return base
158
-
159
- def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
160
- instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento)
161
- prompt = f"## Análise: {tipo_peticao}\n**Caso:**\n```\n{caso}\n```\n**Peça Aluno:**\n```\n{peticao}\n```\n{instrucoes_gerais}\n**Requisitos({tipo_peticao}):**\n"
162
- if tipo_peticao == "Petição Inicial": prompt += "-End. Comp(319,I;CDC101,I).\n-Qualif(319,II).\n-Fatos.\n-Fundam(319,III).\n-Pedidos(319,IV).\n-Vlr Causa(291-2,319,V).\n-Provas(319,VI).\n-Aud(319,VII).\n-Citação.\n-Docs essenc.\n-Fecho.\n**Adic:**'XXX'.Falta custas/JG(-1).Vírgula pede defer.(-0.2)."
163
- elif tipo_peticao == "Contestação": prompt += "-End. Qualif réu. Proc nº. Temp.\n-Prelim(337):TODAS.\n-Impug.Esp Fatos(341):PONTOxPONTO.\n-Mérito:Versão réu;defesa dir/indir.\n-Fundam defesa.\n-Pedidos:Acolher prelim;IMPROC TOTAL;Sucumb(85).\n-Provas.\n**Adic:**Prelim preclui?Falha impug(-2a-4).Sem ped improc(-1.5)."
164
- elif tipo_peticao == "Reconvenção": prompt += "-NA MESMA PEÇA(343).End. Proc nº.Qualif.\n-Conexão(343).\n-Estrut PI:Fatos,Fund,Ped,Vlr,Provas(reconv).\n-Ped intim(343§1).\n**Adic:**S/conex(-2).Falta ped/vlr/prov(-1/item).Peça separada(-3)."
165
- elif tipo_peticao == "Impugnação à Contestação": prompt += "-End.Proc nº.Autor.Temp(15d-350/1).\n-Resp Prelim.\n-Resp Fatos Novos(350).\n-Resp Docs(437).\n-Ratific Inicial.\n-Espec Provas.\n-Peds:Rej prelim;refutar def;proced inicial.\n**Adic:**Ñ resp prelim(-1.5).Ñ manif fato novo(-2).Ñ ratificar(-0.5)."
166
- elif tipo_peticao == "Embargos de Declaração": prompt += "-Contra Decisão Espec.End juiz prolator.Proc nº.Partes.Temp(5d-1023).\n-Cabim(1022):Apontar PRECISAMENTE:Omissão/Contrad/Obscur/ErroMat.\n-NÃO REJULGAR.\n-Ped:Conhec e ACOLHER p/ sanar vício.\n**Adic:**Rediscutir mérito(-4).Ñ apontar vício(-3)."
167
- elif tipo_peticao == "Recurso de Apelação": prompt += "-Interpos:Juízo a quo.Proc nº.Partes.Pressup(cabim 1009;temp 15d;prep/JG).Ped receb/remessa.\n-Razões:TRIBUNAL.Relato.Fundam Reforma/Anul(1010):Error in proc/judic.DIALETICIDADE.\n-Peds:Conhec;PROVIM(anul/ref);Inv sucumb.\n**Adic:**Falta peça(-10).End razões(-1).S/dialetic(-3a-5)."
168
- elif tipo_peticao == "Agravo de Instrumento": prompt += "-Peça única TRIBUNAL.Proc origem.Partes+Advs(end 1016).Cópia Decisão(1017,I).\n-Cabim(1015/mitig).Temp(15d).Prep/JG.\n-Peças Obrig(1017,I):Dec,Intim,Proc.Facult(III).\n-Razões(1016):Motivos ref/inval.\n-Efeito Susp/Tut Antecip Rec(1019,I)(se risco).\n-Peds Finais:Conhec;Efeito/Tut;PROVIM;Comunic juiz.\n**Adic:**Falta peça obrig(-4).S/cabim(-3)."
169
- elif tipo_peticao == "Petição de Tutela Provisória": prompt += "-Tipo(Urg/Evid).Caráter(Antec/Incid).End.Qualif.Proc(se incid).\n-Urg(300):Fumus Boni Iuris E Periculum in Mora.Reversib(300§3).\n-Evid(311):Hipótese I-IV.NÃO precisa periculum.\n-Ped Específico:Medida liminar CLARA.\n-Se Antec(303/5):Indicar ped final ou aditar.Vlr Causa.\n-Peds:Concessão tutela;cit/intim;indic ação/adit.\n**Adic:**S/Fumus/Peric(-3a-5).Erro Evid(-3).Ped genérico(-2)."
170
- else: prompt += "-Adequação? Clareza? Fundam? Pedido?"
171
- prompt += "\n\n**Instruções Finais:** Analise estritamente. Rigoroso e construtivo. Gere **exclusivamente** no formato solicitado."
172
- return prompt
173
-
174
- def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
175
- prompt = f"**Caso:**\n```\n{caso}\n```\n**Peça Original ({tipo_peticao}):**\n```\n{peticao_original}\n```\n**Tarefa:** Gere versão corrigida ({tipo_peticao}), seguindo práticas OAB.\n**Instruções ({tipo_peticao}):**\n"
176
- if tipo_peticao == "Petição Inicial": prompt += "1.End. 2.Qualif(319,II,'XXX'). 3.Fatos. 4.Fundam. 5.Pedidos(quantif). 6.Vlr Causa(292). 7.Provas. 8.Aud(319,VII). 9.Citação(246,I). 10.Custas/JG(NÃO OMITIR). 11.Fecho('Nestes termos...')."
177
- elif tipo_peticao == "Contestação": prompt += "1.End. 2.ID(proc nº,réu). 3.Temp. 4.Estrut:Síntese;Prelim(337);Mérito(Impug+Defesa);Reconv(se antes peds cont). 5.Peds Cont:Acolher prelim;IMPROC TOTAL;Sucumb. 6.Provas réu. 7.Fecho."
178
- elif tipo_peticao == "Reconvenção": prompt += "*Parte da Contest.* 1.Após mérito cont, antes peds: 'DA RECONVENÇÃO'. 2.Qualif Recvte/Recvdo. 3.Conexão. 4.Fatos/Dir Recv. 5.Peds Recv. 6.Vlr Causa/Provas Recv. 7.Intim Recvdo. 8.Peds Cont. 9.Fecho."
179
- elif tipo_peticao == "Impugnação à Contestação": prompt += "1.End,proc,autor,temp. 2.Tópico PRELIM - Rebater. 3.Tópico MÉRITO/FATOS NOVOS - Manifestar. 4.Tópico RATIFICAÇÃO. 5.Tópico PROVAS. 6.Peds:Rej prelim, refutar def, proced inicial. 7.Fecho."
180
- elif tipo_peticao == "Embargos de Declaração": prompt += "1.End juiz prolator.Proc,Partes.Decisão ID.Temp(5d). 2.Tópico p/ vício(1022):OMISSÃO/CONTRAD/OBSCUR/ERRO MAT. 3.Dentro:Trecho + Explicar vício. Ñ REJULGAR. 4.Ped:Conhec e ACOLHER p/ sanar. 5.Fecho."
181
- elif tipo_peticao == "Recurso de Apelação": prompt += "1.Interpos:Juízo a quo.Proc,Partes.Pressup(cab,temp,prep/JG).Ped receb/remessa. 2.Razões:Tribunal.Relato.FUND REFORMA/ANUL(Error in proc/judic).DIALETICIDADE. 3.Peds:Conhec;PROVIM(anul/ref);Inv sucumb. 4.Fecho."
182
- elif tipo_peticao == "Agravo de Instrumento": prompt += "1.Peça única Trib.Proc origem,Partes,Advs(ends).Decisão ID(cópia). 2.CABIM(1015/mitig).TEMP.PREP/JG.PEÇAS OBRIG/FACULT. 3.RAZÕES:Motivos ref/inval. 4.EFEITO SUSP/TUT ANTEC(se cabível). 5.PEDS:Conhec;Efeito/Tut;PROVIM;Comunic juiz. 6.Fecho."
183
- elif tipo_peticao == "Petição de Tutela Provisória": prompt += "1.TIPO(Urg/Evid) e CARÁTER(Antec/Incid).End.Qualif.Proc(se incid). 2.REQUISITOS:Urg=Fumus+Peric(+Reversib).Evid=Hipót 311. 3.PED LIMINAR:Medida CLARA. 4.Se Antec:Indic ped final/aditar.Vlr Causa. 5.PEDS:Concessão tutela;cit/intim;indic ação/adit. 6.Fecho."
184
- else: prompt += "1.Estrutura. 2.Args Claros. 3.Ped Claro. 4.Formais OAB."
185
- prompt += "\n\n**Instrução Final:** Gere **APENAS** texto da peça corrigida (endereçamento até 'OAB/...'). Sem comentários."
186
- return prompt
187
-
188
- def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
189
- if "Nestes termos, pede deferimento" in peticao_corrigida:
190
- peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
191
- if tipo_peticao == "Petição Inicial":
192
- p_lower = peticao_corrigida.lower()
193
- if "juntada da guia de custas" not in p_lower and "gratuidade de justiça" not in p_lower:
194
- partes = peticao_corrigida.split("Nestes termos pede deferimento")
195
- if len(partes)==2: peticao_corrigida = partes[0].strip()+"\nRequer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento"+partes[1]
196
- else: peticao_corrigida = peticao_corrigida.replace("Local, data.","Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.")
197
- if "audiência de conciliação" not in p_lower and "audiência de mediação" not in p_lower:
198
- partes = peticao_corrigida.split("Nestes termos pede deferimento")
199
- if len(partes)==2: peticao_corrigida = partes[0].strip()+"\nOpta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\nNestes termos pede deferimento"+partes[1]
200
- else: 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.")
201
- linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
202
- fechamento = ["Local, data.", "ADVOGADO(A)", "OAB/..."]
203
- while linhas and any(t in linhas[-1] for t in ["ADVOGADO","OAB","Local","data"]): linhas.pop()
204
- linhas.extend(fechamento)
205
- return "\n".join(linhas)
206
-
207
- def chamar_api(prompt):
208
- logger.info(f"Chamando API Gemini (Modelo: {GEMINI_MODEL_NAME})...")
209
- model = genai.GenerativeModel(GEMINI_MODEL_NAME, safety_settings=SAFETY_SETTINGS)
210
- gen_config = genai.types.GenerationConfig(temperature=CONFIG["temperatura"], max_output_tokens=CONFIG["max_tokens"], top_p=0.95)
211
- for tentativa in range(CONFIG["max_retry_attempts"]):
212
- try:
213
- with rate_limiter:
214
- logger.debug(f"API Tentativa {tentativa + 1}")
215
- response = model.generate_content(contents=prompt, generation_config=gen_config)
216
- if response.prompt_feedback.block_reason:
217
- logger.error(f"Prompt bloqueado: {response.prompt_feedback.block_reason}")
218
- return f"❌ Prompt bloqueado: {response.prompt_feedback.block_reason}. Reformule."
219
- if not response.text:
220
- if response.candidates and response.candidates[0].finish_reason != genai.types.FinishReason.STOP:
221
- finish_reason = response.candidates[0].finish_reason.name
222
- logger.error(f"Geração interrompida (s/ texto): {finish_reason}")
223
- safety = response.candidates[0].safety_ratings
224
- harms = [r.category.name for r in safety if r.probability.name not in ['NEGLIGIBLE', 'LOW']]
225
- if harms: return f"❌ Bloqueado segurança: {finish_reason} ({','.join(harms)})."
226
- else: return f"❌ Geração interrompida: {finish_reason}."
227
- else: logger.warning("API retornou s/ texto ou bloqueio."); return "⚠️ API retornou resposta vazia."
228
- logger.info(f"API OK ({len(response.text)} chars).")
229
- return response.text
230
- except google_exceptions.ResourceExhausted as e:
231
- logger.warning(f"Erro 429 - Tentativa {tentativa + 1}: {e}")
232
- if tentativa < CONFIG["max_retry_attempts"] - 1: wait = CONFIG['retry_delay']*(2**tentativa); logger.info(f"Aguardando {wait}s..."); time.sleep(wait); continue
233
- return "❌ Erro 429: Muitas requisições."
234
- except google_exceptions.PermissionDenied as e: logger.error(f"Erro Permissão API: {e}"); return f"❌ Erro Permissão: Verifique API Key/acesso modelo '{GEMINI_MODEL_NAME}'."
235
- except google_exceptions.InvalidArgument as e: logger.error(f"Erro Argumento API: {e}"); return f"❌ Erro parâmetros API: {e}"
236
- except genai.types.BlockedPromptException as e: logger.error(f"Prompt bloqueado (exc): {e}"); return f"❌ Prompt bloqueado. Reformule."
237
- except genai.types.StopCandidateException as e: logger.error(f"Geração interrompida (exc): {e}"); return f"❌ Geração interrompida (segurança/outro)."
238
- except google_exceptions.GoogleAPIError as e:
239
- logger.error(f"Erro API Google - Tentativa {tentativa + 1}: {e}")
240
- if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay']*(tentativa + 1)); continue
241
- return f"❌ Erro API Google: {e}"
242
- except Exception as e:
243
- logger.exception(f"Erro inesperado API - Tentativa {tentativa + 1}: {e}")
244
- if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay']*(tentativa + 1)); continue
245
- return f"❌ Erro inesperado API: {e}"
246
- logger.error("Falha API após todas as tentativas.")
247
- return "❌ Falha API após múltiplas tentativas."
248
-
249
- def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
250
- logger.info(f"Correção: Tipo={tipo_peticao}, Treinamento={modo_treinamento}")
251
- start = time.time()
252
- analise_fmt, pet_corr, res_diff, crit_diff, html_diff = "", "", "", "", ""
253
- pet_cont = ""
254
- try:
255
- if not caso or not caso.strip(): return "⚠️ Forneça caso.", "", "", "", ""
256
- if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo.", "", "", "", ""
257
- if peticao_arquivo: pet_cont = process_file(peticao_arquivo)
258
- elif peticao_texto: pet_cont = peticao_texto
259
- else: return "⚠️ Forneça petição.", "", "", "", ""
260
- if pet_cont.startswith(("❌", "⚠️")): return pet_cont, "", "", "", ""
261
- logger.info(f"Petição recebida ({len(pet_cont)} chars).")
262
- troll = ["escreva a petição", "faça a petição", "gere a petição"]
263
- if any(t in pet_cont.lower() for t in troll): return random.choice(["Hummm 😉","🚫 Corrijo, não crio."]), "", "", "", ""
264
- cache_key = f"{tipo_peticao}|{caso[:100]}|{pet_cont[:100]}|{modo_treinamento}"
265
- if cache_key in CORRECAO_CACHE:
266
- logger.info("Cache hit.")
267
- cached = CORRECAO_CACHE[cache_key]
268
- return cached["analise_formatada"], cached["peticao_corrigida"], cached["resumo_diff"], cached["criticas_diff"], cached["diff_html"]
269
- prompt_analise = estruturar_prompt_analise(caso, pet_cont, tipo_peticao, modo_treinamento)
270
- analise_bruta = chamar_api(prompt_analise)
271
- if analise_bruta.startswith(("❌", "⏱️", "⚠️")): return analise_bruta, "", "", "", ""
272
- analise_fmt = formatar_correcao(analise_bruta)
273
- prompt_peticao = estruturar_prompt_peticao_corrigida(caso, pet_cont, tipo_peticao)
274
- pet_corr_bruta = chamar_api(prompt_peticao)
275
- if pet_corr_bruta.startswith(("❌", "⏱️", "⚠️")): pet_corr = f"⚠️ Falha gerar corrigida:\n{pet_corr_bruta}"
276
  else:
277
- markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
278
- start_index = -1
279
- p_lower = pet_corr_bruta.lower()
280
- # CORRIGIDO: Bloco for com indentação correta
281
- for m in markers:
282
- idx = p_lower.find(m)
283
- if idx != -1:
284
- start_index = idx
285
- break
286
- pet_extr = pet_corr_bruta[start_index:] if start_index != -1 else pet_corr_bruta
287
- pet_corr = pos_corrigir_peticao(pet_extr, tipo_peticao)
288
- html_diff, res_diff, crit_diff = calcular_diff_interno(pet_cont, pet_corr)
289
- res_cache = {"analise_formatada":analise_fmt,"peticao_corrigida":pet_corr,"resumo_diff":res_diff,"criticas_diff":crit_diff,"diff_html":html_diff}
290
- CORRECAO_CACHE[cache_key] = res_cache
291
- HISTORICO.append({"data":datetime.now().strftime("%Y%m%d_%H%M%S"),"tipo_peticao":tipo_peticao,"caso":caso,"peticao_original":pet_cont,**res_cache})
292
- if len(HISTORICO) > 10: HISTORICO.pop(0)
293
- logger.info(f"Correção concluída em {time.time() - start:.2f}s.")
294
- return analise_fmt, pet_corr, res_diff, crit_diff, html_diff
295
- except Exception as e: logger.exception("Erro GERAL corrigir_peticao:"); return f"❌ Erro correção: {e}", "", "", "", ""
296
-
297
- def formatar_correcao(texto):
298
- texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
299
- texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
300
- texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
301
- texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
302
- texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica")
303
- linhas = texto.split('\n'); linhas_fmt = []
304
- in_list = False
305
- for l in linhas:
306
- ls = l.strip()
307
- if ls.startswith(("1.","2.","3.","4.","5.")): linhas_fmt.append(f"\n**{ls}**"); in_list = False
308
- elif ls.startswith(("- ","* ")): prefix = " * " if in_list else "* "; linhas_fmt.append(prefix + ls[2:]); in_list = True
309
- elif ls.startswith(("a)","b)","c)","d)","e)")): linhas_fmt.append(f" * {ls}"); in_list = True
310
- else: linhas_fmt.append(l); in_list = False if not ls.startswith(("*","- ","a)","b)","c)","d)","e)"," ")) else in_list
311
- txt_final = "\n".join(linhas_fmt)
312
- m = re.search(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})", txt_final, re.I)
313
- if m: nota = m.group(2).replace(",","."); txt_final = re.sub(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})", f"\\1**{nota}/10**", txt_final, count=1, flags=re.I)
314
- return txt_final
315
-
316
- def fornecer_dica_contextual(texto_peticao, tipo_peticao):
317
- if not texto_peticao or not texto_peticao.strip(): return "*Digite para dicas...*"
318
- dicas = []; txt_l = texto_peticao.lower()
319
- for k in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]:
320
- if k in DICAS_OAB_MELHORADO: dicas.append(f"💡 **{k}:** {DICAS_OAB_MELHORADO[k]}")
321
- kw = { "Petição Inicial": ["endereçamento","qualificação","fatos","pedidos","valor","provas","audiência","custas"],
322
- "Contestação": ["preliminar","mérito","impugnação","art. 337"], "Embargos de Declaração": ["omissão","contradição","art. 1022"],
323
- "Recurso de Apelação": ["sentença","reforma","dialeticidade","art. 1009"], "Agravo de Instrumento": ["interlocutória","cabimento","art. 1015"],
324
- "Petição de Tutela Provisória": ["urgência","evidência","periculum","fumus","art. 300"]}
325
- if tipo_peticao in kw:
326
- for k, v in DICAS_OAB_MELHORADO.items():
327
- cl = k.lower()
328
- if f"({tipo_peticao.split()[0].lower()})" in cl or any(w in cl for w in kw[tipo_peticao] if w in txt_l):
329
- fmt = f"💡 **{k}:** {v}"
330
- if fmt not in dicas: dicas.append(fmt)
331
- if not dicas: return "*Continue para dicas...*"
332
- return "\n\n".join(dicas[:4])
333
-
334
- def calcular_diff_interno(texto_original, texto_corrigido):
335
- if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")): return "","",""
336
- orig = texto_original.splitlines(); corr = texto_corrigido.splitlines()
337
- diff = difflib.ndiff(orig, corr)
338
- html, crit, rem, add = [], [], 0, 0
339
- kw_r = ["invenção","identificação","erro","incorreto","prazo"]
340
- kw_a = ["custas","art. 319","art. 337","art. 1022","art. 1015","art. 300","fumus","periculum","essencial","preliminar","tempestividade","cabimento"]
341
- for l in diff:
342
- txt = l[2:]; txt_esc = escape(txt)
343
- if l.startswith(' '): html.append(f"<div class='linha-igual'>{txt_esc}</div>")
344
- elif l.startswith('- '):
345
- rem += 1; html.append(f"<div class='linha-removida'><del>{txt_esc}</del></div>")
346
- if txt.strip() and any(kw in txt.lower() for kw in kw_r): crit.append(f"<span style='color:red'>🔴 REM:</span> {txt_esc}")
347
- elif l.startswith('+ '):
348
- add += 1; html.append(f"<div class='linha-adicionada'><ins>{txt_esc}</ins></div>")
349
- if txt.strip() and any(kw in txt.lower() for kw in kw_a): crit.append(f"<span style='color:green'>🟢 ADD:</span> {txt_esc}")
350
- resumo = f"**Resumo:** Rem={rem}, Add={add}, Saldo={add-rem}"
351
- crit_txt = "### 🚨 Críticas:\n"+"\n".join(crit) if crit else "*Nenhuma crítica.*"
352
- css = "<style>.diff-container{font-family:monospace;white-space:pre-wrap;border:1px solid #ccc;padding:10px;background-color:#f9f9f9;max-height:600px;overflow-y:auto}.linha-igual{color:#555}.linha-removida{background-color:#ffebee;color:#c62828}.linha-adicionada{background-color:#e8f5e9;color:#2e7d32}.linha-removida del,.linha-adicionada ins{text-decoration:none;padding:0 2px;border-radius:2px}.linha-removida del{background-color:#ffcdd2}.linha-adicionada ins{background-color:#c8e6c9}</style>"
353
- html_diff = f"{css}<div class='diff-container'>{''.join(html)}</div>"
354
- return html_diff, resumo, crit_txt
355
-
356
- def calcular_progresso():
357
- if not HISTORICO: return "📊 Nenhum progresso."
358
- notas, tipos = [], set()
359
- for entry in HISTORICO:
360
- tipos.add(entry["tipo_peticao"])
361
- m = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.I)
362
- if m:
363
- try: notas.append(float(m.group(1).replace(",", ".")))
364
- except ValueError: pass
365
- tot = len(HISTORICO); media = sum(notas)/len(notas) if notas else 0; tend = ""
366
- if len(notas) > 1: tend = "⬆️" if notas[-1] > notas[-2] else ("⬇️" if notas[-1] < notas[-2] else "↔️")
367
- return f"**📊 Resumo:** Corrs:{tot}, Tipos:{len(tipos)}, Média:{media:.1f}/10 ({len(notas)} val), Tend:{tend if tend else 'N/A'}"
368
-
369
- def mostrar_historico():
370
- if not HISTORICO: return "📜 Histórico vazio."
371
- md = "### 📜 Histórico (Últimas 10)\n---\n"
372
- for i, entry in enumerate(reversed(HISTORICO)):
373
- caso = (entry['caso'][:70] + '...') if len(entry['caso']) > 70 else entry['caso']
374
- nota = "N/A"; m = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.I)
375
- if m: nota = f"{m.group(1).replace(',', '.')}/10"
376
- md += f"**{len(HISTORICO)-i}.** {entry['data']} | **{entry['tipo_peticao']}** | **Nota:{nota}**\n **Caso:** _{caso}_\n---\n"
377
- return md
378
-
379
- def salvar_progresso(caso, peticao, tipo_peticao):
380
- logger.info("Salvando progresso...")
381
- try:
382
- if not caso or not peticao or not tipo_peticao: return "", "⚠️ Inputs necessários.", None
383
- dados = {"tipo_peticao":tipo_peticao,"caso":caso,"peticao":peticao,"timestamp":datetime.now().isoformat()}
384
- json_cont = json.dumps(dados, ensure_ascii=False, indent=4)
385
- fname = f"progresso_peticao_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
386
- fpath = os.path.join(tempfile.gettempdir(), fname)
387
- with open(fpath, 'w', encoding='utf-8') as f: f.write(json_cont)
388
- logger.info(f"Progresso salvo: {fpath}")
389
- msg = f"✅ Salvo! Baixe `{fname}`."
390
- return json_cont, msg, fpath
391
- except Exception as e: logger.error(f"Erro salvar: {e}", exc_info=True); return "", f"❌ Erro salvar: {e}", None
392
-
393
- def carregar_progresso(arquivo_entrada):
394
- logger.info("Carregando progresso...")
395
- if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo."
396
- try:
397
- fpath = arquivo_entrada.name; logger.info(f"Carregando: {fpath}")
398
- with open(fpath, 'r', encoding='utf-8') as f: dados = json.load(f)
399
- caso, peticao, tipo = dados.get("caso"), dados.get("peticao"), dados.get("tipo_peticao")
400
- if not all([caso, peticao, tipo]): return "", "", "", "❌ Arquivo inválido."
401
- if tipo not in TIPOS_DE_PETICAO: return caso, peticao, "", f"⚠️ Tipo '{tipo}' inválido."
402
- logger.info("Progresso carregado."); return tipo, caso, peticao, "✅ Progresso carregado!"
403
- except Exception as e: logger.error(f"Erro carregar: {e}", exc_info=True); return "", "", "", f"❌ Erro carregar: {e}"
404
-
405
- def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
406
- prompt = f"Crie caso fictício **detalhado e realista** p/ peça **'{tipo_peticao}'** (simulado OAB).\n**Geral:**\n1.Complexidade adequada (15-25l).\n2.Dados Essenciais (nomes,locais,datas,valores,relação,conflito).\n3.Coerência.\n4.Foco no Tipo Peça.\n**Específico '{tipo_peticao}':**\n"
407
- if tipo_peticao == "Petição Inicial": prompt += "* Descreva violação. * Inclua danos. * Dados qualificação. * Tema: Consumo, Contrato, etc."
408
- elif tipo_peticao == "Contestação": prompt += "* Resumo PI recebida. * Versão Réu (p/ prelim, impug, mérito). * Dados qualif réu."
409
- elif tipo_peticao == "Reconvenção": prompt += "* Estrutura Contestação + Fatos/Fundamentos p/ pedido PRÓPRIO CONEXO do Réu contra Autor."
410
- elif tipo_peticao == "Impugnação à Contestação": prompt += "* Resumo PI Autor. * Resumo Contestação Réu (prelim, fatos novos, docs). * Objetivo: Rebater."
411
- elif tipo_peticao == "Embargos de Declaração": prompt += "* Breve caso. * TRECHO DECISÃO. * INDIQUE VÍCIO (omissão/contradição/obscuridade/erro material)."
412
- elif tipo_peticao == "Recurso de Apelação": prompt += "* Caso original. * Resumo SENTENÇA. * ELEMENTOS ERRO (in judicando/procedendo). * Indique apelante."
413
- elif tipo_peticao == "Agravo de Instrumento": prompt += "* Caso andamento. * DECISÃO INTERLOCUTÓRIA. * Enquadrar Art.1015/mitig. * ELEMENTOS ERRO + URGÊNCIA. * Indique agravante."
414
- elif tipo_peticao == "Petição de Tutela Provisória": prompt += "* Situação URGENTE ou EVIDENTE. * ELEMENTOS: Urg=Fumus+Peric; Evid=Hipót Art.311. * Medida específica. * Antec/Incid."
415
- else: prompt += "* Crie cenário cível com conflito."
416
- prompt += f'\n**Finalize:**\n"**Situação-Problema:** Advogado(a) de [Nome Cliente], elabore a peça cabível ({tipo_peticao})."\n**Importante:** Temas comuns.'
417
- return prompt
418
 
419
- def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
420
- logger.info(f"Gerando caso para: {tipo_peticao}")
 
 
 
 
 
 
 
 
 
 
 
 
421
  try:
422
- if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo válido."
423
- prompt = estruturar_prompt_geracao_caso(tipo_peticao)
424
- caso_gerado = chamar_api(prompt)
425
- if caso_gerado.startswith(("❌","⏱️","⚠️")): return f"⚠️ Falha gerar caso:\n{caso_gerado}"
426
- frase = "**Situação-Problema:** Na qualidade de advogado(a) de"
427
- if frase.lower() not in caso_gerado.lower():
428
- logger.warning("Frase final não encontrada. Adicionando.")
429
- nomes = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado)
430
- cliente = nomes[0] if nomes else "[Cliente]"
431
- caso_gerado += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente}, elabore a peça processual cabível ({tipo_peticao})."
432
- logger.info("Caso gerado.")
433
- return caso_gerado
434
- except Exception as e: logger.exception("Erro gerar_caso:"); return f"❌ Erro gerar caso: {e}"
435
-
436
- def atualizar_config(temp, max_tokens):
437
- CONFIG["temperatura"] = temp
438
- CONFIG["max_tokens"] = max_tokens
439
- logger.info(f"Configs: Temp={temp}, Max Tokens={max_tokens}")
440
- return f"✅ Configs Atualizadas!"
441
-
442
- def interface_gradio():
443
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
444
- gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️\nAnalise e aprimore suas peças processuais!")
445
- with gr.Row():
446
- with gr.Column(scale=1):
447
- gr.Markdown("### ⚙️ Configs")
448
- tipo_peticao_input = gr.Dropdown(label="1. Tipo Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial")
449
- modo_treinamento = gr.Checkbox(label="🎓 Treinamento", value=False)
450
- with gr.Accordion("🛠️ Opções IA", open=False):
451
- temperatura_slider = gr.Slider(0.0, 1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade")
452
- max_tokens_slider = gr.Slider(2048, 8192, value=CONFIG["max_tokens"], step=256, label="Max Tokens")
453
- config_update_btn = gr.Button("💾 Salvar Configs")
454
- config_status = gr.Textbox(label="Status", interactive=False, lines=1)
455
- with gr.Accordion("📊 Dashboard", open=False):
456
- progresso_output = gr.Markdown("*Atualize...*")
457
- progresso_btn = gr.Button("🔄 Progresso")
458
- with gr.Accordion("📜 Histórico", open=False):
459
- historico_output = gr.Markdown("*Atualize...*")
460
- historico_btn = gr.Button("🔄 Histórico")
461
- with gr.Accordion("💾📂 Salvar/Carregar", open=False):
462
- salvar_btn = gr.Button("💾 Salvar")
463
- carregar_btn = gr.Button("📂 Carregar")
464
- arquivo_progresso_upload = gr.File(label=".json", file_types=[".json"])
465
- progresso_download_file = gr.File(label="Baixar", interactive=False)
466
- with gr.Column(scale=3):
467
- gr.Markdown("### 1. Caso / Contexto")
468
- with gr.Row():
469
- caso_input = gr.Textbox(label="📋 Caso", lines=8, placeholder="Insira o texto do caso...")
470
- caso_btn = gr.Button("🎲 Gerar", variant="secondary", scale=0)
471
- gr.Markdown("### 2. Sua Peça")
472
- with gr.Tabs():
473
- with gr.TabItem("✏️ Texto"):
474
- peticao_texto_input = gr.Textbox(label="Texto", lines=12, placeholder="Cole ou digite...")
475
- dica_contextual_output = gr.Markdown("*Dicas...*")
476
- btn_corrigir = gr.Button("🚀 CORRIGIR!", variant="primary")
477
- with gr.TabItem("📎 Arquivo"):
478
- peticao_arquivo_input = gr.File(label="Arquivo (.pdf,.docx,.txt)", file_types=[".pdf",".docx",".txt"])
479
- arquivo_preview_output = gr.Textbox(label="🔍 Prévia", lines=4, interactive=False)
480
- btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR ARQUIVO!", variant="primary")
481
-
482
- processing_message = gr.Markdown("", visible=False)
483
- gr.Markdown("--- \n ### 3. Resultados")
484
- with gr.Tabs():
485
- with gr.TabItem("📊 Análise e Correção"):
486
- resultado_analise_output = gr.Markdown(label="Análise e Nota")
487
- resultado_peticao_corrigida_output = gr.Textbox(label="✨ Versão Sugerida", lines=18, show_copy_button=True)
488
- with gr.TabItem("⚖️ Comparativo"):
489
- gr.Markdown("### ↔️ Comparativo (Diff)")
490
- with gr.Row():
491
- with gr.Column(scale=1): resumo_alteracoes_output = gr.Markdown(label="Resumo"); criticas_output = gr.Markdown(label="Críticas")
492
- with gr.Column(scale=3): visualizacao_detalhada_output = gr.HTML(label="Detalhes")
493
- clear_btn = gr.Button("🗑️ Limpar Tudo")
494
-
495
- caso_btn.click(lambda: gr.update(value="⏳ Gerando...", visible=True), outputs=processing_message).then(
496
- fn=gerar_caso_ficticio, inputs=[tipo_peticao_input], outputs=[caso_input]
497
- ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
498
-
499
- peticao_texto_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output)
500
- tipo_peticao_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output)
501
- peticao_arquivo_input.change(process_file, [peticao_arquivo_input], [arquivo_preview_output])
502
-
503
- btn_corrigir.click(lambda: gr.update(value="⏳ Processando texto...", visible=True), outputs=processing_message).then(
504
- fn=corrigir_peticao,
505
- inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento],
506
- outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
507
- concurrency_limit=1
508
- ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
509
-
510
- btn_corrigir_arquivo.click(lambda: gr.update(value="⏳ Processando arquivo...", visible=True), outputs=processing_message).then(
511
- fn=corrigir_peticao,
512
- inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento],
513
- outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
514
- concurrency_limit=1
515
- ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
516
-
517
- config_update_btn.click(atualizar_config, [temperatura_slider, max_tokens_slider], config_status)
518
- progresso_btn.click(calcular_progresso, outputs=progresso_output)
519
- historico_btn.click(mostrar_historico, outputs=historico_output)
520
-
521
- salvar_btn.click(lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message).then(
522
- fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
523
- outputs=[gr.Textbox(visible=False), config_status, progresso_download_file]
524
- ).then(lambda x: gr.update(visible=bool(x)), [progresso_download_file], [progresso_download_file]
525
- ).then(lambda: gr.update(value="", visible=False), None, processing_message)
526
-
527
- carregar_btn.click(lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message).then(
528
- fn=carregar_progresso, inputs=[arquivo_progresso_upload],
529
- outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status]
530
- ).then(lambda: gr.update(value="", visible=False), None, processing_message)
531
-
532
- clear_btn.click(
533
- fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas...*", "", "", "", "", "", None, None, ""],
534
- outputs=[tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, peticao_arquivo_input,
535
- arquivo_preview_output, dica_contextual_output, resultado_analise_output, resultado_peticao_corrigida_output,
536
- resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output,
537
- arquivo_progresso_upload, progresso_download_file, config_status]
538
  )
539
- return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
540
 
541
  if __name__ == "__main__":
542
- logger.info("Iniciando a aplicação Gradio...")
543
- app = interface_gradio()
544
- app.launch()
545
- logger.info("Aplicação Gradio encerrada.")
 
 
1
  import gradio as gr
2
+ import requests
 
3
  import os
4
  import json
5
+ import PyPDF2
6
  import docx
7
  import io
8
  import time
 
11
  from pdfplumber import open as pdfplumber_open
12
  from PIL import Image
13
  import pytesseract
14
+ import cv2
15
+ import numpy as np
 
 
 
 
 
16
  from datetime import datetime
17
  from ratelimiter import RateLimiter
18
  import difflib
19
  from html import escape
20
  import random
21
  import logging
22
+ import uuid
 
23
 
24
+ # Configura o logging para depuração
25
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
26
  logger = logging.getLogger(__name__)
27
 
28
  load_dotenv()
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
+ GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}"
 
 
 
 
34
 
35
+ CONFIG = {
36
+ "max_retry_attempts": 3,
37
+ "retry_delay": 5,
38
+ "timeout": 60,
39
+ "temperatura": 0.2,
40
+ "max_tokens": 12882
41
+ }
 
 
 
42
 
43
+ rate_limiter = RateLimiter(max_calls=60, period=60)
44
 
45
+ # Dicas gerais para petições
46
  DICAS_OAB_MELHORADO = {
47
+ "Endereçamento": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). Exemplo: 'Excelentíssimo Senhor Juiz de Direito da Vara Cível da Comarca de [Cidade do Autor]'.",
48
+ "Qualificação": "Mencione nome, estado civil (se informado), endereço (use 'Rua XXX' se não fornecido). Dica: Verifique se todos os dados estão corretos.",
49
+ "Fatos": "Descreva os fatos cronologicamente, sem inventar informações. Exemplo: 'Em [data], o autor celebrou contrato com o réu para [descrição]'.",
50
+ "Pedidos": "Seja específico nos pedidos (ex.: 'condenação ao pagamento de R$ X'). Inclua danos morais e materiais, se aplicável.",
51
+ "Custas": "Inclua 'Requer a juntada da guia de custas devidamente recolhida'. Nunca omita para evitar extinção do processo.",
52
+ "Fundamentação": "Baseie-se em artigos do CPC ou CDC e súmulas do STJ (ex.: Súmula 387 para danos morais).",
53
+ "Fechamento": "Finalize com 'Nestes termos pede deferimento', sem vírgula. Evite erros de pontuação.",
54
+ "Estratégia Jurídica": "Utilize precedentes e súmulas para fortalecer a argumentação.",
55
+ "Provas": "Especifique as provas a produzir. Inclua testemunhas, documentos e perícias, se necessário.",
56
+ "Contra-argumentação": "Antecipar defesas da parte contrária e preparar respostas."
57
+ }
58
+
59
+ # Prompts específicos para cada tipo de petição
60
+ PROMPTS_PETICOES = {
61
+ "Petição Inicial": """
62
+ Você é um corretor de petições iniciais para prática cível no Brasil, com base no CPC. Analise a petição fornecida e corrija-a considerando:
63
+ - **Endereçamento**: Verifique se está correto (ex.: "Excelentíssimo Senhor Juiz de Direito da [Vara] da Comarca de [Cidade]"). Sugira foro do domicílio do autor em relações de consumo (Art. 101, I, CDC).
64
+ - **Qualificação**: Confirme se inclui nome, estado civil, CPF/CNPJ, endereço e representação legal (se aplicável).
65
+ - **Fatos**: Avalie se os fatos estão claros, cronológicos e suficientes para embasar o pedido.
66
+ - **Fundamentação Jurídica**: Verifique se cita artigos do CPC, CC ou CDC e jurisprudência relevante (ex.: Súmula 387/STJ para danos morais).
67
+ - **Pedidos**: Confirme se são específicos (ex.: "condenação ao pagamento de R$ X") e incluem tutela provisória, se cabível.
68
+ - **Provas**: Avalie se especifica documentos, testemunhas ou perícias.
69
+ - **Valor da Causa**: Verifique se está presente e correto (Art. 292, CPC).
70
+ - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
71
+ Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos para maior clareza e conformidade. Retorne o resultado em markdown com seções claras (ex.: ### Endereçamento, ### Qualificação).
72
+ """,
73
+ "Contestação": """
74
+ Você é um corretor de contestações para prática cível no Brasil, com base no CPC. Analise a contestação fornecida e corrija-a considerando:
75
+ - **Endereçamento**: Igual ao da petição inicial, mas com menção à ação principal (ex.: "Processo nº [número]").
76
+ - **Qualificação**: Confirme se o réu está corretamente qualificado.
77
+ - **Preliminares**: Verifique se aponta ilegitimidade, incompetência ou outras defesas processuais (Art. 337, CPC).
78
+ - **Mérito**: Avalie se refuta os fatos da inicial e apresenta defesa sólida, com base legal (ex.: Arts. CC, CDC).
79
+ - **Reconvenção**: Se presente, confirme se está vinculada à ação principal e bem fundamentada (Art. 343, CPC).
80
+ - **Provas**: Avalie se especifica provas a produzir (ex.: documentos, testemunhas).
81
+ - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
82
+ Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos para maior clareza e conformidade. Retorne o resultado em markdown com seções claras.
83
+ """,
84
+ "Reconvenção": """
85
+ Você é um corretor de reconvenções para prática cível no Brasil, com base no CPC. Analise a reconvenção fornecida e corrija-a considerando:
86
+ - **Endereçamento**: Igual à contestação, com menção ao processo principal.
87
+ - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
88
+ - **Vinculação**: Verifique se o pedido está relacionado à ação principal (Art. 343, CPC).
89
+ - **Fatos**: Avalie se os fatos são claros e suficientes para embasar o pedido reconvencional.
90
+ - **Fundamentação Jurídica**: Confirme se cita dispositivos legais e jurisprudência.
91
+ - **Pedidos**: Verifique se são específicos e compatíveis com a reconvenção.
92
+ - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
93
+ Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
94
+ """,
95
+ "Impugnação à Contestação": """
96
+ Você é um corretor de impugnações à contestação para prática cível no Brasil, com base no CPC. Analise a impugnação fornecida e corrija-a considerando:
97
+ - **Endereçamento**: Igual ao da ação principal.
98
+ - **Qualificação**: Confirme se as partes estão corretamente mencionadas.
99
+ - **Refutação**: Avalie se contesta ponto a ponto as alegações da contestação, com argumentos claros.
100
+ - **Fundamentação Jurídica**: Verifique se reforça os argumentos da inicial com base legal e jurisprudência.
101
+ - **Provas**: Confirme se reitera ou adiciona provas relevantes.
102
+ - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
103
+ Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
104
+ """,
105
+ "Embargos de Declaração": """
106
+ Você é um corretor de embargos de declaração para prática cível no Brasil, com base no CPC. Analise os embargos fornecidos e corrija-os considerando:
107
+ - **Endereçamento**: Dirigido ao juiz ou tribunal da decisão embargada.
108
+ - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
109
+ - **Objetivo**: Verifique se aponta obscuridade, contradição, omissão ou erro material (Art. 1.022, CPC).
110
+ - **Fundamentação**: Avalie se explica claramente o vício apontado, sem rediscutir o mérito.
111
+ - **Pedidos**: Confirme se solicita esclarecimento ou correção da decisão.
112
+ - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
113
+ Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
114
+ """,
115
+ "Recurso de Apelação": """
116
+ Você é um corretor de recursos de apelação para prática cível no Brasil, com base no CPC. Analise o recurso fornecido e corrija-o considerando:
117
+ - **Endereçamento**: Dirigido ao tribunal competente (ex.: "Tribunal de Justiça do Estado de [Estado]").
118
+ - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
119
+ - **Tempestividade**: Verifique se menciona o prazo de 15 dias (Art. 1.003, CPC).
120
+ - **Razões**: Avalie se aponta erros de fato ou de direito na sentença, com fundamentação legal e jurisprudencial.
121
+ - **Pedidos**: Confirme se solicita reforma ou anulação da sentença.
122
+ - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
123
+ Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
124
+ """,
125
+ "Agravo de Instrumento": """
126
+ Você é um corretor de agravos de instrumento para prática cível no Brasil, com base no CPC. Analise o agravo fornecido e corrija-o considerando:
127
+ - **Endereçamento**: Dirigido ao tribunal competente (ex.: "Tribunal de Justiça do Estado de [Estado]").
128
+ - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
129
+ - **Tempestividade**: Verifique se menciona o prazo de 15 dias (Art. 1.003, CPC).
130
+ - **Decisão Impugnada**: Avalie se identifica claramente a decisão interlocutória (Art. 1.015, CPC).
131
+ - **Risco de Dano**: Confirme se demonstra o risco de dano irreparável ou de difícil reparação.
132
+ - **Fundamentação**: Verifique se aponta erro na decisão com base legal e jurisprudencial.
133
+ - **Pedidos**: Confirme se solicita efeito suspensivo ou reforma da decisão.
134
+ - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
135
+ Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
136
+ """,
137
+ "Petição de Tutela Provisória": """
138
+ Você é um corretor de petições de tutela provisória para prática cível no Brasil, com base no CPC. Analise a petição fornecida e corrija-a considerando:
139
+ - **Endereçamento**: Igual ao da ação principal ou ao juiz competente.
140
+ - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
141
+ - **Tipo de Tutela**: Verifique se especifica tutela de urgência (cautelar/antecipada) ou de evidência (Art. 294, CPC).
142
+ - **Requisitos**: Avalie se demonstra probabilidade do direito e perigo de dano ou risco ao resultado útil do processo (Art. 300, CPC).
143
+ - **Fundamentação Jurídica**: Confirme se cita dispositivos do CPC e jurisprudência.
144
+ - **Pedidos**: Verifique se solicita medida específica (ex.: liminar para suspender ato).
145
+ - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
146
+ Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
147
+ """
148
  }
149
 
150
  HISTORICO = []
151
  CORRECAO_CACHE = {}
152
 
153
+ def preprocess_image_for_ocr(img):
 
 
 
154
  try:
155
+ gray = img.convert('L')
156
+ np_img = np.array(gray)
157
+ _, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
158
  return Image.fromarray(thresh)
159
  except Exception as e:
160
+ logger.error(f"Erro no pré-processamento da imagem: {str(e)}")
161
+ return f"❌ Erro no pré-processamento: {str(e)}"
162
 
163
  def extract_text_from_pdf(file_path_or_obj):
 
164
  try:
165
+ if isinstance(file_path_or_obj, str):
166
+ with open(file_path_or_obj, 'rb') as f:
167
+ text = ""
168
+ with pdfplumber_open(f) as pdf:
169
+ for page in pdf.pages:
170
+ text += (page.extract_text() or "") + "\n"
171
+ if not text.strip():
172
+ images = pdfplumber_open(f).pages
173
+ for i, image in enumerate(images):
174
+ try:
175
+ img = image.to_image()
176
+ preprocessed_img = preprocess_image_for_ocr(img)
177
+ text += pytesseract.image_to_string(preprocessed_img) + "\n"
178
+ except Exception as e:
179
+ text += f"[Erro na página {i+1}: {str(e)}]\n"
180
+ else:
181
+ text = ""
182
+ with pdfplumber_open(file_path_or_obj) as pdf:
183
+ for page in pdf.pages:
184
+ text += (page.extract_text() or "") + "\n"
185
+ if not text.strip():
186
+ images = pdfplumber_open(file_path_or_obj).pages
187
+ for i, image in enumerate(images):
188
+ try:
189
+ img = image.to_image()
190
+ preprocessed_img = preprocess_image_for_ocr(img)
191
+ text += pytesseract.image_to_string(preprocessed_img) + "\n"
192
+ except Exception as e:
193
+ text += f"[Erro na página {i+1}: {str(e)}]\n"
194
+ return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF."
195
+ except Exception as e:
196
+ logger.error(f"Erro ao processar o PDF: {str(e)}")
197
+ return f"❌ Erro ao processar o PDF: {str(e)}"
198
 
199
  def extract_text_from_docx(file_path_or_obj):
200
  try:
201
+ if isinstance(file_path_or_obj, str):
202
+ with open(file_path_or_obj, 'rb') as f:
203
+ doc = docx.Document(f)
204
+ text = ""
205
+ for para in doc.paragraphs:
206
+ text += para.text + "\n"
207
+ for table in doc.tables:
208
+ for row in table.rows:
209
+ for cell in row.cells:
210
+ text += cell.text + " "
211
+ text += "\n"
212
+ else:
213
+ doc = docx.Document(file_path_or_obj)
214
+ text = ""
215
+ for para in doc.paragraphs:
216
+ text += para.text + "\n"
217
+ for table in doc.tables:
218
+ for row in table.rows:
219
+ for cell in row.cells:
220
+ text += cell.text + " "
221
+ text += "\n"
222
+ return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX."
223
+ except Exception as e:
224
+ logger.error(f"Erro ao processar o DOCX: {str(e)}")
225
+ return f"❌ Erro ao processar o DOCX: {str(e)}"
226
 
227
  def extract_text_from_txt(file_path_or_obj):
228
  try:
 
229
  if isinstance(file_path_or_obj, str):
230
+ with open(file_path_or_obj, 'rb') as f:
231
+ content = f.read()
232
+ encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
233
+ for encoding in encodings:
234
+ try:
235
+ return content.decode(encoding)
236
+ except UnicodeDecodeError:
237
+ continue
238
+ return "❌ Não foi possível decodificar o arquivo TXT."
239
+ else:
240
  content = file_path_or_obj.read()
241
+ encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
242
+ for encoding in encodings:
243
+ try:
244
+ return content.decode(encoding)
245
+ except UnicodeDecodeError:
246
+ continue
247
+ return "❌ Não foi possível decodificar o arquivo TXT."
248
+ except Exception as e:
249
+ logger.error(f"Erro ao processar o TXT: {str(e)}")
250
+ return f"❌ Erro ao processar o TXT: {str(e)}"
251
 
252
  def process_file(file_obj):
253
+ if file_obj is None:
254
+ return "Nenhum arquivo enviado."
255
  try:
256
+ if hasattr(file_obj, 'name'):
257
+ file_path = file_obj.name
258
+ filename = os.path.basename(file_path).lower()
259
+ file_size = os.path.getsize(file_path)
260
+ if file_size > 10 * 1024 * 1024:
261
+ return "⚠️ O arquivo é muito grande. Máx. 10 MB."
262
+ if filename.endswith('.pdf'):
263
+ return extract_text_from_pdf(file_path)
264
+ elif filename.endswith('.docx'):
265
+ return extract_text_from_docx(file_path)
266
+ elif filename.endswith('.txt'):
267
+ return extract_text_from_txt(file_path)
268
+ else:
269
+ return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  else:
271
+ return "❌ Erro: Objeto de arquivo inválido."
272
+ except Exception as e:
273
+ logger.error(f"Erro ao processar o arquivo: {str(e)}")
274
+ return f"❌ Erro ao processar o arquivo: {str(e)}"
275
+
276
+ def gerar_caso_simulado(tipo_peticao):
277
+ casos = {
278
+ "Petição Inicial": """
279
+ João, residente em São Paulo, celebrou contrato de compra de um veículo com a concessionária AutoLux em 01/01/2025. O veículo apresentou defeitos graves em 15/01/2025, e a concessionária recusou o reparo. João deseja rescindir o contrato, receber o valor pago (R$ 50.000) e indenização por danos morais (R$ 10.000).
280
+ """,
281
+ "Contestação": """
282
+ Na ação movida por João contra a AutoLux, a concessionária alega que o defeito no veículo decorre de mau uso por João e que não há prova de falha na fabricação. A AutoLux contesta o pedido de rescisão e danos morais, requerendo a improcedência da ação.
283
+ """,
284
+ "Reconvenção": """
285
+ Na mesma ação, a AutoLux, ao contestar, alega que João deve R$ 5.000 por serviços de manutenção não pagos, requerendo a condenação de João ao pagamento dessa quantia.
286
+ """,
287
+ "Impugnação à Contestação": """
288
+ João, ao responder à contestação da AutoLux, nega o mau uso do veículo e apresenta laudo técnico comprovando defeito de fabricação. Reitera os pedidos da inicial e contesta a reconvenção, negando a dívida de R$ 5.000.
289
+ """,
290
+ "Embargos de Declaração": """
291
+ Em sentença que julgou a ação de João contra a AutoLux, o juiz omitiu-se sobre o pedido de danos morais, apesar de deferir a rescisão contratual. João deseja esclarecer a omissão.
292
+ """,
293
+ "Recurso de Apelação": """
294
+ A sentença na ação de João contra a AutoLux deferiu a rescisão, mas negou os danos morais, sob o argumento de ausência de prova. João apela, alegando que os danos morais decorrem do descaso da concessionária.
295
+ """,
296
+ "Agravo de Instrumento": """
297
+ Na ação de João contra a AutoLux, o juiz indeferiu liminar para suspender a cobrança de parcelas do veículo. João agrava, alegando risco de inclusão de seu nome em cadastros de inadimplentes.
298
+ """,
299
+ "Petição de Tutela Provisória": """
300
+ João, na ação contra a AutoLux, requer tutela de urgência para suspender a cobrança de parcelas do veículo, sob risco de negativação, enquanto se discute a rescisão contratual.
301
+ """
302
+ }
303
+ return casos.get(tipo_peticao, "⚠️ Tipo de petição não suportado.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
+ def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
306
+ prompt = PROMPTS_PETICOES.get(tipo_peticao, PROMPTS_PETICOES["Petição Inicial"])
307
+ base_prompt = f"""
308
+ {prompt}
309
+ **Caso fornecido**: {caso}
310
+ **Petição fornecida**: {peticao}
311
+ **Instruções adicionais**:
312
+ - Se o caso ou a petição estiverem incompletos, sugira informações plausíveis com base no CPC e nas práticas forenses.
313
+ - Em modo de treinamento, forneça exemplos práticos e compare com a petição fornecida.
314
+ - Retorne a análise em markdown, com seções claras e sugestões específicas.
315
+ """
316
+ return base_prompt
317
+
318
+ def analisar_peticao(file_obj, tipo_peticao, modo_treinamento):
319
  try:
320
+ # Processa o arquivo da petição
321
+ peticao_texto = process_file(file_obj)
322
+ if peticao_texto.startswith("❌") or peticao_texto.startswith("⚠️"):
323
+ return peticao_texto
324
+
325
+ # Gera um caso simulado para o tipo de petição
326
+ caso_simulado = gerar_caso_simulado(tipo_peticao)
327
+
328
+ # Estrutura o prompt com base no tipo de petição
329
+ prompt = estruturar_prompt_analise(caso_simulado, peticao_texto, tipo_peticao, modo_treinamento)
330
+
331
+ # Chama a API do Gemini para análise
332
+ headers = {"Content-Type": "application/json"}
333
+ data = {
334
+ "contents": [{"parts": [{"text": prompt}]}],
335
+ "generationConfig": {
336
+ "temperature": CONFIG["temperatura"],
337
+ "maxOutputTokens": CONFIG["max_tokens"]
338
+ }
339
+ }
340
+
341
+ with rate_limiter:
342
+ for attempt in range(CONFIG["max_retry_attempts"]):
343
+ try:
344
+ response = requests.post(GEMINI_API_URL, headers=headers, json=data, timeout=CONFIG["timeout"])
345
+ response.raise_for_status()
346
+ result = response.json()
347
+ if "candidates" in result and result["candidates"]:
348
+ correcao = result["candidates"][0]["content"]["parts"][0]["text"]
349
+ CORRECAO_CACHE[peticao_texto[:50]] = correcao
350
+ HISTORICO.append({
351
+ "id": str(uuid.uuid4()),
352
+ "timestamp": datetime.now().isoformat(),
353
+ "tipo_peticao": tipo_peticao,
354
+ "peticao": peticao_texto[:500],
355
+ "correcao": correcao
356
+ })
357
+ return correcao
358
+ else:
359
+ logger.error("Nenhuma resposta válida retornada pela API.")
360
+ return "❌ Nenhuma resposta válida retornada pela API."
361
+ except requests.exceptions.RequestException as e:
362
+ logger.error(f"Tentativa {attempt + 1} falhou: {str(e)}")
363
+ if attempt < CONFIG["max_retry_attempts"] - 1:
364
+ time.sleep(CONFIG["retry_delay"])
365
+ else:
366
+ return f"❌ Erro após {CONFIG['max_retry_attempts']} tentativas: {str(e)}"
367
+ except Exception as e:
368
+ logger.error(f"Erro ao analisar a petição: {str(e)}")
369
+ return f"❌ Erro ao analisar a petição: {str(e)}"
370
+
371
+ # Interface do Gradio
372
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
373
+ gr.Markdown("# Corretor de Petições Jurídicas")
374
+ gr.Markdown("Faça o upload de uma petição em PDF, DOCX ou TXT e selecione o tipo de petição para análise detalhada.")
375
+
376
+ with gr.Row():
377
+ tipo_peticao = gr.Dropdown(
378
+ choices=list(PROMPTS_PETICOES.keys()),
379
+ label="Tipo de Petição",
380
+ value="Petição Inicial"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  )
382
+ file_input = gr.File(label="Upload da Petição (PDF, DOCX, TXT)", file_types=[".pdf", ".docx", ".txt"])
383
+
384
+ modo_treinamento = gr.Checkbox(label="Modo de Treinamento (inclui exemplos práticos)", value=False)
385
+
386
+ with gr.Row():
387
+ submit_button = gr.Button("Analisar Petição", variant="primary")
388
+
389
+ output = gr.Markdown(label="Resultado da Análise")
390
+
391
+ submit_button.click(
392
+ fn=analisar_peticao,
393
+ inputs=[file_input, tipo_peticao, modo_treinamento],
394
+ outputs=output
395
+ )
396
 
397
  if __name__ == "__main__":
398
+ demo.launch()