import gradio as gr import requests import os import json import docx import io import time import tempfile from dotenv import load_dotenv from pdfplumber import open as pdfplumber_open from PIL import Image import pytesseract try: import cv2 import numpy as np CV2_AVAILABLE = True except ImportError: CV2_AVAILABLE = False print("Aviso: OpenCV (cv2) não encontrado. Pré-processamento de imagem para OCR será limitado.") from datetime import datetime from ratelimiter import RateLimiter import difflib from html import escape import random import logging import traceback import re logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s') logger = logging.getLogger(__name__) load_dotenv() GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or os.environ.get("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 no Hugging Face.") GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}" logger.info(f"Usando API URL: ...{GEMINI_API_URL[-50:]}") CONFIG = {"max_retry_attempts": 3, "retry_delay": 5, "timeout": 120, "temperatura": 0.3, "max_tokens": 8192} rate_limiter = RateLimiter(max_calls=20, period=60) 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"] DICAS_OAB_MELHORADO = { "Clareza e Concisão": "Seja direto e evite jargões excessivos.", "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes.", "Revisão Ortográfica": "Erros de português prejudicam a credibilidade.", "Endereçamento (Inicial)": "Verifique a competência (Art. 46 CPC / Art. 101 CDC).", "Pedidos (Inicial)": "Certos, determinados e líquidos (se possível).", "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes do mérito.", "Impugnação Específica (Contestação)": "Conteste cada fato alegado (Art. 341, CPC)." } HISTORICO = [] CORRECAO_CACHE = {} def preprocess_image_for_ocr(img_pil): if not CV2_AVAILABLE: logger.warning("OpenCV não disponível, usando conversão simples para OCR.") return img_pil.convert('L') try: img_cv = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2GRAY) _, thresh = cv2.threshold(img_cv, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) return Image.fromarray(thresh) except Exception as e: logger.error(f"Erro no pré-processamento da imagem com OpenCV: {str(e)}") return img_pil.convert('L') def extract_text_from_pdf(file_path_or_obj): text = "" ocr_text_parts = [] try: logger.info(f"Abrindo PDF: {getattr(file_path_or_obj, 'name', file_path_or_obj)}") pdf_reader = pdfplumber_open(file_path_or_obj) logger.info(f"PDF com {len(pdf_reader.pages)} páginas.") for i, page in enumerate(pdf_reader.pages): page_text = page.extract_text(x_tolerance=2, y_tolerance=2) if page_text and len(page_text.strip()) > 20: text += page_text + "\n" else: logger.warning(f"Página {i+1} sem texto significativo extraído, tentando OCR.") try: img = page.to_image(resolution=200) preprocessed_img = preprocess_image_for_ocr(img.original) page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por', config='--psm 6') if page_ocr_text and page_ocr_text.strip(): ocr_text_parts.append(page_ocr_text) else: logger.warning(f"OCR retornou vazio para a página {i+1}.") ocr_text_parts.append(f"[OCR Página {i+1}: Vazio ou falhou]") except Exception as ocr_e: logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}") ocr_text_parts.append(f"[Erro OCR Página {i+1}: {str(ocr_e)}]") pdf_reader.close() ocr_text = "\n".join(ocr_text_parts) if text.strip() and len(text) > len(ocr_text) * 0.7: logger.info(f"Usando texto extraído do PDF ({len(text)} chars).") return text elif ocr_text.strip(): logger.info(f"Usando texto do OCR do PDF ({len(ocr_text)} chars).") return ocr_text elif text.strip(): logger.info("Usando texto extraído curto como fallback.") return text else: logger.warning("Não foi possível extrair texto significativo do PDF.") return "⚠️ Não foi possível extrair texto do PDF (extração e OCR falharam)." except Exception as e: logger.error(f"Erro geral ao processar o PDF: {str(e)}", exc_info=True) return f"❌ Erro crítico ao processar o PDF: {str(e)}" def extract_text_from_docx(file_path_or_obj): try: doc = docx.Document(file_path_or_obj) text = "\n".join([para.text for para in doc.paragraphs if para.text]) return text if text.strip() else "⚠️ Documento DOCX vazio ou sem texto." except Exception as e: logger.error(f"Erro ao processar o DOCX: {str(e)}", exc_info=True) return f"❌ Erro ao processar o DOCX: {str(e)}" def extract_text_from_txt(file_path_or_obj): try: content = b'' if isinstance(file_path_or_obj, str): with open(file_path_or_obj, 'rb') as f: content = f.read() elif hasattr(file_path_or_obj, 'read'): if hasattr(file_path_or_obj, 'seek') and callable(file_path_or_obj.seek): try: file_path_or_obj.seek(0) except Exception as seek_e: logger.warning(f"Não foi possível resetar ponteiro do arquivo: {seek_e}") content = file_path_or_obj.read() else: return "❌ Tipo de entrada TXT inválido." encodings = ['utf-8', 'latin-1', 'cp1252'] for encoding in encodings: try: decoded_text = content.decode(encoding) logger.info(f"Arquivo TXT decodificado com {encoding}.") return decoded_text if decoded_text.strip() else "⚠️ Arquivo TXT vazio." except UnicodeDecodeError: continue logger.error("Não foi possível decodificar o arquivo TXT com encodings comuns.") return "❌ Não foi possível decodificar o arquivo TXT. Verifique o encoding." except Exception as e: logger.error(f"Erro ao processar o TXT: {str(e)}", exc_info=True) return f"❌ Erro ao processar o TXT: {str(e)}" def process_file(file_obj): if file_obj is None: logger.warning("process_file chamado com file_obj=None") return "⚠️ Nenhum arquivo enviado." try: file_path = getattr(file_obj, 'name', None) if not file_path: logger.error("Não foi possível obter o nome do arquivo (file_obj.name está ausente).") return "❌ Erro: Não foi possível identificar o arquivo enviado." filename = os.path.basename(file_path).lower() logger.info(f"Processando arquivo: {filename}") try: file_size = os.path.getsize(file_path) logger.info(f"Tamanho do arquivo: {file_size} bytes") if file_size > 15 * 1024 * 1024: logger.warning(f"Arquivo {filename} ({file_size} bytes) excede o limite de 15MB.") return "⚠️ Arquivo muito grande (Máx 15MB)." except OSError as size_e: logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}") 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: logger.warning(f"Formato de arquivo não suportado: {filename}") return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT." except Exception as e: logger.error(f"Erro GERAL ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True) return f"❌ Erro inesperado ao processar o arquivo: {str(e)}" def get_instrucoes_gerais_avaliacao(modo_treinamento=False): base = """ **Estrutura da Avaliação:** 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. 2. **Nota Final:** 0 a 10 (uma casa decimal), com justificativa BREVE dos principais fatores. 3. **Pontos Positivos:** Liste pelo menos 2-3 aspectos bem executados, com justificativa (legal/técnica). 4. **Pontos a Melhorar:** Liste **TODOS** os problemas em ordem de gravidade (erros crassos primeiro). Para cada ponto: * Descrição clara do erro/omissão. * Fundamentação legal/doutrinária do porquê é um erro. * **Impacto estimado na nota** (ex: -0.5, -1.0, -2.0). * Sugestão de correção ou como deveria ter sido feito. 5. **Análise Específica dos Requisitos:** Avalie os itens chave da peça (listados abaixo para cada tipo). **Critérios Fundamentais (Aplicáveis a Quase Todos):** * **Coerência:** A peça responde ao caso/situação processual? Os argumentos são lógicos? * **Fundamentação:** Usou a lei, doutrina e jurisprudência corretamente? Citou artigos relevantes? * **Linguagem:** Clareza, objetividade, correção gramatical e formalidade adequada? * **Pedidos:** São claros, certos, determinados e coerentes com a fundamentação? * **NÃO INVENTAR FATOS:** Penalidade severa (-3.0 a -5.0) se criar fatos não presentes no caso. * **NÃO SE IDENTIFICAR:** Nota ZERO se houver nome, OAB, CPF, etc., do aluno na peça (exceto nomes das partes do caso). Use "ADVOGADO(A)", "OAB/...". """ if modo_treinamento: base += """ **Modo de Treinamento:** * Para cada Ponto a Melhorar, detalhe AINDA MAIS: a) Explicação aprofundada do erro e sua consequência prática. b) Base legal/doutrinária específica. c) **Exemplo de Redação Corrigida** para aquele trecho específico. d) Dica prática relevante (use o banco de dicas se aplicável). e) Possível estratégia alternativa. * Destaque inconsistências entre a peça e o caso com citações diretas. * Sugira estratégias jurídicas adicionais ou alternativas viáveis. """ return base def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False): instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento) prompt_especifico = f""" ## Análise de Peça Processual: {tipo_peticao} (Critérios OAB Simulados) **Caso/Contexto Fornecido:** ``` {caso} ``` **Peça do Aluno ({tipo_peticao}):** ``` {peticao} ``` {instrucoes_gerais} **Requisitos Específicos para {tipo_peticao}:** """ if tipo_peticao == "Petição Inicial": prompt_especifico += """ - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I). - Qualificação completa das partes (Art. 319, II, CPC). - Exposição clara e lógica dos fatos (causa de pedir remota). - Fundamentação jurídica pertinente (causa de pedir próxima - Art. 319, III, CPC). - Pedidos certos, determinados e líquidos (se possível) (Art. 319, IV, CPC). - Valor da causa correto e justificado (Art. 291-292, 319, V, CPC). - Indicação das provas pretendidas (Art. 319, VI, CPC). - Opção pela audiência de conciliação/mediação (Art. 319, VII, CPC). - Pedido de citação do réu (forma específica se necessário). - Requerimento de juntada de documentos essenciais (procuração, custas/JG). - Fechamento formal ("Nestes termos pede deferimento", Local, data, Advogado, OAB). **Critérios Adicionais (Inicial):** * Uso de "XXX" para dados ausentes. Penalizar invenção (-0.5 por ocorrência). * Falta de pedido de juntada de custas/JG: -1.0 ponto. * Erro em "Nestes termos, pede deferimento" (com vírgula): -0.2 ponto. * 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. """ elif tipo_peticao == "Contestação": prompt_especifico += """ - Endereçamento ao juízo correto onde tramita a ação. - Qualificação completa do réu (se já não constante nos autos, referenciar). - Indicação do processo (número). - Tempestividade (menção implícita ou explícita de que está no prazo). - **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). - **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! - **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). - Fundamentação jurídica da defesa. - **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). - Indicação das provas que pretende produzir. - Fechamento formal. **Critérios Adicionais (Contestação):** * A não alegação de preliminar arguível (exceto incompetência absoluta, conexão, convenção de arbitragem) preclui? Avaliar impacto. * Falha grave na impugnação específica: -2.0 a -4.0 pontos. * Ausência de pedido de improcedência: -1.5 pontos. """ elif tipo_peticao == "Reconvenção": prompt_especifico += """ - Apresentada NA MESMA PEÇA da Contestação (Art. 343, CPC). - Endereçamento ao mesmo juízo. - Indicação do processo. - Qualificação completa do réu-reconvinte e do autor-reconvindo (podem ser referenciadas se já completas). - **Conexão com a Ação Principal ou Fundamento de Defesa:** O pedido da reconvenção deve ser conexo (Art. 343, caput). - **Estrutura Similar à Petição Inicial:** Fatos, Fundamentação, Pedidos específicos, Valor da causa, Provas. - Pedido de intimação do autor-reconvindo para contestar a reconvenção (na pessoa de seu advogado - Art. 343, §1º). - Fechamento formal (pode ser conjunto com a contestação). **Critérios Adicionais (Reconvenção):** * 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. """ elif tipo_peticao == "Impugnação à Contestação": prompt_especifico += """ - Endereçamento ao juízo correto. Indicação do processo. Qualificação do autor (impugnante). Tempestividade (15 dias - Art. 350/351 CPC). - **Resposta às Preliminares:** Rebater cada preliminar arguida. - **Resposta a Fatos Impeditivos/Modificativos/Extintivos:** Manifestar-se sobre fatos novos (Art. 350). - **Resposta a Documentos Novos:** Manifestar-se sobre documentos juntados (Art. 437 CPC). - **Ratificação da Inicial:** Reiterar os termos e pedidos da inicial. - **Especificação de Provas:** Indicar provas para contrapor defesa indireta/preliminares (Art. 350/351). - Pedidos: Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial. Fechamento formal. **Critérios Adicionais (Impugnação):** * 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. """ elif tipo_peticao == "Embargos de Declaração": prompt_especifico += """ - **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). - **Cabimento (Art. 1022, CPC):** Apontar **PRECISAMENTE** e **FUNDAMENTADAMENTE** a ocorrência de: Omissão, Contradição, Obscuridade, Erro Material. - **NÃO PODE REJULGAR A CAUSA:** Objetivo é sanar vício, não modificar o mérito (salvo efeitos infringentes excepcionais). - **Pedido:** Requerer o conhecimento e ACOLHIMENTO para sanar o vício (com ou sem efeitos infringentes, se for o caso). Fechamento formal. **Critérios Adicionais (Embargos):** * 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. """ elif tipo_peticao == "Recurso de Apelação": prompt_especifico += """ - **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. - **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. - **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão da sucumbência. Fechamento formal. **Critérios Adicionais (Apelação):** * 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. """ elif tipo_peticao == "Agravo de Instrumento": prompt_especifico += """ - **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). - **Cabimento (Art. 1015 ou taxatividade mitigada):** Demonstrar hipótese legal ou urgência (Tema 988 STJ). Tempestividade (15 dias). Preparo/JG. - **Peças Obrigatórias (Art. 1017, I):** Decisão agravada, certidão de intimação, procurações. Peças Facultativas (Art. 1017, III). - **Razões (Art. 1016, II e III):** Exposição do fato e direito; razões para reforma/invalidação da decisão interlocutória. - **Pedido de Efeito Suspensivo ou Tutela Antecipada Recursal (Art. 1019, I):** Se houver risco, pedir fundamentadamente. - **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. **Critérios Adicionais (Agravo):** * 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. """ elif tipo_peticao == "Petição de Tutela Provisória": prompt_especifico += """ - **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). - **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). - **Tutela de Evidência (Art. 311):** Demonstrar uma das hipóteses (I a IV). **NÃO precisa periculum in mora.** - **Pedido Específico:** Indicar CLARAMENTE a medida liminar solicitada. - **Se Antecedente (Art. 303 ou 305):** Indicar pedido final ou aditar/apresentar inicial no prazo. Valor da Causa. - **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. **Critérios Adicionais (Tutela):** * 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. """ else: prompt_especifico += """ - **Adequação ao Tipo:** A peça corresponde à estrutura e finalidade do tipo? - **Clareza:** É possível entender a pretensão? - **Fundamentação Mínima:** Tentativa de basear em lei ou fatos? - **Pedido:** Existe um pedido? """ 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." return prompt_especifico def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao): prompt = f""" **Caso/Contexto Fornecido:** ``` {caso} ``` **Peça Original do Aluno ({tipo_peticao}):** ``` {peticao_original} ``` **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. **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):** """ if tipo_peticao == "Petição Inicial": prompt += """ 1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável). 2. **Qualificação:** Completa (Art. 319, II), usando "XXX" para dados ausentes. 3. **Fatos:** Narre de forma cronológica, clara e fiel ao caso. 4. **Fundamentação:** Cite artigos pertinentes (CC, CDC, CPC) e súmulas/jurisprudência, se aplicável. 5. **Pedidos:** Claros, certos, determinados (quantificar danos se possível). Cumulados se cabível. 6. **Valor da Causa:** Correto (soma dos benefícios - Art. 292). 7. **Provas:** Indicar ("protesta provar o alegado por todos os meios..."). 8. **Audiência:** Indicar opção (Art. 319, VII). 9. **Citação:** Pedido de citação (ex: "via postal, Art. 246, I, CPC"). 10. **Custas:** Requerimento expresso de juntada da guia OU pedido de gratuidade (com pedido de declaração). **NUNCA OMITIR.** 11. **Fechamento:** "Nestes termos pede deferimento." (SEM vírgula). Seguido de "Local, data.", "ADVOGADO(A)", "OAB/...". **NUNCA identificar.** """ elif tipo_peticao == "Contestação": prompt += """ 1. **Endereçamento:** Ao juízo da ação. 2. **Identificação:** Número do processo, qualificação do réu. 3. **Tempestividade:** Mencionar ("A presente contestação é tempestiva..."). 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). 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. 6. **Provas:** Indicar as provas do réu. 7. **Fechamento:** Padrão OAB. """ elif tipo_peticao == "Reconvenção": prompt += """ **IMPORTANTE:** Gere a Reconvenção como parte da Contestação. 1. **Após a defesa de mérito da Contestação e ANTES dos pedidos da Contestação**, insira: "DA RECONVENÇÃO". 2. **Qualificação:** Réu-Reconvinte e Autor-Reconvindo. 3. **Conexão:** Justifique brevemente. 4. **Fatos e Direito Reconvencionais:** Narre e fundamente o pedido do reconvinte. 5. **Pedidos da Reconvenção:** Claros e específicos contra o autor-reconvindo. 6. **Valor da Causa e Provas da Reconvenção:** Específicos. 7. **Intimação:** Peça a intimação do autor-reconvindo para contestar. 8. **Continue com os Pedidos da Contestação**. 9. **Fechamento:** Padrão OAB. """ elif tipo_peticao == "Impugnação à Contestação": prompt += """ 1. Endereçamento, processo, qualificação autor, tempestividade. 2. Tópico: **DA(S) PRELIMINAR(ES) ARGUIDA(S)** - Rebater cada uma. 3. Tópico: **DO MÉRITO E DOS FATOS NOVOS** - Manifestar-se sobre fatos impeditivos/modificativos/extintivos e documentos. 4. Tópico: **DA RATIFICAÇÃO DA INICIAL** - Reiterar termos e pedidos. 5. Tópico: **DAS PROVAS** - Especificar provas contrapostas. 6. **Pedidos:** Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial. 7. Fechamento padrão OAB. """ elif tipo_peticao == "Embargos de Declaração": prompt += """ 1. Endereçamento ao juízo prolator. Processo, Embargante, Embargado. Decisão embargada identificada. Tempestividade (5 dias). 2. Tópico para cada vício (Art. 1022): **DA OMISSÃO / DA CONTRADIÇÃO / DA OBSCURIDADE / DO ERRO MATERIAL**. 3. Dentro de cada tópico: **Transcrever trecho relevante da decisão** e explicar **objetivamente** o vício. NÃO REJULGAR. 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. 5. Fechamento padrão OAB. """ elif tipo_peticao == "Recurso de Apelação": prompt += """ 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. 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). 3. **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão sucumbência. 4. Fechamento padrão OAB. """ elif tipo_peticao == "Agravo de Instrumento": prompt += """ 1. Peça única ao Tribunal. Processo origem, Agravante, Agravado, Advogados (com endereços). Decisão agravada identificada (juntar cópia). 2. **DO CABIMENTO:** Demonstrar hipótese Art. 1015 ou mitigação. **DA TEMPESTIVIDADE.** **DO PREPARO/JG.** **DAS PEÇAS OBRIGATÓRIAS E FACULTATIVAS.** 3. **DAS RAZÕES RECURSAIS:** Exposição fato/direito, motivos para reforma/invalidação da interlocutória. 4. **DO PEDIDO DE EFEITO SUSPENSIVO / TUTELA ANTECIPADA RECURSAL** (se cabível e fundamentado). 5. **Pedidos:** Conhecimento; (se aplicável) efeito suspensivo/tutela; PROVIMENTO para reformar/invalidar; comunicação ao juiz. 6. Fechamento padrão OAB. """ elif tipo_peticao == "Petição de Tutela Provisória": prompt += """ 1. Indicar TIPO (Urgência/Evidência) e CARÁTER (Antecedente/Incidental). Endereçamento. Qualificação. Processo (se incidental). 2. **DOS REQUISITOS:** * **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**. * **Evidência:** Tópico **DA HIPÓTESE DE EVIDÊNCIA (ART. 311, INCISO __)** - Demonstrar o enquadramento. 3. **DO PEDIDO LIMINAR:** Descrever CLARAMENTE a medida solicitada. 4. **Se Antecedente:** Indicar pedido final ou aditamento/inicial completa. Valor da Causa. 5. **Pedidos:** Concessão da tutela; citação/intimação; (se antecedente) indicação ação principal/aditamento. 6. Fechamento padrão OAB. """ else: prompt += """ 1. Estrutura Lógica: Começo, meio, fim. 2. Argumentos Claros. 3. Pedido Claro. 4. Formalidades: Linguagem adequada, fechamento OAB. """ 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." return prompt def pos_corrigir_peticao(peticao_corrigida, tipo_peticao): if "Nestes termos, pede deferimento" in peticao_corrigida: peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento") if tipo_peticao == "Petição Inicial": peticao_lower = peticao_corrigida.lower() if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower: partes = peticao_corrigida.split("Nestes termos pede deferimento") if len(partes) == 2: peticao_corrigida = partes[0].strip() + "\nRequer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento" + partes[1] else: peticao_corrigida = peticao_corrigida.replace("Local, data.", "Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.") if "audiência de conciliação" not in peticao_lower and "audiência de mediação" not in peticao_lower: partes = peticao_corrigida.split("Nestes termos pede deferimento") if len(partes) == 2: peticao_corrigida = partes[0].strip() + "\nOpta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\nNestes termos pede deferimento" + partes[1] else: peticao_corrigida = peticao_corrigida.replace("Local, data.", "Opta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\n\nLocal, data.") linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()] fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."] while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]): linhas.pop() linhas.extend(fechamento_padrao) peticao_corrigida = "\n".join(linhas) 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"} logger.info(f"Chamando API Gemini. Temp={CONFIG['temperatura']}, MaxTokens={CONFIG['max_tokens']}") for tentativa in range(CONFIG["max_retry_attempts"]): try: with rate_limiter: logger.debug(f"API Call - Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}") response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"]) logger.debug(f"API Response - Status: {response.status_code}") if response.status_code == 429: retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1))) logger.warning(f"Erro 429 (Too Many Requests). Aguardando {retry_after}s...") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(retry_after); continue else: return "❌ Erro 429: Muitas requisições. Tente novamente mais tarde." if response.status_code != 200: error_msg = "Erro desconhecido na API" try: error_info = response.json() error_msg = error_info.get("error", {}).get("message", error_msg) except json.JSONDecodeError: error_msg = f"Erro {response.status_code}: {response.text[:200]}" # Mostra início do erro não-JSON logger.error(f"API Error (Status {response.status_code}): {error_msg}") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return f"❌ Erro da API ({response.status_code}): {error_msg}" response_data = response.json() if not response_data.get("candidates"): prompt_feedback = response_data.get("promptFeedback", {}) block_reason = prompt_feedback.get("blockReason") logger.error(f"API Response sem 'candidates'. Block Reason: {block_reason}") if block_reason: return f"❌ A solicitação foi bloqueada pela API. Motivo: {block_reason}. Tente reformular." else: return "❌ Resposta vazia ou inválida da API (sem 'candidates')." candidate = response_data["candidates"][0] finish_reason = candidate.get("finishReason") if finish_reason and finish_reason not in ["STOP", "MAX_TOKENS", "FINISH_REASON_UNSPECIFIED"]: logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}") safety_ratings = candidate.get("safetyRatings", []) harm_categories = [r['category'] for r in safety_ratings if r['probability'] not in ['NEGLIGIBLE', 'LOW']] if harm_categories: return f"❌ Resposta bloqueada por segurança. Motivo: {finish_reason} ({', '.join(harm_categories)})." else: return f"❌ Geração interrompida. Motivo: {finish_reason}." if not candidate.get("content") or not candidate["content"].get("parts"): logger.error("Estrutura de resposta inesperada: Faltando content/parts.") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return "❌ Resposta inválida da API (estrutura inesperada)." texto_gerado = candidate["content"]["parts"][0]["text"].strip() logger.info(f"API retornou com sucesso ({len(texto_gerado)} chars). Finish Reason: {finish_reason}") return texto_gerado except requests.Timeout: logger.warning(f"Timeout na API - Tentativa {tentativa + 1}.") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return "⏱️ Tempo esgotado na comunicação com a API." except requests.RequestException as e: logger.error(f"Erro de Rede/Requisição API - Tentativa {tentativa + 1}: {e}") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return f"❌ Erro de conexão com a API: {e}" except json.JSONDecodeError as e: logger.error(f"Erro ao decodificar JSON da API: {e}") logger.debug(f"Resposta não-JSON: {response.text[:500]}") return f"❌ Erro ao processar a resposta da API (JSON inválido)." except Exception as e: logger.exception(f"Erro inesperado na chamada da API - Tentativa {tentativa + 1}: {str(e)}") if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue return f"❌ Erro inesperado no processamento da API: {str(e)}" logger.error("Falha ao chamar a API após todas as tentativas.") return "❌ Falha ao comunicar com a API após múltiplas tentativas." def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): logger.info(f"Iniciando correção. Tipo={tipo_peticao}, Treinamento={modo_treinamento}") start_time = time.time() analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html = "", "", "", "", "" peticao_conteudo = "" try: if not caso or not caso.strip(): logger.warning("Caso não fornecido.") return "⚠️ Forneça o caso/contexto da peça.", "", "", "", "" if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: logger.warning(f"Tipo de petição inválido ou não fornecido: {tipo_peticao}") return "⚠️ Selecione um tipo de petição válido.", "", "", "", "" if peticao_arquivo is not None: logger.info("Processando arquivo de petição...") peticao_conteudo = process_file(peticao_arquivo) if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"): logger.error(f"Erro ao processar arquivo: {peticao_conteudo}") return peticao_conteudo, "", "", "", "" elif peticao_texto and peticao_texto.strip(): peticao_conteudo = peticao_texto else: logger.warning("Nenhuma petição fornecida (texto ou arquivo).") return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", "" logger.info(f"Petição recebida ({len(peticao_conteudo)} chars).") 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"] if any(trigger in peticao_conteudo.lower() for trigger in troll_triggers): logger.info("Detectado pedido para escrever petição.") return random.choice([ "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?", "🚫 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!", ]), "", "", "", "" cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}" if cache_key in CORRECAO_CACHE: logger.info("Correção encontrada no cache.") cached_data = CORRECAO_CACHE[cache_key] return cached_data["analise_formatada"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"], cached_data["diff_html"] logger.info("Estruturando prompt de análise...") prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento) logger.info("Chamando API para análise...") analise_bruta = chamar_api(prompt_analise) if analise_bruta.startswith(("❌", "⏱️", "⚠️")): logger.error(f"Erro retornado pela API (Análise): {analise_bruta}") return analise_bruta, "", "", "", "" logger.info("Análise recebida. Formatando...") analise_formatada = formatar_correcao(analise_bruta) logger.info("Estruturando prompt para peça corrigida...") prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao) logger.info("Chamando API para gerar peça corrigida...") peticao_corrigida_bruta = chamar_api(prompt_peticao) if peticao_corrigida_bruta.startswith(("❌", "⏱️", "⚠️")): logger.error(f"Erro retornado pela API (Correção): {peticao_corrigida_bruta}") peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}" else: logger.info("Peça corrigida recebida. Aplicando pós-correção...") start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"] start_index = -1 peticao_lower_corr = peticao_corrigida_bruta.lower() for marker in start_markers: idx = peticao_lower_corr.find(marker) if idx != -1: start_index = idx; break peticao_extraida = peticao_corrigida_bruta[start_index:] if start_index != -1 else peticao_corrigida_bruta peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao) logger.info("Pós-correção aplicada.") logger.info("Calculando diferenças...") diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final) logger.info("Diferenças calculadas.") resultado_cache = {"analise_formatada": analise_formatada,"peticao_corrigida": peticao_corrigida_final,"resumo_diff": resumo_diff,"criticas_diff": criticas_diff,"diff_html": diff_html} CORRECAO_CACHE[cache_key] = resultado_cache HISTORICO.append({"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"tipo_peticao": tipo_peticao,"caso": caso,"peticao_original": peticao_conteudo,**resultado_cache}) if len(HISTORICO) > 10: HISTORICO.pop(0) end_time = time.time() logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.") return analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html except Exception as e: logger.exception("Erro GERAL inesperado dentro de corrigir_peticao:") error_message = f"❌ Erro inesperado na função de correção: {str(e)}\nConsulte os logs para detalhes." return error_message, "", "", "", "" def formatar_correcao(texto): texto = texto.replace("### Análise Geral", "## 📝 Análise Geral") texto = texto.replace("### Nota Final", "## ⭐ Nota Final") texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos") texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar") texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos") linhas = texto.split('\n') linhas_formatadas = [] in_list = False for linha in linhas: linha_strip = linha.strip() if linha_strip.startswith(("1.","2.","3.","4.","5.")): linhas_formatadas.append(f"\n**{linha_strip}**") in_list = False elif linha_strip.startswith(("- ","* ")): prefix = " * " if in_list else "* " linhas_formatadas.append(prefix + linha_strip[2:]) in_list = True elif linha_strip.startswith(("a)","b)","c)","d)","e)")): linhas_formatadas.append(f" * {linha_strip}") in_list = True else: linhas_formatadas.append(linha) if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")): in_list = False texto_final = "\n".join(linhas_formatadas) nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})" match = re.search(nota_pattern, texto_final, re.IGNORECASE) if match: nota = match.group(2).replace(",",".") texto_final = re.sub(nota_pattern, f"\\1**{nota}/10**", texto_final, count=1, flags=re.IGNORECASE) return texto_final def fornecer_dica_contextual(texto_peticao, tipo_peticao): if not texto_peticao or not texto_peticao.strip(): return "*Digite algo na peça para receber dicas...*" dicas_relevantes = [] texto_lower = texto_peticao.lower() for chave in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]: if chave in DICAS_OAB_MELHORADO: dicas_relevantes.append(f"💡 **{chave}:** {DICAS_OAB_MELHORADO[chave]}") keywords_por_tipo = { "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor", "provas", "audiência", "citação", "custas"], "Contestação": ["preliminar", "mérito", "impugnação", "improcedência", "honorários", "art. 337", "art. 341"], "Reconvenção": ["reconvinte", "reconvindo", "conexão", "art. 343"], "Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica", "art. 350"], "Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "art. 1022"], "Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "art. 1009"], "Agravo de Instrumento": ["interlocutória", "cabimento", "urgência", "efeito suspensivo", "art. 1015", "art. 1017"], "Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "art. 300", "art. 311"], } if tipo_peticao in keywords_por_tipo: for chave, dica in DICAS_OAB_MELHORADO.items(): chave_lower = chave.lower() if f"({tipo_peticao.split()[0].lower()})" in chave_lower or \ any(kw in chave_lower for kw in keywords_por_tipo[tipo_peticao] if kw in texto_lower): dica_formatada = f"💡 **{chave}:** {dica}" if dica_formatada not in dicas_relevantes: dicas_relevantes.append(dica_formatada) if not dicas_relevantes: return "*Continue escrevendo ou selecione o tipo de peça para dicas mais específicas.*" return "\n\n".join(dicas_relevantes[:4]) def calcular_diff_interno(texto_original, texto_corrigido): if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")): return "","","" orig_lines = texto_original.splitlines() corr_lines = texto_corrigido.splitlines() diff = difflib.ndiff(orig_lines, corr_lines) resultado_diff_html = [] criticas = [] linhas_removidas = 0 linhas_adicionadas = 0 keywords_criticas_removidas = ["invenção", "identificação", "erro crasso", "incorreto", "prazo"] keywords_criticas_adicionadas = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "essencial", "preliminar", "tempestividade", "cabimento"] for linha in diff: texto = linha[2:] texto_escaped = escape(texto) if linha.startswith(' '): resultado_diff_html.append(f"
{texto_escaped}
") elif linha.startswith('- '): linhas_removidas += 1 resultado_diff_html.append(f"
{texto_escaped}
") if texto.strip() and any(kw in texto.lower() for kw in keywords_criticas_removidas): criticas.append(f"🔴 REMOVIDO: {texto_escaped}") elif linha.startswith('+ '): linhas_adicionadas += 1 resultado_diff_html.append(f"
{texto_escaped}
") if texto.strip() and any(kw in texto.lower() for kw in keywords_criticas_adicionadas): criticas.append(f"🟢 ADICIONADO: {texto_escaped}") resumo_estatistico = f"**Resumo:**\n* Removidas: {linhas_removidas}\n* Adicionadas: {linhas_adicionadas}\n* **Saldo:** {linhas_adicionadas - linhas_removidas} linhas" criticas_texto = "### 🚨 Mudanças Críticas / Essenciais:\n" + "\n".join(criticas) if criticas else "*Nenhuma mudança crítica/essencial detectada.*" css = """""" visualizacao_detalhada_html = f"{css}
{''.join(resultado_diff_html)}
" return visualizacao_detalhada_html, resumo_estatistico, criticas_texto def calcular_progresso(): if not HISTORICO: return "📊 Nenhum progresso registrado ainda." notas = [] tipos_corrigidos = set() for entry in HISTORICO: tipos_corrigidos.add(entry["tipo_peticao"]) match = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.IGNORECASE) if match: try: notas.append(float(match.group(1).replace(",", "."))) except ValueError: pass total_corridas = len(HISTORICO) media = sum(notas) / len(notas) if notas else 0 tendencia = "" if len(notas) > 1: if notas[-1] > notas[-2]: tendencia = "⬆️ Melhorando" elif notas[-1] < notas[-2]: tendencia = "⬇️ Piorando" else: tendencia = "↔️ Estável" 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'}" return progresso_texto def mostrar_historico(): if not HISTORICO: return "📜 Histórico vazio." historico_md = "### 📜 Histórico (Últimas 10)\n---\n" for i, entry in enumerate(reversed(HISTORICO)): caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso'] nota_str = "N/A" match = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.IGNORECASE) if match: nota_str = f"{match.group(1).replace(',', '.')}/10" historico_md += f"**{len(HISTORICO)-i}.** {entry['data']} | **Tipo:** {entry['tipo_peticao']} | **Nota:** {nota_str}\n **Caso:** _{caso_resumo}_\n---\n" return historico_md def salvar_progresso(caso, peticao, tipo_peticao): logger.info("Iniciando salvamento de progresso...") try: if not caso or not peticao or not tipo_peticao: logger.warning("Inputs insuficientes para salvar progresso.") return "", "⚠️ Caso, Petição e Tipo são necessários para salvar.", None dados_progresso = {"tipo_peticao": tipo_peticao, "caso": caso, "peticao": peticao, "timestamp": datetime.now().isoformat()} json_content = json.dumps(dados_progresso, ensure_ascii=False, indent=4) timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"progresso_peticao_{timestamp_str}.json" temp_dir = tempfile.gettempdir() temp_file_path = os.path.join(temp_dir, filename) with open(temp_file_path, 'w', encoding='utf-8') as f: f.write(json_content) logger.info(f"Progresso salvo com sucesso em: {temp_file_path}") success_message = f"✅ Progresso salvo! Baixe `{filename}`." return json_content, success_message, temp_file_path except Exception as e: logger.error(f"Erro ao salvar progresso: {str(e)}", exc_info=True) return "", f"❌ Erro ao salvar: {str(e)}", None def carregar_progresso(arquivo_entrada): logger.info("Iniciando carregamento de progresso...") if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo selecionado." try: file_path = arquivo_entrada.name logger.info(f"Carregando progresso do arquivo: {file_path}") with open(file_path, 'r', encoding='utf-8') as f: dados = json.load(f) caso = dados.get("caso") peticao = dados.get("peticao") tipo_peticao = dados.get("tipo_peticao") if caso is None or peticao is None or tipo_peticao is None: logger.warning(f"Arquivo JSON carregado não contém chaves esperadas: {dados.keys()}") return "", "", "", "❌ Erro: Arquivo de progresso inválido." if tipo_peticao not in TIPOS_DE_PETICAO: logger.warning(f"Tipo de petição '{tipo_peticao}' do arquivo não é válido.") return caso, peticao, "", f"⚠️ Tipo '{tipo_peticao}' inválido. Selecione manualmente." logger.info("Progresso carregado com sucesso.") return tipo_peticao, caso, peticao, "✅ Progresso carregado!" except json.JSONDecodeError: logger.error(f"Erro ao decodificar JSON do arquivo: {file_path}") return "", "", "", "❌ Erro: Arquivo não é JSON válido." except Exception as e: logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True) return "", "", "", f"❌ Erro inesperado ao carregar: {str(e)}" def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"): prompt = f""" 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). **Instruções Gerais:** 1. Complexidade Adequada (15-25 linhas). 2. Dados Essenciais: Nomes, locais, datas, valores, relação jurídica, conflito. 3. Coerência: Fatos lógicos justificando a peça. 4. Foco no Tipo de Peça: Cenário deve exigir a **{tipo_peticao}**. **Instruções Específicas para "{tipo_peticao}":** """ if tipo_peticao == "Petição Inicial": 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.""" elif tipo_peticao == "Contestação": 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.""" elif tipo_peticao == "Reconvenção": 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.""" elif tipo_peticao == "Impugnação à Contestação": 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.""" elif tipo_peticao == "Embargos de Declaração": 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)." """ elif tipo_peticao == "Recurso de Apelação": 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).""" elif tipo_peticao == "Agravo de Instrumento": 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).""" elif tipo_peticao == "Petição de Tutela Provisória": 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.""" else: prompt += "* Crie cenário jurídico cível com conflito claro." prompt += """ **Finalize o caso com:** "**Situação-Problema:** Na qualidade de advogado(a) de [Nome do Cliente], elabore a peça processual cabível ({tipo_peticao})." **Importante:** Evite casos muito específicos. Foque em temas comuns. Seja criativo! """ return prompt def gerar_caso_ficticio(tipo_peticao="Petição Inicial"): logger.info(f"Solicitada geração de caso para: {tipo_peticao}") try: if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: logger.warning(f"Tentativa de gerar caso com tipo inválido: {tipo_peticao}") return "⚠️ Erro: Selecione um tipo de petição válido antes de gerar o caso." logger.info("Estruturando prompt para geração de caso...") prompt = estruturar_prompt_geracao_caso(tipo_peticao) logger.info("Chamando API para gerar caso...") caso_gerado = chamar_api(prompt) if caso_gerado.startswith(("❌","⏱️","⚠️")): logger.error(f"Erro da API ao gerar caso: {caso_gerado}") return f"⚠️ Falha ao gerar caso fictício:\n{caso_gerado}" frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de" if frase_final_padrao.lower() not in caso_gerado.lower(): logger.warning("Frase 'Situação-Problema' não encontrada. Adicionando manualmente.") nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado) cliente_nome = nomes_possiveis[0] if nomes_possiveis else "[Cliente]" 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})." logger.info("Caso fictício gerado com sucesso.") return caso_gerado except Exception as e: logger.exception("Erro inesperado dentro de gerar_caso_ficticio:") return f"❌ Erro inesperado ao gerar o caso: {str(e)}" def interface_gradio(): with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo: gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️") gr.Markdown("Analise e aprimore suas peças processuais!") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### ⚙️ Configurações") tipo_peticao_input = gr.Dropdown(label="1. Selecione o Tipo de Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial") modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento", value=False) with gr.Accordion("🛠️ Opções Avançadas da IA", open=False): temperatura_slider = gr.Slider(minimum=0.0, maximum=1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade") timeout_slider = gr.Slider(minimum=30, maximum=180, value=CONFIG["timeout"], step=10, label="Timeout API (seg)") max_tokens_slider = gr.Slider(minimum=2048, maximum=8192, value=CONFIG["max_tokens"], step=256, label="Max Resposta (Tokens)") config_update_btn = gr.Button("💾 Salvar Configs") config_status = gr.Textbox(label="Status", interactive=False, lines=1) with gr.Accordion("📊 Dashboard", open=False): progresso_output = gr.Markdown("*Atualize para ver estatísticas.*") progresso_btn = gr.Button("🔄 Atualizar Progresso") with gr.Accordion("📜 Histórico", open=False): historico_output = gr.Markdown("*Atualize para ver histórico.*") historico_btn = gr.Button("🔄 Ver Histórico") with gr.Accordion("💾📂 Salvar/Carregar", open=False): salvar_btn = gr.Button("💾 Salvar Caso/Peça") carregar_btn = gr.Button("📂 Carregar Caso/Peça") arquivo_progresso_upload = gr.File(label="Selecione .json", file_types=[".json"], scale=2) progresso_download_file = gr.File(label="Baixar Progresso", interactive=False) with gr.Column(scale=3): gr.Markdown("### 1. Forneça o Caso / Contexto") with gr.Row(): caso_input = gr.Textbox(label="📋 Caso ou Situação", lines=8, placeholder="Insira o texto COMPLETO do caso...") caso_btn = gr.Button("🎲 Gerar Caso", variant="secondary", scale=0) gr.Markdown("### 2. Insira Sua Peça") with gr.Tabs(): with gr.TabItem("✏️ Digitar Texto"): peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=12, placeholder="Cole ou digite sua peça aqui...") dica_contextual_output = gr.Markdown(value="*Dicas aparecerão aqui...*") btn_corrigir = gr.Button("🚀 CORRIGIR E ANALISAR!", variant="primary") with gr.TabItem("📎 Anexar Arquivo"): peticao_arquivo_input = gr.File(label="Arquivo (.pdf, .docx, .txt - Máx 15MB)", file_types=[".pdf", ".docx", ".txt"]) arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Arquivo", lines=4, interactive=False) btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary") processing_message = gr.Markdown("", visible=False) gr.Markdown("--- \n ### 3. Resultados da Correção") with gr.Tabs(): with gr.TabItem("📊 Análise e Correção Sugerida"): resultado_analise_output = gr.Markdown(label="Análise Detalhada e Nota") resultado_peticao_corrigida_output = gr.Textbox(label="✨ Versão Sugerida", lines=18, show_copy_button=True) with gr.TabItem("⚖️ Comparativo (Diff)"): gr.Markdown("### ↔️ Comparativo: Sua Peça vs. Versão Sugerida") with gr.Row(): with gr.Column(scale=1): resumo_alteracoes_output = gr.Markdown(label="Resumo") criticas_output = gr.Markdown(label="Mudanças Críticas") with gr.Column(scale=3): visualizacao_detalhada_output = gr.HTML(label="Alterações Detalhadas") clear_btn = gr.Button("🗑️ Limpar Campos e Resultados") caso_btn.click( fn=lambda: gr.update(value="⏳ Gerando caso...", visible=True), outputs=processing_message ).then( fn=gerar_caso_ficticio, inputs=[tipo_peticao_input], outputs=[caso_input] ).then( fn=lambda: gr.update(value="", visible=False), outputs=processing_message ) peticao_texto_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output) tipo_peticao_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output) peticao_arquivo_input.change(fn=process_file, inputs=[peticao_arquivo_input], outputs=[arquivo_preview_output]) btn_corrigir.click( fn=lambda: gr.update(value="⏳ Processando texto... Gerando análise e correção...", visible=True), outputs=processing_message ).then( fn=corrigir_peticao, inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento], outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output], concurrency_limit=1 ).then( fn=lambda: gr.update(value="", visible=False), outputs=processing_message ) btn_corrigir_arquivo.click( fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção...", visible=True), outputs=processing_message ).then( fn=corrigir_peticao, inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento], outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output], concurrency_limit=1 ).then( fn=lambda: gr.update(value="", visible=False), outputs=processing_message ) config_update_btn.click(atualizar_config, inputs=[temperatura_slider, timeout_slider, max_tokens_slider], outputs=config_status) progresso_btn.click(fn=calcular_progresso, outputs=progresso_output) historico_btn.click(fn=mostrar_historico, outputs=historico_output) salvar_btn.click( fn=lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message ).then( fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input], outputs=[gr.Textbox(visible=False), config_status, progresso_download_file] ).then( fn=lambda x: gr.update(visible=bool(x)), inputs=[progresso_download_file], outputs=[progresso_download_file] ).then( lambda: gr.update(value="", visible=False), None, processing_message ) carregar_btn.click( fn=lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message ).then( fn=carregar_progresso, inputs=[arquivo_progresso_upload], outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status] ).then( lambda: gr.update(value="", visible=False), None, processing_message ) clear_btn.click( fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas aparecerão aqui...*", "", "", "", "", "", None, None, ""], outputs=[ tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, peticao_arquivo_input, arquivo_preview_output, dica_contextual_output, resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output, arquivo_progresso_upload, progresso_download_file, config_status ] ) return demo if __name__ == "__main__": logger.info("Iniciando a aplicação Gradio...") app = interface_gradio() app.launch() logger.info("Aplicação Gradio encerrada.")