diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -30,34 +30,73 @@ 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}" +# Use um modelo mais recente se disponível e adequado, como gemini-1.5-flash +# GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={GEMINI_API_KEY}" +# Se gemini-1.5-flash não estiver disponível ou causar problemas, volte para gemini-pro ou o que funcionava +# GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={GEMINI_API_KEY}" +GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}" + CONFIG = { "max_retry_attempts": 3, "retry_delay": 5, - "timeout": 60, - "temperatura": 0.2, - "max_tokens": 12882 + "timeout": 120, # Aumentar timeout para prompts mais complexos + "temperatura": 0.3, # Um pouco mais de criatividade pode ser útil + "max_tokens": 8192 # Ajustar conforme necessidade e limites do modelo } -rate_limiter = RateLimiter(max_calls=60, period=60) - +rate_limiter = RateLimiter(max_calls=30, period=60) # Reduzir um pouco a taxa para evitar 429 + +# --- TIPOS DE PEÇAS PROCESSUAIS --- +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" +] + +# --- DICIONÁRIO DE DICAS (Pode ser expandido por tipo) --- 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." + # Dicas Gerais (aplicáveis a várias peças) + "Clareza e Concisão": "Seja direto e evite jargões excessivos. Facilite a leitura do juiz.", + "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes ao seu argumento.", + "Coerência Argumentativa": "Mantenha uma linha lógica de raciocínio do início ao fim da peça.", + "Revisão Ortográfica e Gramatical": "Erros de português podem prejudicar a credibilidade da sua peça.", + "Formatação": "Utilize parágrafos bem definidos, espaçamento adequado e destaque pontos importantes (negrito, itálico) com moderação.", + + # Dicas Específicas (Exemplos para Petição Inicial) + "Endereçamento (Inicial)": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). Verifique a competência (material, territorial).", + "Qualificação (Inicial)": "Inclua todos os dados do Art. 319, II, CPC. Use 'XXX' para dados não fornecidos.", + "Fatos (Inicial)": "Narrativa cronológica, clara e objetiva, sem juízo de valor.", + "Pedidos (Inicial)": "Certos, determinados e líquidos (sempre que possível). Cumule pedidos se necessário (Art. 327, CPC).", + "Custas (Inicial)": "Sempre peça a juntada da guia ou a gratuidade de justiça (com declaração).", + "Valor da Causa (Inicial)": "Deve corresponder ao benefício econômico pretendido (Art. 292, CPC).", + + # Dicas Específicas (Exemplos para Contestação) + "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes de discutir o mérito.", + "Impugnação Específica (Contestação)": "Conteste cada fato alegado pelo autor, sob pena de presunção de veracidade (Art. 341, CPC).", + "Mérito (Contestação)": "Apresente sua versão dos fatos e a fundamentação jurídica que ampara a improcedência dos pedidos do autor.", + "Pedidos (Contestação)": "Requeira o acolhimento das preliminares e/ou a improcedência total dos pedidos do autor, além da condenação em custas e honorários.", + + # Dicas Específicas (Exemplo para Recurso) + "Pressupostos Recursais": "Verifique cabimento, tempestividade, preparo (custas recursais), regularidade formal e interesse/legitimidade.", + "Dialeticidade (Recurso)": "Impugne especificamente os fundamentos da decisão recorrida (Art. 1010, II e III, CPC para Apelação).", + "Delimitação da Matéria (Recurso)": "Indique claramente qual parte da decisão você está recorrendo.", + + # Dicas Específicas (Tutela Provisória) + "Fumus Boni Iuris": "Demonstre a plausibilidade do direito alegado com base em provas e argumentos sólidos.", + "Periculum in Mora": "Evidencie o risco de dano irreparável ou de difícil reparação se a medida não for concedida imediatamente.", + "Reversibilidade": "Se possível, argumente que a medida é reversível caso a decisão final seja diferente (Art. 300, §3º, CPC).", } HISTORICO = [] CORRECAO_CACHE = {} +# --- Funções de Extração de Texto (sem alterações significativas) --- def preprocess_image_for_ocr(img): try: gray = img.convert('L') @@ -66,744 +105,1475 @@ def preprocess_image_for_ocr(img): return Image.fromarray(thresh) except Exception as e: logger.error(f"Erro no pré-processamento da imagem: {str(e)}") - return f"❌ Erro no pré-processamento: {str(e)}" + return f"❌ Erro no pré-processamento: {str(e)}" # Retorna o erro def extract_text_from_pdf(file_path_or_obj): + text = "" + ocr_text = "" 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" + pdf_reader = pdfplumber_open(file_path_or_obj) + for i, page in enumerate(pdf_reader.pages): + page_text = page.extract_text() + if page_text: + text += page_text + "\n" + else: # Tenta OCR se texto extraído for pobre/vazio + logger.warning(f"Página {i+1} sem texto extraível, tentando OCR.") + try: + img = page.to_image(resolution=300) # Aumenta resolução para OCR + preprocessed_img = preprocess_image_for_ocr(img.original) + page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por') # Especifica português + if page_ocr_text.strip(): + ocr_text += page_ocr_text + "\n" + else: + logger.warning(f"OCR falhou na página {i+1}.") + ocr_text += f"[OCR falhou na página {i+1}]\n" + except Exception as ocr_e: + logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}") + ocr_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n" + pdf_reader.close() + + # Decide qual texto retornar + if text.strip() and len(text) > len(ocr_text) * 0.5: # Se texto extraído parece razoável + logger.info("Usando texto extraído do PDF.") + return text + elif ocr_text.strip(): + logger.info("Usando texto do OCR do PDF.") + return ocr_text 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." + logger.warning("Não foi possível extrair texto significativo do PDF.") + return "⚠️ Não foi possível extrair texto do PDF (tentativa de OCR falhou ou texto inexistente)." + except Exception as e: - logger.error(f"Erro ao processar o PDF: {str(e)}") - return f"❌ Erro ao processar o PDF: {str(e)}" + logger.error(f"Erro geral ao processar o PDF: {str(e)}") + # Tenta reabrir e usar PyPDF2 como fallback básico se pdfplumber falhar + try: + logger.info("Tentando fallback com PyPDF2.") + text_fallback = "" + if isinstance(file_path_or_obj, str): + with open(file_path_or_obj, 'rb') as f: + reader = PyPDF2.PdfReader(f) + for page in reader.pages: + text_fallback += page.extract_text() or "" + else: + # Precisa resetar o ponteiro se for objeto de arquivo + file_path_or_obj.seek(0) + reader = PyPDF2.PdfReader(file_path_or_obj) + for page in reader.pages: + text_fallback += page.extract_text() or "" + + if text_fallback.strip(): + logger.info("Texto extraído com fallback PyPDF2.") + return text_fallback + else: + return f"❌ Erro ao processar o PDF e fallback falhou: {str(e)}" + except Exception as fallback_e: + logger.error(f"Erro no fallback com PyPDF2: {str(fallback_e)}") + return f"❌ Erro crítico 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." + doc = docx.Document(file_path_or_obj) # Funciona com path ou file object + text = "" + for para in doc.paragraphs: + text += para.text + "\n" + # Opcional: Extrair texto de tabelas + # for table in doc.tables: + # for row in table.rows: + # for cell in row.cells: + # text += cell.text + "\t" # Separar células com tab + # text += "\n" + 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)}") return f"❌ Erro ao processar o DOCX: {str(e)}" def extract_text_from_txt(file_path_or_obj): try: + # Se for um path, abre o arquivo 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: + # Se for um objeto de arquivo (como do Gradio), lê diretamente + elif hasattr(file_path_or_obj, 'read'): 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." + # Tenta resetar o ponteiro se possível, para reutilização + if hasattr(file_path_or_obj, 'seek'): + try: file_path_or_obj.seek(0) + except: pass + else: + return "❌ Tipo de entrada TXT inválido." + + # Tenta decodificar com encodings comuns + encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1'] + 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)}") return f"❌ Erro ao processar o TXT: {str(e)}" def process_file(file_obj): if file_obj is None: - return "Nenhum arquivo enviado." + logger.warning("Nenhum arquivo fornecido para processamento.") + 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." + # Gradio > 3.0 usa tempfile._TemporaryFileWrapper que tem 'name' + # Gradio < 3.0 pode usar outros tipos, vamos checar a extensão pelo nome se disponível + file_path = getattr(file_obj, 'name', None) + if not file_path: + logger.error("Não foi possível obter o nome do arquivo do objeto.") + return "❌ Erro: Não foi possível identificar o tipo do arquivo." + + filename = os.path.basename(file_path).lower() + logger.info(f"Processando arquivo: {filename}") + + # Verifica tamanho (usando o arquivo temporário diretamente se possível) + try: + file_size = os.path.getsize(file_path) + logger.info(f"Tamanho do arquivo: {file_size} bytes") + if file_size > 15 * 1024 * 1024: # Aumentado para 15MB + logger.warning(f"Arquivo {filename} excede o limite de tamanho.") + return "⚠️ O arquivo é muito grande. Máx. 15 MB." + except OSError as size_e: + logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}") + # Não impede o processamento, mas loga o aviso + + # Determina o tipo e extrai o texto + if filename.endswith('.pdf'): + # Passa o path do arquivo temporário + return extract_text_from_pdf(file_path) + elif filename.endswith('.docx'): + # Passa o path do arquivo temporário + return extract_text_from_docx(file_path) + elif filename.endswith('.txt'): + # Passa o path do arquivo temporário + return extract_text_from_txt(file_path) else: - return "❌ Erro: Objeto de arquivo inválido." + 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 ao processar o arquivo: {str(e)}") - return f"❌ Erro ao processar o arquivo: {str(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)}" +# --- ESTRUTURAÇÃO DE PROMPTS POR TIPO --- -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} +def get_instrucoes_gerais_avaliacao(modo_treinamento=False): + base = """ **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 + 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_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) + 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_prompt + return base -def estruturar_prompt_peticao_corrigida(caso, peticao): - return f""" - **Caso Fornecido:** +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} - **Petição do Aluno:** + ``` + + **Peça do Aluno ({tipo_peticao}):** + ``` {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" + ``` + + {instrucoes_gerais} + + **Requisitos Específicos para {tipo_peticao}:** + """ + + # Adiciona requisitos específicos por tipo + 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 que fundamentam a reconvenção. + - Fundamentação jurídica do pedido reconvencional. + - Pedidos específicos da reconvenção (condenação do autor-reconvindo). + - Valor da causa para a reconvenção. + - Indicação de provas para a reconvenção. + - 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 para a reconvenção: -1.0 por item. + * Apresentada em peça separada da contestação (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 (prazo de 15 dias - Art. 350/351 CPC). + - **Resposta às Preliminares:** Rebater cada preliminar arguida na contestação. + - **Resposta a Fatos Impeditivos/Modificativos/Extintivos:** Se o réu alegou fatos novos (Art. 350), o autor DEVE se manifestar sobre eles. + - **Resposta a Documentos Novos:** Manifestar-se sobre documentos juntados com a contestação (Art. 437 CPC). + - **Ratificação da Inicial:** Reiterar os termos e pedidos da petição inicial. + - **Especificação de Provas:** Se o réu apresentou defesa de mérito indireta ou preliminares que exigem prova, o autor deve especificar as provas que pretende produzir para contrapor (Art. 350/351). + - Pedidos: Rejeição das preliminares, refutação da defesa de mérito, procedência dos pedidos da inicial. + - Fechamento formal. + **Critérios Adicionais (Impugnação):** + * Não responder a uma preliminar relevante: -1.5 pontos. + * Não se manifestar sobre fato novo impeditivo/modificativo/extintivo alegado: -2.0 pontos. + * Não ratificar a inicial: -0.5 pontos. + """ + elif tipo_peticao == "Embargos de Declaração": + prompt_especifico += """ + - **Contra Decisão Judicial Específica:** Indicar claramente qual decisão está sendo embargada (sentença, acórdão, decisão interlocutória). + - Endereçamento ao PRÓPRIO juízo que proferiu a decisão. + - Indicação do processo. + - Qualificação das partes (embargante e embargado). + - Tempestividade (prazo de 5 dias - Art. 1023 CPC). + - **Cabimento (Art. 1022, CPC):** Apontar **PRECISAMENTE** e **FUNDAMENTADAMENTE** a ocorrência de: + - **Omissão:** Ponto ou questão sobre o qual o juiz deveria ter se pronunciado e não o fez. + - **Contradição:** Afirmações conflitantes DENTRO da própria decisão. + - **Obscuridade:** Falta de clareza que impede a compreensão da decisão. + - **Erro Material:** Engano manifesto e corrigível de ofício (nome, cálculo, etc.). + - **NÃO PODE REJULGAR A CAUSA:** O objetivo é sanar vício, não modificar o mérito (salvo efeitos infringentes excepcionais). + - **Pedido:** Requerer o conhecimento dos embargos e seu ACOLHIMENTO para sanar o vício apontado (com ou sem efeitos infringentes, se for o caso). + - Fechamento formal. + **Critérios Adicionais (Embargos):** + * Usar Embargos para rediscutir o mérito (erro grave): -4.0 pontos. + * Não apontar claramente o vício (omissão/contradição/obscuridade/erro material): -3.0 pontos. + * Endereçar ao tribunal em vez do juízo prolator (erro): -1.0 ponto. + """ + elif tipo_peticao == "Recurso de Apelação": + prompt_especifico += """ + - **Peça de Interposição:** + - Endereçamento ao JUIZ que proferiu a sentença (juízo a quo). + - Indicação do processo. + - Qualificação das partes (apelante e apelado). + - Demonstração dos pressupostos de admissibilidade (cabimento - contra sentença Art. 1009; tempestividade - 15 dias Art. 1003, §5º; preparo - juntada das guias ou pedido de JG; regularidade formal). + - Pedido de recebimento do recurso, processamento e remessa ao Tribunal (juízo ad quem). + - **Razões Recursais:** + - Endereçamento ao TRIBUNAL competente (Ex: Egrégio Tribunal de Justiça). + - Breve exposição dos fatos e do ocorrido no processo (resumo da lide e da sentença). + - **Fundamentos de Fato e de Direito para Reforma/Anulação (Art. 1010, II e III):** + - **Error in procedendo:** Erro na condução do processo (ex: cerceamento de defesa). Leva à anulação. + - **Error in judicando:** Erro no julgamento do mérito (má aplicação da lei, má valoração da prova). Leva à reforma. + - **Princípio da Dialeticidade:** Impugnar ESPECIFICAMENTE os fundamentos da sentença recorrida. Não pode ser cópia da inicial/contestação. + - **Pedidos:** Requerer o conhecimento do recurso; o PROVIMENTO para ANULAR ou REFORMAR a sentença (especificar o que se quer: procedência, improcedência, etc.); inversão do ônus da sucumbência. + - Fechamento formal (Local, data, Advogado, OAB). + **Critérios Adicionais (Apelação):** + * Falta da peça de interposição ou das razões: Nota zero. + * Endereçamento incorreto das razões (para o juiz a quo): -1.0 ponto. + * Ausência de impugnação específica aos fundamentos da sentença (violação dialeticidade): -3.0 a -5.0 pontos. + * Não pedir anulação/reforma de forma clara: -1.5 pontos. + * Falta de comprovação do preparo ou pedido de JG: Recurso deserto (na prática). Avaliar como erro grave (-2.0). + """ + elif tipo_peticao == "Agravo de Instrumento": + prompt_especifico += """ + - **Peça Única (Interposição e Razões):** + - Endereçamento diretamente ao TRIBUNAL competente (Presidente ou Relator sorteado). + - Indicação do processo de origem (número, vara, comarca). + - Qualificação completa das partes (agravante e agravado) e seus advogados (com endereços - Art. 1016, IV). + - Indicação da decisão agravada (cópia integral OBRIGATÓRIA - Art. 1017, I). + - **Cabimento (Art. 1015, CPC ou teses de taxatividade mitigada):** Demonstrar que a decisão interlocutória se encaixa numa das hipóteses legais ou na urgência decorrente da inutilidade futura do julgamento do mérito (Tema 988 STJ). + - **Tempestividade:** Prazo de 15 dias (Art. 1003, §5º). + - **Preparo:** Juntada das guias ou pedido de JG. + - **Peças Obrigatórias (Art. 1017, I):** Cópia da decisão agravada, certidão de intimação (ou outro doc. que comprove tempestividade), procurações das partes. + - **Peças Facultativas (Art. 1017, III):** Outras peças úteis para compreensão da controvérsia. + - **Razões do Pedido de Reforma/Invalidação (Art. 1016, II e III):** Exposição do fato e do direito; razões pelas quais a decisão deve ser alterada. Foco no erro da decisão interlocutória. + - **Pedido de Efeito Suspensivo ou Tutela Antecipada Recursal (Art. 1019, I):** Se houver risco de dano grave, pedir fundamentadamente. + - **Pedidos Finais:** Requerer o conhecimento do recurso; (se aplicável) a concessão de efeito suspensivo/tutela antecipada; o PROVIMENTO do agravo para reformar ou invalidar a decisão agravada; a comunicação ao juiz da causa (Art. 1018 - opcional se processo eletrônico). + - Fechamento formal. + **Critérios Adicionais (Agravo):** + * Ausência de peça obrigatória (decisão, intimação, procurações): Inadmissibilidade (na prática). Erro gravíssimo (-4.0). + * Não demonstrar o cabimento (Art. 1015 ou mitigação): -3.0 pontos. + * Endereçar ao juiz a quo: -1.0 ponto. + * Não indicar o nome e endereço dos advogados: Falha formal (-0.5). + """ + elif tipo_peticao == "Petição de Tutela Provisória": + prompt_especifico += """ + - **Tipo (Urgência ou Evidência):** A petição deve deixar claro qual modalidade se busca. Pode ser requerida em caráter antecedente ou incidental. + - Endereçamento ao juízo competente (se antecedente) ou ao juízo da causa (se incidental). + - Qualificação completa das partes. + - Indicação do processo (se incidental). + - **Tutela de Urgência (Art. 300 CPC):** + - **Fumus Boni Iuris:** Demonstrar a **probabilidade do direito** alegado. Argumentação e provas pré-constituídas. + - **Periculum in Mora:** Demonstrar o **perigo de dano** ou o risco ao resultado útil do processo se tiver que esperar a decisão final. Urgência concreta. + - **Reversibilidade (Art. 300, §3º):** Argumentar, se possível, que a medida pode ser revertida caso a decisão final seja improcedente. + - **Tutela de Evidência (Art. 311 CPC):** + - Demonstrar uma das hipóteses: I - abuso do direito de defesa/propósito protelatório; II - alegações comprovadas apenas documentalmente + tese firmada em julgamento repetitivo ou súmula vinculante; III - pedido reipersecutório com prova documental adequada do contrato de depósito; IV - inicial instruída com prova documental suficiente + réu não opõe prova capaz de gerar dúvida razoável. + - **NÃO precisa demonstrar periculum in mora.** + - **Pedido Específico:** Indicar CLARAMENTE qual medida liminar está sendo solicitada (obrigação de fazer/não fazer, bloqueio de bens, etc.). + - **Se Antecedente (Art. 303 ou 305):** Indicar o pedido final a ser formulado ou apresentar a petição inicial completa no prazo legal. + - **Valor da Causa:** Se antecedente, estimar. Se incidental, geralmente já existe. + - **Pedidos:** Requerer a concessão da tutela provisória (liminarmente ou após justificação prévia); citação/intimação do réu; (se antecedente) indicação da futura ação principal ou aditamento. + - Fechamento formal. + **Critérios Adicionais (Tutela):** + * Não demonstrar claramente o Fumus Boni Iuris ou o Periculum in Mora (para Urgência): -3.0 a -5.0 pontos. + * Não enquadrar corretamente numa das hipóteses da Tutela de Evidência: -3.0 pontos. + * Pedido genérico ou pouco claro sobre a medida desejada: -2.0 pontos. + * Confundir os requisitos de Urgência e Evidência: -1.5 pontos. + """ + else: + prompt_especifico += """ + - **Adequação ao Tipo:** A peça corresponde minimamente à estrutura e finalidade do tipo selecionado? + - **Clareza:** É possível entender o que o aluno pretende com a peça? + - **Fundamentação Mínima:** Há alguma tentativa de basear os argumentos em lei ou fatos? + - **Pedido:** Existe um pedido, mesmo que mal formulado? + """ + + 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, especialmente nos 'Pontos a Melhorar'. Gere a resposta **exclusivamente** no formato solicitado (Análise Geral, Nota, Pontos Positivos, etc.)." + return prompt_especifico + +def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao): + # Prompt base para gerar a versão corrigida + prompt = f""" + **Caso/Contexto Fornecido:** + ``` + {caso} + ``` + + **Peça Original do Aluno ({tipo_peticao}):** + ``` + {peticao_original} + ``` + + **Tarefa:** Com base no caso e nos problemas identificados na peça original (implícitos na sua estrutura/conteúdo), **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Siga **rigorosamente** as melhores práticas e os requisitos formais/materiais para este tipo de peça no exame da OAB. + + **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):** """ -def pos_corrigir_peticao(peticao_corrigida): + # Adiciona instruções específicas de CADA TIPO para a GERAÇÃO da peça corrigida + if tipo_peticao == "Petição Inicial": + prompt += """ + 1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável e justificado). + 2. **Qualificação:** Completa (Art. 319, II), usando "XXX" para dados ausentes no caso. + 3. **Fatos:** Narre de forma cronológica, clara e fiel ao caso, sem invenções. + 4. **Fundamentação:** Cite artigos de lei pertinentes (ex: CC, CDC, CPC) e, se aplicável, súmulas/jurisprudência relevante. + 5. **Pedidos:** Claros, certos, determinados (quantificar danos se possível com base no caso). Incluir pedidos cumulados se cabível (principal, danos morais, materiais, etc.). + 6. **Valor da Causa:** Correto (soma dos benefícios econômicos - Art. 292). + 7. **Provas:** Indicar as provas de forma genérica mas adequada (ex: "protesta provar o alegado por todos os meios de prova em direito admitidos, em especial documental, testemunhal e pericial"). + 8. **Audiência:** Indicar opção pela audiência (Art. 319, VII). + 9. **Citação:** Pedido de citação do réu (ex: "via postal, nos termos do Art. 246, I, CPC"). + 10. **Custas:** Requerimento expresso de juntada da guia paga OU pedido de gratuidade de justiça (se o caso sugerir hipossuficiência, com pedido de juntada de declaração). **NUNCA OMITIR.** + 11. **Fechamento:** "Nestes termos pede deferimento." (SEM vírgula). Seguido de "Local, data.", "ADVOGADO(A)", "OAB/...". **NUNCA identificar o advogado.** + """ + elif tipo_peticao == "Contestação": + prompt += """ + 1. **Endereçamento:** Ao juízo onde a ação tramita. + 2. **Identificação:** Número do processo, qualificação do réu. + 3. **Tempestividade:** Pode mencionar ("A presente contestação é tempestiva..."). + 4. **Estrutura:** + * **Síntese da Inicial:** Breve resumo do que o autor pede. + * **Preliminares (Art. 337):** Argua todas as cabíveis, fundamentando cada uma. + * **Mérito:** + * **Impugnação Específica:** Conteste cada fato relevante da inicial. + * **Defesa de Mérito:** Apresente a versão do réu, fatos impeditivos/modificativos/extintivos, e a fundamentação legal para a improcedência. + * **Reconvenção (Se houver no caso):** Incluir como tópico separado ANTES dos pedidos da contestação. + 5. **Pedidos da Contestação:** + * Acolhimento das preliminares (com a consequência - extinção/remessa). + * **IMPROCEDÊNCIA TOTAL** dos pedidos do autor. + * Condenação do autor em custas e honorários. + 6. **Provas:** Indicar as provas que o réu pretende produzir. + 7. **Fechamento:** Padrão OAB (Local, data, Advogado(a), 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 um tópico claro: "DA RECONVENÇÃO". + 2. **Qualificação:** Identifique o Réu como Reconvinte e o Autor como Reconvindo. + 3. **Conexão:** Justifique brevemente a conexão com a ação principal ou defesa. + 4. **Fatos Reconvencionais:** Narre os fatos que baseiam o pedido do reconvinte. + 5. **Direito Reconvencional:** Fundamente juridicamente o pedido do reconvinte. + 6. **Pedidos da Reconvenção:** Formule pedidos claros e específicos contra o autor-reconvindo (ex: condenação a pagar X, fazer Y). + 7. **Valor da Causa da Reconvenção:** Atribua um valor específico para a reconvenção. + 8. **Provas da Reconvenção:** Indique as provas para a reconvenção. + 9. **Intimação:** Peça a intimação do autor-reconvindo para contestar a reconvenção. + 10. **Continue com os Pedidos da Contestação** (improcedência da ação original, etc.). + 11. **Fechamento:** Padrão OAB. + """ + # Adicionar instruções detalhadas para os outros tipos... (Impugnação, ED, Apelação, Agravo, Tutela) + # Exemplo para Embargos de Declaração: + elif tipo_peticao == "Embargos de Declaração": + prompt += """ + 1. **Endereçamento:** Ao juízo que proferiu a decisão embargada. + 2. **Identificação:** Processo, Embargante, Embargado. + 3. **Decisão Embargada:** Indique claramente qual decisão (sentença fls. X, acórdão ID Y). + 4. **Tempestividade:** Mencione o prazo de 5 dias. + 5. **Cabimento (Art. 1022):** + * Crie um tópico para cada vício alegado (Da Omissão, Da Contradição, etc.). + * Dentro de cada tópico, **transcreva o trecho relevante da decisão** e explique **objetivamente** onde está o vício. NÃO REFAÇA O JULGAMENTO. + 6. **Pedido:** Peça o conhecimento e ACOLHIMENTO dos embargos para sanar o(s) vício(s) apontado(s), especificando o que se espera (ex: "para que seja suprida a omissão quanto a...", "para que seja corrigida a contradição entre..."). Se cabível e fundamentado, pedir efeitos infringentes. + 7. **Fechamento:** Padrão OAB. + """ + # (Continuar para os outros tipos: Apelação, Agravo, Tutela, Impugnação) + # ... (Omitido por brevidade, mas seguir a mesma lógica dos prompts de análise) + + else: # Fallback genérico + prompt += """ + 1. **Estrutura Lógica:** Organize a peça com começo, meio e fim. + 2. **Argumentos:** Apresente os argumentos de forma clara. + 3. **Pedidos:** Formule um pedido claro ao final. + 4. **Formalidades:** Use linguagem adequada e o fechamento padrão 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): + # Aplica correções finais comuns ou específicas do tipo + + # Correção geral de fechamento 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") + + # Adiciona elementos faltantes ESSENCIAIS (com base no tipo) + if tipo_peticao == "Petição Inicial": + if "juntada da guia de custas" not in peticao_corrigida.lower() and "gratuidade de justiça" not in peticao_corrigida.lower(): + # Tenta inserir antes do fechamento final + partes = peticao_corrigida.split("Nestes termos pede deferimento") + if len(partes) == 2: + peticao_corrigida = partes[0] + "Requer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento" + partes[1] + else: # Adiciona no final antes do fechamento se a separação falhar + 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_corrigida.lower() and "audiência de mediação" not in peticao_corrigida.lower(): + # Tenta inserir antes do fechamento final + partes = peticao_corrigida.split("Nestes termos pede deferimento") + if len(partes) == 2: + peticao_corrigida = partes[0] + "Opta 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: # Adiciona no final antes do fechamento + 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.") + + + # Garantir o fechamento padrão OAB no final + linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()] + fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."] + # Remove qualquer linha de fechamento existente que não seja a padrão + while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]): + linhas.pop() + # Adiciona o fechamento padrão + 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} + "generationConfig": { + "temperature": CONFIG["temperatura"], + "maxOutputTokens": CONFIG["max_tokens"], + "topP": 0.95, + # Stop sequences podem ajudar a evitar texto indesejado após a peça + # "stopSequences": ["```", "---"] + } } headers = {"Content-Type": "application/json"} + logger.info(f"Chamando API Gemini com {len(prompt)} caracteres de prompt. Temperatura: {CONFIG['temperatura']}, Max Tokens: {CONFIG['max_tokens']}") + # logger.debug(f"Prompt enviado (primeiros 500 chars): {prompt[:500]}") + for tentativa in range(CONFIG["max_retry_attempts"]): try: with rate_limiter: + logger.debug(f"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"Status Code recebido: {response.status_code}") + # logger.debug(f"Resposta da API (raw): {response.text[:500]}") # Log da resposta crua (cuidado com dados sensíveis) + + 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). Tentando novamente em {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 = response.json().get("error", {}).get("message", "Erro desconhecido") + error_info = response.json() + error_msg = error_info.get("error", {}).get("message", "Erro desconhecido na API") + logger.error(f"Erro da API (Status {response.status_code}): {error_msg}") if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue - return f"❌ Erro da API: {error_msg}" + return f"❌ Erro da API ({response.status_code}): {error_msg}" + response_data = response.json() - if "candidates" not in response_data or not response_data["candidates"]: - if tentativa < CONFIG["max_retry_attempts"] - 1: + + # Verifica bloqueio por segurança ou conteúdo + if not response_data.get("candidates"): + prompt_feedback = response_data.get("promptFeedback", {}) + block_reason = prompt_feedback.get("blockReason") + safety_ratings = prompt_feedback.get("safetyRatings") + logger.error(f"Resposta da API sem 'candidates'. Block Reason: {block_reason}, Safety Ratings: {safety_ratings}") + if block_reason: + return f"❌ A solicitação foi bloqueada pela API. Motivo: {block_reason}. Tente reformular o caso ou a petição." + else: + return "❌ Resposta vazia ou inválida da API (sem 'candidates'). Verifique o log para detalhes." + + + candidate = response_data["candidates"][0] + + # Verifica se a GERAÇÃO foi bloqueada + finish_reason = candidate.get("finishReason") + if finish_reason not in [None, "STOP", "MAX_TOKENS"]: + logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}") + # Tenta obter a razão específica do bloqueio de segurança se houver + safety_ratings_candidate = candidate.get("safetyRatings", []) + harm_categories = [r['category'] for r in safety_ratings_candidate if r['probability'] not in ['NEGLIGIBLE', 'LOW']] + if harm_categories: + return f"❌ A resposta foi bloqueada por segurança pela API. Motivo: {finish_reason} (Categorias: {', '.join(harm_categories)}). Revise o conteúdo." + else: + return f"❌ A geração da resposta foi interrompida pela API. Motivo: {finish_reason}. Tente novamente ou simplifique a solicitação." + + + if "content" not in candidate or "parts" not in candidate["content"] or not candidate["content"]["parts"]: + logger.error("Estrutura de resposta inesperada da API: Faltando content/parts.") + if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue - return "❌ Resposta vazia da API." - return response_data["candidates"][0]["content"]["parts"][0]["text"].strip() + return "❌ Resposta inválida da API (estrutura inesperada)." + + texto_gerado = candidate["content"]["parts"][0]["text"].strip() + logger.info(f"API retornou com sucesso. Tamanho da resposta: {len(texto_gerado)} caracteres.") + # logger.debug(f"Texto gerado (primeiros 500 chars): {texto_gerado[:500]}") + return texto_gerado + except requests.Timeout: + logger.warning(f"Timeout na tentativa {tentativa + 1}.") if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) continue - return "⏱️ Tempo esgotado." + return "⏱️ Tempo esgotado após múltiplas tentativas. Verifique a conexão ou aumente o timeout." except requests.RequestException as e: + logger.error(f"Erro de rede/requisição na tentativa {tentativa + 1}: {e}") if tentativa < CONFIG["max_retry_attempts"] - 1: - time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) - continue + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") + time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) + continue return f"❌ Erro na requisição: {e}" + except json.JSONDecodeError as e: + logger.error(f"Erro ao decodificar JSON da resposta da API: {e}") + logger.debug(f"Resposta que causou erro de JSON: {response.text[:500]}") # Log da resposta problemática + return f"❌ Erro ao processar a resposta da API (JSON inválido)." except Exception as e: - logger.error(f"Erro inesperado na chamada da API: {str(e)}") + logger.exception(f"Erro inesperado na chamada da API na tentativa {tentativa + 1}: {str(e)}") # Loga o traceback completo + if tentativa < CONFIG["max_retry_attempts"] - 1: + logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...") + time.sleep(CONFIG["retry_delay"] * (tentativa + 1)) + continue return f"❌ Erro inesperado: {str(e)}" -def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): - logger.info("Iniciando correção de petição...") - if not caso.strip(): + return "❌ Falha ao chamar a API após todas as tentativas." + + +def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): + logger.info(f"Iniciando correção para tipo: {tipo_peticao}") + start_time = time.time() + + if not caso or not caso.strip(): logger.warning("Caso não fornecido.") - return "⚠️ Forneça o caso da banca." + return "⚠️ Forneça o caso/contexto da peça.", "", "", "", "" # Retorna tupla vazia para diff + + 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.", "", "", "", "" + + + peticao_conteudo = "" + 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, "", "", "", "" + logger.info(f"Texto extraído do arquivo ({len(peticao_conteudo)} caracteres).") + elif peticao_texto and peticao_texto.strip(): + peticao_conteudo = peticao_texto + logger.info(f"Usando texto da petição fornecido ({len(peticao_conteudo)} caracteres).") + else: + logger.warning("Nenhuma petição fornecida (nem texto, nem arquivo).") + return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", "" + # --- TROLL DETECTION --- 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", + "faça para mim", "faça por mim", "escreva para mim", ] - peticao_conteudo = "" - if peticao_arquivo is not None: - 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 - 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á!'", - "🚫 **Aviso**: 'Petição não é self-service, escreva você mesmo ou chame um advogado de verdade!'", - "😂 **Piada**: 'Por que o aluno pediu pra IA escrever a petição? Porque ele achou que OAB é brincadeira!'", - "🎮 **Dica de Jogo**: 'Tente o nível fácil: abra o CPC, Art. 319, e escreva sozinho!'", - "🍔 **Receita de Hambúrguer**: Carne, pão, alface e tomate. Frite e esqueça a petição, é mais fácil assim.", - "😴 **Conselho**: 'Descanse, porque eu não vou fazer isso por você. Boa sorte na próxima tentativa!'", - "📚 **Lição**: 'Aula 1: IA não é seu estagiário. Abra o livro e comece a escrever.'", - "🌧️ **Previsão**: 'Dia nublado com 100% de chance de você ter que escrever a petição sozinho.'", - "💡 **Ideia Brilhante**: 'Que tal estudar o caso e tentar de novo? Eu só corrijo, não escrevo!'", - "🎸 **Rockstar**: 'Você queria uma petição, mas eu só toco guitarra. Tente de novo!'", - "🌟 **Horóscopo**: 'As estrelas dizem: hoje não é dia de pedir ajuda para IA. Escreva você mesmo!'", - "🏋️ **Dica de Treino**: 'Faça 10 flexões e tente escrever a petição novamente. Você consegue!'", - "🎬 **Cena de Filme**: 'Em um mundo onde IAs ajudam, mas não escrevem petições. Você é o herói!'", - "🎨 **Arte Abstrata**: 'Pegue um pincel e pinte sua petição. Talvez fique mais fácil de entender!'", - "🎧 **Podcast**: 'Ouça nosso podcast: Como escrever sua petição sem ajuda de IA.'", - "🏆 **Desafio**: 'Desafio aceito? Escreva a petição em 1 hora e ganhe um prêmio: satisfação pessoal!'", - "🎭 **Teatro**: 'Ato 1, Cena 1: Você escreve a petição. Eu? Apenas assisto.'", - "🎲 **Jogo de Dados**: 'Role os dados. Se der 6, eu ajudo. Se não, você escreve sozinho!'", - "🎹 **Música Clássica**: 'Sonata em Petição Maior: composta por você, executada por você.'", - "🌳 **Sabedoria da Floresta**: 'As árvores sussurram: escreva sua petição com calma e paciência.'", - "🎓 **Aula de História**: 'Na época das cavernas, eles escreviam petições nas paredes. Sua vez!'", - "🎬 **Trailer de Filme**: 'Em breve nos cinemas: A Petição Impossível. Escrita e dirigida por você!'", - "🎮 **Nível Bônus**: 'Desbloqueie o nível bônus: escreva a petição sem ajuda e ganhe experiência!'", - "🎧 **Música de Elevador**: 'Enquanto você escreve, eu toco uma música relaxante. Boa sorte!'" - ] - logger.info("Detectado pedido para escrever petição. Retornando resposta troll.") - return random.choice(respostas_troll) - - if not peticao_conteudo.strip(): - logger.warning("Petição não fornecida.") - 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}" + peticao_lower = peticao_conteudo.lower() + if any(trigger in peticao_lower for trigger in troll_triggers): + respostas_troll = [ + "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? Assim você aprende de verdade!", + "🚫 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!", + "📚 Lembre-se: a prática leva à perfeição (e à aprovação!). Tente redigir a peça com base no caso, e depois eu analiso para você.", + "🤖 Sou um corretor, não um escritor fantasma! Escreva sua versão que eu aponto os caminhos para melhorar.", + "💡 Dica de ouro: Comece! Esboce a peça, mesmo que imperfeita. Depois a gente lapida juntos. Mande bala!", + "😂 Pedir pra IA fazer a peça é tipo pedir pro GPS dirigir o carro. Ele te guia, mas quem tá no volante é você! Escreva aí!", + ] + logger.info("Detectado pedido para escrever petição. Retornando resposta troll.") + return random.choice(respostas_troll), "", "", "", "" + + # --- VALIDAÇÃO BÁSICA (Opcional e específica) --- + # Pode ser desativada ou adaptada, pois é muito focada na Inicial + aviso_validacao = "" + # if tipo_peticao == "Petição Inicial": + # problemas = validar_peticao_basica(peticao_conteudo) + # if problemas: + # aviso_validacao = f"### ⚠️ Alertas Rápidos (Validação Inicial)\n" + "\n".join([f"- {p}" for p in problemas]) + "\n\n---\n\n" + + # --- CACHE CHECK --- + 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.") - return aviso_validacao + CORRECAO_CACHE[cache_key] - - prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, modo_treinamento) + cached_data = CORRECAO_CACHE[cache_key] + # Retorna a tupla completa do cache + return cached_data["analise_formatada"], cached_data["peticao_original"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"] + + # --- CHAMADA PARA ANÁLISE --- + logger.info("Gerando prompt de análise...") + prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento) + logger.info("Enviando para análise da API...") analise = chamar_api(prompt_analise) - if "❌" in analise or "⏱️" in analise: + if analise.startswith("❌") or analise.startswith("⏱️") or analise.startswith("⚠️"): logger.error(f"Erro na análise da petição: {analise}") - return aviso_validacao + analise - analise_formatada = formatar_correcao(analise) + # Retorna o erro e strings vazias para os outros campos + return aviso_validacao + analise, peticao_conteudo, "", "", "" + logger.info("Análise recebida da API.") + analise_formatada = formatar_correcao(analise) # Formata a análise para melhor visualização + + # --- CHAMADA PARA GERAR PETIÇÃO CORRIGIDA --- + logger.info("Gerando prompt para peça corrigida...") + prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao) + logger.info("Enviando para geração da peça corrigida API...") + peticao_corrigida_bruta = chamar_api(prompt_peticao) + + peticao_corrigida_final = "" # Inicializa + if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️") or peticao_corrigida_bruta.startswith("⚠️"): + logger.error(f"Erro ao gerar peça corrigida: {peticao_corrigida_bruta}") + # Retorna a análise formatada e o erro da peça corrigida + 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...") + # Tenta extrair APENAS a peça, caso a IA adicione texto extra + # Isso pode ser ajustado conforme o comportamento da IA + # Exemplo: procurar por "Excelentíssimo" ou similar e pegar dali pra frente + 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 + + if start_index != -1: + peticao_extraida = peticao_corrigida_bruta[start_index:] + else: + peticao_extraida = peticao_corrigida_bruta # Usa como está se não achar marcador - prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo) - peticao_corrigida = chamar_api(prompt_peticao) - if "❌" in peticao_corrigida or "⏱️" in peticao_corrigida: - logger.error(f"Erro ao gerar petição corrigida: {peticao_corrigida}") - return aviso_validacao + analise_formatada + "\n\n" + peticao_corrigida + peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao) + logger.info("Pós-correção aplicada.") - peticao_corrigida = pos_corrigir_peticao(peticao_corrigida) - correcao_completa = analise + "\n\n" + peticao_corrigida - HISTORICO.append({ + # --- DIFERENÇA --- + logger.info("Calculando diferenças...") + # Usar peticao_conteudo (original) e peticao_corrigida_final + # A função comparar_peticao_melhorada agora será chamada internamente + # para gerar o diff e os resumos, retornando tudo junto. + diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final) + logger.info("Diferenças calculadas.") + + + # --- HISTÓRICO E CACHE --- + correcao_completa_formatada = aviso_validacao + analise_formatada # Não inclui a peça corrigida aqui + resultado_historico = { "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "tipo_peticao": tipo_peticao, "caso": caso, - "peticao": peticao_conteudo, - "correcao_completa": correcao_completa - }) - if len(HISTORICO) > 10: + "peticao_original": peticao_conteudo, + "analise_formatada": correcao_completa_formatada, # Salva análise formatada + "peticao_corrigida": peticao_corrigida_final, # Salva peça corrigida + "resumo_diff": resumo_diff, + "criticas_diff": criticas_diff + # Não salva o diff_html no histórico/cache por ser grande + } + HISTORICO.append(resultado_historico) + if len(HISTORICO) > 10: # Limita o histórico HISTORICO.pop(0) - CORRECAO_CACHE[cache_key] = analise_formatada - logger.info("Correção de petição concluída com sucesso.") - return aviso_validacao + analise_formatada + + # Salva no cache os dados relevantes para exibição + CORRECAO_CACHE[cache_key] = { + "analise_formatada": correcao_completa_formatada, + "peticao_original": peticao_conteudo, + "peticao_corrigida": peticao_corrigida_final, + "resumo_diff": resumo_diff, + "criticas_diff": criticas_diff, + # "diff_html": diff_html # Opcional: salvar se o cache puder lidar + } + + end_time = time.time() + logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.") + + # Retorna todos os resultados necessários para a interface + return correcao_completa_formatada, peticao_conteudo, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html + def formatar_correcao(texto): + # Esta função pode ser melhorada para usar Markdown de forma mais eficaz + # Ex: usar ** para títulos, * para ênfase, etc. + 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") + + # Adiciona um pouco mais de formatação básica linhas = texto.split('\n') - texto_formatado = "" + linhas_formatadas = [] + in_list = False 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" + linha_strip = linha.strip() + if linha_strip.startswith("1.") or linha_strip.startswith("2.") or linha_strip.startswith("3.") or linha_strip.startswith("4.") or linha_strip.startswith("5."): + linhas_formatadas.append(f"\n**{linha_strip}**") + in_list = False + elif linha_strip.startswith("- ") or linha_strip.startswith("* "): + # Melhora listas dentro de pontos a melhorar + prefix = " * " if in_list else "* " + linhas_formatadas.append(prefix + linha_strip[2:]) + in_list = True + elif linha_strip.startswith("a)") or linha_strip.startswith("b)") or linha_strip.startswith("c)") or linha_strip.startswith("d)") or linha_strip.startswith("e)"): + linhas_formatadas.append(f" * {linha_strip}") + in_list = True 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 + linhas_formatadas.append(linha) + if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")): # Reseta se não for item de lista + in_list = False + + + # Tenta formatar a nota + texto_final = "\n".join(linhas_formatadas) + import re + 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 petição para receber dicas..." + + dicas_relevantes = [] + texto_lower = texto_peticao.lower() + + # Adiciona dicas gerais primeiro + for chave, dica in DICAS_OAB_MELHORADO.items(): + if chave in ["Clareza e Concisão", "Fundamentação Legal", "Coerência Argumentativa", "Revisão Ortográfica e Gramatical", "Formatação"]: + dicas_relevantes.append(f"💡 **{chave}:** {dica}") + + # Adiciona dicas específicas do tipo (se houver palavras-chave) + keywords_por_tipo = { + "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor da causa", "provas", "audiência", "citação", "custas"], + "Contestação": ["preliminar", "mérito", "impugnação", "fatos", "improcedência", "provas", "honorários"], + "Reconvenção": ["reconvinte", "reconvindo", "conexão", "pedidos reconvencionais"], + "Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica"], + "Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "decisão embargada"], + "Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "razões"], + "Agravo de Instrumento": ["decisão interlocutória", "tribunal", "cabimento", "urgência", "efeito suspensivo", "peças obrigatórias"], + "Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "probabilidade", "risco"], + } -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: - logger.error(f"Erro ao extrair petição corrigida: {str(e)}") - return f"❌ Erro ao extrair petição: {str(e)}" + if tipo_peticao in keywords_por_tipo: + for keyword in keywords_por_tipo[tipo_peticao]: + if keyword in texto_lower: + # Busca dicas que contenham a keyword ou o tipo no nome + for chave, dica in DICAS_OAB_MELHORADO.items(): + chave_lower = chave.lower() + if f"({tipo_peticao.split()[0].lower()})" in chave_lower or keyword in chave_lower: + dica_formatada = f"💡 **{chave}:** {dica}" + if dica_formatada not in dicas_relevantes: + dicas_relevantes.append(dica_formatada) -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." + if not dicas_relevantes: + return "💡 Continue escrevendo ou selecione o tipo de peça para dicas mais específicas." -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 = [] + # Limita o número de dicas exibidas + return "\n\n".join(dicas_relevantes[:5]) # Mostra no máximo 5 dicas + +# --- Funções de Comparação (Diff) --- + +def calcular_diff_interno(texto_original, texto_corrigido): + """Calcula a diferença e retorna HTML, resumo e críticas.""" + if not texto_original or not texto_corrigido or texto_corrigido.startswith("⚠️") or texto_corrigido.startswith("❌"): + return "","","" # Retorna vazio se não houver o que comparar + + 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", "fundamento incorreto"] + keywords_criticas_adicionadas = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "pedido essencial", "preliminar obrigatória"] + for linha in diff: + texto = linha[2:] + texto_escaped = escape(texto) if linha.startswith(' '): - texto = linha[2:] - resultado_diff.append(f"
{escape(texto)}
") + resultado_diff_html.append(f"
{texto_escaped}
") elif linha.startswith('- '): - texto = linha[2:] - if any(kw in texto.lower() for kw in ["invenção", "custas", "art. 319"]): - criticas.append(f"🔴 REMOVIDO: {texto} (inconsistente ou incorreto)") - resultado_diff.append(f"
{escape(texto)}
") + linhas_removidas += 1 + resultado_diff_html.append(f"
{texto_escaped}
") + if any(kw in texto.lower() for kw in keywords_criticas_removidas): + criticas.append(f"🔴 REMOVIDO (Potencialmente Crítico): {texto_escaped}") elif linha.startswith('+ '): - texto = linha[2:] - if any(kw in texto.lower() for kw in ["custas", "art. 319"]): - criticas.append(f"🟢 ADICIONADO: {texto} (essencial para conformidade)") - resultado_diff.append(f"
{escape(texto)}
") - elif linha.startswith('? '): - continue - - total_removidas = len([linha for linha in resultado_diff if "linha-removida" in linha]) - total_adicionadas = len([linha for linha in resultado_diff if "linha-adicionada" in linha]) + linhas_adicionadas += 1 + resultado_diff_html.append(f"
{texto_escaped}
") + if any(kw in texto.lower() for kw in keywords_criticas_adicionadas): + criticas.append(f"🟢 ADICIONADO (Potencialmente Essencial): {texto_escaped}") + #elif linha.startswith('? '): # Linha de informação do diff, pode ser ignorada ou estilizada + # resultado_diff_html.append(f"
{texto_escaped}
") resumo_estatistico = f""" -Linhas Removidas: {total_removidas} -Linhas Adicionadas: {total_adicionadas} -Diferença Líquida: {total_adicionadas - total_removidas} linhas +**Resumo das Mudanças:** +* Linhas Removidas: {linhas_removidas} +* Linhas Adicionadas: {linhas_adicionadas} +* **Diferença Líquida:** {linhas_adicionadas - linhas_removidas} linhas """ - criticas_texto = "\n".join(criticas) if criticas else "Nenhuma mudança crítica detectada." + criticas_texto = "### 🚨 Mudanças Críticas / Essenciais Identificadas:\n" + "\n".join(criticas) if criticas else "Nenhuma mudança crítica/essencial automaticamente detectada." - visualizacao_detalhada = f""" + # Estilo CSS para o HTML do Diff + css = """ -{"".join(resultado_diff)} """ - return peticao, peticao_corrigida, resumo_estatistico, criticas_texto, visualizacao_detalhada + visualizacao_detalhada_html = f"{css}
{''.join(resultado_diff_html)}
" -def salvar_progresso(caso, peticao): - logger.info("Iniciando salvamento de progresso...") - logger.debug(f"Caso recebido: {caso[:100]}...") # Log do caso (primeiros 100 caracteres) - logger.debug(f"Petição recebida: {peticao[:100]}...") # Log da petição (primeiros 100 caracteres) + return visualizacao_detalhada_html, resumo_estatistico, criticas_texto + + +# --- Funções de Progresso e Histórico --- + +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"]) + # Extrai a nota da análise formatada + nota_str = "N/A" + match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"]) + if match: + try: + nota = float(match.group(1).replace(",", ".")) + notas.append(nota) + nota_str = f"{nota:.1f}" + except ValueError: + pass # Ignora se não conseguir converter + + 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 do Progresso:** +* **Total de Correções:** {total_corridas} +* **Tipos de Peças Corrigidas:** {', '.join(sorted(list(tipos_corrigidos)))} +* **Média das Notas (quando aplicável):** {media:.1f}/10 (de {len(notas)} notas válidas) +* **Tendência da Última Nota:** {tendencia if tendencia else 'N/A'} +""" + return progresso_texto + + +def mostrar_historico(): + if not HISTORICO: + return "📜 Histórico vazio." + + historico_md = "### 📜 Histórico de Correções (Últimas 10)\n\n---\n" + # Mostra do mais recente para o mais antigo + for i, entry in enumerate(reversed(HISTORICO)): + caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso'] + peticao_resumo = (entry['peticao_original'][:80] + '...') if len(entry['peticao_original']) > 80 else entry['peticao_original'] + + # Tenta pegar a nota + nota_str = "N/A" + match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"]) + if match: + nota_str = f"{match.group(1).replace(',', '.')}/10" + + + historico_md += f"**{len(HISTORICO)-i}. Data:** {entry['data']}\n" + historico_md += f"**Tipo:** {entry['tipo_peticao']}\n" + historico_md += f"**Nota:** {nota_str}\n" + historico_md += f"**Caso:** _{caso_resumo}_\n" + # historico_md += f"**Petição Original:** _{peticao_resumo}_\n" # Pode poluir muito + historico_md += "---\n" + return historico_md + +# --- Funções de Salvar/Carregar Progresso --- + +def salvar_progresso(caso, peticao, tipo_peticao): + logger.info("Iniciando salvamento de progresso...") try: - # Verifica se os inputs estão vazios ou None - if not caso or not peticao: - logger.warning("Inputs vazios detectados: caso ou peticao estão vazios.") - return "", "⚠️ Caso ou petição estão vazios. Preencha ambos para salvar.", None - - # Cria o conteúdo JSON como string - logger.debug("Gerando JSON content...") - json_content = json.dumps({"caso": caso, "peticao": peticao}, ensure_ascii=False, indent=4) - logger.debug(f"JSON content gerado: {json_content[:100]}...") - - # Cria um arquivo temporário para salvar o JSON - logger.debug("Criando arquivo temporário...") - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json', prefix='progresso_', encoding='utf-8') as temp_file: - temp_file.write(json_content) - temp_file_path = temp_file.name - logger.debug(f"Arquivo temporário criado: {temp_file_path}") - - # Mensagem de sucesso com instruções para download ou fallback + if not caso or not peticao or not tipo_peticao: + logger.warning("Inputs insuficientes para salvar progresso.") + return "", "⚠️ Caso, Petição e Tipo de Petição são necessários para salvar.", None + + # Salva apenas os inputs atuais, não o histórico inteiro + 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) + + # Cria nome de arquivo com data/hora para evitar conflitos + timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"progresso_peticao_{timestamp_str}.json" + + # Usa NamedTemporaryFile para segurança e limpeza automática se algo der errado + # Mas precisamos do path para o Gradio File output, então gerenciamos manualmente + 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 no arquivo temporário: {temp_file_path}") + success_message = ( - "✅ Progresso salvo! O download do arquivo `progresso.json` deve iniciar automaticamente. " - "Se o download não iniciar, copie o conteúdo abaixo e cole em um arquivo com extensão `.json` " - "(ex.: `progresso.json`). No celular, você pode copiar o texto, abrir um app de notas, colar o conteúdo, " - "e salvar como `progresso.json`. Use a opção 'Carregar Progresso' para restaurar." + f"✅ Progresso salvo! Baixe o arquivo `{filename}`. " + "Use o botão 'Carregar Progresso' e selecione este arquivo para restaurar." ) - logger.info("Progresso salvo com sucesso.") + # Retorna o conteúdo (para visualização opcional), a mensagem e o path do arquivo para download 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 o progresso: {str(e)}", None + def carregar_progresso(arquivo_entrada): logger.info("Iniciando carregamento de progresso...") + if arquivo_entrada is None: + return "", "", "", "⚠️ Nenhum arquivo selecionado para carregar." + try: - with open(arquivo_entrada, 'r', encoding='utf-8') as f: + # Gradio File object tem o atributo 'name' com o path do arquivo temporário + 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) + + # Verifica se as chaves esperadas existem + 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 as chaves esperadas: {dados.keys()}") + return "", "", "", "❌ Erro: Arquivo de progresso inválido ou incompleto." + + 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 de petição '{tipo_peticao}' do arquivo não é mais válido. Selecione manualmente." + + logger.info("Progresso carregado com sucesso.") - return dados.get("caso", ""), dados.get("peticao", ""), "Progresso carregado com sucesso." + return tipo_peticao, caso, peticao, "✅ Progresso carregado com sucesso!" + + except json.JSONDecodeError: + logger.error(f"Erro ao decodificar JSON do arquivo: {file_path}") + return "", "", "", "❌ Erro: Arquivo selecionado não é um JSON válido." except Exception as e: - logger.error(f"Erro ao carregar o progresso: {str(e)}") - return "", "", f"❌ Erro ao carregar o progresso: {str(e)}" + logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True) + return "", "", "", f"❌ Erro inesperado ao carregar o progresso: {str(e)}" + + +# --- GERAÇÃO DE CASOS --- + +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 para o Caso:** + 1. **Complexidade Adequada:** Deve ter informações suficientes para redigir a peça solicitada, mas sem ser excessivamente longo ou complicado. (Ideal: 15-25 linhas). + 2. **Dados Essenciais:** Inclua nomes completos (fictícios) das partes envolvidas, locais (cidades/estados fictícios ou genéricos), datas relevantes, valores monetários (se aplicável), e uma descrição clara da relação jurídica e do conflito. + 3. **Coerência:** Os fatos devem ser lógicos e justificar a peça processual indicada. + 4. **Foco no Tipo de Peça:** O cenário deve ser construído para exigir especificamente a elaboração de um(a) **{tipo_peticao}**. + + **Instruções Específicas para Gerar Caso para "{tipo_peticao}":** + """ + + if tipo_peticao == "Petição Inicial": + prompt += """ + * Descreva uma situação onde direitos foram violados (contrato descumprido, dano sofrido, etc.) e o cliente (autor) busca a tutela jurisdicional pela primeira vez. + * Inclua elementos que permitam calcular danos materiais e/ou justificar danos morais. + * Forneça dados para qualificação das partes (autor e réu). + * **Exemplo de Tema:** Acidente de consumo, inadimplemento contratual, cobrança indevida, ação de despejo, etc. + """ + elif tipo_peticao == "Contestação": + prompt += """ + * **Apresente PRIMEIRO um resumo da Petição Inicial** que o réu (seu cliente) recebeu (quem processou, o que alegou, o que pediu). + * **Depois, apresente a versão dos fatos do RÉU**, incluindo informações que permitam: + * Alegar preliminares (ex: incompetência do juízo, ilegitimidade da parte, etc.). + * Impugnar os fatos da inicial. + * Apresentar defesa de mérito (ex: pagamento já realizado, culpa exclusiva da vítima, prescrição, etc.). + * Forneça dados para qualificação do réu. + """ + elif tipo_peticao == "Reconvenção": + prompt += """ + * Siga a estrutura da **Contestação**: Apresente o resumo da Petição Inicial do autor original. + * Apresente a versão dos fatos do Réu (seu cliente) para a defesa. + * **ADICIONE fatos e fundamentos que permitam ao RÉU fazer um pedido PRÓPRIO contra o AUTOR ORIGINAL**, que seja CONEXO com a ação principal ou com a defesa. + * **Exemplo:** Numa ação de cobrança, o réu contesta e reconvém pedindo indenização por danos que o autor lhe causou no mesmo negócio. + """ + elif tipo_peticao == "Impugnação à Contestação": + prompt += """ + * Apresente um resumo da Petição Inicial original do seu cliente (Autor). + * **Apresente um resumo da Contestação** apresentada pelo réu, destacando: + * As preliminares arguidas pelo réu (se houver). + * Os fatos novos (impeditivos, modificativos, extintivos) alegados pelo réu. + * Os documentos juntados pelo réu (se houver menção a algum relevante). + * O objetivo é fornecer elementos para o Autor rebater a contestação. + """ + elif tipo_peticao == "Embargos de Declaração": + prompt += """ + * Descreva brevemente o caso original (quem processou quem e por quê). + * **Apresente o DISPOSITIVO ou um TRECHO FUNDAMENTAL de uma DECISÃO JUDICIAL (sentença, acórdão, etc.)** proferida no caso. + * **INDIQUE CLARAMENTE qual o vício (omissão, contradição, obscuridade ou erro material) presente NESSA DECISÃO**, fornecendo contexto para que o aluno possa identificar e fundamentar os Embargos. + * **Exemplo:** "A sentença julgou procedente o pedido X, mas foi omissa quanto ao pedido Y que também constava na inicial." OU "O acórdão afirmou X na fundamentação, mas concluiu Y no dispositivo, gerando contradição." + """ + elif tipo_peticao == "Recurso de Apelação": + prompt += """ + * Descreva o caso original (Autor vs. Réu, causa de pedir, pedidos). + * **Apresente um resumo da SENTENÇA proferida pelo juiz de primeira instância**, indicando se foi procedente, improcedente, parcialmente procedente, e os principais fundamentos do juiz. + * **FORNEÇA ELEMENTOS que indiquem um ERRO na sentença (error in judicando ou in procedendo)**, que justifiquem a reforma ou anulação pelo Tribunal. + * **Exemplo:** "A sentença julgou improcedente o pedido por falta de provas, ignorando o documento X juntado aos autos." OU "O juiz indeferiu a oitiva da testemunha chave Y, cerceando a defesa do réu, e julgou procedente." + * Indique quem é o cliente (apelante). + """ + elif tipo_peticao == "Agravo de Instrumento": + prompt += """ + * Descreva o caso original em andamento. + * **Apresente uma DECISÃO INTERLOCUTÓRIA específica** proferida pelo juiz (NÃO a sentença final). + * **A decisão DEVE se enquadrar numa das hipóteses do Art. 1015 do CPC** (ou justificar a taxatividade mitigada). + * **FORNEÇA ELEMENTOS que indiquem um ERRO nessa decisão interlocutória** E, se possível, a **URGÊNCIA** em revertê-la (risco de dano). + * **Exemplo:** "O juiz indeferiu o pedido de gratuidade de justiça, embora o autor tenha comprovado baixa renda." OU "O juiz deferiu a liminar de busca e apreensão do bem X sem a devida comprovação da mora." + * Indique quem é o cliente (agravante). + """ + elif tipo_peticao == "Petição de Tutela Provisória": + prompt += """ + * Descreva uma situação de conflito onde uma das partes precisa de uma medida **urgente** (risco iminente) ou onde seu direito é **evidente**. + * **FORNEÇA ELEMENTOS claros que demonstrem:** + * **Para Urgência:** A probabilidade do direito (fumus boni iuris) E o perigo de dano ou risco ao resultado útil do processo (periculum in mora). + * **Para Evidência:** Uma das hipóteses do Art. 311 do CPC (ex: defesa protelatória, tese firmada em repetitivo, etc.). + * Indique qual medida específica o cliente deseja (ex: bloqueio de bens, retirada de nome do SPC, etc.). + * Especifique se a tutela é pedida ANTES do processo principal (antecedente) ou DENTRO de um processo já existente (incidental). + """ + else: + prompt += "* Crie um cenário jurídico cível com um conflito claro entre duas ou mais partes." + + 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 criar casos que dependam de conhecimento jurídico EXTREMAMENTE específico ou de nicho. Foque em temas comuns do direito civil e processual civil. Seja criativo! + """ + return prompt + +# --- Interface Gradio --- def interface_gradio(): - with gr.Blocks(theme=gr.themes.Soft()) as demo: - gr.Markdown("# 📝 Corretor Avançado de Petições Iniciais (Critérios OAB)") - gr.Markdown("⚠️ Nota: Devido a limites da API, pode haver atrasos ou erros em muitas requisições rápidas.") + 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! Selecione o tipo, forneça o caso e sua peça (texto ou arquivo).") + with gr.Row(): with gr.Column(scale=1): - gr.Markdown(""" - ### 🔍 Critérios de Avaliação - - ✅ Endereçamento correto (Art. 319, I, CPC) - - ✅ Qualificação das partes (Art. 319, II, CPC) - - ✅ Fatos e fundamentos (Art. 319, III, CPC) - - ✅ Pedidos claros e específicos (Art. 319, IV, CPC) - - ✅ Valor da causa (Art. 319, V, CPC) - - ✅ Provas pretendidas (Art. 319, VI, CPC) - - ✅ Opção sobre conciliação (Art. 319, VII, CPC) - - ✅ Pedido de citação adequado - - ✅ Juntada da guia de custas - - ✅ Fechamento formal - ### ⚖️ Penalizações - - ❌ Identificação do candidato: nota máxima 3,0 - - ❌ Invenção de fatos: -5,0 pontos - - ❌ Sem pedido de custas: -1,0 ponto - - ❌ Identificação específica na OAB: nota 0,0 - """) - modo_treinamento = gr.Checkbox(label="Modo de Treinamento Interativo", value=False) - with gr.Accordion("⚙️ Configurações", open=False): - temperatura_slider = gr.Slider(minimum=0.0, maximum=1.0, value=CONFIG["temperatura"], step=0.1, label="Temperatura") - timeout_slider = gr.Slider(minimum=30, maximum=120, value=CONFIG["timeout"], step=5, label="Timeout (segundos)") - def atualizar_config(temp, timeout): + gr.Markdown("### настроики (Settings)") # Configurações em Russo para brincar ;) + tipo_peticao_input = gr.Dropdown( + label=" Selecione o Tipo de Peça Processual", + choices=TIPOS_DE_PETICAO, + value="Petição Inicial" # Default + ) + modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento (Feedback Detalhado)", value=False) + + with gr.Accordion("⚙️ Configurações Avançadas", open=False): + temperatura_slider = gr.Slider(minimum=0.0, maximum=1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade (Temperatura)", info="Valores maiores geram respostas mais criativas, porém menos previsíveis.") + timeout_slider = gr.Slider(minimum=30, maximum=180, value=CONFIG["timeout"], step=10, label="Timeout da API (segundos)") + max_tokens_slider = gr.Slider(minimum=2048, maximum=8192, value=CONFIG["max_tokens"], step=256, label="Tamanho Máx. Resposta (Tokens)") + def atualizar_config(temp, timeout, tokens): CONFIG["temperatura"] = temp CONFIG["timeout"] = timeout - return f"✅ Configurações atualizadas!" - config_update_btn = gr.Button("📝 Atualizar Configurações") - config_status = gr.Textbox(label="Status", visible=True) - config_update_btn.click(atualizar_config, inputs=[temperatura_slider, timeout_slider], outputs=config_status) + CONFIG["max_tokens"] = tokens + logger.info(f"Configurações atualizadas: Temp={temp}, Timeout={timeout}, Tokens={tokens}") + return f"✅ Configs Atualizadas!" + config_update_btn = gr.Button("💾 Salvar Configs da Sessão") + config_status = gr.Textbox(label="Status Config", interactive=False) + config_update_btn.click(atualizar_config, inputs=[temperatura_slider, timeout_slider, max_tokens_slider], outputs=config_status) + with gr.Accordion("📊 Dashboard de Progresso", open=False): - progresso_output = gr.Markdown() + progresso_output = gr.Markdown("Clique em 'Atualizar Progresso' para ver suas estatísticas.") progresso_btn = gr.Button("🔄 Atualizar Progresso") - progresso_btn.click(fn=calcular_progresso, outputs=progresso_output) - with gr.Column(scale=2): + with gr.Accordion("📜 Histórico da Sessão", open=False): + historico_output = gr.Markdown("Clique em 'Ver Histórico' para ver suas últimas correções.") + historico_btn = gr.Button("🔄 Ver Histórico") + + with gr.Accordion("💾/📂 Salvar/Carregar Progresso Atual", open=False): + salvar_btn = gr.Button("💾 Salvar Caso/Peça Atual") + carregar_btn = gr.Button("📂 Carregar Caso/Peça de Arquivo") + arquivo_progresso_upload = gr.File(label="Selecione o arquivo .json para carregar", file_types=[".json"], scale=2) + # Para o download + progresso_download_file = gr.File(label="Baixar Progresso Salvo", interactive=False) + # Para visualização opcional do JSON salvo + # progresso_json_output = gr.Textbox(label="Progresso Salvo (JSON)", lines=3, interactive=False, visible=False) + + + with gr.Column(scale=3): + gr.Markdown("### 1. Forneça o Caso / Contexto") with gr.Row(): - gr.Markdown("### 🧩 Gerar Caso Fictício") - caso_btn = gr.Button("🔄 Gerar Novo Caso Fictício", variant="secondary") - caso_input = gr.Textbox(label="📋 Caso Fornecido pela Banca", lines=8, placeholder="Insira o texto COMPLETO do caso...") + caso_input = gr.Textbox(label="📋 Caso ou Situação Processual", lines=10, placeholder="Insira o texto COMPLETO do caso ou da situação (ex: resumo da inicial para contestação, decisão para embargos/recurso).") + caso_btn = gr.Button("🎲 Gerar Caso Fictício", variant="secondary", scale=0) + + + gr.Markdown("### 2. Insira Sua Peça Processual") with gr.Tabs(): - with gr.TabItem("✏️ Inserir Texto"): - peticao_texto = gr.Textbox(label="Texto da Petição do Aluno", lines=15, placeholder="Insira a petição completa...") - dica_contextual = gr.Textbox(label="Dicas Contextuais", interactive=False) - peticao_texto.change(fn=fornecer_dica_contextual, inputs=peticao_texto, outputs=dica_contextual) - btn_texto = gr.Button("🔍 Corrigir Petição (Texto)", variant="primary") - with gr.TabItem("📎 Enviar Arquivo"): - gr.Markdown("**Envie um arquivo PDF, DOCX ou TXT contendo a petição do aluno.**") - peticao_arquivo = gr.File(label="Arquivo da Petição", file_types=[".pdf", ".docx", ".txt"]) - arquivo_preview = gr.Textbox(label="Prévia do Conteúdo do Arquivo", lines=5, interactive=False) - btn_arquivo = gr.Button("🔍 Corrigir Petição (Arquivo)", variant="primary") - peticao_arquivo.change(fn=process_file, inputs=[peticao_arquivo], outputs=[arquivo_preview]) - processing_message = gr.Markdown("", visible=False) + with gr.TabItem("✏️ Digitar Texto"): + peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=15, placeholder="Cole ou digite sua peça processual aqui...") + dica_contextual_output = gr.Markdown(value="*Dicas aparecerão aqui enquanto você digita...*") # Usar Markdown + btn_corrigir = gr.Button("🚀 CORRIGIR E ANALISAR!", variant="primary") - with gr.Row(): - clear_btn = gr.Button("🗑️ Limpar Todos os Campos") - comparar_btn = gr.Button("⚖️ Comparar com Correção") - salvar_btn = gr.Button("💾 Salvar Progresso") - carregar_btn = gr.Button("📂 Carregar Progresso") - arquivo_progresso = gr.File(label="Arquivo de Progresso", file_types=[".json"]) + with gr.TabItem("📎 Anexar Arquivo"): + peticao_arquivo_input = gr.File(label="Arquivo da Peça (.pdf, .docx, .txt - Máx 15MB)", file_types=[".pdf", ".docx", ".txt"]) + arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Conteúdo do Arquivo (Texto Extraído)", lines=5, interactive=False) + btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary") - resultado_output = gr.Markdown(label="📊 Correção Oficial e Nota", show_copy_button=True) - # Componentes para exibir o progresso salvo - progresso_json_output = gr.Textbox(label="Progresso Salvo (JSON)", lines=5, interactive=False, visible=False) - progresso_download_file = gr.File(label="Download do Progresso", visible=False) + processing_message = gr.Markdown("", visible=False) # Para feedback de processamento - with gr.Row(): - peticao_aluno_output = gr.Textbox(label="Petição do Aluno", lines=15, interactive=False) - peticao_corrigida_output = gr.Textbox(label="Petição Corrigida", lines=15, interactive=False) + # --- ABA DE RESULTADOS --- + with gr.Tabs(): + with gr.TabItem("📊 Análise e Correção"): + gr.Markdown("### 3. Resultados da Análise") + resultado_analise_output = gr.Markdown(label="Análise Detalhada e Nota") - with gr.Row(): - with gr.Column(scale=1): - resumo_alteracoes_output = gr.Markdown(label="📊 Resumo das Alterações") - criticas_output = gr.Markdown(label="🚨 Mudanças Críticas") - with gr.Column(scale=2): - visualizacao_detalhada_output = gr.HTML(label="📝 Alterações Detalhadas") - - with gr.Accordion("📜 Histórico de Correções", open=False): - historico_output = gr.Markdown() - historico_btn = gr.Button("🔄 Ver Histórico") - historico_btn.click( - fn=lambda: "\n\n".join([f"**{h['data']}**\nCaso: {h['caso'][:100]}...\nCorreção: {h['correcao_completa'][:100]}..." for h in HISTORICO]), - outputs=historico_output - ) - - caso_btn.click(fn=gerar_caso_ficticio, inputs=[], outputs=[caso_input]) - btn_texto.click( - fn=lambda: gr.update(value="⏳ Aguarde enquanto a correção é gerada...", visible=True), + gr.Markdown("### ✨ Versão Sugerida (Corrigida/Aprimorada)") + resultado_peticao_corrigida_output = gr.Textbox(label="Peça Processual Corrigida", lines=20, 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 das Mudanças") + criticas_output = gr.Markdown(label="Mudanças Críticas/Essenciais") + with gr.Column(scale=3): + visualizacao_detalhada_output = gr.HTML(label="📝 Alterações Detalhadas (Diff)") + # Saídas ocultas para guardar os textos comparados + # peticao_aluno_output_diff = gr.Textbox(visible=False) + # peticao_corrigida_output_diff = gr.Textbox(visible=False) + + + # --- Botão Limpar --- + clear_btn = gr.Button("🗑️ Limpar Tudo") + + + # --- LÓGICA DOS BOTÕES E EVENTOS --- + + # Gerar Caso Fictício + caso_btn.click( + fn=gerar_caso_ficticio, + inputs=[tipo_peticao_input], # Passa o tipo selecionado + outputs=[caso_input] + ) + + # Atualizar dicas contextuais + peticao_texto_input.change( + fn=fornecer_dica_contextual, + inputs=[peticao_texto_input, tipo_peticao_input], + outputs=dica_contextual_output + ) + # Atualizar dicas se o TIPO mudar e já houver texto + tipo_peticao_input.change( + fn=fornecer_dica_contextual, + inputs=[peticao_texto_input, tipo_peticao_input], + outputs=dica_contextual_output + ) + + + # Processar arquivo para preview + peticao_arquivo_input.change( + fn=process_file, + inputs=[peticao_arquivo_input], + outputs=[arquivo_preview_output] + ) + + # Botão Corrigir (Texto) + btn_corrigir.click( + fn=lambda: gr.update(value="⏳ Processando... Gerando análise e correção. Isso pode levar um minuto...", visible=True), inputs=None, outputs=[processing_message] ).then( - fn=lambda caso, texto, treinamento: corrigir_peticao(caso, texto, None, treinamento), - inputs=[caso_input, peticao_texto, modo_treinamento], - outputs=[resultado_output] + fn=corrigir_peticao, + # Inputs: caso, tipo_peticao, peticao_texto, peticao_arquivo=None, modo_treinamento + inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento], + # Outputs: analise_fmt, peticao_orig, peticao_corr, resumo_diff, criticas_diff, diff_html + outputs=[resultado_analise_output, gr.Textbox(visible=False), resultado_peticao_corrigida_output, # peticao_aluno_output_diff (oculto) + resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output] ).then( fn=lambda: gr.update(value="", visible=False), inputs=None, outputs=[processing_message] ) - btn_arquivo.click( - fn=lambda: gr.update(value="⏳ Aguarde enquanto a correção é gerada...", visible=True), + + # Botão Corrigir (Arquivo) + btn_corrigir_arquivo.click( + fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção. Isso pode levar um minuto...", visible=True), inputs=None, outputs=[processing_message] ).then( - fn=lambda caso, arquivo, treinamento: corrigir_peticao(caso, None, arquivo, treinamento), - inputs=[caso_input, peticao_arquivo, modo_treinamento], - outputs=[resultado_output] + fn=corrigir_peticao, + # Inputs: caso, tipo_peticao, peticao_texto=None, peticao_arquivo, modo_treinamento + inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento], + # Outputs: analise_fmt, peticao_orig, peticao_corr, resumo_diff, criticas_diff, diff_html + outputs=[resultado_analise_output, gr.Textbox(visible=False), resultado_peticao_corrigida_output, # peticao_aluno_output_diff (oculto) + resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output] ).then( fn=lambda: gr.update(value="", visible=False), inputs=None, outputs=[processing_message] ) + + # Botão Limpar clear_btn.click( - fn=lambda: [None, None, None, None, "", "", "", "", "", gr.update(visible=False), gr.update(visible=False)], + fn=lambda: [ + "Petição Inicial", # Reseta Dropdown + False, # Reseta Modo Treinamento + None, # Caso + None, # Petição Texto + None, # Petição Arquivo + None, # Arquivo Preview + "", # Mensagem Processamento + "", # Análise + "", # Petição Corrigida + "", # Resumo Diff + "", # Críticas Diff + "", # HTML Diff + "", # Dicas Contextuais + None, # Arquivo Progresso Upload + None, # Arquivo Progresso Download + ], inputs=None, - outputs=[caso_input, peticao_texto, peticao_arquivo, arquivo_preview, resultado_output, dica_contextual, - peticao_aluno_output, peticao_corrigida_output, resumo_alteracoes_output, criticas_output, - visualizacao_detalhada_output, progresso_json_output, progresso_download_file, processing_message] - ) - comparar_btn.click( - fn=comparar_peticao_melhorada, - inputs=[caso_input, peticao_texto, resultado_output], - outputs=[peticao_aluno_output, peticao_corrigida_output, resumo_alteracoes_output, criticas_output, - visualizacao_detalhada_output] + outputs=[ + tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, peticao_arquivo_input, + arquivo_preview_output, processing_message, resultado_analise_output, + resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, + visualizacao_detalhada_output, dica_contextual_output, arquivo_progresso_upload, + progresso_download_file + ] ) + + # Botões de Progresso e Histórico + progresso_btn.click(fn=calcular_progresso, outputs=progresso_output) + historico_btn.click(fn=mostrar_historico, outputs=historico_output) + + # Salvar Progresso salvar_btn.click( - fn=lambda: gr.update(value="⏳ Salvando progresso...", visible=True), - inputs=None, - outputs=[processing_message] + fn=lambda: gr.update(value="⏳ Salvando progresso atual...", visible=True), + inputs=None, outputs=processing_message ).then( - fn=lambda caso, peticao: salvar_progresso(caso, peticao), - inputs=[caso_input, peticao_texto], - outputs=[progresso_json_output, resultado_output, progresso_download_file] + # Passa os campos relevantes para salvar + fn=salvar_progresso, + inputs=[caso_input, peticao_texto_input, tipo_peticao_input], + # Retorna: json_content (não usado direto), success_message, temp_file_path + outputs=[gr.Textbox(visible=False), config_status, progresso_download_file] # Usa config_status para msg, e file para download ).then( - fn=lambda: gr.update(value="", visible=False), - inputs=None, - outputs=[processing_message] + fn=lambda x: gr.update(visible=bool(x)), # Mostra o botão de download se path for retornado + inputs=[progresso_download_file], + outputs=[progresso_download_file] ).then( - fn=lambda: gr.update(visible=True), - inputs=None, - outputs=[progresso_json_output, progresso_download_file] + lambda: gr.update(value="", visible=False), None, processing_message # Limpa msg processamento ) + + + # Carregar Progresso carregar_btn.click( - fn=lambda arquivo: carregar_progresso(arquivo.name), - inputs=[arquivo_progresso], - outputs=[caso_input, peticao_texto, resultado_output] + fn=lambda: gr.update(value="⏳ Carregando progresso do arquivo...", visible=True), + inputs=None, outputs=processing_message + ).then( + fn=carregar_progresso, + inputs=[arquivo_progresso_upload], + # Retorna: tipo_peticao, caso, peticao, status_message + outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status] # Atualiza os campos e mostra status + ).then( + lambda: gr.update(value="", visible=False), None, processing_message # Limpa msg processamento ) - with gr.Accordion("📘 Critérios Detalhados", open=False): - gr.Markdown(""" - ### 📋 Critérios Realistas para Correção - #### 1. Qualificação das Partes (Art. 319, II, CPC) - - ✅ CORRETO: Usar "Rua XXXX" se endereço não for fornecido - - ❌ INCORRETO: Inventar dados - #### 2. Citação - - ✅ CORRETO: Pedir citação conforme dados do caso - #### 3. Pedidos Obrigatórios - - ✅ OBRIGATÓRIO: Incluir "a juntada da guia de custas" - #### 4. Fechamento Formal - - ✅ OBRIGATÓRIO: Finalizar com "Local e data.\nADVOGADO.\nOAB." sem identificação específica - #### 5. Penalização Grave - - ❌ INCORRETO: Identificação do candidato (nome ou número) - - ❌ INCORRETO: Invenção de fatos - - ❌ INCORRETO: Identificação na OAB (número ou estado) - """) - gr.Markdown(""" - ### ℹ️ Modo de Treinamento Interativo - Ative para explicações detalhadas com exemplos e sugestões. - ### 📋 Como usar - 1. Gere ou cole o caso - 2. Insira a petição - 3. Corrija e compare - ### 📄 Formatos suportados - - PDF (texto e escaneados com OCR) - - DOCX - - TXT - """) return demo -def gerar_caso_ficticio(): - prompt = estruturar_prompt_geracao_caso() - return chamar_api(prompt) - -def estruturar_prompt_geracao_caso(): - return """ - Crie um caso fictício detalhado para uma petição inicial cível, similar ao exemplo: - "Ana, modelo profissional, residente em Manaus, viajou para São Paulo, para o casamento de sua filha. Para lavar, pintar seus cabelos e realizar um penteado para o casamento, Ana procurou os serviços de João Macedo, cabeleireiro e dono do salão de beleza 'Hair', sediado na cidade de São Paulo, que lhe cobrou R$500,00 (quinhentos reais) pela prestação do serviço. Após lavar os cabelos de Ana, João aplicou-lhe uma tintura da marca francesa ABC, importada pela empresa Brasil Connection Ltda, sediada na cidade de Curitiba (PR). Meia hora após a aplicação da tintura, Ana sofreu uma reação alérgica, que demandou atendimento médico-hospitalar, no valor de R$1.000,00 (um mil reais), bem como dois dias de absoluto repouso que impossibilitou sua presença no casamento de sua filha. Além disso, perdeu grande parte de seu cabelo, tendo permanecido com manchas em seu rosto por dois meses, perdendo um ensaio fotográfico, para o qual já havia sido contratada, pelo valor de R$50.000,00 (cinquenta mil reais). Posteriormente constatou-se que a tintura utilizada continha substâncias químicas extremamente perigosas à vida e à saúde das pessoas e a fabricante ABC já havia sido condenada pela justiça francesa a encerrar a fabricação e comercialização do produto. Indignada com os danos sofridos, Ana procura um advogado para pleitear o devido ressarcimento." - O caso deve: - 1. Conter uma violação clara de direitos que justifique uma ação cível - 2. Incluir valores específicos de danos materiais e/ou morais - 3. Mencionar nomes completos, cidades e informações para identificar as partes - 4. Estabelecer relação entre as partes (contratual, consumo, etc.) - 5. Ter complexidade adequada para uma petição inicial de nível OAB - 6. Ter entre 15-25 linhas - 7. Focar em um tema aleatório: responsabilidade civil por acidente de consumo, inadimplemento contratual, dano ambiental, direitos da personalidade, vício de produto/serviço, ou danos patrimoniais - Termine com: "Situação problema: Como advogado(a) de [NOME DO CLIENTE], promova a demanda cabível." - """ - +# --- Funções de Validação (Manter simples ou remover/adaptar) --- def validar_peticao_basica(peticao): + # Esta validação é MUITO focada em Petição Inicial. + # Poderia ser desativada ou ter lógica condicional por tipo. + # Por enquanto, vamos mantê-la simples e avisar que é básica. problemas = [] - if "EXCELENTÍSSIMO" not in peticao.upper(): - problemas.append("⚠️ Endereçamento ausente ou incompleto (Art. 319, I, CPC).") - if "Art. 319" not in peticao and "CPC" not in peticao: - problemas.append("⚠️ Fundamentação jurídica não menciona o CPC (Art. 319).") - if "Dá-se à causa o valor" not in peticao: - problemas.append("⚠️ Valor da causa não especificado (Art. 319, V, CPC).") - if "juntada da guia de custas" not in peticao.lower(): - problemas.append("⚠️ Pedido de juntada da guia de custas ausente (-1,0 ponto).") - if "Nestes termos" not in peticao: - problemas.append("⚠️ Fechamento formal ausente.") + peticao_lower = peticao.lower() + if "excelentíssimo" not in peticao_lower and "eméritos" not in peticao_lower and "meritíssimo" not in peticao_lower : + problemas.append("Endereçamento inicial parece ausente ou incompleto.") + #if "art. 319" not in peticao and "cpc" not in peticao_lower: + # problemas.append("Menção ao Art. 319 do CPC não encontrada (comum na Inicial).") + if "valor da causa" not in peticao_lower and "valor à causa" not in peticao_lower: + problemas.append("Atribuição de Valor da Causa não encontrada (essencial na Inicial).") + if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower : + problemas.append("Pedido de juntada de custas ou gratuidade de justiça não encontrado (essencial na Inicial).") + if "nestes termos" not in peticao_lower and "pede deferimento" not in peticao_lower: + problemas.append("Fechamento formal padrão ('Nestes termos pede deferimento') parece ausente.") return problemas -app = interface_gradio() -app.launch() \ No newline at end of file + +# --- Geração de Caso Fictício (Chamada Principal) --- +def gerar_caso_ficticio(tipo_peticao="Petição Inicial"): + """Função principal para gerar caso, chamando a API com o prompt estruturado.""" + if not tipo_peticao: + return "Erro: Tipo de petição não selecionado para gerar caso." + logger.info(f"Gerando caso fictício para: {tipo_peticao}") + prompt = estruturar_prompt_geracao_caso(tipo_peticao) + caso_gerado = chamar_api(prompt) + + # Verifica se a resposta da API foi um erro + if caso_gerado.startswith("❌") or caso_gerado.startswith("⏱️") or caso_gerado.startswith("⚠️"): + logger.error(f"Erro ao gerar caso fictício: {caso_gerado}") + return f"Falha ao gerar caso fictício.\n{caso_gerado}" # Retorna o erro + + # Pequeno pós-processamento para garantir que a frase final esteja lá + frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de" + if frase_final_padrao.lower() not in caso_gerado.lower(): + # Tenta adicionar uma frase genérica se a específica falhar + logger.warning("Frase 'Situação-Problema' não encontrada na geração do caso. Adicionando manualmente.") + # Extrai um nome de cliente potencial do texto gerado + nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado) # Tenta achar nomes compostos + 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 + +if __name__ == "__main__": + + app = interface_gradio() + app.launch() \ No newline at end of file