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