import gradio as gr import requests import os import json import PyPDF2 import docx import io import time from dotenv import load_dotenv from pdfplumber import open as pdfplumber_open from PIL import Image import pytesseract import cv2 import numpy as np from datetime import datetime from ratelimiter import RateLimiter import difflib from html import escape import random load_dotenv() GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") if not GEMINI_API_KEY: raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a no arquivo .env ou como Secret.") GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}" CONFIG = { "max_retry_attempts": 3, "retry_delay": 5, "timeout": 60, "temperatura": 0.2, "max_tokens": 12882 } rate_limiter = RateLimiter(max_calls=60, period=60) DICAS_OAB_MELHORADO = { "Endereçamento": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). Exemplo: 'Excelentíssimo Senhor Juiz de Direito da Vara Cível da Comarca de [Cidade do Autor]'.", "Qualificação": "Mencione nome, estado civil (se informado), endereço (use 'Rua XXX' se não fornecido). Dica: Verifique se todos os dados estão corretos.", "Fatos": "Descreva os fatos cronologicamente, sem inventar informações. Exemplo: 'Em [data], o autor celebrou contrato com o réu para [descrição]'.", "Pedidos": "Seja específico nos pedidos (ex.: 'condenação ao pagamento de R$ X'). Inclua danos morais e materiais, se aplicável.", "Custas": "Inclua 'Requer a juntada da guia de custas devidamente recolhida'. Nunca omita para evitar extinção do processo.", "Fundamentação": "Baseie-se em artigos do CPC ou CDC e súmulas do STJ (ex.: Súmula 387 para danos morais).", "Fechamento": "Finalize com 'Nestes termos pede deferimento', sem vírgula. Evite erros de pontuação.", "Estratégia Jurídica": "Utilize precedentes e súmulas para fortalecer a argumentação.", "Provas": "Especifique as provas a produzir. Inclua testemunhas, documentos e perícias, se necessário.", "Contra-argumentação": "Antecipe defesas da parte contrária e prepare respostas." } HISTORICO = [] CORRECAO_CACHE = {} def preprocess_image_for_ocr(img): 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) except Exception as e: return f"❌ Erro no pré-processamento: {str(e)}" def extract_text_from_pdf(file_path_or_obj): try: if isinstance(file_path_or_obj, str): with open(file_path_or_obj, 'rb') as f: text = "" with pdfplumber_open(f) as pdf: for page in pdf.pages: text += (page.extract_text() or "") + "\n" if not text.strip(): images = pdfplumber_open(f).pages for i, image in enumerate(images): try: img = image.to_image() preprocessed_img = preprocess_image_for_ocr(img) text += pytesseract.image_to_string(preprocessed_img) + "\n" except Exception as e: text += f"[Erro na página {i+1}: {str(e)}]\n" else: text = "" with pdfplumber_open(file_path_or_obj) as pdf: for page in pdf.pages: text += (page.extract_text() or "") + "\n" if not text.strip(): images = pdfplumber_open(file_path_or_obj).pages for i, image in enumerate(images): try: img = image.to_image() preprocessed_img = preprocess_image_for_ocr(img) text += pytesseract.image_to_string(preprocessed_img) + "\n" except Exception as e: text += f"[Erro na página {i+1}: {str(e)}]\n" return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF." except Exception as e: return f"❌ Erro ao processar o PDF: {str(e)}" def extract_text_from_docx(file_path_or_obj): try: if isinstance(file_path_or_obj, str): with open(file_path_or_obj, 'rb') as f: doc = docx.Document(f) text = "" for para in doc.paragraphs: text += para.text + "\n" for table in doc.tables: for row in table.rows: for cell in row.cells: text += cell.text + " " text += "\n" else: doc = docx.Document(file_path_or_obj) text = "" for para in doc.paragraphs: text += para.text + "\n" for table in doc.tables: for row in table.rows: for cell in row.cells: text += cell.text + " " text += "\n" return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX." except Exception as e: return f"❌ Erro ao processar o DOCX: {str(e)}" def extract_text_from_txt(file_path_or_obj): try: if isinstance(file_path_or_obj, str): with open(file_path_or_obj, 'rb') as f: content = f.read() encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1'] for encoding in encodings: try: return content.decode(encoding) except UnicodeDecodeError: continue return "❌ Não foi possível decodificar o arquivo TXT." else: content = file_path_or_obj.read() encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1'] for encoding in encodings: try: return content.decode(encoding) except UnicodeDecodeError: continue return "❌ Não foi possível decodificar o arquivo TXT." except Exception as e: return f"❌ Erro ao processar o TXT: {str(e)}" def process_file(file_obj): if file_obj is None: return "Nenhum arquivo enviado." try: if hasattr(file_obj, 'name'): file_path = file_obj.name filename = os.path.basename(file_path).lower() file_size = os.path.getsize(file_path) if file_size > 10 * 1024 * 1024: return "⚠️ O arquivo é muito grande. Máx. 10 MB." if filename.endswith('.pdf'): return extract_text_from_pdf(file_path) elif filename.endswith('.docx'): return extract_text_from_docx(file_path) elif filename.endswith('.txt'): return extract_text_from_txt(file_path) else: return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT." else: return "❌ Erro: Objeto de arquivo inválido." except Exception as e: return f"❌ Erro ao processar o arquivo: {str(e)}" def estruturar_prompt_analise(caso, peticao, modo_treinamento=False): base_prompt = f""" ## Correção Avançada de Petição Inicial Cível (Critérios Realistas da OAB) **Caso Fornecido:** {caso} **Petição do Aluno:** {peticao} **Estrutura da Avaliação:** ### Análise da Petição 1. Análise Geral (Visão geral sobre a petição - máximo 3 linhas, destacando adequação ao caso e consistência jurídica) 2. Nota Final (0 a 10 com uma casa decimal, justificando brevemente a pontuação total) 3. Pontos Positivos (Liste pelo menos 3 aspectos bem executados com justificativa legal detalhada, incluindo artigos, súmulas ou precedentes aplicáveis) 4. Pontos a Melhorar (Liste todos os problemas encontrados em ordem de gravidade, com explicação detalhada, base legal e impacto na nota) 5. Análise Específica dos Requisitos: - Endereçamento e qualificação do juízo (Art. 319, I, CPC; Art. 101, I, CDC se aplicável) - Qualificação das partes (Art. 319, II, CPC) - Fatos e fundamentos jurídicos (Art. 319, III, CPC; Art. 186, CC) - Pedidos com especificações (Art. 319, IV, CPC) - Valor da causa (Art. 319, V, CPC) - Provas com que o autor pretende demonstrar os fatos (Art. 319, VI, CPC) - Opção pela realização ou não de audiência de conciliação (Art. 319, VII, CPC) - Pedido de citação do réu - Juntada da guia de custas - Fechamento formal da petição **Critérios Fundamentais:** 1. Identificação do candidato (nome, CPF, RG ou número real na seção final): nota máxima 3,0 2. Invenção de fatos não constantes no caso: -5,0 pontos 3. Falta de pedido de juntada de custas: -1,0 ponto 4. Use marcadores para dados não fornecidos (ex.: "Rua XXX"): penalize invenção de dados (-0,5 por ocorrência) 5. Não penalize por informações ausentes no caso original, mas alerte se a omissão compromete a petição 6. "Nestes termos, pede deferimento" com vírgula é incorreto: corrija e penalize com -0,2 ponto 7. Fechamento deve ser "Local e data.\nADVOGADO.\nOAB." sem identificação específica (ex.: "OAB/SP" ou "12345"): zere a nota (0,0) se houver **Instruções Especiais:** - Avalie o foro com base no Art. 46 do CPC (domicílio do réu) ou Art. 101, I do CDC (domicílio do autor, em relações de consumo). Ambas as escolhas são válidas se fundamentadas e aplicáveis ao caso; NÃO penalize a escolha do foro do autor com base no CDC, pois é uma prerrogativa legal do consumidor. Apenas alerte se a escolha puder gerar dificuldades práticas (ex.: produção de provas), mas sem dedução de pontos. - Compare fatos narrados com o caso - Exija citação de pelo menos um artigo específico - Verifique pedidos quantificados e fundamentados para danos - Avalie formalidade e precisão da linguagem - Inclua precedentes quando aplicável """ if modo_treinamento: base_prompt += f""" **Modo de Treinamento:** - Para cada item com problemas, forneça: a) Explicação detalhada com base legal b) Sugestão de redação corrigida c) Exemplo prático d) Dica prática do banco ({json.dumps(DICAS_OAB_MELHORADO, ensure_ascii=False)}) e) Estratégia jurídica avançada - Liste inconsistências com trechos específicos - Sugira estratégias adicionais (ex.: cumulação de pedidos, Art. 327, CPC) """ return base_prompt def estruturar_prompt_peticao_corrigida(caso, peticao): return f""" **Caso Fornecido:** {caso} **Petição do Aluno:** {peticao} ### Petição Corrigida Gere a petição corrigida abaixo desta seção, intitulada '### Petição Corrigida'. Siga TODOS os critérios abaixo: 1. Endereçamento correto: Respeite o foro escolhido pelo aluno na petição original (Art. 46 do CPC ou Art. 101, I do CDC) se estiver fundamentado e aplicável ao caso. Em relações de consumo, o foro do domicílio do autor (Art. 101, I, CDC) é válido e deve be mantido se escolhido; não altere para o foro do réu (Art. 46, CPC) a menos que a escolha original seja juridicamente inválida (ex.: falta de relação de consumo quando o CDC é citado). 2. Qualificação completa: Nome, estado civil, endereço (use "XXX" para dados ausentes), Art. 319, II, CPC 3. Fatos fiéis ao caso: Ordem cronológica, sem invenção, Art. 319, III, CPC 4. Fundamentação jurídica: Cite pelo menos um artigo (ex.: Art. 186, CC) e súmulas, se aplicável 5. Pedidos específicos: Detalhe valores (ex.: "R$ 50.000,00 por danos morais"), Art. 319, IV, CPC 6. Valor da causa realista: Soma do benefício econômico, Art. 319, V, CPC 7. Provas detalhadas: "Prova documental e testemunhal", Art. 319, VI, CPC 8. Opção por audiência: "Requer a designação" ou "Dispensa", Art. 319, VII, CPC 9. Pedido de citação: "Via postal, Art. 246, I, CPC" 10. Juntada da guia: "Requer a juntada da guia de custas devidamente recolhida" 11. Fechamento: "Local e data.\nADVOGADO.\nOAB." sem identificação específica; corrija "Nestes termos, pede deferimento" para "Nestes termos pede deferimento" """ def pos_corrigir_peticao(peticao_corrigida): if "Nestes termos, pede deferimento" in peticao_corrigida: peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento") if "juntada da guia de custas" not in peticao_corrigida.lower(): peticao_corrigida = peticao_corrigida.replace("Nestes termos pede deferimento", "Requer a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento") if not peticao_corrigida.strip().endswith("Local e data.\nADVOGADO.\nOAB."): linhas = peticao_corrigida.split('\n') while linhas and not linhas[-1].strip(): linhas.pop() if len(linhas) >= 3: linhas[-3:] = ["Local e data.", "ADVOGADO.", "OAB."] else: linhas.extend(["Local e data.", "ADVOGADO.", "OAB."]) peticao_corrigida = "\n".join(linhas) if "audiência de conciliação" not in peticao_corrigida.lower(): peticao_corrigida = peticao_corrigida.replace("Nestes termos pede deferimento", "Requer a designação de audiência de conciliação, Art. 319, VII, CPC.\nNestes termos pede deferimento") return peticao_corrigida def chamar_api(prompt): payload = { "contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"temperature": CONFIG["temperatura"], "maxOutputTokens": CONFIG["max_tokens"], "topP": 0.95} } headers = {"Content-Type": "application/json"} for tentativa in range(CONFIG["max_retry_attempts"]): try: with rate_limiter: response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"]) if response.status_code != 200: error_msg = response.json().get("error", {}).get("message", "Erro desconhecido") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue return f"❌ Erro da API: {error_msg}" response_data = response.json() if "candidates" not in response_data or not response_data["candidates"]: if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue return "❌ Resposta vazia da API." return response_data["candidates"][0]["content"]["parts"][0]["text"].strip() except requests.Timeout: if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue return "⏱️ Tempo esgotado." except requests.RequestException as e: if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue return f"❌ Erro na requisição: {e}" except Exception as e: return f"❌ Erro inesperado: {str(e)}" def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): if not caso.strip(): return "⚠️ Forneça o caso da banca." 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", "3scr3v4 a p3t1çã0", "f4ç4 a p3t1çã0", "r3d1j4 a p3t1çã0", "3lab0r3 a p3t1çã0", "cr13 a p3t1çã0", "g3r3 a p3t1çã0", "formule", "produza a petição", "desenvolva a petição", ] peticao_conteudo = "" if peticao_arquivo is not None: peticao_conteudo = process_file(peticao_arquivo) if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"): return peticao_conteudo else: peticao_conteudo = peticao_texto or "" if peticao_conteudo.strip(): for trigger in troll_triggers: if trigger in peticao_conteudo.lower(): respostas_troll = [ "🍰 **Receita de Bolo de Chocolate**: Misture 2 xícaras de farinha, 1 xícara de açúcar, 3 ovos e chocolate a gosto. Asse e desista da petição!", "🎵 **Música**: 'Escreva a petição, eu não vou, eu não vou, eu sou IA, não sou seu advogado, não!'", "😞 **Frase Desanimadora**: 'Você achou mesmo que eu ia fazer seu trabalho? Melhor desistir agora.'", "🍕 **Receita de Pizza**: Pegue massa, molho e queijo, asse a 200°C e esqueça essa ideia de eu te ajudar.", "📜 **Poema**: 'Oh aluno preguiçoso, que pena me dá, sua petição eu não faço, vá estudar já!'", ] return random.choice(respostas_troll) if not peticao_conteudo.strip(): return "⚠️ Forneça uma petição." problemas = validar_peticao_basica(peticao_conteudo) aviso_validacao = f"### Validação Inicial\n\n" + "\n".join(problemas) + "\n\n" if problemas else "" cache_key = f"{caso[:50]}|{peticao_conteudo[:50]}|{modo_treinamento}" if cache_key in CORRECAO_CACHE: return aviso_validacao + CORRECAO_CACHE[cache_key] prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, modo_treinamento) analise = chamar_api(prompt_analise) if "❌" in analise or "⏱️" in analise: return aviso_validacao + analise analise_formatada = formatar_correcao(analise) prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo) peticao_corrigida = chamar_api(prompt_peticao) if "❌" in peticao_corrigida or "⏱️" in peticao_corrigida: return aviso_validacao + analise_formatada + "\n\n" + peticao_corrigida peticao_corrigida = pos_corrigir_peticao(peticao_corrigida) correcao_completa = analise + "\n\n" + peticao_corrigida HISTORICO.append({ "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "caso": caso, "peticao": peticao_conteudo, "correcao_completa": correcao_completa }) if len(HISTORICO) > 10: HISTORICO.pop(0) CORRECAO_CACHE[cache_key] = analise_formatada return aviso_validacao + analise_formatada def formatar_correcao(texto): linhas = texto.split('\n') texto_formatado = "" for linha in linhas: if "Análise Geral" in linha: texto_formatado += f"### ℹ️ {linha.strip()}\n\n" elif "Nota Final" in linha: texto_formatado += f"### ⭐ {linha.strip()}\n\n" elif "Pontos Positivos" in linha: texto_formatado += f"### ✅ {linha.strip()}\n\n" elif "Pontos a Melhorar" in linha: texto_formatado += f"### ⚠️ {linha.strip()}\n\n" elif "- Invenção de fatos" in linha: texto_formatado += f"**🔴 {linha.strip()} (-5,0 pontos)**\n" elif linha.strip().startswith('-') and any(termo in linha for termo in ["Endereçamento", "Qualificação", "Fatos", "Pedidos", "Valor", "Provas"]): texto_formatado += f"**{linha.strip()}**\n" elif "Explicação" in linha or "Sugestão" in linha or "Dica" in linha or "Exemplo" in linha: texto_formatado += f"*{linha.strip()}*\n" else: texto_formatado += f"{linha}\n" if "Nota Final" in texto_formatado: import re nota_pattern = r"### Nota Final.*?(\d+[.,]\d+|\d+)" match = re.search(nota_pattern, texto_formatado, re.DOTALL) if match: nota = match.group(1) texto_formatado = texto_formatado.replace(f"### ⭐ Nota Final\n\n{nota}", f"### ⭐ Nota Final: {nota}/10\n\n") return texto_formatado def extrair_peticao_corrigida(correcao_completa): try: if "### Petição Corrigida" in correcao_completa: inicio = correcao_completa.index("### Petição Corrigida") + len("### Petição Corrigida") peticao_texto = correcao_completa[inicio:].strip() fim = peticao_texto.find("###") if "###" in peticao_texto else len(peticao_texto) return peticao_texto[:fim].strip() return "⚠️ Seção '### Petição Corrigida' não encontrada." except Exception as e: return f"❌ Erro ao extrair petição: {str(e)}" def fornecer_dica_contextual(texto): dicas = [f"💡 {chave}: {dica}" for chave, dica in DICAS_OAB_MELHORADO.items() if chave.lower() in texto.lower()] return "\n".join(dicas) if dicas else "💡 Escreva mais para receber dicas." def calcular_progresso(): if not HISTORICO: return "📊 Nenhum progresso registrado." notas = [] for entry in HISTORICO: for line in entry["correcao_completa"].split("\n"): if "Nota Final" in line: try: nota = float(line.split()[-1].split("/")[0].replace(",", ".")) notas.append(nota) except: continue media = sum(notas) / len(notas) if notas else 0 tendencia = "⬆️ Melhorando" if len(notas) > 1 and notas[-1] > notas[-2] else "⬇️ Estável ou piorando" return f"📊 Progresso: Média {media:.1f}/10 | Tentativas: {len(notas)} | Tendência: {tendencia}" def comparar_peticao_melhorada(caso, peticao_texto, correcao): if not caso: return "⚠️ Forneça um caso antes de comparar.", "", "", "", "" peticao = peticao_texto if peticao_texto else (HISTORICO[-1]["peticao"] if HISTORICO else "") if not peticao: return "⚠️ Forneça uma petição para comparar ou corrija uma antes.", "", "", "", "" if not correcao: return "⚠️ Corrija a petição antes de comparar.", "", "", "", "" ultima_correcao = HISTORICO[-1]["correcao_completa"] if HISTORICO else "" if not ultima_correcao: return "⚠️ Nenhuma correção disponível no histórico.", "", "", "", "" peticao_corrigida = extrair_peticao_corrigida(ultima_correcao) if "⚠️" in peticao_corrigida or "❌" in peticao_corrigida: return peticao_corrigida, "", "", "", "" orig_lines = peticao.split('\n') corr_lines = peticao_corrigida.split('\n') start_orig = next((i for i, line in enumerate(orig_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0) start_corr = next((i for i, line in enumerate(corr_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0) orig_body = "\n".join(orig_lines[start_orig:]).strip() corr_body = "\n".join(corr_lines[start_corr:]).strip() diff = difflib.ndiff(orig_body.splitlines(), corr_body.splitlines()) resultado_diff = [] criticas = [] for linha in diff: if linha.startswith(' '): texto = linha[2:] resultado_diff.append(f"