Download app.py from GabrielGD/CAPI: direct link, hf CLI and curl.
- Browser
- Download file 67.7 kB
-
https://huggingface.co/spaces/GabrielGD/CAPI/resolve/bf9a75475524230b69dbfa933ea2d1095ce42185/app.py
- Command line
-
hf download hf://spaces/GabrielGD/CAPI@bf9a75475524230b69dbfa933ea2d1095ce42185/app.py
-
curl -L -o app.py https://huggingface.co/spaces/GabrielGD/CAPI/resolve/bf9a75475524230b69dbfa933ea2d1095ce42185/app.py
67.7 kB
| import gradio as gr | |
| import requests | |
| import os | |
| import json | |
| import docx | |
| import io | |
| import time | |
| import tempfile | |
| from dotenv import load_dotenv | |
| from pdfplumber import open as pdfplumber_open | |
| from PIL import Image | |
| import pytesseract | |
| try: | |
| import cv2 | |
| import numpy as np | |
| CV2_AVAILABLE = True | |
| except ImportError: | |
| CV2_AVAILABLE = False | |
| print("Aviso: OpenCV (cv2) não encontrado. Pré-processamento de imagem para OCR será limitado.") | |
| from datetime import datetime | |
| from ratelimiter import RateLimiter | |
| import difflib | |
| from html import escape | |
| import random | |
| import logging | |
| import traceback | |
| import re | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| load_dotenv() | |
| GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or os.environ.get("GEMINI_API_KEY") | |
| if not GEMINI_API_KEY: | |
| raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a no arquivo .env ou como Secret no Hugging Face.") | |
| GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}" | |
| logger.info(f"Usando API URL: ...{GEMINI_API_URL[-50:]}") | |
| CONFIG = {"max_retry_attempts": 3, "retry_delay": 5, "timeout": 120, "temperatura": 0.3, "max_tokens": 8192} | |
| rate_limiter = RateLimiter(max_calls=20, period=60) | |
| TIPOS_DE_PETICAO = ["Petição Inicial", "Contestação", "Reconvenção", "Impugnação à Contestação", "Embargos de Declaração", "Recurso de Apelação", "Agravo de Instrumento", "Petição de Tutela Provisória"] | |
| DICAS_OAB_MELHORADO = { | |
| "Clareza e Concisão": "Seja direto e evite jargões excessivos.", | |
| "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes.", | |
| "Revisão Ortográfica": "Erros de português prejudicam a credibilidade.", | |
| "Endereçamento (Inicial)": "Verifique a competência (Art. 46 CPC / Art. 101 CDC).", | |
| "Pedidos (Inicial)": "Certos, determinados e líquidos (se possível).", | |
| "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes do mérito.", | |
| "Impugnação Específica (Contestação)": "Conteste cada fato alegado (Art. 341, CPC)." | |
| } | |
| HISTORICO = [] | |
| CORRECAO_CACHE = {} | |
| def preprocess_image_for_ocr(img_pil): | |
| if not CV2_AVAILABLE: | |
| logger.warning("OpenCV não disponível, usando conversão simples para OCR.") | |
| return img_pil.convert('L') | |
| try: | |
| img_cv = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2GRAY) | |
| _, thresh = cv2.threshold(img_cv, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) | |
| return Image.fromarray(thresh) | |
| except Exception as e: | |
| logger.error(f"Erro no pré-processamento da imagem com OpenCV: {str(e)}") | |
| return img_pil.convert('L') | |
| def extract_text_from_pdf(file_path_or_obj): | |
| text = "" | |
| ocr_text_parts = [] | |
| try: | |
| logger.info(f"Abrindo PDF: {getattr(file_path_or_obj, 'name', file_path_or_obj)}") | |
| pdf_reader = pdfplumber_open(file_path_or_obj) | |
| logger.info(f"PDF com {len(pdf_reader.pages)} páginas.") | |
| for i, page in enumerate(pdf_reader.pages): | |
| page_text = page.extract_text(x_tolerance=2, y_tolerance=2) | |
| if page_text and len(page_text.strip()) > 20: | |
| text += page_text + "\n" | |
| else: | |
| logger.warning(f"Página {i+1} sem texto significativo extraído, tentando OCR.") | |
| try: | |
| img = page.to_image(resolution=200) | |
| preprocessed_img = preprocess_image_for_ocr(img.original) | |
| page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por', config='--psm 6') | |
| if page_ocr_text and page_ocr_text.strip(): | |
| ocr_text_parts.append(page_ocr_text) | |
| else: | |
| logger.warning(f"OCR retornou vazio para a página {i+1}.") | |
| ocr_text_parts.append(f"[OCR Página {i+1}: Vazio ou falhou]") | |
| except Exception as ocr_e: | |
| logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}") | |
| ocr_text_parts.append(f"[Erro OCR Página {i+1}: {str(ocr_e)}]") | |
| pdf_reader.close() | |
| ocr_text = "\n".join(ocr_text_parts) | |
| if text.strip() and len(text) > len(ocr_text) * 0.7: | |
| logger.info(f"Usando texto extraído do PDF ({len(text)} chars).") | |
| return text | |
| elif ocr_text.strip(): | |
| logger.info(f"Usando texto do OCR do PDF ({len(ocr_text)} chars).") | |
| return ocr_text | |
| elif text.strip(): | |
| logger.info("Usando texto extraído curto como fallback.") | |
| return text | |
| else: | |
| logger.warning("Não foi possível extrair texto significativo do PDF.") | |
| return "⚠️ Não foi possível extrair texto do PDF (extração e OCR falharam)." | |
| except Exception as e: | |
| logger.error(f"Erro geral ao processar o PDF: {str(e)}", exc_info=True) | |
| return f"❌ Erro crítico ao processar o PDF: {str(e)}" | |
| def extract_text_from_docx(file_path_or_obj): | |
| try: | |
| doc = docx.Document(file_path_or_obj) | |
| text = "\n".join([para.text for para in doc.paragraphs if para.text]) | |
| return text if text.strip() else "⚠️ Documento DOCX vazio ou sem texto." | |
| except Exception as e: | |
| logger.error(f"Erro ao processar o DOCX: {str(e)}", exc_info=True) | |
| return f"❌ Erro ao processar o DOCX: {str(e)}" | |
| def extract_text_from_txt(file_path_or_obj): | |
| try: | |
| content = b'' | |
| if isinstance(file_path_or_obj, str): | |
| with open(file_path_or_obj, 'rb') as f: | |
| content = f.read() | |
| elif hasattr(file_path_or_obj, 'read'): | |
| if hasattr(file_path_or_obj, 'seek') and callable(file_path_or_obj.seek): | |
| try: file_path_or_obj.seek(0) | |
| except Exception as seek_e: logger.warning(f"Não foi possível resetar ponteiro do arquivo: {seek_e}") | |
| content = file_path_or_obj.read() | |
| else: | |
| return "❌ Tipo de entrada TXT inválido." | |
| encodings = ['utf-8', 'latin-1', 'cp1252'] | |
| for encoding in encodings: | |
| try: | |
| decoded_text = content.decode(encoding) | |
| logger.info(f"Arquivo TXT decodificado com {encoding}.") | |
| return decoded_text if decoded_text.strip() else "⚠️ Arquivo TXT vazio." | |
| except UnicodeDecodeError: | |
| continue | |
| logger.error("Não foi possível decodificar o arquivo TXT com encodings comuns.") | |
| return "❌ Não foi possível decodificar o arquivo TXT. Verifique o encoding." | |
| except Exception as e: | |
| logger.error(f"Erro ao processar o TXT: {str(e)}", exc_info=True) | |
| return f"❌ Erro ao processar o TXT: {str(e)}" | |
| def process_file(file_obj): | |
| if file_obj is None: | |
| logger.warning("process_file chamado com file_obj=None") | |
| return "⚠️ Nenhum arquivo enviado." | |
| try: | |
| file_path = getattr(file_obj, 'name', None) | |
| if not file_path: | |
| logger.error("Não foi possível obter o nome do arquivo (file_obj.name está ausente).") | |
| return "❌ Erro: Não foi possível identificar o arquivo enviado." | |
| filename = os.path.basename(file_path).lower() | |
| logger.info(f"Processando arquivo: {filename}") | |
| try: | |
| file_size = os.path.getsize(file_path) | |
| logger.info(f"Tamanho do arquivo: {file_size} bytes") | |
| if file_size > 15 * 1024 * 1024: | |
| logger.warning(f"Arquivo {filename} ({file_size} bytes) excede o limite de 15MB.") | |
| return "⚠️ Arquivo muito grande (Máx 15MB)." | |
| except OSError as size_e: | |
| logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}") | |
| if filename.endswith('.pdf'): | |
| return extract_text_from_pdf(file_path) | |
| elif filename.endswith('.docx'): | |
| return extract_text_from_docx(file_path) | |
| elif filename.endswith('.txt'): | |
| return extract_text_from_txt(file_path) | |
| else: | |
| logger.warning(f"Formato de arquivo não suportado: {filename}") | |
| return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT." | |
| except Exception as e: | |
| logger.error(f"Erro GERAL ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True) | |
| return f"❌ Erro inesperado ao processar o arquivo: {str(e)}" | |
| def get_instrucoes_gerais_avaliacao(modo_treinamento=False): | |
| base = """ | |
| **Estrutura da Avaliação:** | |
| 1. **Análise Geral:** Visão geral concisa (2-3 linhas) sobre a adequação da peça ao caso e tipo, consistência e clareza. | |
| 2. **Nota Final:** 0 a 10 (uma casa decimal), com justificativa BREVE dos principais fatores. | |
| 3. **Pontos Positivos:** Liste pelo menos 2-3 aspectos bem executados, com justificativa (legal/técnica). | |
| 4. **Pontos a Melhorar:** Liste **TODOS** os problemas em ordem de gravidade (erros crassos primeiro). Para cada ponto: | |
| * Descrição clara do erro/omissão. | |
| * Fundamentação legal/doutrinária do porquê é um erro. | |
| * **Impacto estimado na nota** (ex: -0.5, -1.0, -2.0). | |
| * Sugestão de correção ou como deveria ter sido feito. | |
| 5. **Análise Específica dos Requisitos:** Avalie os itens chave da peça (listados abaixo para cada tipo). | |
| **Critérios Fundamentais (Aplicáveis a Quase Todos):** | |
| * **Coerência:** A peça responde ao caso/situação processual? Os argumentos são lógicos? | |
| * **Fundamentação:** Usou a lei, doutrina e jurisprudência corretamente? Citou artigos relevantes? | |
| * **Linguagem:** Clareza, objetividade, correção gramatical e formalidade adequada? | |
| * **Pedidos:** São claros, certos, determinados e coerentes com a fundamentação? | |
| * **NÃO INVENTAR FATOS:** Penalidade severa (-3.0 a -5.0) se criar fatos não presentes no caso. | |
| * **NÃO SE IDENTIFICAR:** Nota ZERO se houver nome, OAB, CPF, etc., do aluno na peça (exceto nomes das partes do caso). Use "ADVOGADO(A)", "OAB/...". | |
| """ | |
| if modo_treinamento: | |
| base += """ | |
| **Modo de Treinamento:** | |
| * Para cada Ponto a Melhorar, detalhe AINDA MAIS: | |
| a) Explicação aprofundada do erro e sua consequência prática. | |
| b) Base legal/doutrinária específica. | |
| c) **Exemplo de Redação Corrigida** para aquele trecho específico. | |
| d) Dica prática relevante (use o banco de dicas se aplicável). | |
| e) Possível estratégia alternativa. | |
| * Destaque inconsistências entre a peça e o caso com citações diretas. | |
| * Sugira estratégias jurídicas adicionais ou alternativas viáveis. | |
| """ | |
| return base | |
| def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False): | |
| instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento) | |
| prompt_especifico = f""" | |
| ## Análise de Peça Processual: {tipo_peticao} (Critérios OAB Simulados) | |
| **Caso/Contexto Fornecido:** | |
| ``` | |
| {caso} | |
| ``` | |
| **Peça do Aluno ({tipo_peticao}):** | |
| ``` | |
| {peticao} | |
| ``` | |
| {instrucoes_gerais} | |
| **Requisitos Específicos para {tipo_peticao}:** | |
| """ | |
| if tipo_peticao == "Petição Inicial": | |
| prompt_especifico += """ | |
| - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I). | |
| - Qualificação completa das partes (Art. 319, II, CPC). | |
| - Exposição clara e lógica dos fatos (causa de pedir remota). | |
| - Fundamentação jurídica pertinente (causa de pedir próxima - Art. 319, III, CPC). | |
| - Pedidos certos, determinados e líquidos (se possível) (Art. 319, IV, CPC). | |
| - Valor da causa correto e justificado (Art. 291-292, 319, V, CPC). | |
| - Indicação das provas pretendidas (Art. 319, VI, CPC). | |
| - Opção pela audiência de conciliação/mediação (Art. 319, VII, CPC). | |
| - Pedido de citação do réu (forma específica se necessário). | |
| - Requerimento de juntada de documentos essenciais (procuração, custas/JG). | |
| - Fechamento formal ("Nestes termos pede deferimento", Local, data, Advogado, OAB). | |
| **Critérios Adicionais (Inicial):** | |
| * Uso de "XXX" para dados ausentes. Penalizar invenção (-0.5 por ocorrência). | |
| * Falta de pedido de juntada de custas/JG: -1.0 ponto. | |
| * Erro em "Nestes termos, pede deferimento" (com vírgula): -0.2 ponto. | |
| * Analisar foro (Art. 46 CPC vs. Art. 101 CDC): Escolha do foro do consumidor é válida se aplicável; não penalizar, apenas comentar se gerar dificuldades práticas. | |
| """ | |
| elif tipo_peticao == "Contestação": | |
| prompt_especifico += """ | |
| - Endereçamento ao juízo correto onde tramita a ação. | |
| - Qualificação completa do réu (se já não constante nos autos, referenciar). | |
| - Indicação do processo (número). | |
| - Tempestividade (menção implícita ou explícita de que está no prazo). | |
| - **Preliminares (Art. 337, CPC):** Alegar TODAS as aplicáveis (inexistência/nulidade citação, incompetência, inépcia, perempção, litispendência, coisa julgada, conexão, incapacidade, convenção de arbitragem, ausência de legitimidade/interesse, falta de caução, indevida concessão de JG). | |
| - **Impugnação Específica dos Fatos:** Rebater PONTO A PONTO os fatos alegados na inicial (Art. 341, CPC). A falta de impugnação específica torna o fato incontroverso! | |
| - **Mérito:** Apresentar a versão do réu sobre os fatos; apresentar a defesa de mérito direta (negando direito do autor) e/ou indireta (fatos impeditivos, modificativos, extintivos do direito do autor - ex: pagamento, prescrição, compensação). | |
| - Fundamentação jurídica da defesa. | |
| - **Pedidos:** Requerer acolhimento das preliminares (com extinção ou remessa); NO MÉRITO, requerer a IMPROCEDÊNCIA TOTAL dos pedidos do autor; Requerer a condenação do autor nas custas e honorários advocatícios (Art. 85, CPC). | |
| - Indicação das provas que pretende produzir. | |
| - Fechamento formal. | |
| **Critérios Adicionais (Contestação):** | |
| * A não alegação de preliminar arguível (exceto incompetência absoluta, conexão, convenção de arbitragem) preclui? Avaliar impacto. | |
| * Falha grave na impugnação específica: -2.0 a -4.0 pontos. | |
| * Ausência de pedido de improcedência: -1.5 pontos. | |
| """ | |
| elif tipo_peticao == "Reconvenção": | |
| prompt_especifico += """ | |
| - Apresentada NA MESMA PEÇA da Contestação (Art. 343, CPC). | |
| - Endereçamento ao mesmo juízo. | |
| - Indicação do processo. | |
| - Qualificação completa do réu-reconvinte e do autor-reconvindo (podem ser referenciadas se já completas). | |
| - **Conexão com a Ação Principal ou Fundamento de Defesa:** O pedido da reconvenção deve ser conexo (Art. 343, caput). | |
| - **Estrutura Similar à Petição Inicial:** Fatos, Fundamentação, Pedidos específicos, Valor da causa, Provas. | |
| - Pedido de intimação do autor-reconvindo para contestar a reconvenção (na pessoa de seu advogado - Art. 343, §1º). | |
| - Fechamento formal (pode ser conjunto com a contestação). | |
| **Critérios Adicionais (Reconvenção):** | |
| * Ausência de conexão clara: -2.0 pontos. Falta de pedido/valor da causa/provas específicos: -1.0 por item. Apresentada em peça separada (erro formal grave): -3.0 pontos. | |
| """ | |
| elif tipo_peticao == "Impugnação à Contestação": | |
| prompt_especifico += """ | |
| - Endereçamento ao juízo correto. Indicação do processo. Qualificação do autor (impugnante). Tempestividade (15 dias - Art. 350/351 CPC). | |
| - **Resposta às Preliminares:** Rebater cada preliminar arguida. | |
| - **Resposta a Fatos Impeditivos/Modificativos/Extintivos:** Manifestar-se sobre fatos novos (Art. 350). | |
| - **Resposta a Documentos Novos:** Manifestar-se sobre documentos juntados (Art. 437 CPC). | |
| - **Ratificação da Inicial:** Reiterar os termos e pedidos da inicial. | |
| - **Especificação de Provas:** Indicar provas para contrapor defesa indireta/preliminares (Art. 350/351). | |
| - Pedidos: Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial. Fechamento formal. | |
| **Critérios Adicionais (Impugnação):** | |
| * Não responder a preliminar relevante: -1.5 pontos. Não se manifestar sobre fato novo: -2.0 pontos. Não ratificar a inicial: -0.5 pontos. | |
| """ | |
| elif tipo_peticao == "Embargos de Declaração": | |
| prompt_especifico += """ | |
| - **Contra Decisão Específica:** Indicar claramente qual decisão está sendo embargada. Endereçamento ao PRÓPRIO juízo prolator. Indicação do processo. Qualificação das partes. Tempestividade (5 dias - Art. 1023 CPC). | |
| - **Cabimento (Art. 1022, CPC):** Apontar **PRECISAMENTE** e **FUNDAMENTADAMENTE** a ocorrência de: Omissão, Contradição, Obscuridade, Erro Material. | |
| - **NÃO PODE REJULGAR A CAUSA:** Objetivo é sanar vício, não modificar o mérito (salvo efeitos infringentes excepcionais). | |
| - **Pedido:** Requerer o conhecimento e ACOLHIMENTO para sanar o vício (com ou sem efeitos infringentes, se for o caso). Fechamento formal. | |
| **Critérios Adicionais (Embargos):** | |
| * Usar para rediscutir mérito (erro grave): -4.0 pontos. Não apontar vício claramente: -3.0 pontos. Endereçar ao tribunal (erro): -1.0 ponto. | |
| """ | |
| elif tipo_peticao == "Recurso de Apelação": | |
| prompt_especifico += """ | |
| - **Peça de Interposição:** Endereçamento ao juízo a quo. Indicação do processo. Qualificação das partes. Pressupostos (cabimento - Art. 1009; tempestividade - 15 dias Art. 1003, §5º; preparo/JG; regularidade). Pedido de recebimento e remessa ao Tribunal. | |
| - **Razões Recursais:** Endereçamento ao TRIBUNAL competente. Exposição dos fatos e da sentença. **Fundamentos para Reforma/Anulação (Art. 1010, II e III):** Error in procedendo (anulação) ou Error in judicando (reforma). **Princípio da Dialeticidade:** Impugnar ESPECIFICAMENTE os fundamentos da sentença. | |
| - **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão da sucumbência. Fechamento formal. | |
| **Critérios Adicionais (Apelação):** | |
| * Falta de interposição ou razões: Nota zero. Endereçamento incorreto das razões: -1.0. Ausência de dialeticidade: -3.0 a -5.0. Não pedir anulação/reforma: -1.5. Falta de preparo/JG: -2.0. | |
| """ | |
| elif tipo_peticao == "Agravo de Instrumento": | |
| prompt_especifico += """ | |
| - **Peça Única:** Endereçamento direto ao TRIBUNAL. Indicação processo origem. Qualificação partes e advogados (com endereços - Art. 1016, IV). Cópia da decisão agravada (OBRIGATÓRIA - Art. 1017, I). | |
| - **Cabimento (Art. 1015 ou taxatividade mitigada):** Demonstrar hipótese legal ou urgência (Tema 988 STJ). Tempestividade (15 dias). Preparo/JG. | |
| - **Peças Obrigatórias (Art. 1017, I):** Decisão agravada, certidão de intimação, procurações. Peças Facultativas (Art. 1017, III). | |
| - **Razões (Art. 1016, II e III):** Exposição do fato e direito; razões para reforma/invalidação da decisão interlocutória. | |
| - **Pedido de Efeito Suspensivo ou Tutela Antecipada Recursal (Art. 1019, I):** Se houver risco, pedir fundamentadamente. | |
| - **Pedidos Finais:** Conhecimento; (se aplicável) efeito suspensivo/tutela antecipada; PROVIMENTO para reformar/invalidar; comunicação ao juiz (Art. 1018 - opcional se eletrônico). Fechamento formal. | |
| **Critérios Adicionais (Agravo):** | |
| * Ausência de peça obrigatória: Inadmissibilidade (-4.0). Não demonstrar cabimento: -3.0. Endereçar ao juiz a quo: -1.0. Não indicar advogados: -0.5. | |
| """ | |
| elif tipo_peticao == "Petição de Tutela Provisória": | |
| prompt_especifico += """ | |
| - **Tipo (Urgência ou Evidência):** Deixar claro. Antecedente ou incidental. Endereçamento (competente ou da causa). Qualificação partes. Indicação processo (se incidental). | |
| - **Tutela de Urgência (Art. 300):** **Fumus Boni Iuris** (probabilidade do direito) E **Periculum in Mora** (perigo de dano/risco ao resultado útil). **Reversibilidade** (Art. 300, §3º - se possível). | |
| - **Tutela de Evidência (Art. 311):** Demonstrar uma das hipóteses (I a IV). **NÃO precisa periculum in mora.** | |
| - **Pedido Específico:** Indicar CLARAMENTE a medida liminar solicitada. | |
| - **Se Antecedente (Art. 303 ou 305):** Indicar pedido final ou aditar/apresentar inicial no prazo. Valor da Causa. | |
| - **Pedidos:** Concessão da tutela (liminar/justificação prévia); citação/intimação; (se antecedente) indicação da ação principal/aditamento. Fechamento formal. | |
| **Critérios Adicionais (Tutela):** | |
| * Não demonstrar Fumus/Periculum (Urgência): -3.0 a -5.0. Não enquadrar na Evidência: -3.0. Pedido genérico: -2.0. Confundir requisitos: -1.5. | |
| """ | |
| else: | |
| prompt_especifico += """ | |
| - **Adequação ao Tipo:** A peça corresponde à estrutura e finalidade do tipo? | |
| - **Clareza:** É possível entender a pretensão? | |
| - **Fundamentação Mínima:** Tentativa de basear em lei ou fatos? | |
| - **Pedido:** Existe um pedido? | |
| """ | |
| prompt_especifico += "\n\n**Instruções Finais:** Analise a peça do aluno estritamente com base no caso/contexto fornecido e nos critérios acima. Seja rigoroso como um examinador da OAB, mas forneça feedback construtivo e detalhado. Gere a resposta **exclusivamente** no formato solicitado." | |
| return prompt_especifico | |
| def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao): | |
| prompt = f""" | |
| **Caso/Contexto Fornecido:** | |
| ``` | |
| {caso} | |
| ``` | |
| **Peça Original do Aluno ({tipo_peticao}):** | |
| ``` | |
| {peticao_original} | |
| ``` | |
| **Tarefa:** Com base no caso e nos problemas da peça original, **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Siga **rigorosamente** as melhores práticas e requisitos formais/materiais para este tipo de peça no exame da OAB. | |
| **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):** | |
| """ | |
| if tipo_peticao == "Petição Inicial": | |
| prompt += """ | |
| 1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável). | |
| 2. **Qualificação:** Completa (Art. 319, II), usando "XXX" para dados ausentes. | |
| 3. **Fatos:** Narre de forma cronológica, clara e fiel ao caso. | |
| 4. **Fundamentação:** Cite artigos pertinentes (CC, CDC, CPC) e súmulas/jurisprudência, se aplicável. | |
| 5. **Pedidos:** Claros, certos, determinados (quantificar danos se possível). Cumulados se cabível. | |
| 6. **Valor da Causa:** Correto (soma dos benefícios - Art. 292). | |
| 7. **Provas:** Indicar ("protesta provar o alegado por todos os meios..."). | |
| 8. **Audiência:** Indicar opção (Art. 319, VII). | |
| 9. **Citação:** Pedido de citação (ex: "via postal, Art. 246, I, CPC"). | |
| 10. **Custas:** Requerimento expresso de juntada da guia OU pedido de gratuidade (com pedido de declaração). **NUNCA OMITIR.** | |
| 11. **Fechamento:** "Nestes termos pede deferimento." (SEM vírgula). Seguido de "Local, data.", "ADVOGADO(A)", "OAB/...". **NUNCA identificar.** | |
| """ | |
| elif tipo_peticao == "Contestação": | |
| prompt += """ | |
| 1. **Endereçamento:** Ao juízo da ação. | |
| 2. **Identificação:** Número do processo, qualificação do réu. | |
| 3. **Tempestividade:** Mencionar ("A presente contestação é tempestiva..."). | |
| 4. **Estrutura:** Síntese da Inicial; Preliminares (Art. 337, fundamentadas); Mérito (Impugnação Específica + Defesa de Mérito Direta/Indireta com fundamentos); Reconvenção (Se houver, ANTES dos pedidos da contestação). | |
| 5. **Pedidos da Contestação:** Acolhimento das preliminares (com consequência); **IMPROCEDÊNCIA TOTAL** dos pedidos do autor; Condenação em custas e honorários. | |
| 6. **Provas:** Indicar as provas do réu. | |
| 7. **Fechamento:** Padrão OAB. | |
| """ | |
| elif tipo_peticao == "Reconvenção": | |
| prompt += """ | |
| **IMPORTANTE:** Gere a Reconvenção como parte da Contestação. | |
| 1. **Após a defesa de mérito da Contestação e ANTES dos pedidos da Contestação**, insira: "DA RECONVENÇÃO". | |
| 2. **Qualificação:** Réu-Reconvinte e Autor-Reconvindo. | |
| 3. **Conexão:** Justifique brevemente. | |
| 4. **Fatos e Direito Reconvencionais:** Narre e fundamente o pedido do reconvinte. | |
| 5. **Pedidos da Reconvenção:** Claros e específicos contra o autor-reconvindo. | |
| 6. **Valor da Causa e Provas da Reconvenção:** Específicos. | |
| 7. **Intimação:** Peça a intimação do autor-reconvindo para contestar. | |
| 8. **Continue com os Pedidos da Contestação**. | |
| 9. **Fechamento:** Padrão OAB. | |
| """ | |
| elif tipo_peticao == "Impugnação à Contestação": | |
| prompt += """ | |
| 1. Endereçamento, processo, qualificação autor, tempestividade. | |
| 2. Tópico: **DA(S) PRELIMINAR(ES) ARGUIDA(S)** - Rebater cada uma. | |
| 3. Tópico: **DO MÉRITO E DOS FATOS NOVOS** - Manifestar-se sobre fatos impeditivos/modificativos/extintivos e documentos. | |
| 4. Tópico: **DA RATIFICAÇÃO DA INICIAL** - Reiterar termos e pedidos. | |
| 5. Tópico: **DAS PROVAS** - Especificar provas contrapostas. | |
| 6. **Pedidos:** Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial. | |
| 7. Fechamento padrão OAB. | |
| """ | |
| elif tipo_peticao == "Embargos de Declaração": | |
| prompt += """ | |
| 1. Endereçamento ao juízo prolator. Processo, Embargante, Embargado. Decisão embargada identificada. Tempestividade (5 dias). | |
| 2. Tópico para cada vício (Art. 1022): **DA OMISSÃO / DA CONTRADIÇÃO / DA OBSCURIDADE / DO ERRO MATERIAL**. | |
| 3. Dentro de cada tópico: **Transcrever trecho relevante da decisão** e explicar **objetivamente** o vício. NÃO REJULGAR. | |
| 4. **Pedido:** Conhecimento e ACOLHIMENTO para sanar o(s) vício(s) apontado(s), especificando o que se espera. Pedir efeitos infringentes se cabível e fundamentado. | |
| 5. Fechamento padrão OAB. | |
| """ | |
| elif tipo_peticao == "Recurso de Apelação": | |
| prompt += """ | |
| 1. **Peça de Interposição:** Endereçamento ao juízo a quo. Processo, Apelante, Apelado. Pressupostos (cabimento, tempestividade, preparo/JG). Pedido de recebimento e remessa. | |
| 2. **Razões Recursais:** Endereçamento ao Tribunal. Breve relato. **DOS FUNDAMENTOS PARA REFORMA/ANULAÇÃO:** Error in procedendo ou Error in judicando. Impugnar especificamente a sentença (dialeticidade). | |
| 3. **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão sucumbência. | |
| 4. Fechamento padrão OAB. | |
| """ | |
| elif tipo_peticao == "Agravo de Instrumento": | |
| prompt += """ | |
| 1. Peça única ao Tribunal. Processo origem, Agravante, Agravado, Advogados (com endereços). Decisão agravada identificada (juntar cópia). | |
| 2. **DO CABIMENTO:** Demonstrar hipótese Art. 1015 ou mitigação. **DA TEMPESTIVIDADE.** **DO PREPARO/JG.** **DAS PEÇAS OBRIGATÓRIAS E FACULTATIVAS.** | |
| 3. **DAS RAZÕES RECURSAIS:** Exposição fato/direito, motivos para reforma/invalidação da interlocutória. | |
| 4. **DO PEDIDO DE EFEITO SUSPENSIVO / TUTELA ANTECIPADA RECURSAL** (se cabível e fundamentado). | |
| 5. **Pedidos:** Conhecimento; (se aplicável) efeito suspensivo/tutela; PROVIMENTO para reformar/invalidar; comunicação ao juiz. | |
| 6. Fechamento padrão OAB. | |
| """ | |
| elif tipo_peticao == "Petição de Tutela Provisória": | |
| prompt += """ | |
| 1. Indicar TIPO (Urgência/Evidência) e CARÁTER (Antecedente/Incidental). Endereçamento. Qualificação. Processo (se incidental). | |
| 2. **DOS REQUISITOS:** | |
| * **Urgência:** Tópico **DA PROBABILIDADE DO DIREITO (FUMUS BONI IURIS)** e tópico **DO PERIGO DE DANO OU RISCO AO RESULTADO ÚTIL (PERICULUM IN MORA)**. Se possível, **DA REVERSIBILIDADE**. | |
| * **Evidência:** Tópico **DA HIPÓTESE DE EVIDÊNCIA (ART. 311, INCISO __)** - Demonstrar o enquadramento. | |
| 3. **DO PEDIDO LIMINAR:** Descrever CLARAMENTE a medida solicitada. | |
| 4. **Se Antecedente:** Indicar pedido final ou aditamento/inicial completa. Valor da Causa. | |
| 5. **Pedidos:** Concessão da tutela; citação/intimação; (se antecedente) indicação ação principal/aditamento. | |
| 6. Fechamento padrão OAB. | |
| """ | |
| else: | |
| prompt += """ | |
| 1. Estrutura Lógica: Começo, meio, fim. | |
| 2. Argumentos Claros. | |
| 3. Pedido Claro. | |
| 4. Formalidades: Linguagem adequada, fechamento OAB. | |
| """ | |
| prompt += "\n\n**Instrução Final:** Gere **APENAS** o texto completo da peça corrigida abaixo, começando pelo endereçamento e terminando com 'OAB/...'. Não inclua NENHUM comentário, introdução ou explicação ANTES ou DEPOIS do texto da peça." | |
| return prompt | |
| def pos_corrigir_peticao(peticao_corrigida, tipo_peticao): | |
| if "Nestes termos, pede deferimento" in peticao_corrigida: | |
| peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento") | |
| if tipo_peticao == "Petição Inicial": | |
| peticao_lower = peticao_corrigida.lower() | |
| if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower: | |
| partes = peticao_corrigida.split("Nestes termos pede deferimento") | |
| if len(partes) == 2: | |
| peticao_corrigida = partes[0].strip() + "\nRequer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento" + partes[1] | |
| else: | |
| peticao_corrigida = peticao_corrigida.replace("Local, data.", "Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.") | |
| if "audiência de conciliação" not in peticao_lower and "audiência de mediação" not in peticao_lower: | |
| partes = peticao_corrigida.split("Nestes termos pede deferimento") | |
| if len(partes) == 2: | |
| peticao_corrigida = partes[0].strip() + "\nOpta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\nNestes termos pede deferimento" + partes[1] | |
| else: | |
| peticao_corrigida = peticao_corrigida.replace("Local, data.", "Opta o Autor pela realização da audiência de conciliação ou mediação (Art. 319, VII, CPC).\n\nLocal, data.") | |
| linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()] | |
| fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."] | |
| while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]): | |
| linhas.pop() | |
| linhas.extend(fechamento_padrao) | |
| peticao_corrigida = "\n".join(linhas) | |
| return peticao_corrigida | |
| def chamar_api(prompt): | |
| payload = {"contents": [{"parts": [{"text": prompt}]}],"generationConfig": {"temperature": CONFIG["temperatura"],"maxOutputTokens": CONFIG["max_tokens"],"topP": 0.95,}} | |
| headers = {"Content-Type": "application/json"} | |
| logger.info(f"Chamando API Gemini. Temp={CONFIG['temperatura']}, MaxTokens={CONFIG['max_tokens']}") | |
| for tentativa in range(CONFIG["max_retry_attempts"]): | |
| try: | |
| with rate_limiter: | |
| logger.debug(f"API Call - Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}") | |
| response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"]) | |
| logger.debug(f"API Response - Status: {response.status_code}") | |
| if response.status_code == 429: | |
| retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1))) | |
| logger.warning(f"Erro 429 (Too Many Requests). Aguardando {retry_after}s...") | |
| if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(retry_after); continue | |
| else: return "❌ Erro 429: Muitas requisições. Tente novamente mais tarde." | |
| if response.status_code != 200: | |
| error_msg = "Erro desconhecido na API" | |
| try: | |
| error_info = response.json() | |
| error_msg = error_info.get("error", {}).get("message", error_msg) | |
| except json.JSONDecodeError: | |
| error_msg = f"Erro {response.status_code}: {response.text[:200]}" # Mostra início do erro não-JSON | |
| logger.error(f"API Error (Status {response.status_code}): {error_msg}") | |
| if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue | |
| return f"❌ Erro da API ({response.status_code}): {error_msg}" | |
| response_data = response.json() | |
| if not response_data.get("candidates"): | |
| prompt_feedback = response_data.get("promptFeedback", {}) | |
| block_reason = prompt_feedback.get("blockReason") | |
| logger.error(f"API Response sem 'candidates'. Block Reason: {block_reason}") | |
| if block_reason: return f"❌ A solicitação foi bloqueada pela API. Motivo: {block_reason}. Tente reformular." | |
| else: return "❌ Resposta vazia ou inválida da API (sem 'candidates')." | |
| candidate = response_data["candidates"][0] | |
| finish_reason = candidate.get("finishReason") | |
| if finish_reason and finish_reason not in ["STOP", "MAX_TOKENS", "FINISH_REASON_UNSPECIFIED"]: | |
| logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}") | |
| safety_ratings = candidate.get("safetyRatings", []) | |
| harm_categories = [r['category'] for r in safety_ratings if r['probability'] not in ['NEGLIGIBLE', 'LOW']] | |
| if harm_categories: return f"❌ Resposta bloqueada por segurança. Motivo: {finish_reason} ({', '.join(harm_categories)})." | |
| else: return f"❌ Geração interrompida. Motivo: {finish_reason}." | |
| if not candidate.get("content") or not candidate["content"].get("parts"): | |
| logger.error("Estrutura de resposta inesperada: Faltando content/parts.") | |
| if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue | |
| return "❌ Resposta inválida da API (estrutura inesperada)." | |
| texto_gerado = candidate["content"]["parts"][0]["text"].strip() | |
| logger.info(f"API retornou com sucesso ({len(texto_gerado)} chars). Finish Reason: {finish_reason}") | |
| return texto_gerado | |
| except requests.Timeout: | |
| logger.warning(f"Timeout na API - Tentativa {tentativa + 1}.") | |
| if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue | |
| return "⏱️ Tempo esgotado na comunicação com a API." | |
| except requests.RequestException as e: | |
| logger.error(f"Erro de Rede/Requisição API - Tentativa {tentativa + 1}: {e}") | |
| if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue | |
| return f"❌ Erro de conexão com a API: {e}" | |
| except json.JSONDecodeError as e: | |
| logger.error(f"Erro ao decodificar JSON da API: {e}") | |
| logger.debug(f"Resposta não-JSON: {response.text[:500]}") | |
| return f"❌ Erro ao processar a resposta da API (JSON inválido)." | |
| except Exception as e: | |
| logger.exception(f"Erro inesperado na chamada da API - Tentativa {tentativa + 1}: {str(e)}") | |
| if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue | |
| return f"❌ Erro inesperado no processamento da API: {str(e)}" | |
| logger.error("Falha ao chamar a API após todas as tentativas.") | |
| return "❌ Falha ao comunicar com a API após múltiplas tentativas." | |
| def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False): | |
| logger.info(f"Iniciando correção. Tipo={tipo_peticao}, Treinamento={modo_treinamento}") | |
| start_time = time.time() | |
| analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html = "", "", "", "", "" | |
| peticao_conteudo = "" | |
| try: | |
| if not caso or not caso.strip(): | |
| logger.warning("Caso não fornecido.") | |
| return "⚠️ Forneça o caso/contexto da peça.", "", "", "", "" | |
| if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: | |
| logger.warning(f"Tipo de petição inválido ou não fornecido: {tipo_peticao}") | |
| return "⚠️ Selecione um tipo de petição válido.", "", "", "", "" | |
| if peticao_arquivo is not None: | |
| logger.info("Processando arquivo de petição...") | |
| peticao_conteudo = process_file(peticao_arquivo) | |
| if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"): | |
| logger.error(f"Erro ao processar arquivo: {peticao_conteudo}") | |
| return peticao_conteudo, "", "", "", "" | |
| elif peticao_texto and peticao_texto.strip(): | |
| peticao_conteudo = peticao_texto | |
| else: | |
| logger.warning("Nenhuma petição fornecida (texto ou arquivo).") | |
| return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", "" | |
| logger.info(f"Petição recebida ({len(peticao_conteudo)} chars).") | |
| troll_triggers = ["escreva a petição", "faça a petição", "redija a petição", "elabore a petição", "crie a petição", "gere a petição", "faça para mim", "faça por mim"] | |
| if any(trigger in peticao_conteudo.lower() for trigger in troll_triggers): | |
| logger.info("Detectado pedido para escrever petição.") | |
| return random.choice([ | |
| "Hummm... Parece que você quer que *eu* faça o trabalho, né? 😉 Que tal você tentar escrever a peça e *eu* te ajudo a corrigir?", | |
| "🚫 Opa! Meu trabalho é *corrigir* e *ensinar*, não fazer a peça do zero. Mostre o que você fez e eu te dou o feedback!", | |
| ]), "", "", "", "" | |
| cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}" | |
| if cache_key in CORRECAO_CACHE: | |
| logger.info("Correção encontrada no cache.") | |
| cached_data = CORRECAO_CACHE[cache_key] | |
| return cached_data["analise_formatada"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"], cached_data["diff_html"] | |
| logger.info("Estruturando prompt de análise...") | |
| prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento) | |
| logger.info("Chamando API para análise...") | |
| analise_bruta = chamar_api(prompt_analise) | |
| if analise_bruta.startswith(("❌", "⏱️", "⚠️")): | |
| logger.error(f"Erro retornado pela API (Análise): {analise_bruta}") | |
| return analise_bruta, "", "", "", "" | |
| logger.info("Análise recebida. Formatando...") | |
| analise_formatada = formatar_correcao(analise_bruta) | |
| logger.info("Estruturando prompt para peça corrigida...") | |
| prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao) | |
| logger.info("Chamando API para gerar peça corrigida...") | |
| peticao_corrigida_bruta = chamar_api(prompt_peticao) | |
| if peticao_corrigida_bruta.startswith(("❌", "⏱️", "⚠️")): | |
| logger.error(f"Erro retornado pela API (Correção): {peticao_corrigida_bruta}") | |
| peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}" | |
| else: | |
| logger.info("Peça corrigida recebida. Aplicando pós-correção...") | |
| start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"] | |
| start_index = -1 | |
| peticao_lower_corr = peticao_corrigida_bruta.lower() | |
| for marker in start_markers: | |
| idx = peticao_lower_corr.find(marker) | |
| if idx != -1: start_index = idx; break | |
| peticao_extraida = peticao_corrigida_bruta[start_index:] if start_index != -1 else peticao_corrigida_bruta | |
| peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao) | |
| logger.info("Pós-correção aplicada.") | |
| logger.info("Calculando diferenças...") | |
| diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final) | |
| logger.info("Diferenças calculadas.") | |
| resultado_cache = {"analise_formatada": analise_formatada,"peticao_corrigida": peticao_corrigida_final,"resumo_diff": resumo_diff,"criticas_diff": criticas_diff,"diff_html": diff_html} | |
| CORRECAO_CACHE[cache_key] = resultado_cache | |
| HISTORICO.append({"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"tipo_peticao": tipo_peticao,"caso": caso,"peticao_original": peticao_conteudo,**resultado_cache}) | |
| if len(HISTORICO) > 10: HISTORICO.pop(0) | |
| end_time = time.time() | |
| logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.") | |
| return analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html | |
| except Exception as e: | |
| logger.exception("Erro GERAL inesperado dentro de corrigir_peticao:") | |
| error_message = f"❌ Erro inesperado na função de correção: {str(e)}\nConsulte os logs para detalhes." | |
| return error_message, "", "", "", "" | |
| def formatar_correcao(texto): | |
| texto = texto.replace("### Análise Geral", "## 📝 Análise Geral") | |
| texto = texto.replace("### Nota Final", "## ⭐ Nota Final") | |
| texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos") | |
| texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar") | |
| texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos") | |
| linhas = texto.split('\n') | |
| linhas_formatadas = [] | |
| in_list = False | |
| for linha in linhas: | |
| linha_strip = linha.strip() | |
| if linha_strip.startswith(("1.","2.","3.","4.","5.")): | |
| linhas_formatadas.append(f"\n**{linha_strip}**") | |
| in_list = False | |
| elif linha_strip.startswith(("- ","* ")): | |
| prefix = " * " if in_list else "* " | |
| linhas_formatadas.append(prefix + linha_strip[2:]) | |
| in_list = True | |
| elif linha_strip.startswith(("a)","b)","c)","d)","e)")): | |
| linhas_formatadas.append(f" * {linha_strip}") | |
| in_list = True | |
| else: | |
| linhas_formatadas.append(linha) | |
| if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")): | |
| in_list = False | |
| texto_final = "\n".join(linhas_formatadas) | |
| nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})" | |
| match = re.search(nota_pattern, texto_final, re.IGNORECASE) | |
| if match: | |
| nota = match.group(2).replace(",",".") | |
| texto_final = re.sub(nota_pattern, f"\\1**{nota}/10**", texto_final, count=1, flags=re.IGNORECASE) | |
| return texto_final | |
| def fornecer_dica_contextual(texto_peticao, tipo_peticao): | |
| if not texto_peticao or not texto_peticao.strip(): | |
| return "*Digite algo na peça para receber dicas...*" | |
| dicas_relevantes = [] | |
| texto_lower = texto_peticao.lower() | |
| for chave in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]: | |
| if chave in DICAS_OAB_MELHORADO: | |
| dicas_relevantes.append(f"💡 **{chave}:** {DICAS_OAB_MELHORADO[chave]}") | |
| keywords_por_tipo = { | |
| "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor", "provas", "audiência", "citação", "custas"], | |
| "Contestação": ["preliminar", "mérito", "impugnação", "improcedência", "honorários", "art. 337", "art. 341"], | |
| "Reconvenção": ["reconvinte", "reconvindo", "conexão", "art. 343"], | |
| "Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica", "art. 350"], | |
| "Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "art. 1022"], | |
| "Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "art. 1009"], | |
| "Agravo de Instrumento": ["interlocutória", "cabimento", "urgência", "efeito suspensivo", "art. 1015", "art. 1017"], | |
| "Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "art. 300", "art. 311"], | |
| } | |
| if tipo_peticao in keywords_por_tipo: | |
| for chave, dica in DICAS_OAB_MELHORADO.items(): | |
| chave_lower = chave.lower() | |
| if f"({tipo_peticao.split()[0].lower()})" in chave_lower or \ | |
| any(kw in chave_lower for kw in keywords_por_tipo[tipo_peticao] if kw in texto_lower): | |
| dica_formatada = f"💡 **{chave}:** {dica}" | |
| if dica_formatada not in dicas_relevantes: | |
| dicas_relevantes.append(dica_formatada) | |
| if not dicas_relevantes: | |
| return "*Continue escrevendo ou selecione o tipo de peça para dicas mais específicas.*" | |
| return "\n\n".join(dicas_relevantes[:4]) | |
| def calcular_diff_interno(texto_original, texto_corrigido): | |
| if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")): | |
| return "","","" | |
| orig_lines = texto_original.splitlines() | |
| corr_lines = texto_corrigido.splitlines() | |
| diff = difflib.ndiff(orig_lines, corr_lines) | |
| resultado_diff_html = [] | |
| criticas = [] | |
| linhas_removidas = 0 | |
| linhas_adicionadas = 0 | |
| keywords_criticas_removidas = ["invenção", "identificação", "erro crasso", "incorreto", "prazo"] | |
| keywords_criticas_adicionadas = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "essencial", "preliminar", "tempestividade", "cabimento"] | |
| for linha in diff: | |
| texto = linha[2:] | |
| texto_escaped = escape(texto) | |
| if linha.startswith(' '): resultado_diff_html.append(f"<div class='linha-igual'>{texto_escaped}</div>") | |
| elif linha.startswith('- '): | |
| linhas_removidas += 1 | |
| resultado_diff_html.append(f"<div class='linha-removida' title='Removido'><del>{texto_escaped}</del></div>") | |
| if texto.strip() and any(kw in texto.lower() for kw in keywords_criticas_removidas): criticas.append(f"<span style='color: red;'>🔴 REMOVIDO:</span> {texto_escaped}") | |
| elif linha.startswith('+ '): | |
| linhas_adicionadas += 1 | |
| resultado_diff_html.append(f"<div class='linha-adicionada' title='Adicionado'><ins>{texto_escaped}</ins></div>") | |
| if texto.strip() and any(kw in texto.lower() for kw in keywords_criticas_adicionadas): criticas.append(f"<span style='color: green;'>🟢 ADICIONADO:</span> {texto_escaped}") | |
| resumo_estatistico = f"**Resumo:**\n* Removidas: {linhas_removidas}\n* Adicionadas: {linhas_adicionadas}\n* **Saldo:** {linhas_adicionadas - linhas_removidas} linhas" | |
| criticas_texto = "### 🚨 Mudanças Críticas / Essenciais:\n" + "\n".join(criticas) if criticas else "*Nenhuma mudança crítica/essencial detectada.*" | |
| css = """<style>.diff-container { font-family: monospace; white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; max-height: 600px; overflow-y: auto;} .linha-igual { color: #555; margin: 1px 0; } .linha-removida { background-color: #ffebee; color: #c62828; margin: 1px 0; } .linha-adicionada { background-color: #e8f5e9; color: #2e7d32; margin: 1px 0; } .linha-removida del, .linha-adicionada ins { text-decoration: none; padding: 0 2px; border-radius: 2px; } .linha-removida del { background-color: #ffcdd2; } .linha-adicionada ins { background-color: #c8e6c9; }</style>""" | |
| visualizacao_detalhada_html = f"{css}<div class='diff-container'>{''.join(resultado_diff_html)}</div>" | |
| return visualizacao_detalhada_html, resumo_estatistico, criticas_texto | |
| def calcular_progresso(): | |
| if not HISTORICO: return "📊 Nenhum progresso registrado ainda." | |
| notas = [] | |
| tipos_corrigidos = set() | |
| for entry in HISTORICO: | |
| tipos_corrigidos.add(entry["tipo_peticao"]) | |
| match = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.IGNORECASE) | |
| if match: | |
| try: notas.append(float(match.group(1).replace(",", "."))) | |
| except ValueError: pass | |
| total_corridas = len(HISTORICO) | |
| media = sum(notas) / len(notas) if notas else 0 | |
| tendencia = "" | |
| if len(notas) > 1: | |
| if notas[-1] > notas[-2]: tendencia = "⬆️ Melhorando" | |
| elif notas[-1] < notas[-2]: tendencia = "⬇️ Piorando" | |
| else: tendencia = "↔️ Estável" | |
| progresso_texto = f"**📊 Resumo:**\n* Correções: {total_corridas}\n* Tipos: {', '.join(sorted(list(tipos_corrigidos)))}\n* Média: {media:.1f}/10 ({len(notas)} válidas)\n* Tendência: {tendencia if tendencia else 'N/A'}" | |
| return progresso_texto | |
| def mostrar_historico(): | |
| if not HISTORICO: return "📜 Histórico vazio." | |
| historico_md = "### 📜 Histórico (Últimas 10)\n---\n" | |
| for i, entry in enumerate(reversed(HISTORICO)): | |
| caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso'] | |
| nota_str = "N/A" | |
| match = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.IGNORECASE) | |
| if match: nota_str = f"{match.group(1).replace(',', '.')}/10" | |
| historico_md += f"**{len(HISTORICO)-i}.** {entry['data']} | **Tipo:** {entry['tipo_peticao']} | **Nota:** {nota_str}\n **Caso:** _{caso_resumo}_\n---\n" | |
| return historico_md | |
| def salvar_progresso(caso, peticao, tipo_peticao): | |
| logger.info("Iniciando salvamento de progresso...") | |
| try: | |
| if not caso or not peticao or not tipo_peticao: | |
| logger.warning("Inputs insuficientes para salvar progresso.") | |
| return "", "⚠️ Caso, Petição e Tipo são necessários para salvar.", None | |
| dados_progresso = {"tipo_peticao": tipo_peticao, "caso": caso, "peticao": peticao, "timestamp": datetime.now().isoformat()} | |
| json_content = json.dumps(dados_progresso, ensure_ascii=False, indent=4) | |
| timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| filename = f"progresso_peticao_{timestamp_str}.json" | |
| temp_dir = tempfile.gettempdir() | |
| temp_file_path = os.path.join(temp_dir, filename) | |
| with open(temp_file_path, 'w', encoding='utf-8') as f: f.write(json_content) | |
| logger.info(f"Progresso salvo com sucesso em: {temp_file_path}") | |
| success_message = f"✅ Progresso salvo! Baixe `{filename}`." | |
| return json_content, success_message, temp_file_path | |
| except Exception as e: | |
| logger.error(f"Erro ao salvar progresso: {str(e)}", exc_info=True) | |
| return "", f"❌ Erro ao salvar: {str(e)}", None | |
| def carregar_progresso(arquivo_entrada): | |
| logger.info("Iniciando carregamento de progresso...") | |
| if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo selecionado." | |
| try: | |
| file_path = arquivo_entrada.name | |
| logger.info(f"Carregando progresso do arquivo: {file_path}") | |
| with open(file_path, 'r', encoding='utf-8') as f: dados = json.load(f) | |
| caso = dados.get("caso") | |
| peticao = dados.get("peticao") | |
| tipo_peticao = dados.get("tipo_peticao") | |
| if caso is None or peticao is None or tipo_peticao is None: | |
| logger.warning(f"Arquivo JSON carregado não contém chaves esperadas: {dados.keys()}") | |
| return "", "", "", "❌ Erro: Arquivo de progresso inválido." | |
| if tipo_peticao not in TIPOS_DE_PETICAO: | |
| logger.warning(f"Tipo de petição '{tipo_peticao}' do arquivo não é válido.") | |
| return caso, peticao, "", f"⚠️ Tipo '{tipo_peticao}' inválido. Selecione manualmente." | |
| logger.info("Progresso carregado com sucesso.") | |
| return tipo_peticao, caso, peticao, "✅ Progresso carregado!" | |
| except json.JSONDecodeError: | |
| logger.error(f"Erro ao decodificar JSON do arquivo: {file_path}") | |
| return "", "", "", "❌ Erro: Arquivo não é JSON válido." | |
| except Exception as e: | |
| logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True) | |
| return "", "", "", f"❌ Erro inesperado ao carregar: {str(e)}" | |
| def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"): | |
| prompt = f""" | |
| Crie um caso fictício **detalhado e realista** para um exercício prático de elaboração de uma peça do tipo **"{tipo_peticao}"**, simulando um cenário de Exame de Ordem (OAB). | |
| **Instruções Gerais:** | |
| 1. Complexidade Adequada (15-25 linhas). | |
| 2. Dados Essenciais: Nomes, locais, datas, valores, relação jurídica, conflito. | |
| 3. Coerência: Fatos lógicos justificando a peça. | |
| 4. Foco no Tipo de Peça: Cenário deve exigir a **{tipo_peticao}**. | |
| **Instruções Específicas para "{tipo_peticao}":** | |
| """ | |
| if tipo_peticao == "Petição Inicial": | |
| prompt += """* Descreva violação de direitos (contrato descumprido, dano, etc.) onde cliente busca tutela inicial. * Inclua elementos para danos materiais/morais. * Forneça dados para qualificação das partes. * **Exemplo Tema:** Consumo, inadimplemento, cobrança indevida, despejo.""" | |
| elif tipo_peticao == "Contestação": | |
| prompt += """* **PRIMEIRO:** Resumo da Petição Inicial recebida pelo réu (autor, alegações, pedidos). * **DEPOIS:** Versão dos fatos do RÉU, incluindo informações para: Alegar preliminares; Impugnar fatos; Apresentar defesa de mérito (pagamento, prescrição, etc.). * Forneça dados qualificação réu.""" | |
| elif tipo_peticao == "Reconvenção": | |
| prompt += """* Siga estrutura **Contestação**: Resumo Petição Inicial + Versão Réu. * **ADICIONE fatos/fundamentos** para RÉU fazer pedido PRÓPRIO contra AUTOR ORIGINAL (CONEXO com ação/defesa). * **Exemplo:** Cobrança -> Réu contesta e reconvém pedindo indenização por danos no mesmo negócio.""" | |
| elif tipo_peticao == "Impugnação à Contestação": | |
| prompt += """* Resumo Petição Inicial do Autor. * **Resumo Contestação** do réu, destacando: Preliminares arguidas; Fatos novos (impeditivos/modificativos/extintivos); Documentos juntados. * Objetivo: fornecer elementos para Autor rebater contestação.""" | |
| elif tipo_peticao == "Embargos de Declaração": | |
| prompt += """* Breve descrição caso original. * **Apresente DISPOSITIVO ou TRECHO FUNDAMENTAL de DECISÃO JUDICIAL** (sentença/acórdão). * **INDIQUE CLARAMENTE vício** (omissão, contradição, obscuridade, erro material) NESSA DECISÃO, dando contexto. * **Exemplo:** "Sentença julgou X, mas omissa quanto a Y." OU "Acórdão afirmou X e concluiu Y (contradição)." """ | |
| elif tipo_peticao == "Recurso de Apelação": | |
| prompt += """* Descrição caso original. * **Resumo da SENTENÇA** (procedente/improcedente/parcial, fundamentos juiz). * **FORNEÇA ELEMENTOS indicando ERRO na sentença** (in judicando/in procedendo) que justifiquem reforma/anulação. * **Exemplo:** "Sentença ignorou prova X." OU "Juiz indeferiu testemunha chave Y (cerceamento)." * Indique cliente (apelante).""" | |
| elif tipo_peticao == "Agravo de Instrumento": | |
| prompt += """* Descrição caso em andamento. * **Apresente DECISÃO INTERLOCUTÓRIA específica**. * Decisão DEVE se enquadrar no Art. 1015 ou justificar mitigação. * **FORNEÇA ELEMENTOS indicando ERRO** na decisão E, se possível, **URGÊNCIA**. * **Exemplo:** "Juiz indeferiu JG apesar de prova." OU "Deferiu liminar sem comprovação mora." * Indique cliente (agravante).""" | |
| elif tipo_peticao == "Petição de Tutela Provisória": | |
| prompt += """* Situação de conflito com **urgência** (risco iminente) ou direito **evidente**. * **FORNEÇA ELEMENTOS claros demonstrando:** **Urgência:** Probabilidade do direito (fumus) E Perigo de dano (periculum). **Evidência:** Hipótese do Art. 311 (abuso defesa, repetitivo, etc.). * Indique medida específica desejada. * Especifique se antecedente ou incidental.""" | |
| else: | |
| prompt += "* Crie cenário jurídico cível com conflito claro." | |
| prompt += """ | |
| **Finalize o caso com:** | |
| "**Situação-Problema:** Na qualidade de advogado(a) de [Nome do Cliente], elabore a peça processual cabível ({tipo_peticao})." | |
| **Importante:** Evite casos muito específicos. Foque em temas comuns. Seja criativo! | |
| """ | |
| return prompt | |
| def gerar_caso_ficticio(tipo_peticao="Petição Inicial"): | |
| logger.info(f"Solicitada geração de caso para: {tipo_peticao}") | |
| try: | |
| if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: | |
| logger.warning(f"Tentativa de gerar caso com tipo inválido: {tipo_peticao}") | |
| return "⚠️ Erro: Selecione um tipo de petição válido antes de gerar o caso." | |
| logger.info("Estruturando prompt para geração de caso...") | |
| prompt = estruturar_prompt_geracao_caso(tipo_peticao) | |
| logger.info("Chamando API para gerar caso...") | |
| caso_gerado = chamar_api(prompt) | |
| if caso_gerado.startswith(("❌","⏱️","⚠️")): | |
| logger.error(f"Erro da API ao gerar caso: {caso_gerado}") | |
| return f"⚠️ Falha ao gerar caso fictício:\n{caso_gerado}" | |
| frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de" | |
| if frase_final_padrao.lower() not in caso_gerado.lower(): | |
| logger.warning("Frase 'Situação-Problema' não encontrada. Adicionando manualmente.") | |
| nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado) | |
| cliente_nome = nomes_possiveis[0] if nomes_possiveis else "[Cliente]" | |
| caso_gerado += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente_nome}, elabore a peça processual cabível ({tipo_peticao})." | |
| logger.info("Caso fictício gerado com sucesso.") | |
| return caso_gerado | |
| except Exception as e: | |
| logger.exception("Erro inesperado dentro de gerar_caso_ficticio:") | |
| return f"❌ Erro inesperado ao gerar o caso: {str(e)}" | |
| def interface_gradio(): | |
| with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo: | |
| gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️") | |
| gr.Markdown("Analise e aprimore suas peças processuais!") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### ⚙️ Configurações") | |
| tipo_peticao_input = gr.Dropdown(label="1. Selecione o Tipo de Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial") | |
| modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento", value=False) | |
| with gr.Accordion("🛠️ Opções Avançadas da IA", open=False): | |
| temperatura_slider = gr.Slider(minimum=0.0, maximum=1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade") | |
| timeout_slider = gr.Slider(minimum=30, maximum=180, value=CONFIG["timeout"], step=10, label="Timeout API (seg)") | |
| max_tokens_slider = gr.Slider(minimum=2048, maximum=8192, value=CONFIG["max_tokens"], step=256, label="Max Resposta (Tokens)") | |
| config_update_btn = gr.Button("💾 Salvar Configs") | |
| config_status = gr.Textbox(label="Status", interactive=False, lines=1) | |
| with gr.Accordion("📊 Dashboard", open=False): | |
| progresso_output = gr.Markdown("*Atualize para ver estatísticas.*") | |
| progresso_btn = gr.Button("🔄 Atualizar Progresso") | |
| with gr.Accordion("📜 Histórico", open=False): | |
| historico_output = gr.Markdown("*Atualize para ver histórico.*") | |
| historico_btn = gr.Button("🔄 Ver Histórico") | |
| with gr.Accordion("💾📂 Salvar/Carregar", open=False): | |
| salvar_btn = gr.Button("💾 Salvar Caso/Peça") | |
| carregar_btn = gr.Button("📂 Carregar Caso/Peça") | |
| arquivo_progresso_upload = gr.File(label="Selecione .json", file_types=[".json"], scale=2) | |
| progresso_download_file = gr.File(label="Baixar Progresso", interactive=False) | |
| with gr.Column(scale=3): | |
| gr.Markdown("### 1. Forneça o Caso / Contexto") | |
| with gr.Row(): | |
| caso_input = gr.Textbox(label="📋 Caso ou Situação", lines=8, placeholder="Insira o texto COMPLETO do caso...") | |
| caso_btn = gr.Button("🎲 Gerar Caso", variant="secondary", scale=0) | |
| gr.Markdown("### 2. Insira Sua Peça") | |
| with gr.Tabs(): | |
| with gr.TabItem("✏️ Digitar Texto"): | |
| peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=12, placeholder="Cole ou digite sua peça aqui...") | |
| dica_contextual_output = gr.Markdown(value="*Dicas aparecerão aqui...*") | |
| btn_corrigir = gr.Button("🚀 CORRIGIR E ANALISAR!", variant="primary") | |
| with gr.TabItem("📎 Anexar Arquivo"): | |
| peticao_arquivo_input = gr.File(label="Arquivo (.pdf, .docx, .txt - Máx 15MB)", file_types=[".pdf", ".docx", ".txt"]) | |
| arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Arquivo", lines=4, interactive=False) | |
| btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary") | |
| processing_message = gr.Markdown("", visible=False) | |
| gr.Markdown("--- \n ### 3. Resultados da Correção") | |
| with gr.Tabs(): | |
| with gr.TabItem("📊 Análise e Correção Sugerida"): | |
| resultado_analise_output = gr.Markdown(label="Análise Detalhada e Nota") | |
| resultado_peticao_corrigida_output = gr.Textbox(label="✨ Versão Sugerida", lines=18, show_copy_button=True) | |
| with gr.TabItem("⚖️ Comparativo (Diff)"): | |
| gr.Markdown("### ↔️ Comparativo: Sua Peça vs. Versão Sugerida") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| resumo_alteracoes_output = gr.Markdown(label="Resumo") | |
| criticas_output = gr.Markdown(label="Mudanças Críticas") | |
| with gr.Column(scale=3): | |
| visualizacao_detalhada_output = gr.HTML(label="Alterações Detalhadas") | |
| clear_btn = gr.Button("🗑️ Limpar Campos e Resultados") | |
| caso_btn.click( | |
| fn=lambda: gr.update(value="⏳ Gerando caso...", visible=True), outputs=processing_message | |
| ).then( | |
| fn=gerar_caso_ficticio, inputs=[tipo_peticao_input], outputs=[caso_input] | |
| ).then( | |
| fn=lambda: gr.update(value="", visible=False), outputs=processing_message | |
| ) | |
| peticao_texto_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output) | |
| tipo_peticao_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output) | |
| peticao_arquivo_input.change(fn=process_file, inputs=[peticao_arquivo_input], outputs=[arquivo_preview_output]) | |
| btn_corrigir.click( | |
| fn=lambda: gr.update(value="⏳ Processando texto... Gerando análise e correção...", visible=True), | |
| outputs=processing_message | |
| ).then( | |
| fn=corrigir_peticao, | |
| inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento], | |
| outputs=[resultado_analise_output, resultado_peticao_corrigida_output, | |
| resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output], | |
| concurrency_limit=1 | |
| ).then( | |
| fn=lambda: gr.update(value="", visible=False), | |
| outputs=processing_message | |
| ) | |
| btn_corrigir_arquivo.click( | |
| fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção...", visible=True), | |
| outputs=processing_message | |
| ).then( | |
| fn=corrigir_peticao, | |
| inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento], | |
| outputs=[resultado_analise_output, resultado_peticao_corrigida_output, | |
| resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output], | |
| concurrency_limit=1 | |
| ).then( | |
| fn=lambda: gr.update(value="", visible=False), | |
| outputs=processing_message | |
| ) | |
| config_update_btn.click(atualizar_config, inputs=[temperatura_slider, timeout_slider, max_tokens_slider], outputs=config_status) | |
| progresso_btn.click(fn=calcular_progresso, outputs=progresso_output) | |
| historico_btn.click(fn=mostrar_historico, outputs=historico_output) | |
| salvar_btn.click( | |
| fn=lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message | |
| ).then( | |
| fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input], | |
| outputs=[gr.Textbox(visible=False), config_status, progresso_download_file] | |
| ).then( | |
| fn=lambda x: gr.update(visible=bool(x)), inputs=[progresso_download_file], outputs=[progresso_download_file] | |
| ).then( | |
| lambda: gr.update(value="", visible=False), None, processing_message | |
| ) | |
| carregar_btn.click( | |
| fn=lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message | |
| ).then( | |
| fn=carregar_progresso, inputs=[arquivo_progresso_upload], | |
| outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status] | |
| ).then( | |
| lambda: gr.update(value="", visible=False), None, processing_message | |
| ) | |
| clear_btn.click( | |
| fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas aparecerão aqui...*", "", "", "", "", "", None, None, ""], | |
| outputs=[ | |
| tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, | |
| peticao_arquivo_input, arquivo_preview_output, dica_contextual_output, | |
| resultado_analise_output, resultado_peticao_corrigida_output, | |
| resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output, | |
| arquivo_progresso_upload, progresso_download_file, config_status | |
| ] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| logger.info("Iniciando a aplicação Gradio...") | |
| app = interface_gradio() | |
| app.launch() | |
| logger.info("Aplicação Gradio encerrada.") |