Spaces:
Runtime error
Runtime error
import gradio as gr | |
import google.generativeai as genai | |
import os | |
import PyPDF2 | |
import pandas as pd | |
from datetime import datetime | |
from io import BytesIO | |
import logging | |
import weasyprint | |
import base64 | |
import re | |
# Configurar logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
# Verificar dependências | |
try: | |
import google.generativeai | |
import weasyprint | |
logger.info("google.generativeai e weasyprint importados com sucesso") | |
except ImportError as e: | |
logger.error(f"Erro: dependência não instalada - {str(e)}. Verifique requirements.txt") | |
raise | |
# Configurar a API do Gemini | |
try: | |
api_key = os.getenv('GEMINI_API_KEY') | |
if not api_key: | |
raise ValueError("GEMINI_API_KEY não encontrada no ambiente") | |
genai.configure(api_key=api_key) | |
logger.info("API do Gemini configurada com sucesso") | |
except Exception as e: | |
logger.error(f"Erro ao configurar API do Gemini: {str(e)}") | |
raise | |
# Configurar o modelo Gemini | |
try: | |
model = genai.GenerativeModel('gemini-2.0-flash') | |
logger.info("Modelo Gemini configurado") | |
except Exception as e: | |
logger.error(f"Erro ao configurar modelo Gemini: {str(e)}") | |
raise | |
# Estilo HTML com Tailwind CSS | |
html_template = """ | |
<!DOCTYPE html> | |
<html lang="pt-BR"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Contestação Jurídica</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
body { font-family: 'Inter', sans-serif; } | |
.toc a { color: #1d4ed8; text-decoration: none; } | |
.toc a:hover { text-decoration: underline; } | |
.section-number { font-weight: bold; color: #1e3a8a; } | |
.download-btn { transition: background-color 0.3s; } | |
</style> | |
</head> | |
<body class="bg-gray-100 text-gray-800 leading-relaxed"> | |
<div class="max-w-4xl mx-auto p-8 bg-white shadow-lg rounded-lg mt-8"> | |
<h1 class="text-3xl font-bold text-center text-indigo-900 mb-6">CONTESTAÇÃO</h1> | |
<p class="text-lg font-semibold text-center mb-8">EXCELENTÍSSIMO(A) SENHOR(A) DOUTOR(A) JUIZ(A) DE DIREITO DA __ VARA CÍVEL DA COMARCA DE __</p> | |
<div class="toc mb-8 p-4 bg-gray-50 rounded-lg"> | |
<h2 class="text-xl font-semibold text-indigo-700">Índice</h2> | |
<ul class="list-disc pl-6"> | |
{toc} | |
</ul> | |
</div> | |
{content} | |
<p class="text-right italic text-gray-600 mt-8">Termos em que,<br>Pede deferimento.<br>[Local], {date}<br>[Nome do Advogado]<br>OAB [Número]</p> | |
<div class="mt-6 text-center"> | |
<a href="data:application/pdf;base64,{pdf_base64}" download="contestacao.pdf" class="download-btn inline-block bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700">Baixar PDF</a> | |
</div> | |
</div> | |
</body> | |
</html> | |
""" | |
# Estilo CSS para a tabela do DataFrame | |
css = """ | |
.dataframe { | |
width: 100%; | |
border-collapse: collapse; | |
font-family: 'Inter', sans-serif; | |
margin-top: 20px; | |
} | |
.dataframe th { | |
background-color: #4f46e5; | |
color: white; | |
padding: 12px; | |
text-align: left; | |
} | |
.dataframe td { | |
padding: 12px; | |
border-bottom: 1px solid #e5e7eb; | |
} | |
.dataframe tr:nth-child(even) { | |
background-color: #f9fafb; | |
} | |
.dataframe tr:hover { | |
background-color: #e0e7ff; | |
} | |
""" | |
def extract_text_from_pdf(pdf_file): | |
try: | |
pdf_reader = PyPDF2.PdfReader(BytesIO(pdf_file)) | |
text = "" | |
for page in pdf_reader.pages: | |
page_text = page.extract_text() | |
if page_text: | |
text += page_text + "\n" | |
if not text.strip(): | |
raise ValueError("Nenhum texto extraído do PDF") | |
logger.info("Texto extraído do PDF com sucesso") | |
return text.strip() | |
except Exception as e: | |
logger.error(f"Erro ao extrair texto do PDF: {str(e)}") | |
raise | |
def extract_petition_details(petition_text): | |
"""Extrai detalhes da petição inicial (reclamante, reivindicações, base legal).""" | |
details = { | |
"plaintiff": "Não identificado", | |
"claims": "Não especificado", | |
"legal_basis": "Não especificado", | |
"remedies": "Não especificado" | |
} | |
plaintiff_match = re.search(r"(.+?), por seu advogado", petition_text, re.IGNORECASE) | |
if plaintiff_match: | |
details["plaintiff"] = plaintiff_match.group(1).strip() | |
claims_match = re.search(r"(?:pelos fatos a seguir|fundamentos.*?)(.+?)(?:requer|pedidos)", petition_text, re.IGNORECASE | re.DOTALL) | |
if claims_match: | |
details["claims"] = claims_match.group(1).strip()[:200] + "..." if len(claims_match.group(1)) > 200 else claims_match.group(1).strip() | |
legal_basis_match = re.search(r"(art\..*?|lei.*?)(?:\n|$)", petition_text, re.IGNORECASE) | |
if legal_basis_match: | |
details["legal_basis"] = legal_basis_match.group(1).strip() | |
remedies_match = re.search(r"(?:requer|pede)(.+)", petition_text, re.IGNORECASE | re.DOTALL) | |
if remedies_match: | |
details["remedies"] = remedies_match.group(1).strip()[:200] + "..." if len(remedies_match.group(1)) > 200 else remedies_match.group(1).strip() | |
return details | |
def gerar_contestacao(pdf_file): | |
try: | |
if pdf_file is None: | |
return "Erro: Nenhum arquivo PDF selecionado", "", None | |
# Extrair texto do PDF | |
peticao_inicial = extract_text_from_pdf(pdf_file) | |
petition_details = extract_petition_details(peticao_inicial) | |
# Prompt avançado para o Gemini | |
prompt = f""" | |
Você é um advogado especializado em direito brasileiro com vasta experiência em litígios cíveis. Com base na petição inicial fornecida abaixo, redija uma contestação jurídica detalhada em português, seguindo as normas da ABNT e a estrutura formal de uma peça processual brasileira. A contestação deve ser estruturada, profissional e incluir: | |
1. **Preliminares**: Aborde questões processuais, como ilegitimidade, incompetência do juízo ou prescrição, se aplicável, com fundamentação legal (cite artigos do Código de Processo Civil - CPC). | |
2. **Mérito**: Refute cada argumento da petição inicial com análise jurídica aprofundada, incluindo: | |
- Contraposição aos fatos alegados. | |
- Fundamentação legal com citações específicas (e.g., artigos do Código Civil, Constituição Federal). | |
- Referências a jurisprudência relevante (e.g., decisões do STJ ou STF), se possível. | |
3. **Pedidos**: Solicite a improcedência da ação, custas processuais e honorários advocatícios, com justificativa. | |
4. **Estrutura**: Divida a contestação em seções numeradas (e.g., 1. PRELIMINARES, 2. MÉRITO, 3. PEDIDOS) com subtítulos claros (e.g., 2.1. Da Inexistência de Nexo Causal). | |
Forneça apenas o conteúdo da contestação (sem cabeçalho ou assinatura), formatado em texto simples com quebras de linha para parágrafos. Evite marcadores ou formatação que não seja compatível com texto puro. | |
Petição inicial: | |
{peticao_inicial} | |
""" | |
# Chamar a API do Gemini | |
response = model.generate_content(prompt) | |
contestacao = response.text | |
logger.info("Contestação gerada com sucesso") | |
# Criar índice e formatar conteúdo | |
sections = [] | |
toc = "" | |
content_html = "" | |
section_counter = 1 | |
subsection_counter = 0 | |
current_section = "" | |
for line in contestacao.split('\n'): | |
line = line.strip() | |
if line in ['PRELIMINARES', 'MÉRITO', 'PEDIDOS']: | |
sections.append(line) | |
toc += f'<li><a href="#section-{section_counter}">{section_counter}. {line}</a></li>' | |
content_html += f'<h2 id="section-{section_counter}" class="section-number text-2xl font-semibold text-indigo-700 mt-6">{section_counter}. {line}</h2>' | |
current_section = line | |
section_counter += 1 | |
subsection_counter = 0 | |
elif line and line[0].isdigit() and '.' in line and line.split('.')[1].strip(): | |
subsection_counter += 1 | |
subsection_title = line | |
toc += f'<li class="ml-4"><a href="#section-{section_counter-1}-{subsection_counter}">{section_counter-1}.{subsection_counter} {subsection_title}</a></li>' | |
content_html += f'<h3 id="section-{section_counter-1}-{subsection_counter}" class="text-xl font-medium text-indigo-600 mt-4">{section_counter-1}.{subsection_counter} {subsection_title}</h3>' | |
elif line: | |
content_html += f'<p class="text-justify mt-2">{line}</p>' | |
# Gerar HTML | |
contestacao_html = html_template.format( | |
toc=toc, | |
content=content_html, | |
date=datetime.now().strftime('%d/%m/%Y'), | |
pdf_base64="" | |
) | |
# Gerar PDF | |
pdf_io = BytesIO() | |
weasyprint.HTML(string=contestacao_html).write_pdf(pdf_io) | |
pdf_base64 = base64.b64encode(pdf_io.getvalue()).decode('utf-8') | |
contestacao_html = html_template.format( | |
toc=toc, | |
content=content_html, | |
date=datetime.now().strftime('%d/%m/%Y'), | |
pdf_base64=pdf_base64 | |
) | |
# Criar DataFrame com detalhes avançados | |
try: | |
df = pd.DataFrame({ | |
'Seção': ['Petição - Reclamante', 'Petição - Reivindicações', 'Petição - Base Legal', 'Petição - Pedidos', 'Contestação - Preliminares', 'Contestação - Mérito', 'Contestação - Pedidos'], | |
'Resumo': [ | |
petition_details['plaintiff'], | |
petition_details['claims'], | |
petition_details['legal_basis'], | |
petition_details['remedies'], | |
contestacao.split('MÉRITO')[0][:150] + '...' if len(contestacao.split('MÉRITO')[0]) > 150 else contestacao.split('MÉRITO')[0], | |
contestacao.split('MÉRITO')[1].split('PEDIDOS')[0][:150] + '...' if len(contestacao.split('MÉRITO')[1].split('PEDIDOS')[0]) > 150 else contestacao.split('MÉRITO')[1].split('PEDIDOS')[0], | |
contestacao.split('PEDIDOS')[1][:150] + '...' if len(contestacao.split('PEDIDOS')[1]) > 150 else contestacao.split('PEDIDOS')[1] | |
] | |
}) | |
except IndexError: | |
logger.warning("Estrutura da contestação não segue o formato esperado. Retornando DataFrame simplificado.") | |
df = pd.DataFrame({ | |
'Seção': ['Petição - Reclamante', 'Petição - Reivindicações', 'Contestação'], | |
'Resumo': [ | |
petition_details['plaintiff'], | |
petition_details['claims'], | |
contestacao[:150] + '...' if len(contestacao) > 150 else contestacao | |
] | |
}) | |
df_html = df.to_html(index=False, classes='dataframe', border=0) | |
return contestacao_html, df_html, pdf_base64 | |
except Exception as e: | |
logger.error(f"Erro ao gerar contestação: {str(e)}") | |
return f"Erro: {str(e)}", "", None | |
# Definir a interface Gradio | |
logger.info("Iniciando configuração da interface Gradio") | |
with gr.Blocks(css=css) as iface: | |
gr.Markdown("# Gerador de Contestação Jurídica") | |
gr.Markdown("Faça upload de um arquivo PDF com a petição inicial para gerar uma contestação jurídica detalhada em HTML, um resumo em tabela e um PDF para download.") | |
pdf_input = gr.UploadButton(label="Upload do PDF da Petição Inicial", file_types=[".pdf"]) | |
contestacao_output = gr.HTML(label="Contestação Gerada") | |
dataframe_output = gr.HTML(label="Resumo da Petição e Contestação") | |
pdf_output = gr.File(label="Baixar Contestação em PDF", visible=False) | |
submit_button = gr.Button("Gerar Contestação") | |
submit_button.click( | |
fn=gerar_contestacao, | |
inputs=pdf_input, | |
outputs=[contestacao_output, dataframe_output, pdf_output] | |
).then( | |
fn=lambda pdf_base64: BytesIO(base64.b64decode(pdf_base64)) if pdf_base64 else None, | |
inputs=pdf_output, | |
outputs=pdf_output | |
) | |
if __name__ == '__main__': | |
logger.info("Iniciando aplicação Gradio") | |
try: | |
iface.launch(server_name="0.0.0.0", server_port=7860) | |
except Exception as e: | |
logger.error(f"Erro ao iniciar Gradio: {str(e)}") | |
raise |