Update app.py
Browse files
app.py
CHANGED
|
@@ -21,6 +21,7 @@ from html import escape
|
|
| 21 |
import random
|
| 22 |
import logging
|
| 23 |
import re
|
|
|
|
| 24 |
|
| 25 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 26 |
logger = logging.getLogger(__name__)
|
|
@@ -29,991 +30,517 @@ load_dotenv()
|
|
| 29 |
|
| 30 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 31 |
if not GEMINI_API_KEY:
|
| 32 |
-
raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada.
|
| 33 |
|
| 34 |
GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}"
|
| 35 |
|
| 36 |
-
CONFIG = {
|
| 37 |
-
|
| 38 |
-
"retry_delay": 5,
|
| 39 |
-
"timeout": 180,
|
| 40 |
-
"temperatura": 0.3,
|
| 41 |
-
"max_tokens": 8192
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
CONFIG_OAB = {
|
| 45 |
-
"temperatura": 0.2,
|
| 46 |
-
"max_tokens": 8192
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
rate_limiter = RateLimiter(max_calls=30, period=60)
|
| 50 |
|
| 51 |
TIPOS_DE_PETICAO = [
|
| 52 |
-
"Petição Inicial",
|
| 53 |
-
"
|
| 54 |
-
"
|
| 55 |
-
"Impugnação à Contestação",
|
| 56 |
-
"Embargos de Declaração",
|
| 57 |
-
"Recurso de Apelação",
|
| 58 |
-
"Agravo de Instrumento",
|
| 59 |
-
"Petição de Tutela Provisória",
|
| 60 |
-
"Ação de Alimentos Gravídicos",
|
| 61 |
]
|
| 62 |
|
| 63 |
DICAS_OAB_MELHORADO = {
|
| 64 |
-
"Clareza e Concisão": "Seja direto e evite jargões excessivos.
|
| 65 |
-
"
|
| 66 |
-
"
|
| 67 |
-
"
|
| 68 |
-
"
|
| 69 |
-
"
|
| 70 |
-
"
|
| 71 |
-
"
|
| 72 |
-
"Pedidos (
|
| 73 |
-
"
|
| 74 |
-
"
|
| 75 |
-
"
|
| 76 |
-
"Impugnação Específica (Contestação)": "Conteste cada fato alegado pelo autor, sob pena de presunção de veracidade (Art. 341, CPC).",
|
| 77 |
-
"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.",
|
| 78 |
-
"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.",
|
| 79 |
-
"Pressupostos Recursais": "Verifique cabimento, tempestividade, preparo (custas recursais), regularidade formal e interesse/legitimidade.",
|
| 80 |
-
"Dialeticidade (Recurso)": "Impugne especificamente os fundamentos da decisão recorrida (Art. 1010, II e III, CPC para Apelação).",
|
| 81 |
-
"Delimitação da Matéria (Recurso)": "Indique claramente qual parte da decisão você está recorrendo.",
|
| 82 |
-
"Fumus Boni Iuris": "Demonstre a plausibilidade do direito alegado com base em provas e argumentos sólidos.",
|
| 83 |
-
"Periculum in Mora": "Evidencie o risco de dano irreparável ou de difícil reparação se a medida não for concedida imediatamente.",
|
| 84 |
-
"Reversibilidade": "Se possível, argumente que a medida é reversível caso a decisão final seja diferente (Art. 300, §3º, CPC).",
|
| 85 |
}
|
| 86 |
|
| 87 |
HISTORICO = []
|
| 88 |
CORRECAO_CACHE = {}
|
| 89 |
|
| 90 |
def preprocess_image_for_ocr(img):
|
| 91 |
-
try:
|
| 92 |
-
|
| 93 |
-
np_img = np.array(gray)
|
| 94 |
-
_, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
| 95 |
-
return Image.fromarray(thresh)
|
| 96 |
-
except Exception as e:
|
| 97 |
-
logger.error(f"Erro no pré-processamento da imagem: {str(e)}")
|
| 98 |
-
return f"❌ Erro no pré-processamento: {str(e)}"
|
| 99 |
|
| 100 |
def extract_text_from_pdf(file_path_or_obj):
|
| 101 |
-
text = ""
|
| 102 |
-
ocr_text = ""
|
| 103 |
-
full_text = ""
|
| 104 |
try:
|
| 105 |
pdf_reader = pdfplumber_open(file_path_or_obj)
|
| 106 |
for i, page in enumerate(pdf_reader.pages):
|
| 107 |
-
page_full_text = ""
|
| 108 |
-
page_text =
|
| 109 |
-
if page_text:
|
| 110 |
-
page_full_text += page_text + "\n"
|
| 111 |
-
text += page_text + "\n"
|
| 112 |
else:
|
| 113 |
-
logger.warning(f"
|
| 114 |
-
try:
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
if page_ocr_text.strip():
|
| 119 |
-
page_full_text += page_ocr_text + "\n"
|
| 120 |
-
ocr_text += page_ocr_text + "\n"
|
| 121 |
-
else:
|
| 122 |
-
logger.warning(f"OCR falhou na página {i+1}.")
|
| 123 |
-
page_full_text += f"[OCR falhou na página {i+1}]\n"
|
| 124 |
-
ocr_text += f"[OCR falhou na página {i+1}]\n"
|
| 125 |
-
except Exception as ocr_e:
|
| 126 |
-
logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}")
|
| 127 |
-
page_full_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n"
|
| 128 |
-
ocr_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n"
|
| 129 |
full_text += page_full_text
|
| 130 |
pdf_reader.close()
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
logger.info("Texto combinado (extração/OCR) obtido do PDF.")
|
| 134 |
-
return full_text
|
| 135 |
-
else:
|
| 136 |
-
logger.warning("Não foi possível extrair texto significativo do PDF (nem extração, nem OCR).")
|
| 137 |
-
return "⚠️ Não foi possível extrair texto do PDF (tentativa de OCR falhou ou texto inexistente)."
|
| 138 |
-
|
| 139 |
except Exception as e:
|
| 140 |
-
logger.error(f"
|
| 141 |
try:
|
| 142 |
-
logger.info("
|
| 143 |
-
|
| 144 |
-
if
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
else:
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
else:
|
| 157 |
-
return f"❌ Erro ao processar o PDF e fallback falhou: {str(e)}"
|
| 158 |
-
except Exception as fallback_e:
|
| 159 |
-
logger.error(f"Erro no fallback com PyPDF2: {str(fallback_e)}")
|
| 160 |
-
return f"❌ Erro crítico ao processar o PDF: {str(e)}"
|
| 161 |
-
|
| 162 |
-
def extract_text_from_docx(file_path_or_obj):
|
| 163 |
-
try:
|
| 164 |
-
doc = docx.Document(file_path_or_obj)
|
| 165 |
-
text = "\n".join([para.text for para in doc.paragraphs])
|
| 166 |
-
return text if text.strip() else "⚠️ Documento DOCX vazio ou sem texto."
|
| 167 |
-
except Exception as e:
|
| 168 |
-
logger.error(f"Erro ao processar o DOCX: {str(e)}")
|
| 169 |
-
return f"❌ Erro ao processar o DOCX: {str(e)}"
|
| 170 |
-
|
| 171 |
-
def extract_text_from_txt(file_path_or_obj):
|
| 172 |
try:
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
elif hasattr(
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
try:
|
| 187 |
-
decoded_text = content.decode(encoding)
|
| 188 |
-
logger.info(f"Arquivo TXT decodificado com {encoding}.")
|
| 189 |
-
return decoded_text if decoded_text.strip() else "⚠️ Arquivo TXT vazio."
|
| 190 |
-
except UnicodeDecodeError:
|
| 191 |
-
continue
|
| 192 |
-
logger.error("Não foi possível decodificar o arquivo TXT com encodings comuns.")
|
| 193 |
-
return "❌ Não foi possível decodificar o arquivo TXT. Verifique o encoding."
|
| 194 |
-
except Exception as e:
|
| 195 |
-
logger.error(f"Erro ao processar o TXT: {str(e)}")
|
| 196 |
-
return f"❌ Erro ao processar o TXT: {str(e)}"
|
| 197 |
-
|
| 198 |
-
def process_file(file_obj):
|
| 199 |
-
if file_obj is None:
|
| 200 |
-
logger.warning("Nenhum arquivo fornecido para processamento.")
|
| 201 |
-
return "⚠️ Nenhum arquivo enviado."
|
| 202 |
try:
|
| 203 |
-
|
| 204 |
-
if not
|
| 205 |
-
|
| 206 |
-
return "❌ Erro: Não foi possível identificar o tipo do arquivo."
|
| 207 |
-
|
| 208 |
-
filename = os.path.basename(file_path).lower()
|
| 209 |
-
logger.info(f"Processando arquivo: {filename}")
|
| 210 |
-
|
| 211 |
try:
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
if filename.endswith('.pdf'):
|
| 221 |
-
return extract_text_from_pdf(file_path)
|
| 222 |
-
elif filename.endswith('.docx'):
|
| 223 |
-
return extract_text_from_docx(file_path)
|
| 224 |
-
elif filename.endswith('.txt'):
|
| 225 |
-
return extract_text_from_txt(file_path)
|
| 226 |
-
else:
|
| 227 |
-
logger.warning(f"Formato de arquivo não suportado: {filename}")
|
| 228 |
-
return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
|
| 229 |
-
|
| 230 |
-
except Exception as e:
|
| 231 |
-
logger.error(f"Erro geral ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True)
|
| 232 |
-
return f"❌ Erro inesperado ao processar o arquivo: {str(e)}"
|
| 233 |
|
| 234 |
def chamar_api(prompt, use_oab_config=False):
|
| 235 |
-
|
| 236 |
-
payload = {
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
"maxOutputTokens": config_to_use["max_tokens"],
|
| 241 |
-
"topP": 0.95,
|
| 242 |
-
}
|
| 243 |
-
}
|
| 244 |
-
headers = {"Content-Type": "application/json"}
|
| 245 |
-
logger.info(f"Chamando API Gemini com {len(prompt)} chars. Temp: {config_to_use['temperatura']}, MaxTokens: {config_to_use['max_tokens']}")
|
| 246 |
-
|
| 247 |
-
for tentativa in range(CONFIG["max_retry_attempts"]):
|
| 248 |
try:
|
| 249 |
-
with rate_limiter:
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
candidate = response_data["candidates"][0]
|
| 274 |
-
finish_reason = candidate.get("finishReason")
|
| 275 |
-
if finish_reason not in [None, "STOP", "MAX_TOKENS"]:
|
| 276 |
-
logger.error(f"Geração interrompida: {finish_reason}")
|
| 277 |
-
safety_ratings = candidate.get("safetyRatings", [])
|
| 278 |
-
harm_categories = [r['category'] for r in safety_ratings if r['probability'] not in ['NEGLIGIBLE', 'LOW']]
|
| 279 |
-
return f"❌ Resposta bloqueada por segurança: {finish_reason} (Categorias: {', '.join(harm_categories)})." if harm_categories else f"❌ Geração interrompida: {finish_reason}."
|
| 280 |
-
|
| 281 |
-
if "content" not in candidate or "parts" not in candidate["content"] or not candidate["content"]["parts"]:
|
| 282 |
-
logger.error("Estrutura de resposta inesperada.")
|
| 283 |
-
if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG["retry_delay"] * (tentativa + 1)); continue
|
| 284 |
-
return "❌ Resposta inválida (estrutura inesperada)."
|
| 285 |
-
|
| 286 |
-
texto_gerado = candidate["content"]["parts"][0]["text"].strip()
|
| 287 |
-
logger.info(f"API retornou com sucesso. Tamanho: {len(texto_gerado)} chars.")
|
| 288 |
-
return texto_gerado
|
| 289 |
-
|
| 290 |
-
except requests.Timeout:
|
| 291 |
-
logger.warning(f"Timeout tentativa {tentativa + 1}.")
|
| 292 |
-
if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG["retry_delay"] * (tentativa + 1)); continue
|
| 293 |
-
return "⏱️ Tempo esgotado."
|
| 294 |
-
except requests.RequestException as e:
|
| 295 |
-
logger.error(f"Erro de rede/requisição: {e}")
|
| 296 |
-
if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG["retry_delay"] * (tentativa + 1)); continue
|
| 297 |
-
return f"❌ Erro na requisição: {e}"
|
| 298 |
-
except json.JSONDecodeError as e:
|
| 299 |
-
logger.error(f"Erro JSON: {e}. Resposta: {response.text[:200]}...")
|
| 300 |
-
return f"❌ Erro processar resposta API (JSON inválido)."
|
| 301 |
-
except Exception as e:
|
| 302 |
-
logger.exception(f"Erro inesperado API: {str(e)}")
|
| 303 |
-
if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG["retry_delay"] * (tentativa + 1)); continue
|
| 304 |
-
return f"❌ Erro inesperado: {str(e)}"
|
| 305 |
-
|
| 306 |
-
return "❌ Falha API após todas tentativas."
|
| 307 |
|
| 308 |
def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
|
| 309 |
-
base = ""
|
| 310 |
-
|
| 311 |
-
1. **Análise Geral:** Visão geral concisa (2-3 linhas) sobre a adequação da peça ao caso e tipo, consistência e clareza.
|
| 312 |
-
2. **Nota Final:** 0 a 10 (uma casa decimal), com justificativa BREVE dos principais fatores.
|
| 313 |
-
3. **Pontos Positivos:** Liste pelo menos 2-3 aspectos bem executados, com justificativa (legal/técnica).
|
| 314 |
-
4. **Pontos a Melhorar:** Liste **TODOS** os problemas em ordem de gravidade (erros crassos primeiro). Para cada ponto:
|
| 315 |
-
* Descrição clara do erro/omissão.
|
| 316 |
-
* Fundamentação legal/doutrinária do porquê é um erro.
|
| 317 |
-
* **Impacto estimado na nota** (ex: -0.5, -1.0, -2.0).
|
| 318 |
-
* Sugestão de correção ou como deveria ter sido feito.
|
| 319 |
-
5. **Análise Específica dos Requisitos:** Avalie os itens chave da peça (listados abaixo para cada tipo).
|
| 320 |
-
|
| 321 |
-
**Critérios Fundamentais (Aplicáveis a Quase Todos):**
|
| 322 |
-
* **Coerência:** A peça responde ao caso/situação processual? Os argumentos são lógicos?
|
| 323 |
-
* **Fundamentação:** Usou a lei, doutrina e jurisprudência corretamente? Citou artigos relevantes?
|
| 324 |
-
* **Linguagem:** Clareza, objetividade, correção gramatical e formalidade adequada?
|
| 325 |
-
* **Pedidos:** São claros, certos, determinados e coerentes com a fundamentação?
|
| 326 |
-
* **NÃO INVENTAR FATOS:** Penalidade severa (-3.0 a -5.0) se criar fatos não presentes no caso.
|
| 327 |
-
* **NÃO SE IDENTIFICAR:** Nota ZERO se houver nome, OAB, CPF, etc., do aluno na peça (exceto nomes das partes do caso). Use "ADVOGADO(A)", "OAB/...".
|
| 328 |
-
"""
|
| 329 |
-
if modo_treinamento:
|
| 330 |
-
base += """
|
| 331 |
-
**Modo de Treinamento:**
|
| 332 |
-
* Para cada Ponto a Melhorar, detalhe AINDA MAIS:
|
| 333 |
-
a) Explicação aprofundada do erro e sua consequência prática.
|
| 334 |
-
b) Base legal/doutrinária específica.
|
| 335 |
-
c) **Exemplo de Redação Corrigida** para aquele trecho específico.
|
| 336 |
-
d) Dica prática relevante (use o banco de dicas se aplicável).
|
| 337 |
-
e) Possível estratégia alternativa.
|
| 338 |
-
* Destaque inconsistências entre a peça e o caso com citações diretas.
|
| 339 |
-
* Sugira estratégias jurídicas adicionais ou alternativas viáveis.
|
| 340 |
-
"""
|
| 341 |
return base
|
| 342 |
|
| 343 |
def estruturar_prompt_analise_geral(caso, peticao, tipo_peticao, modo_treinamento=False):
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
**
|
| 349 |
-
|
| 350 |
-
{caso}
|
| 351 |
-
```
|
| 352 |
-
|
| 353 |
-
**Peça do Aluno ({tipo_peticao}):**
|
| 354 |
-
```
|
| 355 |
-
{peticao}
|
| 356 |
-
```
|
| 357 |
-
|
| 358 |
-
{instrucoes_gerais}
|
| 359 |
-
|
| 360 |
-
**Requisitos Específicos para {tipo_peticao} (Considerados pela IA):**
|
| 361 |
-
"""
|
| 362 |
-
if tipo_peticao == "Petição Inicial" or tipo_peticao == "Ação de Alimentos Gravídicos":
|
| 363 |
-
prompt_especifico += """
|
| 364 |
-
- Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I, Lei 11.804/08).
|
| 365 |
-
- Qualificação completa das partes (Art. 319, II, CPC).
|
| 366 |
-
- Exposição clara e lógica dos fatos (causa de pedir remota).
|
| 367 |
-
- Fundamentação jurídica pertinente (causa de pedir próxima - Art. 319, III, CPC).
|
| 368 |
-
- Pedidos certos, determinados e líquidos (se possível) (Art. 319, IV, CPC). Incluir pedido de conversão se aplicável (Alimentos Gravídicos).
|
| 369 |
-
- Valor da causa correto e justificado (Art. 291-292, 319, V, CPC).
|
| 370 |
-
- Indicação das provas pretendidas (Art. 319, VI, CPC).
|
| 371 |
-
- Opção pela audiência de conciliação/mediação (Art. 319, VII, CPC).
|
| 372 |
-
- Pedido de citação do réu (forma específica se necessário).
|
| 373 |
-
- Requerimento de juntada de documentos essenciais (procuração, custas/JG).
|
| 374 |
-
- Fechamento formal ("Nestes termos pede deferimento", Local, data, Advogado, OAB).
|
| 375 |
-
"""
|
| 376 |
-
elif tipo_peticao == "Contestação":
|
| 377 |
-
prompt_especifico += """
|
| 378 |
-
- Endereçamento ao juízo correto. Qualificação do réu. Indicação do processo. Tempestividade.
|
| 379 |
-
- **Preliminares (Art. 337):** Alegar TODAS aplicáveis.
|
| 380 |
-
- **Impugnação Específica dos Fatos (Art. 341):** Rebater PONTO A PONTO.
|
| 381 |
-
- **Mérito:** Defesa direta/indireta. Fundamentação jurídica.
|
| 382 |
-
- **Pedidos:** Acolhimento preliminares; IMPROCEDÊNCIA TOTAL; Condenação custas/honorários.
|
| 383 |
-
- Indicação de provas. Fechamento formal.
|
| 384 |
-
"""
|
| 385 |
-
# Add other types...
|
| 386 |
-
|
| 387 |
-
prompt_especifico += "\n\n**Instruções Finais:** Analise a peça do aluno com base no caso e nos critérios gerais e específicos. Use seu conhecimento jurídico para simular um examinador da OAB. Forneça feedback construtivo e detalhado. Gere a resposta **exclusivamente** no formato solicitado (Análise Geral, Nota, Pontos Positivos, etc.)."
|
| 388 |
-
return prompt_especifico
|
| 389 |
|
| 390 |
def estruturar_prompt_peticao_corrigida_geral(caso, peticao_original, tipo_peticao):
|
| 391 |
-
prompt = f""
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
```
|
| 396 |
-
|
| 397 |
-
**Peça Original do Aluno ({tipo_peticao}):**
|
| 398 |
-
```
|
| 399 |
-
{peticao_original}
|
| 400 |
-
```
|
| 401 |
-
|
| 402 |
-
**Tarefa:** Com base no caso e nos problemas identificados na peça original, **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Use seu conhecimento jurídico para aplicar as melhores práticas e os requisitos formais/materiais para este tipo de peça no exame da OAB.
|
| 403 |
-
|
| 404 |
-
**Instruções Específicas para a Peça Corrigida ({tipo_peticao}):**
|
| 405 |
-
"""
|
| 406 |
-
if tipo_peticao == "Petição Inicial" or tipo_peticao == "Ação de Alimentos Gravídicos":
|
| 407 |
-
prompt += """
|
| 408 |
-
1. **Endereçamento:** Correto (Vara Cível/Família, Foro competente).
|
| 409 |
-
2. **Qualificação:** Completa (Art. 319, II), usando "XXX".
|
| 410 |
-
3. **Fatos:** Cronológicos, claros, fiéis ao caso. Indícios paternidade (Alim. Grav.).
|
| 411 |
-
4. **Fundamentação:** Artigos pertinentes (CC, CDC, CPC, Lei 11.804/08).
|
| 412 |
-
5. **Pedidos:** Claros, certos, determinados. Pedido alimentos gravídicos (valor provisório/definitivo), tutela urgência, conversão (Lei 11.804/08, Art. 6º, p. único).
|
| 413 |
-
6. **Valor da Causa:** Correto.
|
| 414 |
-
7. **Provas:** Indicar (genérico).
|
| 415 |
-
8. **Audiência:** Indicar opção (Art. 319, VII).
|
| 416 |
-
9. **Citação:** Pedido.
|
| 417 |
-
10. **Custas:** Pedido expresso juntada OU JG.
|
| 418 |
-
11. **Fechamento:** Padrão OAB.
|
| 419 |
-
"""
|
| 420 |
-
elif tipo_peticao == "Contestação":
|
| 421 |
-
prompt += """
|
| 422 |
-
1. **Endereçamento:** Ao juízo da ação.
|
| 423 |
-
2. **Identificação:** Processo, qualificação réu.
|
| 424 |
-
3. **Tempestividade:** (Opcional).
|
| 425 |
-
4. **Estrutura:** Síntese; Preliminares (Art. 337); Mérito (Impugnação Específica, Defesa); Reconvenção (Se aplicável).
|
| 426 |
-
5. **Pedidos:** Acolhimento preliminares; **IMPROCEDÊNCIA TOTAL**; Condenação custas/honorários.
|
| 427 |
-
6. **Provas:** Indicar.
|
| 428 |
-
7. **Fechamento:** Padrão OAB.
|
| 429 |
-
"""
|
| 430 |
-
# Add other types...
|
| 431 |
-
|
| 432 |
-
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."
|
| 433 |
return prompt
|
| 434 |
|
| 435 |
def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
|
| 436 |
-
|
| 437 |
-
peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
|
| 438 |
-
|
| 439 |
if tipo_peticao in ["Petição Inicial", "Ação de Alimentos Gravídicos"]:
|
| 440 |
-
if "juntada da guia de custas" not in
|
| 441 |
-
partes
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
|
| 453 |
-
fechamento_padrao = ["\nLocal, data.", "ADVOGADO(A)", "OAB/..."]
|
| 454 |
-
|
| 455 |
-
# Remove potential multiple closing lines before adding the standard one
|
| 456 |
-
while linhas and any(term.lower() in linhas[-1].lower() for term in ["advogado", "oab", "local", "data", "pede deferimento", "termos em que"]):
|
| 457 |
-
# More robust check to avoid removing essential content like the final request
|
| 458 |
-
last_line_lower = linhas[-1].lower()
|
| 459 |
-
is_closing_line = any(std_close.strip().lower() in last_line_lower for std_close in fechamento_padrao) or "pede deferimento" in last_line_lower or "termos em que" in last_line_lower
|
| 460 |
-
if not is_closing_line and len(linhas[-1]) > 30: # Heuristic: unlikely to be just closing
|
| 461 |
-
break
|
| 462 |
linhas.pop()
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
in_list = False
|
| 478 |
-
for linha in linhas:
|
| 479 |
-
linha_strip = linha.strip()
|
| 480 |
-
if linha_strip.startswith(("1.","2.","3.","4.","5.")):
|
| 481 |
-
linhas_formatadas.append(f"\n**{linha_strip}**")
|
| 482 |
-
in_list = False
|
| 483 |
-
elif linha_strip.startswith(("- ","* ")):
|
| 484 |
-
prefix = " * " if in_list else "* "
|
| 485 |
-
linhas_formatadas.append(prefix + linha_strip[2:])
|
| 486 |
-
in_list = True
|
| 487 |
-
elif linha_strip.startswith(("a)","b)","c)","d)","e)")):
|
| 488 |
-
linhas_formatadas.append(f" * {linha_strip}")
|
| 489 |
-
in_list = True
|
| 490 |
-
else:
|
| 491 |
-
linhas_formatadas.append(linha)
|
| 492 |
-
if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")):
|
| 493 |
-
in_list = False
|
| 494 |
-
|
| 495 |
-
texto_final = "\n".join(linhas_formatadas)
|
| 496 |
-
nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})"
|
| 497 |
-
match = re.search(nota_pattern, texto_final, re.IGNORECASE)
|
| 498 |
-
if match:
|
| 499 |
-
nota = match.group(2).replace(",",".")
|
| 500 |
-
texto_final = re.sub(nota_pattern, f"\\1**{nota}/10**", texto_final, count=1, flags=re.IGNORECASE)
|
| 501 |
-
return texto_final
|
| 502 |
|
| 503 |
def corrigir_peticao_geral_ia(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
|
| 504 |
-
logger.info(f"
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
if not caso or not caso.strip(): return "⚠️ Forneça o caso.", "", "", "", "", "", ""
|
| 508 |
-
if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo.", "", "", "", "", "", ""
|
| 509 |
-
|
| 510 |
peticao_conteudo = ""
|
| 511 |
-
if peticao_arquivo
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
cached_data["peticao_corrigida"], cached_data["resumo_diff"],
|
| 531 |
-
cached_data["criticas_diff"], cached_data.get("avaliacao_criterios_ia", ""),
|
| 532 |
-
cached_data["diff_html"] )
|
| 533 |
-
|
| 534 |
-
prompt_analise = estruturar_prompt_analise_geral(caso, peticao_conteudo, tipo_peticao, modo_treinamento)
|
| 535 |
-
analise = chamar_api(prompt_analise, use_oab_config=False)
|
| 536 |
-
if analise.startswith("❌") or analise.startswith("⏱️"): return aviso_validacao + analise, peticao_conteudo, "", "", "", "", ""
|
| 537 |
-
analise_formatada = formatar_correcao(analise)
|
| 538 |
-
|
| 539 |
-
prompt_peticao = estruturar_prompt_peticao_corrigida_geral(caso, peticao_conteudo, tipo_peticao)
|
| 540 |
-
peticao_corrigida_bruta = chamar_api(prompt_peticao, use_oab_config=False)
|
| 541 |
-
peticao_corrigida_final = ""
|
| 542 |
-
if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️"):
|
| 543 |
-
peticao_corrigida_final = f"⚠️ Falha gerar corrigida:\n{peticao_corrigida_bruta}"
|
| 544 |
-
else:
|
| 545 |
-
peticao_corrigida_final = pos_corrigir_peticao(peticao_corrigida_bruta, tipo_peticao)
|
| 546 |
-
|
| 547 |
-
avaliacao_criterios_md = ""
|
| 548 |
-
|
| 549 |
-
visualizacao_detalhada_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final)
|
| 550 |
-
|
| 551 |
-
correcao_completa_formatada = aviso_validacao + analise_formatada
|
| 552 |
-
resultado_historico = { "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "modo": "Geral IA", "tipo_peticao": tipo_peticao, "caso": caso, "peticao_original": peticao_conteudo, "analise_formatada": correcao_completa_formatada, "peticao_corrigida": peticao_corrigida_final, "resumo_diff": resumo_diff,"criticas_diff": criticas_diff, "avaliacao_criterios_ia": avaliacao_criterios_md }
|
| 553 |
-
HISTORICO.append(resultado_historico)
|
| 554 |
-
if len(HISTORICO) > 10: HISTORICO.pop(0)
|
| 555 |
-
|
| 556 |
-
CORRECAO_CACHE[cache_key] = { "analise_formatada": correcao_completa_formatada, "peticao_original": peticao_conteudo, "peticao_corrigida": peticao_corrigida_final, "resumo_diff": resumo_diff, "criticas_diff": criticas_diff, "avaliacao_criterios_ia": avaliacao_criterios_md, "diff_html": visualizacao_detalhada_html }
|
| 557 |
-
|
| 558 |
-
end_time = time.time()
|
| 559 |
-
logger.info(f"Correção GERAL (IA) concluída em {end_time - start_time:.2f}s.")
|
| 560 |
-
|
| 561 |
-
return ( correcao_completa_formatada, peticao_conteudo, peticao_corrigida_final,
|
| 562 |
-
resumo_diff, criticas_diff, avaliacao_criterios_md, visualizacao_detalhada_html )
|
| 563 |
-
|
| 564 |
-
# --- Funções de Correção Padrão OAB ---
|
| 565 |
def parse_oab_pdf(file_obj):
|
| 566 |
if file_obj is None: return None, None, None, "⚠️ Nenhum PDF OAB."
|
| 567 |
try:
|
| 568 |
full_text = extract_text_from_pdf(file_obj.name)
|
| 569 |
-
if full_text.startswith("❌"
|
| 570 |
-
|
| 571 |
-
enunciado, gabarito, distribuicao = "", "", ""
|
| 572 |
-
current_section = None
|
| 573 |
-
lines = full_text.split('\n')
|
| 574 |
-
|
| 575 |
for line in lines:
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
if
|
| 580 |
-
if
|
| 581 |
-
if
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
enunciado = enunciado.strip()
|
| 590 |
-
gabarito = gabarito.strip()
|
| 591 |
-
distribuicao = distribuicao.strip()
|
| 592 |
-
|
| 593 |
-
if not enunciado or not gabarito or not distribuicao:
|
| 594 |
-
logger.warning(f"Parse OAB PDF falhou. E:{bool(enunciado)}, G:{bool(gabarito)}, D:{bool(distribuicao)}")
|
| 595 |
-
return None, None, None, f"⚠️ Falha separar seções. Verifique o texto completo:\n\n{full_text[:500]}..."
|
| 596 |
-
|
| 597 |
-
logger.info("PDF OAB parseado.")
|
| 598 |
-
return enunciado, gabarito, distribuicao, "✅ PDF OAB carregado e seções extraídas."
|
| 599 |
-
except Exception as e:
|
| 600 |
-
logger.error(f"Erro parsear PDF OAB: {str(e)}", exc_info=True)
|
| 601 |
-
return None, None, None, f"❌ Erro processar PDF OAB: {str(e)}"
|
| 602 |
|
| 603 |
def estruturar_prompt_geracao_modelo_oab(enunciado, gabarito, distribuicao):
|
| 604 |
-
prompt = f"""
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
**
|
| 608 |
-
```
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
**
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
**
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
**Instruções:**
|
| 623 |
-
1. Use **exclusivamente** informações/requisitos dos textos fornecidos. **Não adicione nada externo.**
|
| 624 |
-
2. Incorpore **todos** argumentos/fundamentos do Gabarito.
|
| 625 |
-
3. Estruture a peça para contemplar **todos** itens da Distribuição.
|
| 626 |
-
4. Linguagem formal OAB.
|
| 627 |
-
5. Use "XXX" apenas se Enunciado omisso e necessário.
|
| 628 |
-
6. Finalize com "Local, data.", "ADVOGADO(A)", "OAB/...". **Não se identifique.**
|
| 629 |
-
7. Gere **apenas** o texto completo da peça. Sem comentários ou títulos fora da peça.
|
| 630 |
"""
|
| 631 |
return prompt
|
| 632 |
|
| 633 |
def gerar_peticao_modelo_oab(enunciado, gabarito, distribuicao):
|
| 634 |
-
if not enunciado
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
logger.info("Gerando peça modelo OAB...")
|
| 638 |
prompt = estruturar_prompt_geracao_modelo_oab(enunciado, gabarito, distribuicao)
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
logger.error(f"Erro API gerar modelo OAB: {peticao_modelo}")
|
| 643 |
-
return f"❌ Falha gerar modelo OAB:\n{peticao_modelo}"
|
| 644 |
-
|
| 645 |
-
logger.info("Peça modelo OAB gerada.")
|
| 646 |
-
peticao_modelo = pos_corrigir_peticao(peticao_modelo, "Petição Desconhecida")
|
| 647 |
-
return peticao_modelo
|
| 648 |
|
| 649 |
def estruturar_prompt_correcao_pela_oab(peticao_usuario, enunciado, gabarito, distribuicao):
|
| 650 |
-
prompt = f"""
|
| 651 |
-
**Tarefa:** Avalie a **Peça do Usuário**, comparando-a **estritamente** com o padrão OAB (Gabarito Comentado, Distribuição dos Pontos) em relação ao **Enunciado**.
|
| 652 |
-
|
| 653 |
-
**Enunciado:**
|
| 654 |
-
```
|
| 655 |
-
{enunciado}
|
| 656 |
-
```
|
| 657 |
-
|
| 658 |
-
**Gabarito Comentado (Esperado):**
|
| 659 |
-
```
|
| 660 |
-
{gabarito}
|
| 661 |
-
```
|
| 662 |
-
|
| 663 |
-
**Distribuição dos Pontos (Critérios):**
|
| 664 |
-
```
|
| 665 |
-
{distribuicao}
|
| 666 |
-
```
|
| 667 |
|
| 668 |
-
**
|
| 669 |
-
```
|
| 670 |
-
{
|
| 671 |
-
```
|
| 672 |
|
| 673 |
**Instruções:**
|
| 674 |
-
1. **Avaliação Pontual:** Para **cada item**
|
| 675 |
-
2. **Justificativa:**
|
| 676 |
-
3. **Feedback:**
|
| 677 |
-
4. **Peça Corrigida (Opcional):**
|
| 678 |
5. **Output Markdown:**
|
| 679 |
-
|
| 680 |
```markdown
|
| 681 |
-
### 📊 Avaliação
|
| 682 |
-
|
| 683 |
**Pontuação por Item:**
|
| 684 |
-
|
| 685 |
* **[Item 1 Distribuição]:** [Pontos Obtidos] / [Máximo]
|
| 686 |
* *Justificativa:* [...]
|
| 687 |
-
*
|
| 688 |
-
* *Justificativa:* [...]
|
| 689 |
-
* ... (Todos os itens)
|
| 690 |
-
|
| 691 |
**Pontuação Total Estimada:** [Soma Obtidos] / [Soma Máximos]
|
| 692 |
-
|
| 693 |
-
**Comentários e Pontos de Melhoria:**
|
| 694 |
-
* [...]
|
| 695 |
-
|
| 696 |
---
|
| 697 |
-
### ✨ Sugestões
|
| 698 |
-
|
| 699 |
-
[Sugestões ou trechos corrigidos]
|
| 700 |
```
|
| 701 |
-
**Importante:** Baseie **toda** avaliação **exclusivamente**
|
| 702 |
"""
|
| 703 |
return prompt
|
| 704 |
|
| 705 |
def corrigir_pela_oab(peticao_usuario, enunciado, gabarito, distribuicao):
|
| 706 |
-
if not peticao_usuario
|
| 707 |
-
return "⚠️ Petição Usuário, Enunciado, Gabarito e Distribuição necessários."
|
| 708 |
-
|
| 709 |
logger.info("Corrigindo pela OAB...")
|
| 710 |
prompt = estruturar_prompt_correcao_pela_oab(peticao_usuario, enunciado, gabarito, distribuicao)
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
css
|
| 740 |
-
|
| 741 |
-
return
|
| 742 |
|
| 743 |
def calcular_progresso():
|
| 744 |
if not HISTORICO: return "📊 Progresso vazio."
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
tipos_geral.add(entry["tipo_peticao"])
|
| 752 |
-
match = re.search(r"\*\*(\d{1,2}[,.]?\d?)/10\*\*", entry.get("analise_formatada",""))
|
| 753 |
-
if match:
|
| 754 |
-
try: notas_geral.append(float(match.group(1).replace(",",".")))
|
| 755 |
-
except: pass
|
| 756 |
-
media_geral = sum(notas_geral) / len(notas_geral) if notas_geral else 0
|
| 757 |
-
prog_txt = f"**📊 Progresso (Geral IA):**\n* Correções: {count_geral}\n* Tipos: {', '.join(sorted(list(tipos_geral)))}\n* Média Notas: {media_geral:.1f}/10"
|
| 758 |
-
return prog_txt
|
| 759 |
|
| 760 |
def mostrar_historico():
|
| 761 |
if not HISTORICO: return "📜 Histórico vazio."
|
| 762 |
-
|
| 763 |
-
for i,
|
| 764 |
-
modo
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
hist_md += f"**{len(HISTORICO)-i}.** {entry['data']} ({modo})\n"
|
| 769 |
-
hist_md += f"**Tipo:** {entry['tipo_peticao']} **Nota (IA):** {nota_str}\n"
|
| 770 |
-
hist_md += f"**Caso:** _{entry['caso'][:60]}..._\n---\n"
|
| 771 |
-
return hist_md
|
| 772 |
|
| 773 |
def salvar_progresso(caso, peticao, tipo_peticao):
|
| 774 |
-
logger.info("Salvando
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 775 |
try:
|
| 776 |
-
if not caso or not peticao or not tipo_peticao: return "", "⚠️ Inputs necessários.", None
|
| 777 |
-
dados = { "tipo_peticao": tipo_peticao, "caso": caso, "peticao": peticao, "ts": datetime.now().isoformat()}
|
| 778 |
-
js = json.dumps(dados, ensure_ascii=False, indent=4)
|
| 779 |
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
|
|
|
| 786 |
except Exception as e:
|
| 787 |
-
logger.error(f"Erro
|
| 788 |
-
return
|
| 789 |
|
| 790 |
-
def
|
| 791 |
-
|
| 792 |
-
if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo."
|
| 793 |
try:
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
tipo = dados.get("tipo_peticao")
|
| 799 |
-
if not all([caso, pet, tipo]): return "", "", "", "❌ Arquivo inválido."
|
| 800 |
-
if tipo not in TIPOS_DE_PETICAO: return caso, pet, "", f"⚠️ Tipo '{tipo}' inválido."
|
| 801 |
-
logger.info("Progresso carregado.")
|
| 802 |
-
return tipo, caso, pet, "✅ Progresso carregado!"
|
| 803 |
-
except Exception as e:
|
| 804 |
-
logger.error(f"Erro carregar: {str(e)}", exc_info=True)
|
| 805 |
-
return "", "", "", f"❌ Erro carregar: {str(e)}"
|
| 806 |
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 820 |
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
dicas_relevantes.append(f"💡 **{chave}:** {dica}")
|
| 829 |
-
keywords_por_tipo = {
|
| 830 |
-
"Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor da causa", "provas", "audiência", "citação", "custas"],
|
| 831 |
-
"Contestação": ["preliminar", "mérito", "impugnação", "fatos", "improcedência", "provas", "honorários"],
|
| 832 |
-
"Reconvenção": ["reconvinte", "reconvindo", "conexão", "pedidos reconvencionais"],
|
| 833 |
-
"Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica"],
|
| 834 |
-
"Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "decisão embargada"],
|
| 835 |
-
"Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "razões"],
|
| 836 |
-
"Agravo de Instrumento": ["decisão interlocutória", "tribunal", "cabimento", "urgência", "efeito suspensivo", "peças obrigatórias"],
|
| 837 |
-
"Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "probabilidade", "risco"],
|
| 838 |
-
"Ação de Alimentos Gravídicos": ["gravídicos", "nascituro", "indícios de paternidade", "binômio", "conversão", "lei 11.804"],
|
| 839 |
-
}
|
| 840 |
-
if tipo_peticao in keywords_por_tipo:
|
| 841 |
-
for keyword in keywords_por_tipo[tipo_peticao]:
|
| 842 |
-
if keyword in texto_lower:
|
| 843 |
-
for chave, dica in DICAS_OAB_MELHORADO.items():
|
| 844 |
-
chave_lower = chave.lower()
|
| 845 |
-
tipo_simples = tipo_peticao.split()[0].lower() if tipo_peticao else ""
|
| 846 |
-
if f"({tipo_simples})" in chave_lower or keyword in chave_lower:
|
| 847 |
-
dica_formatada = f"💡 **{chave}:** {dica}"
|
| 848 |
-
if dica_formatada not in dicas_relevantes:
|
| 849 |
-
dicas_relevantes.append(dica_formatada)
|
| 850 |
-
if not dicas_relevantes:
|
| 851 |
-
return "💡 Continue escrevendo ou selecione o tipo de peça para dicas mais específicas."
|
| 852 |
-
return "\n\n".join(dicas_relevantes[:5])
|
| 853 |
|
| 854 |
def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
|
| 855 |
-
prompt = f""
|
| 856 |
-
|
| 857 |
-
*
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
3. **Coerência:** Fatos lógicos para a peça indicada.
|
| 861 |
-
4. **Foco no Tipo de Peça:** Cenário exige **{tipo_peticao}**.
|
| 862 |
-
**Instruções Específicas para Gerar Caso para "{tipo_peticao}":**
|
| 863 |
-
"""
|
| 864 |
-
if tipo_peticao == "Petição Inicial" or tipo_peticao == "Ação de Alimentos Gravídicos":
|
| 865 |
-
prompt += "* Descreva violação de direitos, busca inicial da tutela. Elementos para danos. Dados para qualificação. Temas: consumo, contrato, cobrança, despejo, família (alimentos gravídicos - incluir indícios paternidade, necessidade mãe/nascituro)."
|
| 866 |
-
elif tipo_peticao == "Contestação":
|
| 867 |
-
prompt += "* Resumo da Inicial recebida. Versão dos fatos do RÉU. Elementos para Preliminares (Art. 337), Impugnação Específica, Defesa de Mérito (direta/indireta). Dados qualificação réu."
|
| 868 |
-
# ... (Add other types as before) ...
|
| 869 |
-
else: prompt += "* Crie um cenário jurídico cível com conflito claro."
|
| 870 |
-
|
| 871 |
-
prompt += """
|
| 872 |
-
**Finalize o caso com:**
|
| 873 |
-
"**Situação-Problema:** Na qualidade de advogado(a) de [Nome do Cliente], elabore a peça processual cabível ({tipo_peticao})."
|
| 874 |
-
**Importante:** Evite temas excessivamente específicos. Foco em Civil/Processo Civil comum.
|
| 875 |
-
"""
|
| 876 |
return prompt
|
| 877 |
|
| 878 |
-
|
| 879 |
-
# --- Interface Gradio ---
|
| 880 |
def interface_gradio():
|
| 881 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
|
| 882 |
gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️")
|
| 883 |
-
gr.Markdown("Use
|
| 884 |
-
|
| 885 |
with gr.Tabs() as main_tabs:
|
| 886 |
with gr.TabItem("🤖 Correção Geral (IA)", id=0):
|
| 887 |
with gr.Row():
|
| 888 |
with gr.Column(scale=1):
|
| 889 |
-
gr.Markdown("### 1.
|
| 890 |
-
tipo_peticao_input_geral
|
| 891 |
-
modo_treinamento_geral
|
| 892 |
-
with gr.Accordion("⚙️
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
def
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
| 900 |
-
with gr.Accordion("
|
| 901 |
-
progresso_output = gr.Markdown("...")
|
| 902 |
-
progresso_btn = gr.Button("🔄 Progresso")
|
| 903 |
-
historico_output = gr.Markdown("...")
|
| 904 |
-
historico_btn = gr.Button("🔄 Histórico")
|
| 905 |
-
progresso_btn.click(calcular_progresso, outputs=progresso_output)
|
| 906 |
-
historico_btn.click(mostrar_historico, outputs=historico_output)
|
| 907 |
-
with gr.Accordion("💾/📂 Salvar/Carregar", open=False):
|
| 908 |
-
salvar_btn_geral = gr.Button("💾 Salvar Inputs")
|
| 909 |
-
carregar_btn_geral = gr.Button("📂 Carregar Inputs")
|
| 910 |
-
arquivo_progresso_upload_geral = gr.File(label=".json", file_types=[".json"])
|
| 911 |
-
progresso_download_file_geral = gr.File(label="Baixar", interactive=False)
|
| 912 |
-
|
| 913 |
with gr.Column(scale=3):
|
| 914 |
-
gr.Markdown("### 2.
|
| 915 |
-
with gr.Row():
|
| 916 |
-
caso_input_geral = gr.Textbox(label="📋 Caso", lines=8)
|
| 917 |
-
caso_btn_geral = gr.Button("🎲 Gerar", variant="secondary", scale=0)
|
| 918 |
with gr.Tabs():
|
| 919 |
-
with gr.TabItem("✏️ Texto"):
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
btn_corrigir_geral = gr.Button("🚀 Corrigir (IA)", variant="primary")
|
| 923 |
-
with gr.TabItem("📎 Arquivo"):
|
| 924 |
-
peticao_arquivo_input_geral = gr.File(label="Arquivo", file_types=[".pdf",".docx",".txt"])
|
| 925 |
-
arquivo_preview_output_geral = gr.Textbox(label="🔍 Prévia", lines=3, interactive=False)
|
| 926 |
-
btn_corrigir_arquivo_geral = gr.Button("🚀 Corrigir Arquivo (IA)", variant="primary")
|
| 927 |
-
|
| 928 |
-
processing_message_geral = gr.Markdown("", visible=False)
|
| 929 |
with gr.Tabs():
|
| 930 |
-
with gr.TabItem("📊 Análise IA"):
|
| 931 |
-
with gr.TabItem("✨ Correção IA"):
|
| 932 |
-
with gr.TabItem("⚖️ Diff"):
|
| 933 |
-
with gr.Row():
|
| 934 |
-
with gr.Column(scale=1): resumo_alteracoes_output_geral = gr.Markdown()
|
| 935 |
-
with gr.Column(scale=3): visualizacao_detalhada_output_geral = gr.HTML()
|
| 936 |
-
criticas_output_geral = gr.Markdown()
|
| 937 |
-
|
| 938 |
with gr.TabItem("📄 Correção Padrão OAB", id=1):
|
| 939 |
-
gr.Markdown("Forneça
|
| 940 |
with gr.Row():
|
| 941 |
with gr.Column(scale=2):
|
| 942 |
-
gr.Markdown("### 1. Material
|
| 943 |
-
with gr.Tabs():
|
| 944 |
-
with gr.TabItem("📎 PDF Oficial"):
|
| 945 |
-
oab_pdf_input = gr.File(label="Padrão Resposta OAB (.pdf)", file_types=[".pdf"])
|
| 946 |
-
parse_status_output = gr.Textbox(label="Status PDF", interactive=False)
|
| 947 |
-
with gr.TabItem("✏️ Manual"):
|
| 948 |
-
oab_enunciado_input = gr.Textbox(label="Enunciado", lines=5)
|
| 949 |
-
oab_gabarito_input = gr.Textbox(label="Gabarito", lines=8)
|
| 950 |
-
oab_distribuicao_input = gr.Textbox(label="Distribuição", lines=8)
|
| 951 |
with gr.Column(scale=2):
|
| 952 |
-
gr.Markdown("### 2. Ações")
|
| 953 |
-
|
| 954 |
-
gr.Markdown("---")
|
| 955 |
-
peticao_usuario_oab_input = gr.Textbox(label="Sua Peça (para corrigir pela OAB)", lines=10)
|
| 956 |
-
btn_corrigir_pela_oab = gr.Button("🎯 Corrigir pela OAB", variant="primary")
|
| 957 |
-
|
| 958 |
-
processing_message_oab = gr.Markdown("", visible=False)
|
| 959 |
with gr.Tabs():
|
| 960 |
-
with gr.TabItem("📝 Modelo OAB"):
|
| 961 |
-
with gr.TabItem("🎯 Avaliação OAB"):
|
| 962 |
-
|
| 963 |
-
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
).then( lambda: gr.update(visible=False), None,
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
|
| 983 |
-
|
| 984 |
-
|
| 985 |
-
|
| 986 |
-
|
| 987 |
-
).
|
| 988 |
-
).
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
|
| 992 |
-
|
| 993 |
-
|
| 994 |
-
|
| 995 |
-
btn_gerar_modelo_oab.click( lambda: gr.update(value="⏳ Gerando Modelo...", visible=True), None, processing_message_oab).then(
|
| 996 |
-
gerar_peticao_modelo_oab, [oab_enunciado_input, oab_gabarito_input, oab_distribuicao_input], [resultado_modelo_oab_output]
|
| 997 |
-
).then( lambda: gr.update(visible=False), None, processing_message_oab )
|
| 998 |
-
btn_corrigir_pela_oab.click( lambda: gr.update(value="⏳ Corrigindo OAB...", visible=True), None, processing_message_oab).then(
|
| 999 |
-
corrigir_pela_oab, [peticao_usuario_oab_input, oab_enunciado_input, oab_gabarito_input, oab_distribuicao_input], [resultado_correcao_oab_output]
|
| 1000 |
-
).then( lambda: gr.update(visible=False), None, processing_message_oab )
|
| 1001 |
-
|
| 1002 |
-
clear_btn.click( fn=lambda: [
|
| 1003 |
-
"Petição Inicial", False, None, None, None, None, "", "", "", "", "", "", "", None, None,
|
| 1004 |
-
None, "", None, None, None, "", "", "", ""
|
| 1005 |
-
], inputs=None, outputs=[
|
| 1006 |
-
tipo_peticao_input_geral, modo_treinamento_geral, caso_input_geral, peticao_texto_input_geral, peticao_arquivo_input_geral,
|
| 1007 |
-
arquivo_preview_output_geral, processing_message_geral, resultado_analise_output_geral,
|
| 1008 |
-
resultado_peticao_corrigida_output_geral, resumo_alteracoes_output_geral, criticas_output_geral,
|
| 1009 |
-
visualizacao_detalhada_output_geral, dica_contextual_output_geral, arquivo_progresso_upload_geral,
|
| 1010 |
-
progresso_download_file_geral,
|
| 1011 |
-
oab_pdf_input, parse_status_output, oab_enunciado_input, oab_gabarito_input, oab_distribuicao_input,
|
| 1012 |
-
peticao_usuario_oab_input, processing_message_oab, resultado_modelo_oab_output, resultado_correcao_oab_output
|
| 1013 |
-
])
|
| 1014 |
-
|
| 1015 |
return demo
|
| 1016 |
|
| 1017 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
| 1018 |
app = interface_gradio()
|
| 1019 |
app.launch()
|
|
|
|
| 21 |
import random
|
| 22 |
import logging
|
| 23 |
import re
|
| 24 |
+
from fpdf import FPDF # Import FPDF
|
| 25 |
|
| 26 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 27 |
logger = logging.getLogger(__name__)
|
|
|
|
| 30 |
|
| 31 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 32 |
if not GEMINI_API_KEY:
|
| 33 |
+
raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada.")
|
| 34 |
|
| 35 |
GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}"
|
| 36 |
|
| 37 |
+
CONFIG = {"max_retry_attempts": 3, "retry_delay": 5, "timeout": 180, "temperatura": 0.3, "max_tokens": 8192}
|
| 38 |
+
CONFIG_OAB = {"temperatura": 0.2, "max_tokens": 8192}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
rate_limiter = RateLimiter(max_calls=30, period=60)
|
| 40 |
|
| 41 |
TIPOS_DE_PETICAO = [
|
| 42 |
+
"Petição Inicial", "Contestação", "Reconvenção", "Impugnação à Contestação",
|
| 43 |
+
"Embargos de Declaração", "Recurso de Apelação", "Agravo de Instrumento",
|
| 44 |
+
"Petição de Tutela Provisória", "Ação de Alimentos Gravídicos",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
]
|
| 46 |
|
| 47 |
DICAS_OAB_MELHORADO = {
|
| 48 |
+
"Clareza e Concisão": "Seja direto e evite jargões excessivos.", "Fundamentação Legal": "Sempre cite lei/súmula/jurisprudência.",
|
| 49 |
+
"Coerência Argumentativa": "Mantenha linha lógica.", "Revisão Ortográfica e Gramatical": "Erros prejudicam credibilidade.",
|
| 50 |
+
"Formatação": "Parágrafos definidos, espaçamento, destaque moderado.",
|
| 51 |
+
"Endereçamento (Inicial)": "Verifique competência. Consumo: foro domicílio autor (Art. 101, I, CDC).",
|
| 52 |
+
"Qualificação (Inicial)": "Dados Art. 319, II, CPC. Use 'XXX'.", "Fatos (Inicial)": "Narrativa cronológica, clara, objetiva.",
|
| 53 |
+
"Pedidos (Inicial)": "Certos, determinados, líquidos. Cumule (Art. 327).", "Custas (Inicial)": "Peça juntada guia OU JG.",
|
| 54 |
+
"Valor da Causa (Inicial)": "Benefício econômico (Art. 292).", "Preliminares (Contestação)": "Art. 337 antes mérito.",
|
| 55 |
+
"Impugnação Específica (Contestação)": "Conteste cada fato (Art. 341).", "Mérito (Contestação)": "Sua versão + fundamento improcedência.",
|
| 56 |
+
"Pedidos (Contestação)": "Acolher prelim./improcedência total + custas/honorários.", "Pressupostos Recursais": "Cabimento, tempestiv., preparo, reg. formal, interesse.",
|
| 57 |
+
"Dialeticidade (Recurso)": "Impugne fundamentos decisão (Art. 1010 II/III Apel.).", "Delimitação da Matéria (Recurso)": "Indique parte recorrida.",
|
| 58 |
+
"Fumus Boni Iuris": "Plausibilidade direito (provas/argumentos).", "Periculum in Mora": "Risco dano irreparável/difícil reparação.",
|
| 59 |
+
"Reversibilidade": "Argumente reversibilidade (Art. 300, §3º).",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
HISTORICO = []
|
| 63 |
CORRECAO_CACHE = {}
|
| 64 |
|
| 65 |
def preprocess_image_for_ocr(img):
|
| 66 |
+
try: gray = img.convert('L'); np_img = np.array(gray); _, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU); return Image.fromarray(thresh)
|
| 67 |
+
except Exception as e: logger.error(f"Preprocess err: {e}"); return f"❌ Err preprocess: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
def extract_text_from_pdf(file_path_or_obj):
|
| 70 |
+
text, ocr_text, full_text = "", "", ""
|
|
|
|
|
|
|
| 71 |
try:
|
| 72 |
pdf_reader = pdfplumber_open(file_path_or_obj)
|
| 73 |
for i, page in enumerate(pdf_reader.pages):
|
| 74 |
+
page_full_text = ""; page_text = page.extract_text()
|
| 75 |
+
if page_text: page_full_text += page_text + "\n"; text += page_text + "\n"
|
|
|
|
|
|
|
|
|
|
| 76 |
else:
|
| 77 |
+
logger.warning(f"P{i+1} OCR try.")
|
| 78 |
+
try: img = page.to_image(resolution=300).original; page_ocr_text = pytesseract.image_to_string(preprocess_image_for_ocr(img), lang='por')
|
| 79 |
+
except Exception as ocr_setup_e: logger.error(f"Tesseract/Lang error? {ocr_setup_e}"); page_ocr_text = None # Handle potential Tesseract setup issues
|
| 80 |
+
if page_ocr_text and page_ocr_text.strip(): page_full_text += page_ocr_text + "\n"; ocr_text += page_ocr_text + "\n"
|
| 81 |
+
else: page_full_text += f"[OCR fail p{i+1}]\n"; ocr_text += f"[OCR fail p{i+1}]\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
full_text += page_full_text
|
| 83 |
pdf_reader.close()
|
| 84 |
+
if full_text.strip(): logger.info("Combined PDF text."); return full_text
|
| 85 |
+
else: logger.warning("No PDF text."); return "⚠️ No text extracted."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
except Exception as e:
|
| 87 |
+
logger.error(f"PDF err: {e}")
|
| 88 |
try:
|
| 89 |
+
logger.info("PyPDF2 fallback.")
|
| 90 |
+
fb_text = ""; reader_args = file_path_or_obj;
|
| 91 |
+
if hasattr(reader_args, 'seek'): reader_args.seek(0)
|
| 92 |
+
reader = PyPDF2.PdfReader(reader_args)
|
| 93 |
+
for page in reader.pages: fb_text += page.extract_text() or ""
|
| 94 |
+
if fb_text.strip(): logger.info("Fallback ok."); return fb_text
|
| 95 |
+
else: return f"❌ PDF err & fallback fail: {e}"
|
| 96 |
+
except Exception as fb_e: logger.error(f"Fallback err: {fb_e}"); return f"❌ Critical PDF err: {e}"
|
| 97 |
+
|
| 98 |
+
def extract_text_from_docx(f):
|
| 99 |
+
try: d=docx.Document(f); t="\n".join([p.text for p in d.paragraphs]); return t if t.strip() else "⚠️ DOCX empty."
|
| 100 |
+
except Exception as e: logger.error(f"DOCX err: {e}"); return f"❌ DOCX error: {e}"
|
| 101 |
+
|
| 102 |
+
def extract_text_from_txt(f):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
try:
|
| 104 |
+
c = None
|
| 105 |
+
if isinstance(f, str):
|
| 106 |
+
with open(f, 'rb') as file: c = file.read()
|
| 107 |
+
elif hasattr(f, 'read'): c = f.read(); [getattr(f, 'seek', lambda x: None)(0)]
|
| 108 |
+
else: return "❌ Invalid TXT."
|
| 109 |
+
for enc in ['utf-8', 'latin-1', 'cp1252']:
|
| 110 |
+
try: dec = c.decode(enc); logger.info(f"TXT decoded {enc}."); return dec if dec.strip() else "⚠️ TXT empty."
|
| 111 |
+
except UnicodeDecodeError: continue
|
| 112 |
+
logger.error("Cannot decode TXT."); return "❌ Cannot decode TXT."
|
| 113 |
+
except Exception as e: logger.error(f"TXT err: {e}"); return f"❌ TXT error: {e}"
|
| 114 |
+
|
| 115 |
+
def process_file(f):
|
| 116 |
+
if f is None: return "⚠️ No file."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
try:
|
| 118 |
+
fp=getattr(f,'name',None); fn=os.path.basename(fp).lower() if fp else None
|
| 119 |
+
if not fp or not fn: return "❌ No file info."
|
| 120 |
+
logger.info(f"Processing: {fn}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
try:
|
| 122 |
+
fs=os.path.getsize(fp)
|
| 123 |
+
if fs > 15*1024*1024: return "⚠️ File >15MB."
|
| 124 |
+
except OSError as se: logger.warning(f"Size err: {se}")
|
| 125 |
+
if fn.endswith('.pdf'): return extract_text_from_pdf(fp)
|
| 126 |
+
if fn.endswith('.docx'): return extract_text_from_docx(fp)
|
| 127 |
+
if fn.endswith('.txt'): return extract_text_from_txt(fp)
|
| 128 |
+
return "⚠️ Format? (PDF,DOCX,TXT)."
|
| 129 |
+
except Exception as e: logger.error(f"File err: {e}", exc_info=True); return f"❌ File error: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
def chamar_api(prompt, use_oab_config=False):
|
| 132 |
+
cfg = CONFIG_OAB if use_oab_config else CONFIG
|
| 133 |
+
payload = {"contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"temperature": cfg["temperatura"], "maxOutputTokens": cfg["max_tokens"], "topP": 0.95}}
|
| 134 |
+
hdrs = {"Content-Type": "application/json"}
|
| 135 |
+
logger.info(f"API: {len(prompt)}c. T:{cfg['temperatura']}, MaxT:{cfg['max_tokens']}")
|
| 136 |
+
for att in range(CONFIG["max_retry_attempts"]):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
try:
|
| 138 |
+
with rate_limiter: resp = requests.post(GEMINI_API_URL, headers=hdrs, json=payload, timeout=CONFIG["timeout"])
|
| 139 |
+
if resp.status_code==429:
|
| 140 |
+
wait=int(resp.headers.get("Retry-After", CONFIG["retry_delay"]*(att+1))); logger.warning(f"429. Wait {wait}s...");
|
| 141 |
+
if att < CONFIG["max_retry_attempts"]-1: time.sleep(wait); continue
|
| 142 |
+
else: return "❌ 429 Error."
|
| 143 |
+
if resp.status_code!=200:
|
| 144 |
+
try: msg=resp.json().get("error",{}).get("message","Unknown")
|
| 145 |
+
except: msg=resp.text[:100]
|
| 146 |
+
logger.error(f"API Err {resp.status_code}: {msg}");
|
| 147 |
+
if att < CONFIG["max_retry_attempts"]-1: time.sleep(CONFIG["retry_delay"]*(att+1)); continue
|
| 148 |
+
return f"❌ API Err {resp.status_code}: {msg}"
|
| 149 |
+
data = resp.json()
|
| 150 |
+
if not data.get("candidates"): reason=data.get("promptFeedback",{}).get("blockReason"); logger.error(f"No candidates. Block: {reason}"); return f"❌ Blocked: {reason}." if reason else "❌ Invalid (no candidates)."
|
| 151 |
+
cand=data["candidates"][0]; reason=cand.get("finishReason")
|
| 152 |
+
if reason not in [None,"STOP","MAX_TOKENS"]:
|
| 153 |
+
logger.error(f"Gen stop: {reason}"); cats=[r['category'] for r in cand.get("safetyRatings",[]) if r['probability'] not in ['NEGLIGIBLE','LOW']];
|
| 154 |
+
return f"❌ Safety block: {reason} ({','.join(cats)})." if cats else f"❌ Gen stop: {reason}."
|
| 155 |
+
if not cand.get("content",{}).get("parts"): logger.error("Bad structure."); if att < CONFIG["max_retry_attempts"]-1: time.sleep(CONFIG["retry_delay"]*(att+1)); continue; return "❌ Invalid structure."
|
| 156 |
+
txt=cand["content"]["parts"][0]["text"].strip(); logger.info(f"API ok. Len: {len(txt)}."); return txt
|
| 157 |
+
except requests.Timeout: logger.warning(f"Timeout {att+1}."); if att<CONFIG["max_retry_attempts"]-1: time.sleep(CONFIG["retry_delay"]*(att+1)); continue; return "⏱️ Timeout."
|
| 158 |
+
except requests.RequestException as e: logger.error(f"Net err: {e}"); if att<CONFIG["max_retry_attempts"]-1: time.sleep(CONFIG["retry_delay"]*(att+1)); continue; return f"❌ Net error: {e}"
|
| 159 |
+
except json.JSONDecodeError as e: logger.error(f"JSON err: {e}. Resp: {resp.text[:100]}..."); return "❌ API JSON err."
|
| 160 |
+
except Exception as e: logger.exception(f"API Unexpected: {e}"); if att<CONFIG["max_retry_attempts"]-1: time.sleep(CONFIG["retry_delay"]*(att+1)); continue; return f"❌ Unexpected: {e}"
|
| 161 |
+
return "❌ API fail all retries."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
|
| 164 |
+
base = "**Estrutura Avaliação (IA):**\n1.Análise Geral.\n2.Nota Final(0-10, justif.).\n3.Pontos Positivos.\n4.Pontos a Melhorar(TODOS, descr, fundam, impacto, sugestão).\n5.Análise Requisitos.\n\n**Critérios:** Coerência? Fundam.? Linguagem? Pedidos? **NÃO INVENTAR**(-3a-5). **NÃO IDENTIFICAR**(ZERO).\n"
|
| 165 |
+
if modo_treinamento: base += "**Modo Treino:** Detalhar+: Explicação, Base legal, Ex.Corrigido, Dica, Estrat.alt. Destaque inconsist. Sugira estrat.adic.\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
return base
|
| 167 |
|
| 168 |
def estruturar_prompt_analise_geral(caso, peticao, tipo_peticao, modo_treinamento=False):
|
| 169 |
+
instr = get_instrucoes_gerais_avaliacao(modo_treinamento)
|
| 170 |
+
prompt = f"## Análise Peça: {tipo_peticao} (IA)\n\n**Caso:**\n```\n{caso}\n```\n\n**Peça Aluno:**\n```\n{peticao}\n```\n\n{instr}\n\n**Reqs {tipo_peticao}(IA):**\n"
|
| 171 |
+
if tipo_peticao in ["Petição Inicial", "Ação de Alimentos Gravídicos"]: prompt += "- Ender/Comp(319 I; CDC 101 I; L.11804)\n- Qualif(319 II)\n- Fatos\n- Fundam(319 III)\n- Pedidos(319 IV; conversão?)\n- V.Causa(291-2; 319 V)\n- Provas(319 VI)\n- Aud(319 VII)\n- Citação\n- Docs(Proc; Custas/JG)\n- Fecho\n"
|
| 172 |
+
elif tipo_peticao == "Contestação": prompt += "- Ender.\n- Qualif.Réu; Proc#.\n- Tempest.\n- Prelim(337)\n- Impug.Esp(341)\n- Mérito(Defesa)\n- Fundam.\n- Pedidos(Acolh Prelim; IMPROCEDÊNCIA; Custas/Hon 85)\n- Provas.\n- Fecho.\n"
|
| 173 |
+
prompt += "\n\n**Instruções Finais:** Analise conf. caso/critérios. Simule OAB. Feedback construtivo. Formato solicitado."
|
| 174 |
+
return prompt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
def estruturar_prompt_peticao_corrigida_geral(caso, peticao_original, tipo_peticao):
|
| 177 |
+
prompt = f"**Caso:**\n```\n{caso}\n```\n\n**Peça Original({tipo_peticao}):**\n```\n{peticao_original}\n```\n\n**Tarefa:** Gere versão corrigida/aprimorada ({tipo_peticao}), baseada caso/problemas. Aplique práticas/reqs OAB.\n\n**Instruções Peça Corrigida({tipo_peticao}):**\n"
|
| 178 |
+
if tipo_peticao in ["Petição Inicial", "Ação de Alimentos Gravídicos"]: prompt += "1.Ender. ok\n2.Qualif. ok(319 II,'XXX')\n3.Fatos detalhados\n4.Fundam.(CC,CDC,CPC,L.11804)\n5.Pedidos ok(Alim.Grav prov/def;Tut Urg;Conversão L.11804)\n6.V.Causa ok\n7.Provas ok\n8.Aud ok(319 VII)\n9.Citação\n10.Custas ok(Req OU JG)\n11.Fecho OAB\n"
|
| 179 |
+
elif tipo_peticao == "Contestação": prompt += "1.Ender. ok\n2.Identif ok\n3.Tempest(opc)\n4.Estrut:Sínt;Prelim(337);Mérito(Impug/Def);Reconv?\n5.Pedidos ok(Acolh Prelim;IMPROCEDÊNCIA;Custas/Hon)\n6.Provas\n7.Fecho OAB\n"
|
| 180 |
+
prompt += "\n\n**Instrução Final:** Gere **APENAS** texto completo peça corrigida. Sem comentários extras."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
return prompt
|
| 182 |
|
| 183 |
def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
|
| 184 |
+
pc = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
|
|
|
|
|
|
|
| 185 |
if tipo_peticao in ["Petição Inicial", "Ação de Alimentos Gravídicos"]:
|
| 186 |
+
if "juntada da guia de custas" not in pc.lower() and "gratuidade de justiça" not in pc.lower():
|
| 187 |
+
partes=pc.split("Nestes termos pede deferimento"); pfx=partes[0].strip() if len(partes)>1 else pc.strip(); sfx="\nNestes termos pede deferimento"+partes[1] if len(partes)>1 else ""
|
| 188 |
+
pc=pfx+"\nRequer, ainda, a juntada da guia de custas devidamente recolhida."+sfx
|
| 189 |
+
if "audiência de conciliação" not in pc.lower() and "audiência de mediação" not in pc.lower():
|
| 190 |
+
partes=pc.split("Nestes termos pede deferimento"); pfx=partes[0].strip() if len(partes)>1 else pc.strip(); sfx="\nNestes termos pede deferimento"+partes[1] if len(partes)>1 else ""
|
| 191 |
+
pc=pfx+"\nOpta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC)."+sfx
|
| 192 |
+
linhas=[l.strip() for l in pc.strip().split('\n') if l.strip()]
|
| 193 |
+
fechamento=["\nLocal, data.","ADVOGADO(A)","OAB/..."]
|
| 194 |
+
while linhas and any(t.lower() in linhas[-1].lower() for t in ["advogado","oab","local","data","pede deferimento","termos em que"]):
|
| 195 |
+
last_lower=linhas[-1].lower(); is_close=any(std.strip().lower() in last_lower for std in fechamento) or "pede deferimento" in last_lower or "termos em que" in last_lower
|
| 196 |
+
if not is_close and len(linhas[-1]) > 30: break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
linhas.pop()
|
| 198 |
+
return "\n".join(linhas).strip()+"".join(fechamento)
|
| 199 |
+
|
| 200 |
+
def formatar_correcao(t):
|
| 201 |
+
t=t.replace("### Análise Geral","## 📝 Análise Geral").replace("### Nota Final","## ⭐ Nota Final").replace("### Pontos Positivos","## ✅ Pontos Positivos").replace("### Pontos a Melhorar","## ⚠️ Pontos a Melhorar").replace("### Análise Específica dos Requisitos","## 🔎 Análise Específica")
|
| 202 |
+
linhas=t.split('\n'); fmt_linhas=[]; in_list=False
|
| 203 |
+
for l in linhas:
|
| 204 |
+
ls=l.strip()
|
| 205 |
+
if ls.startswith(("1.","2.","3.","4.","5.")): fmt_linhas.append(f"\n**{ls}**"); in_list=False
|
| 206 |
+
elif ls.startswith(("- ","* ")): fmt_linhas.append((" * " if in_list else "* ")+ls[2:]); in_list=True
|
| 207 |
+
elif ls.startswith(("a)","b)","c)","d)","e)")): fmt_linhas.append(f" * {ls}"); in_list=True
|
| 208 |
+
else: fmt_linhas.append(l); in_list=False
|
| 209 |
+
tf="\n".join(fmt_linhas); m=re.search(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})",tf,re.I)
|
| 210 |
+
if m: nota=m.group(2).replace(",","."); tf=re.sub(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})",f"\\1**{nota}/10**",tf,1,re.I)
|
| 211 |
+
return tf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
|
| 213 |
def corrigir_peticao_geral_ia(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
|
| 214 |
+
logger.info(f"Correção GERAL IA: {tipo_peticao}"); start=time.time()
|
| 215 |
+
if not caso or not caso.strip(): return "⚠️ Caso?", "", "", "", "", "", ""
|
| 216 |
+
if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Tipo?", "", "", "", "", "", ""
|
|
|
|
|
|
|
|
|
|
| 217 |
peticao_conteudo = ""
|
| 218 |
+
if peticao_arquivo: peticao_conteudo = process_file(peticao_arquivo)
|
| 219 |
+
elif peticao_texto: peticao_conteudo = peticao_texto
|
| 220 |
+
if not peticao_conteudo or peticao_conteudo.startswith(("❌","⚠️")): return peticao_conteudo or "⚠️ Petição?", "", "", "", "", "", ""
|
| 221 |
+
if any(tr in peticao_conteudo.lower() for tr in ["escreva a", "faça a"]): return random.choice(["Nope!", "Tente você!"]), "", "", "", "", "", ""
|
| 222 |
+
cache_key=f"geral|{tipo_peticao}|{caso[:50]}|{peticao_conteudo[:50]}|{modo_treinamento}"
|
| 223 |
+
if cache_key in CORRECAO_CACHE: logger.info("Cache GERAL hit."); return tuple(CORRECAO_CACHE[cache_key].get(k,"") for k in ["analise_formatada","peticao_original","peticao_corrigida","resumo_diff","criticas_diff","avaliacao_criterios_ia","diff_html"])
|
| 224 |
+
analise=chamar_api(estruturar_prompt_analise_geral(caso,peticao_conteudo,tipo_peticao,modo_treinamento),False)
|
| 225 |
+
if analise.startswith(("❌","⏱️")): return analise, peticao_conteudo, "", "", "", "", ""
|
| 226 |
+
analise_fmt=formatar_correcao(analise)
|
| 227 |
+
peticao_corr_bruta=chamar_api(estruturar_prompt_peticao_corrigida_geral(caso,peticao_conteudo,tipo_peticao),False)
|
| 228 |
+
peticao_corr_final = f"⚠️ Falha: {peticao_corr_bruta}" if peticao_corr_bruta.startswith(("❌","⏱️")) else pos_corrigir_peticao(peticao_corr_bruta, tipo_peticao)
|
| 229 |
+
aval_crit_md=""
|
| 230 |
+
html, resumo, crit = calcular_diff_interno(peticao_conteudo, peticao_corr_final if not peticao_corr_final.startswith("��️") else "")
|
| 231 |
+
hist = {"data":datetime.now().strftime("%Y%m%d %H%M"), "modo":"Geral IA", "tipo_peticao":tipo_peticao, "caso":caso, "peticao_original":peticao_conteudo, "analise_formatada":analise_fmt, "peticao_corrigida":peticao_corr_final, "resumo_diff":resumo,"criticas_diff":crit, "avaliacao_criterios_ia":aval_crit_md}
|
| 232 |
+
HISTORICO.append(hist); HISTORICO[:] = HISTORICO[-10:]
|
| 233 |
+
CORRECAO_CACHE[cache_key] = {**hist, "diff_html": html}
|
| 234 |
+
logger.info(f"Correção GERAL IA took {time.time()-start:.2f}s")
|
| 235 |
+
return analise_fmt, peticao_conteudo, peticao_corr_final, resumo, crit, aval_crit_md, html
|
| 236 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
def parse_oab_pdf(file_obj):
|
| 238 |
if file_obj is None: return None, None, None, "⚠️ Nenhum PDF OAB."
|
| 239 |
try:
|
| 240 |
full_text = extract_text_from_pdf(file_obj.name)
|
| 241 |
+
if full_text.startswith(("❌","⚠️")): return None, None, None, full_text
|
| 242 |
+
e, g, d = "", "", ""; current = None; lines = full_text.split('\n')
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
for line in lines:
|
| 244 |
+
ls=line.strip(); ll=ls.lower()
|
| 245 |
+
if ls.startswith("Enunciado"): current="e"; continue
|
| 246 |
+
if ls.startswith("Gabarito comentado"): current="g"; continue
|
| 247 |
+
if ls.startswith("Distribuição dos Pontos"): current="d"; continue
|
| 248 |
+
if ls.startswith("PADRÃO DE RESPOSTA – QUESTÃO"): current=None; break
|
| 249 |
+
if current=="e": e+=line+"\n"
|
| 250 |
+
elif current=="g": g+=line+"\n"
|
| 251 |
+
elif current=="d": d+=line+"\n"
|
| 252 |
+
e,g,d = e.strip(), g.strip(), d.strip()
|
| 253 |
+
if not all([e,g,d]): logger.warning(f"Parse fail. E:{bool(e)}, G:{bool(g)}, D:{bool(d)}"); return None, None, None, f"⚠️ Falha separar. Txt:\n{full_text[:300]}..."
|
| 254 |
+
logger.info("PDF OAB parsed."); return e, g, d, "✅ PDF OAB carregado."
|
| 255 |
+
except Exception as ex: logger.error(f"Parse OAB err: {ex}", exc_info=True); return None,None,None,f"❌ Erro PDF OAB: {ex}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
def estruturar_prompt_geracao_modelo_oab(enunciado, gabarito, distribuicao):
|
| 258 |
+
prompt = f"""**Tarefa:** Elabore a peça processual completa, **detalhada e bem fundamentada** para o **Enunciado**. Utilize o **Gabarito Comentado** como guia para argumentos e a **Distribuição dos Pontos** para estrutura/pedidos. Crie uma peça modelo que **desenvolva** os pontos OAB, usando os fatos.
|
| 259 |
+
|
| 260 |
+
**Enunciado:**\n```\n{enunciado}\n```
|
| 261 |
+
**Gabarito Comentado (DESENVOLVER):**\n```\n{gabarito}\n```
|
| 262 |
+
**Distribuição dos Pontos (INCLUIR):**\n```\n{distribuicao}\n```
|
| 263 |
+
|
| 264 |
+
**Instruções Detalhadas:**
|
| 265 |
+
1. **Qualificação:** Completa (319 II, 'XXX' se ausente).
|
| 266 |
+
2. **Fatos:** Narre **detalhadamente** do Enunciado (datas, locais, indícios, necessidade).
|
| 267 |
+
3. **Fundamentação Jurídica:** **Desenvolva** argumentos do Gabarito (Lei, binômio), **conectando aos fatos**. Cite artigos.
|
| 268 |
+
4. **Tutela de Urgência:** Se aplicável, **argumente** requisitos (fumus/periculum) com fatos.
|
| 269 |
+
5. **Pedidos:** **Todos** da Distribuição/Gabarito, claros/específicos (citação, provas, MP, JG, alimentos, conversão, custas/honorários).
|
| 270 |
+
6. **Valor da Causa:** Atribua e justifique se possível.
|
| 271 |
+
7. **Estrutura:** Lógica (Ender., Qualif., Fatos, Direito, Tutela, Pedidos, Valor, Fecho).
|
| 272 |
+
8. **Linguagem:** Formal OAB.
|
| 273 |
+
9. **Finalização:** "Local, data.", "ADVOGADO(A)", "OAB/...". Não se identifique.
|
| 274 |
+
10. **Exclusividade:** Baseie-se **somente** nos textos fornecidos.
|
| 275 |
+
11. **Output:** **Apenas** texto completo da peça. Sem comentários extras.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
"""
|
| 277 |
return prompt
|
| 278 |
|
| 279 |
def gerar_peticao_modelo_oab(enunciado, gabarito, distribuicao):
|
| 280 |
+
if not all([enunciado, gabarito, distribuicao]): return "⚠️ E, G, D necessários."
|
| 281 |
+
logger.info("Gerando modelo OAB...")
|
|
|
|
|
|
|
| 282 |
prompt = estruturar_prompt_geracao_modelo_oab(enunciado, gabarito, distribuicao)
|
| 283 |
+
modelo = chamar_api(prompt, use_oab_config=True)
|
| 284 |
+
if modelo.startswith(("❌","⏱️")): logger.error(f"Erro gerar modelo OAB: {modelo}"); return f"❌ Falha: {modelo}"
|
| 285 |
+
logger.info("Modelo OAB gerado."); return pos_corrigir_peticao(modelo, "Petição Desconhecida")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
|
| 287 |
def estruturar_prompt_correcao_pela_oab(peticao_usuario, enunciado, gabarito, distribuicao):
|
| 288 |
+
prompt = f"""**Tarefa:** Avalie **Peça do Usuário** vs padrão OAB (Gabarito, Distribuição) p/ **Enunciado**.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
+
**Enunciado:**\n```\n{enunciado}\n```
|
| 291 |
+
**Gabarito (Esperado):**\n```\n{gabarito}\n```
|
| 292 |
+
**Distribuição (Critérios):**\n```\n{distribuicao}\n```
|
| 293 |
+
**Peça Usuário (Avaliar):**\n```\n{peticao_usuario}\n```
|
| 294 |
|
| 295 |
**Instruções:**
|
| 296 |
+
1. **Avaliação Pontual:** Para **cada item** Distribuição, verifique se Usuário abordou conf. Gabarito. Atribua pontuação.
|
| 297 |
+
2. **Justificativa:** Breve p/ cada item.
|
| 298 |
+
3. **Feedback:** Erros/omissões vs padrão.
|
| 299 |
+
4. **Peça Corrigida (Opcional):** Sugira trechos corrigidos p/ alinhar ao padrão.
|
| 300 |
5. **Output Markdown:**
|
|
|
|
| 301 |
```markdown
|
| 302 |
+
### 📊 Avaliação Conforme Padrão OAB
|
|
|
|
| 303 |
**Pontuação por Item:**
|
|
|
|
| 304 |
* **[Item 1 Distribuição]:** [Pontos Obtidos] / [Máximo]
|
| 305 |
* *Justificativa:* [...]
|
| 306 |
+
* ... (Todos itens)
|
|
|
|
|
|
|
|
|
|
| 307 |
**Pontuação Total Estimada:** [Soma Obtidos] / [Soma Máximos]
|
| 308 |
+
**Comentários e Pontos de Melhoria:** * [...]
|
|
|
|
|
|
|
|
|
|
| 309 |
---
|
| 310 |
+
### ✨ Sugestões Correção
|
| 311 |
+
[Sugestões]
|
|
|
|
| 312 |
```
|
| 313 |
+
**Importante:** Baseie **toda** avaliação **exclusivamente** materiais OAB.
|
| 314 |
"""
|
| 315 |
return prompt
|
| 316 |
|
| 317 |
def corrigir_pela_oab(peticao_usuario, enunciado, gabarito, distribuicao):
|
| 318 |
+
if not all([peticao_usuario, enunciado, gabarito, distribuicao]): return "⚠️ Inputs OAB necessários."
|
|
|
|
|
|
|
| 319 |
logger.info("Corrigindo pela OAB...")
|
| 320 |
prompt = estruturar_prompt_correcao_pela_oab(peticao_usuario, enunciado, gabarito, distribuicao)
|
| 321 |
+
correcao = chamar_api(prompt, use_oab_config=True)
|
| 322 |
+
if correcao.startswith(("❌","⏱️")): logger.error(f"Erro corrigir OAB: {correcao}"); return f"❌ Falha OAB: {correcao}"
|
| 323 |
+
logger.info("Correção OAB concluída."); return correcao
|
| 324 |
+
|
| 325 |
+
def fornecer_dica_contextual(texto_peticao, tipo_peticao):
|
| 326 |
+
if not texto_peticao or not texto_peticao.strip(): return "Digite algo..."
|
| 327 |
+
dicas = []; txt_low = texto_peticao.lower()
|
| 328 |
+
for k, d in DICAS_OAB_MELHORADO.items():
|
| 329 |
+
if k in ["Clareza e Concisão","Fundamentação Legal","Coerência Argumentativa","Revisão Ortográfica e Gramatical","Formatação"]: dicas.append(f"💡 **{k}:** {d}")
|
| 330 |
+
kws = {"Petição Inicial":["endereçamento","qualificação","fatos","pedidos","valor da causa","custas"], "Contestação":["preliminar","mérito","impugnação","improcedência"], "Ação de Alimentos Gravídicos":["gravídicos","nascituro","indícios","binômio","conversão"]}
|
| 331 |
+
if tipo_peticao in kws:
|
| 332 |
+
for kw in kws[tipo_peticao]:
|
| 333 |
+
if kw in txt_low:
|
| 334 |
+
for k,d in DICAS_OAB_MELHORADO.items():
|
| 335 |
+
kl,tl=k.lower(),tipo_peticao.split()[0].lower() if tipo_peticao else ""
|
| 336 |
+
if f"({tl})" in kl or kw in kl: df=f"💡 **{k}:** {d}"; [dicas.append(df) if df not in dicas else None]
|
| 337 |
+
return "\n\n".join(dicas[:5]) if dicas else "💡 Sem dicas específicas."
|
| 338 |
+
|
| 339 |
+
def calcular_diff_interno(t1, t2):
|
| 340 |
+
if not t1 or not t2 or t2.startswith(("⚠️","❌")): return "","",""
|
| 341 |
+
l1,l2=t1.splitlines(),t2.splitlines();diff=difflib.ndiff(l1,l2);html,crit,lr,la=[],[],0,0
|
| 342 |
+
kw_rem=["invenção","identificação","erro crasso"]; kw_add=["custas","art. 319","art. 337","lei 11.804"]
|
| 343 |
+
for ln in diff:
|
| 344 |
+
txt=ln[2:];esc=escape(txt)
|
| 345 |
+
if ln.startswith(' '):html.append(f"<div class='l-i'>{esc}</div>")
|
| 346 |
+
elif ln.startswith('- '):lr+=1;html.append(f"<div class='l-r'><del>{esc}</del></div>");[crit.append(f"<span style='color:red'>🔴 REM:</span> {esc}") for k in kw_rem if k in txt.lower()]
|
| 347 |
+
elif ln.startswith('+ '):la+=1;html.append(f"<div class='l-a'><ins>{esc}</ins></div>");[crit.append(f"<span style='color:green'>🟢 ADD:</span> {esc}") for k in kw_add if k in txt.lower()]
|
| 348 |
+
res=f"**Diff:** Rem:{lr}, Add:{la}, Net:{la-lr}"; crit_t="### 🚨 Mudanças Chave:\n"+"\n".join(crit) if crit else "Nenhuma mudança chave."
|
| 349 |
+
css="<style>.l-i{color:#555}.l-r{background:#ffebee;color:#c62828;text-decoration:line-through}.l-a{background:#e8f5e9;color:#2e7d32}.l-r del,.l-a ins{text-decoration:none;padding:1px 3px;border-radius:3px}.l-r del{background:#ffcdd2}.l-a ins{background:#c8e6c9}</style>"
|
| 350 |
+
diff_html=f"{css}<div style='font-family:monospace;white-space:pre-wrap;border:1px solid #ccc;padding:10px;background:#f9f9f9;'>{''.join(html)}</div>"
|
| 351 |
+
return diff_html,res,crit_t
|
| 352 |
|
| 353 |
def calcular_progresso():
|
| 354 |
if not HISTORICO: return "📊 Progresso vazio."
|
| 355 |
+
notas,tipos,count=[],set(),0
|
| 356 |
+
for e in HISTORICO:
|
| 357 |
+
if e.get("modo","Geral IA")=="Geral IA": count+=1;tipos.add(e["tipo_peticao"]); m=re.search(r"\*\*(\d{1,2}[,.]?\d?)/10\*\*",e.get("analise_formatada",""));
|
| 358 |
+
if m: try: notas.append(float(m.group(1).replace(",","."))) except: pass
|
| 359 |
+
media=sum(notas)/len(notas) if notas else 0
|
| 360 |
+
return f"**📊 Progresso (IA):** Corrs:{count}, Tipos:{', '.join(sorted(list(tipos)))}, Média:{media:.1f}/10"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
|
| 362 |
def mostrar_historico():
|
| 363 |
if not HISTORICO: return "📜 Histórico vazio."
|
| 364 |
+
md="### 📜 Histórico (Últimas 10)\n\n---\n"
|
| 365 |
+
for i, e in enumerate(reversed(HISTORICO)):
|
| 366 |
+
modo=e.get("modo","Geral IA"); nota="N/A"; m=re.search(r"\*\*(\d{1,2}[,.]?\d?)/10\*\*",e.get("analise_formatada",""))
|
| 367 |
+
if m: nota=f"{m.group(1).replace(',','.')}/10"
|
| 368 |
+
md+=f"**{len(HISTORICO)-i}.** {e['data']} ({modo}) | **Tipo:** {e['tipo_peticao']} | **Nota IA:** {nota}\n**Caso:** _{e['caso'][:60]}..._\n---\n"
|
| 369 |
+
return md
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
|
| 371 |
def salvar_progresso(caso, peticao, tipo_peticao):
|
| 372 |
+
logger.info("Salvando...")
|
| 373 |
+
try:
|
| 374 |
+
if not all([caso, peticao, tipo_peticao]): return "", "⚠️ Inputs?", None
|
| 375 |
+
d={"tipo_peticao":tipo_peticao, "caso":caso, "peticao":peticao, "ts":datetime.now().isoformat()}; js=json.dumps(d, ensure_ascii=False, indent=2); ts=datetime.now().strftime("%Y%m%d%H%M"); fname=f"prog_{ts}.json"
|
| 376 |
+
fpath=os.path.join(tempfile.gettempdir(),fname);
|
| 377 |
+
with open(fpath,'w',encoding='utf-8') as f: f.write(js)
|
| 378 |
+
logger.info(f"Salvo: {fpath}"); return js, f"✅ Salvo `{fname}`", fpath
|
| 379 |
+
except Exception as e: logger.error(f"Salvar err: {e}"); return "", f"❌ Erro: {e}", None
|
| 380 |
+
|
| 381 |
+
def carregar_progresso(fobj):
|
| 382 |
+
logger.info("Carregando...")
|
| 383 |
+
if fobj is None: return "", "", "", "⚠️ Nenhum arq."
|
| 384 |
+
try:
|
| 385 |
+
with open(fobj.name,'r',encoding='utf-8') as f: d=json.load(f)
|
| 386 |
+
c,p,t=d.get("caso"), d.get("peticao"), d.get("tipo_peticao")
|
| 387 |
+
if not all([c,p,t]): return "", "", "", "❌ Arq inválido."
|
| 388 |
+
if t not in TIPOS_DE_PETICAO: return c,p,"",f"⚠️ Tipo '{t}' inválido."
|
| 389 |
+
logger.info("Carregado."); return t, c, p, "✅ Carregado!"
|
| 390 |
+
except Exception as e: logger.error(f"Carregar err: {e}"); return "", "", "", f"❌ Erro: {e}"
|
| 391 |
+
|
| 392 |
+
def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
|
| 393 |
+
if not tipo_peticao: return "Erro: Tipo?"
|
| 394 |
+
logger.info(f"Gerando caso: {tipo_peticao}")
|
| 395 |
+
prompt=estruturar_prompt_geracao_caso(tipo_peticao); caso=chamar_api(prompt,False)
|
| 396 |
+
if caso.startswith(("❌","⏱️")): return f"Falha:\n{caso}"
|
| 397 |
+
final="**Situação-Problema:** Na qualidade de advogado(a) de";
|
| 398 |
+
if final.lower() not in caso.lower(): nomes=re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+",caso); cli=nomes[0] if nomes else "[Cliente]"; caso+=f"\n\n{final} {cli}, elabore a peça cabível ({tipo_peticao})."
|
| 399 |
+
logger.info("Caso gerado."); return caso
|
| 400 |
+
|
| 401 |
+
def export_to_txt(content, filename_prefix):
|
| 402 |
+
if not content: return None, "⚠️ Conteúdo vazio para exportar."
|
| 403 |
try:
|
|
|
|
|
|
|
|
|
|
| 404 |
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 405 |
+
filename = f"{filename_prefix}_{ts}.txt"
|
| 406 |
+
temp_dir = tempfile.gettempdir()
|
| 407 |
+
file_path = os.path.join(temp_dir, filename)
|
| 408 |
+
with open(file_path, 'w', encoding='utf-8') as f:
|
| 409 |
+
f.write(content)
|
| 410 |
+
logger.info(f"Exportado para TXT: {file_path}")
|
| 411 |
+
return file_path, f"✅ Exportado para {filename}"
|
| 412 |
except Exception as e:
|
| 413 |
+
logger.error(f"Erro exportar TXT: {e}")
|
| 414 |
+
return None, f"❌ Erro TXT: {e}"
|
| 415 |
|
| 416 |
+
def export_to_pdf(content, filename_prefix):
|
| 417 |
+
if not content: return None, "⚠️ Conteúdo vazio para exportar."
|
|
|
|
| 418 |
try:
|
| 419 |
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 420 |
+
filename = f"{filename_prefix}_{ts}.pdf"
|
| 421 |
+
temp_dir = tempfile.gettempdir()
|
| 422 |
+
file_path = os.path.join(temp_dir, filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
|
| 424 |
+
pdf = FPDF()
|
| 425 |
+
pdf.add_page()
|
| 426 |
+
# Use a font that supports UTF-8 characters, handle potential missing font file
|
| 427 |
+
try:
|
| 428 |
+
# Try adding DejaVu, assuming the .ttf file is accessible
|
| 429 |
+
# Download from: https://dejavu-fonts.github.io/
|
| 430 |
+
# Place DejaVuSans.ttf in the same directory or provide the full path
|
| 431 |
+
pdf.add_font('DejaVu', '', 'DejaVuSans.ttf', uni=True)
|
| 432 |
+
pdf.set_font('DejaVu', size=11)
|
| 433 |
+
except RuntimeError:
|
| 434 |
+
logger.warning("Fonte DejaVuSans.ttf não encontrada. Usando Arial (pode ter problemas com caracteres especiais).")
|
| 435 |
+
pdf.set_font('Arial', size=11)
|
| 436 |
+
|
| 437 |
+
# Use multi_cell for automatic line breaks and UTF-8 handling
|
| 438 |
+
# Encode to latin-1 with error replacement if using standard fonts without proper UTF-8 support
|
| 439 |
+
# If using DejaVu, encoding might not be strictly necessary but good practice
|
| 440 |
+
try:
|
| 441 |
+
# Use write for better Markdown basic handling (bold/italic if font supports)
|
| 442 |
+
pdf.write(h=5, txt=content) # h is line height
|
| 443 |
+
except UnicodeEncodeError:
|
| 444 |
+
# Fallback if write fails with current font
|
| 445 |
+
pdf.multi_cell(0, 5, txt=content.encode('latin-1', 'replace').decode('latin-1'))
|
| 446 |
|
| 447 |
+
|
| 448 |
+
pdf.output(file_path)
|
| 449 |
+
logger.info(f"Exportado para PDF: {file_path}")
|
| 450 |
+
return file_path, f"✅ Exportado para {filename}"
|
| 451 |
+
except Exception as e:
|
| 452 |
+
logger.error(f"Erro exportar PDF: {e}", exc_info=True)
|
| 453 |
+
return None, f"❌ Erro PDF: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 454 |
|
| 455 |
def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
|
| 456 |
+
prompt = f"Crie caso fictício detalhado/realista p/ peça **{tipo_peticao}** (simulando OAB).\n**Instruções:**\n1.Complexidade adequada (15-25l).\n2.Dados essenciais(nomes,locais,datas,valores,conflito).\n3.Coerência.\n4.Foco em **{tipo_peticao}**.\n**Específico p/ {tipo_peticao}:**\n"
|
| 457 |
+
if tipo_peticao in ["Petição Inicial","Ação de Alimentos Gravídicos"]: prompt+="* Descreva violação direitos,1ª busca tutela. Elem. p/ danos. Qualif. partes. Temas:consumo,contrato,cobrança,despejo,família(alim.grav:indícios patern.,necess. mãe/nascituro)."
|
| 458 |
+
elif tipo_peticao=="Contestação": prompt+="* Resumo Inicial recebida. Versão RÉU. Elem. p/ Prelim(337),Impug.Esp,Def.Mérito. Qualif.réu."
|
| 459 |
+
else: prompt+="* Cenário cível c/ conflito claro."
|
| 460 |
+
prompt+=f'\n**Finalize:** "**Situação-Problema:** Na qualidade de advogado(a) de [Cliente], elabore a peça cabível ({tipo_peticao})."\n**Importante:** Temas comuns Civil/Proc.Civil.'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
return prompt
|
| 462 |
|
|
|
|
|
|
|
| 463 |
def interface_gradio():
|
| 464 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
|
| 465 |
gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️")
|
| 466 |
+
gr.Markdown("Use **Correção Geral (IA)** ou **Correção Padrão OAB**.")
|
|
|
|
| 467 |
with gr.Tabs() as main_tabs:
|
| 468 |
with gr.TabItem("🤖 Correção Geral (IA)", id=0):
|
| 469 |
with gr.Row():
|
| 470 |
with gr.Column(scale=1):
|
| 471 |
+
gr.Markdown("### 1. Configs (Geral)")
|
| 472 |
+
tipo_peticao_input_geral=gr.Dropdown("Tipo Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial")
|
| 473 |
+
modo_treinamento_geral=gr.Checkbox("🎓 Treinamento", value=False)
|
| 474 |
+
with gr.Accordion("⚙️ IA", open=False):
|
| 475 |
+
temp_s=gr.Slider(0.0, 1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade")
|
| 476 |
+
tout_s=gr.Slider(30, 300, value=CONFIG["timeout"], step=10, label="Timeout(s)")
|
| 477 |
+
maxt_s=gr.Slider(2048, 8192, value=CONFIG["max_tokens"], step=256, label="MaxTokens")
|
| 478 |
+
def upd_cfg(t,o,m): CONFIG["temperatura"],CONFIG["timeout"],CONFIG["max_tokens"]=t,o,m; return "✅"
|
| 479 |
+
cfg_btn=gr.Button("💾 Salvar"); cfg_stat=gr.Textbox("Status", interactive=False)
|
| 480 |
+
cfg_btn.click(upd_cfg, [temp_s, tout_s, maxt_s], cfg_stat)
|
| 481 |
+
with gr.Accordion("📊 Prog/Hist", open=False): prog_out=gr.Markdown("..."); prog_btn=gr.Button("🔄 Prog"); hist_out=gr.Markdown("..."); hist_btn=gr.Button("🔄 Hist"); prog_btn.click(calcular_progresso,outputs=prog_out); hist_btn.click(mostrar_historico,outputs=hist_out)
|
| 482 |
+
with gr.Accordion("💾/📂 I/O", open=False): sv_btn=gr.Button("💾 Salvar"); ld_btn=gr.Button("📂 Carregar"); upld=gr.File(".json",ft=[".json"]); dwnld=gr.File("Baixar", interactive=False, visible=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
with gr.Column(scale=3):
|
| 484 |
+
gr.Markdown("### 2. Caso e Peça")
|
| 485 |
+
with gr.Row(): caso_in=gr.Textbox("📋 Caso", lines=8); caso_btn=gr.Button("🎲",variant="secondary",scale=0)
|
|
|
|
|
|
|
| 486 |
with gr.Tabs():
|
| 487 |
+
with gr.TabItem("✏️ Texto"): pet_txt_in=gr.Textbox("Sua Peça", lines=12); dicas_out=gr.Markdown("*Dicas...*"); btn_corr=gr.Button("🚀 Corrigir (IA)", variant="primary")
|
| 488 |
+
with gr.TabItem("📎 Arquivo"): pet_f_in=gr.File("Arquivo", ft=[".pdf",".docx",".txt"]); prev_out=gr.Textbox("🔍 Prévia", lines=3, interactive=False); btn_f_corr=gr.Button("🚀 Corrigir Arq (IA)", variant="primary")
|
| 489 |
+
msg_geral=gr.Markdown("", visible=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
with gr.Tabs():
|
| 491 |
+
with gr.TabItem("📊 Análise IA"): an_out=gr.Markdown(); with gr.Row(): exp_an_txt_btn=gr.Button("Export TXT"); exp_an_pdf_btn=gr.Button("Export PDF"); an_file_out=gr.File(label="Download Análise", interactive=False, visible=False)
|
| 492 |
+
with gr.TabItem("✨ Correção IA"): corr_out=gr.Textbox(lines=15, show_copy_button=True); with gr.Row(): exp_corr_txt_btn=gr.Button("Export TXT"); exp_corr_pdf_btn=gr.Button("Export PDF"); corr_file_out=gr.File(label="Download Correção", interactive=False, visible=False)
|
| 493 |
+
with gr.TabItem("⚖️ Diff"): with gr.Row(): with gr.Column(scale=1): res_out=gr.Markdown(); with gr.Column(scale=3): diff_out=gr.HTML(); crit_out=gr.Markdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
with gr.TabItem("📄 Correção Padrão OAB", id=1):
|
| 495 |
+
gr.Markdown("Forneça material oficial (PDF/texto) e sua peça.")
|
| 496 |
with gr.Row():
|
| 497 |
with gr.Column(scale=2):
|
| 498 |
+
gr.Markdown("### 1. Material OAB");
|
| 499 |
+
with gr.Tabs(): with gr.TabItem("📎 PDF"): oab_pdf=gr.File("Padrão OAB (.pdf)", ft=[".pdf"]); parse_st=gr.Textbox("Status PDF", interactive=False); with gr.TabItem("✏️ Manual"): oab_e=gr.Textbox("Enunciado", lines=5); oab_g=gr.Textbox("Gabarito", lines=8); oab_d=gr.Textbox("Distribuição", lines=8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
with gr.Column(scale=2):
|
| 501 |
+
gr.Markdown("### 2. Ações"); btn_mod=gr.Button("📝 Gerar Modelo OAB"); gr.Markdown("---"); pet_usr=gr.Textbox("Sua Peça p/ OAB", lines=10); btn_corr_oab=gr.Button("🎯 Corrigir pela OAB", variant="primary")
|
| 502 |
+
msg_oab=gr.Markdown("", visible=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
with gr.Tabs():
|
| 504 |
+
with gr.TabItem("📝 Modelo OAB"): mod_out=gr.Textbox(lines=20, show_copy_button=True); with gr.Row(): exp_mod_txt_btn=gr.Button("Export TXT"); exp_mod_pdf_btn=gr.Button("Export PDF"); mod_file_out=gr.File("Download Modelo", interactive=False, visible=False)
|
| 505 |
+
with gr.TabItem("🎯 Avaliação OAB"): corr_oab_out=gr.Markdown(); with gr.Row(): exp_avaoab_txt_btn=gr.Button("Export TXT"); exp_avaoab_pdf_btn=gr.Button("Export PDF"); avaoab_file_out=gr.File("Download Avaliação", interactive=False, visible=False)
|
| 506 |
+
clr_btn = gr.Button("🗑️ Limpar Tudo")
|
| 507 |
+
|
| 508 |
+
caso_btn.click(gerar_caso_ficticio, [tipo_peticao_input_geral], [caso_in])
|
| 509 |
+
pet_txt_in.change(fornecer_dica_contextual, [pet_txt_in, tipo_peticao_input_geral], dicas_out)
|
| 510 |
+
tipo_peticao_input_geral.change(fornecer_dica_contextual, [pet_txt_in, tipo_peticao_input_geral], dicas_out)
|
| 511 |
+
pet_f_in.change(process_file, [pet_f_in], prev_out)
|
| 512 |
+
|
| 513 |
+
corr_inputs=[caso_in, tipo_peticao_input_geral, pet_txt_in, gr.State(None), modo_treinamento_geral]
|
| 514 |
+
corr_outputs=[an_out, gr.Textbox(visible=False), corr_out, res_out, crit_out, gr.Markdown(visible=False), diff_out]
|
| 515 |
+
btn_corr.click(lambda: gr.update(value="⏳ IA...", visible=True), None, msg_geral).then(corrigir_peticao_geral_ia, corr_inputs, corr_outputs).then(lambda: gr.update(visible=False), None, msg_geral)
|
| 516 |
+
corr_f_inputs=[caso_in, tipo_peticao_input_geral, gr.State(None), pet_f_in, modo_treinamento_geral]
|
| 517 |
+
btn_f_corr.click(lambda: gr.update(value="⏳ IA...", visible=True), None, msg_geral).then(corrigir_peticao_geral_ia, corr_f_inputs, corr_outputs).then(lambda: gr.update(visible=False), None, msg_geral)
|
| 518 |
+
|
| 519 |
+
sv_btn.click(lambda: gr.update(value="⏳...", visible=True), None, msg_geral).then(salvar_progresso, [caso_in, pet_txt_in, tipo_peticao_input_geral], [gr.Textbox(visible=False), cfg_stat, dwnld]).then(lambda x: gr.update(value=x, visible=bool(x)), [dwnld], [dwnld]).then(lambda: gr.update(visible=False), None, msg_geral)
|
| 520 |
+
ld_btn.click(lambda: gr.update(value="⏳...", visible=True), None, msg_geral).then(carregar_progresso, [upld], [tipo_peticao_input_geral, caso_in, pet_txt_in, cfg_stat]).then(lambda: gr.update(visible=False), None, msg_geral)
|
| 521 |
+
|
| 522 |
+
oab_pdf.change(parse_oab_pdf, [oab_pdf], [oab_e, oab_g, oab_d, parse_st])
|
| 523 |
+
btn_mod.click(lambda: gr.update(value="⏳...", visible=True), None, msg_oab).then(gerar_peticao_modelo_oab, [oab_e, oab_g, oab_d], [mod_out]).then(lambda: gr.update(visible=False), None, msg_oab)
|
| 524 |
+
btn_corr_oab.click(lambda: gr.update(value="⏳...", visible=True), None, msg_oab).then(corrigir_pela_oab, [pet_usr, oab_e, oab_g, oab_d], [corr_oab_out]).then(lambda: gr.update(visible=False), None, msg_oab)
|
| 525 |
+
|
| 526 |
+
def export_wrapper(func, content, prefix): status_msg = ""; file_path = None; if content: file_path, status_msg = func(content, prefix); else: status_msg = "⚠️ Nada para exportar."; return file_path, status_msg
|
| 527 |
+
exp_an_txt_btn.click(export_wrapper, [gr.State(export_to_txt), an_out, gr.State("analise_ia")], [an_file_out, cfg_stat]).then(lambda x: gr.update(visible=bool(x)), [an_file_out], [an_file_out])
|
| 528 |
+
exp_an_pdf_btn.click(export_wrapper, [gr.State(export_to_pdf), an_out, gr.State("analise_ia")], [an_file_out, cfg_stat]).then(lambda x: gr.update(visible=bool(x)), [an_file_out], [an_file_out])
|
| 529 |
+
exp_corr_txt_btn.click(export_wrapper, [gr.State(export_to_txt), corr_out, gr.State("correcao_ia")], [corr_file_out, cfg_stat]).then(lambda x: gr.update(visible=bool(x)), [corr_file_out], [corr_file_out])
|
| 530 |
+
exp_corr_pdf_btn.click(export_wrapper, [gr.State(export_to_pdf), corr_out, gr.State("correcao_ia")], [corr_file_out, cfg_stat]).then(lambda x: gr.update(visible=bool(x)), [corr_file_out], [corr_file_out])
|
| 531 |
+
exp_mod_txt_btn.click(export_wrapper, [gr.State(export_to_txt), mod_out, gr.State("modelo_oab")], [mod_file_out, cfg_stat]).then(lambda x: gr.update(visible=bool(x)), [mod_file_out], [mod_file_out])
|
| 532 |
+
exp_mod_pdf_btn.click(export_wrapper, [gr.State(export_to_pdf), mod_out, gr.State("modelo_oab")], [mod_file_out, cfg_stat]).then(lambda x: gr.update(visible=bool(x)), [mod_file_out], [mod_file_out])
|
| 533 |
+
exp_avaoab_txt_btn.click(export_wrapper, [gr.State(export_to_txt), corr_oab_out, gr.State("avaliacao_oab")], [avaoab_file_out, cfg_stat]).then(lambda x: gr.update(visible=bool(x)), [avaoab_file_out], [avaoab_file_out])
|
| 534 |
+
exp_avaoab_pdf_btn.click(export_wrapper, [gr.State(export_to_pdf), corr_oab_out, gr.State("avaliacao_oab")], [avaoab_file_out, cfg_stat]).then(lambda x: gr.update(visible=bool(x)), [avaoab_file_out], [avaoab_file_out])
|
| 535 |
+
|
| 536 |
+
clr_btn.click(lambda: ["Petição Inicial",False,None,None,None,None,"","","","","","",None,None,None,None,"",None,None,None,"","","","",None,None,None,None], None, [
|
| 537 |
+
tipo_peticao_input_geral,modo_treinamento_geral,caso_in,pet_txt_in,pet_f_in,prev_out,msg_geral,an_out,corr_out,res_out,crit_out,diff_out,dwnld,an_file_out,corr_file_out,
|
| 538 |
+
oab_pdf,parse_st,oab_e,oab_g,oab_d,pet_usr,msg_oab,mod_out,corr_oab_out,mod_file_out,avaoab_file_out])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
return demo
|
| 540 |
|
| 541 |
if __name__ == "__main__":
|
| 542 |
+
# Ensure Tesseract path is set if needed, or handle OCR errors gracefully
|
| 543 |
+
# try: pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract' # Example for Linux
|
| 544 |
+
# except: logger.warning("Tesseract not configured/found. OCR might fail.")
|
| 545 |
app = interface_gradio()
|
| 546 |
app.launch()
|