GabrielGD commited on
Commit
4c9c21f
·
verified ·
1 Parent(s): 3b9167a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +492 -351
app.py CHANGED
@@ -1,8 +1,9 @@
 
1
  import gradio as gr
2
- import requests
 
3
  import os
4
  import json
5
- import PyPDF2
6
  import docx
7
  import io
8
  import time
@@ -11,395 +12,535 @@ from dotenv import load_dotenv
11
  from pdfplumber import open as pdfplumber_open
12
  from PIL import Image
13
  import pytesseract
14
- import cv2
15
- import numpy as np
 
 
 
 
 
16
  from datetime import datetime
17
  from ratelimiter import RateLimiter
18
  import difflib
19
  from html import escape
20
  import random
21
  import logging
22
- import uuid
 
23
 
24
- # Configura o logging para depuração
25
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
26
  logger = logging.getLogger(__name__)
27
 
28
- # Carrega variáveis de ambiente
29
  load_dotenv()
30
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
31
  if not GEMINI_API_KEY:
32
- raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a no arquivo .env ou como Secret.")
33
 
34
- GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}"
 
 
 
 
35
 
36
- # Configurações gerais
37
- CONFIG = {
38
- "max_retry_attempts": 3,
39
- "retry_delay": 5,
40
- "timeout": 60,
41
- "temperatura": 0.2,
42
- "max_tokens": 12882
43
- }
44
 
45
- # Limitação de taxa para chamadas à API
46
- rate_limiter = RateLimiter(max_calls=60, period=60)
 
 
 
 
47
 
48
- # Dicas gerais para petições
49
- DICAS_OAB_MELHORADO = {
50
- "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]'.",
51
- "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.",
52
- "Fatos": "Descreva os fatos cronologicamente, sem inventar informações. Exemplo: 'Em [data], o autor celebrou contrato com o réu para [descrição]'.",
53
- "Pedidos": "Seja específico nos pedidos (ex.: 'condenação ao pagamento de R$ X'). Inclua danos morais e materiais, se aplicável.",
54
- "Custas": "Inclua 'Requer a juntada da guia de custas devidamente recolhida'. Nunca omita para evitar extinção do processo.",
55
- "Fundamentação": "Baseie-se em artigos do CPC ou CDC e súmulas do STJ (ex.: Súmula 387 para danos morais).",
56
- "Fechamento": "Finalize com 'Nestes termos pede deferimento', sem vírgula. Evite erros de pontuação.",
57
- "Estratégia Jurídica": "Utilize precedentes e súmulas para fortalecer a argumentação.",
58
- "Provas": "Especifique as provas a produzir. Inclua testemunhas, documentos e perícias, se necessário.",
59
- "Contra-argumentação": "Antecipe defesas da parte contrária e prepare respostas."
60
- }
61
 
62
- # NOVO: Prompts específicos para cada tipo de petição
63
- PROMPTS_PETICOES = {
64
- "Petição Inicial": """
65
- Você é um corretor de petições iniciais para prática cível no Brasil, com base no CPC. Analise a petição fornecida e corrija-a considerando:
66
- - **Endereçamento**: Verifique se está correto (ex.: "Excelentíssimo Senhor Juiz de Direito da [Vara] da Comarca de [Cidade]"). Sugira foro do domicílio do autor em relações de consumo (Art. 101, I, CDC).
67
- - **Qualificação**: Confirme se inclui nome, estado civil, CPF/CNPJ, endereço e representação legal (se aplicável).
68
- - **Fatos**: Avalie se os fatos estão claros, cronológicos e suficientes para embasar o pedido.
69
- - **Fundamentação Jurídica**: Verifique se cita artigos do CPC, CC ou CDC e jurisprudência relevante (ex.: Súmula 387/STJ para danos morais).
70
- - **Pedidos**: Confirme se são específicos (ex.: "condenação ao pagamento de R$ X") e incluem tutela provisória, se cabível.
71
- - **Provas**: Avalie se especifica documentos, testemunhas ou perícias.
72
- - **Valor da Causa**: Verifique se está presente e correto (Art. 292, CPC).
73
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
74
- Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos para maior clareza e conformidade. Retorne o resultado em markdown com seções claras (ex.: ### Endereçamento, ### Qualificação).
75
- """,
76
- "Contestação": """
77
- Você é um corretor de contestações para prática cível no Brasil, com base no CPC. Analise a contestação fornecida e corrija-a considerando:
78
- - **Endereçamento**: Igual ao da petição inicial, mas com menção à ação principal (ex.: "Processo nº [número]").
79
- - **Qualificação**: Confirme se o réu está corretamente qualificado.
80
- - **Preliminares**: Verifique se aponta ilegitimidade, incompetência ou outras defesas processuais (Art. 337, CPC).
81
- - **Mérito**: Avalie se refuta os fatos da inicial e apresenta defesa sólida, com base legal (ex.: Arts. CC, CDC).
82
- - **Reconvenção**: Se presente, confirme se está vinculada à ação principal e bem fundamentada (Art. 343, CPC).
83
- - **Provas**: Avalie se especifica provas a produzir (ex.: documentos, testemunhas).
84
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
85
- Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos para maior clareza e conformidade. Retorne o resultado em markdown com seções claras.
86
- """,
87
- "Reconvenção": """
88
- Você é um corretor de reconvenções para prática cível no Brasil, com base no CPC. Analise a reconvenção fornecida e corrija-a considerando:
89
- - **Endereçamento**: Igual à contestação, com menção ao processo principal.
90
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
91
- - **Vinculação**: Verifique se o pedido está relacionado à ação principal (Art. 343, CPC).
92
- - **Fatos**: Avalie se os fatos são claros e suficientes para embasar o pedido reconvencional.
93
- - **Fundamentação Jurídica**: Confirme se cita dispositivos legais e jurisprudência.
94
- - **Pedidos**: Verifique se são específicos e compatíveis com a reconvenção.
95
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
96
- Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
97
- """,
98
- "Impugnação à Contestação": """
99
- Você é um corretor de impugnações à contestação para prática cível no Brasil, com base no CPC. Analise a impugnação fornecida e corrija-a considerando:
100
- - **Endereçamento**: Igual ao da ação principal.
101
- - **Qualificação**: Confirme se as partes estão corretamente mencionadas.
102
- - **Refutação**: Avalie se contesta ponto a ponto as alegações da contestação, com argumentos claros.
103
- - **Fundamentação Jurídica**: Verifique se reforça os argumentos da inicial com base legal e jurisprudência.
104
- - **Provas**: Confirme se reitera ou adiciona provas relevantes.
105
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
106
- Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
107
- """,
108
- "Embargos de Declaração": """
109
- Você é um corretor de embargos de declaração para prática cível no Brasil, com base no CPC. Analise os embargos fornecidos e corrija-os considerando:
110
- - **Endereçamento**: Dirigido ao juiz ou tribunal da decisão embargada.
111
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
112
- - **Objetivo**: Verifique se aponta obscuridade, contradição, omissão ou erro material (Art. 1.022, CPC).
113
- - **Fundamentação**: Avalie se explica claramente o vício apontado, sem rediscutir o mérito.
114
- - **Pedidos**: Confirme se solicita esclarecimento ou correção da decisão.
115
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
116
- Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
117
- """,
118
- "Recurso de Apelação": """
119
- Você é um corretor de recursos de apelação para prática cível no Brasil, com base no CPC. Analise o recurso fornecido e corrija-o considerando:
120
- - **Endereçamento**: Dirigido ao tribunal competente (ex.: "Tribunal de Justiça do Estado de [Estado]").
121
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
122
- - **Tempestividade**: Verifique se menciona o prazo de 15 dias (Art. 1.003, CPC).
123
- - **Razões**: Avalie se aponta erros de fato ou de direito na sentença, com fundamentação legal e jurisprudencial.
124
- - **Pedidos**: Confirme se solicita reforma ou anulação da sentença.
125
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
126
- Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
127
- """,
128
- "Agravo de Instrumento": """
129
- Você é um corretor de agravos de instrumento para prática cível no Brasil, com base no CPC. Analise o agravo fornecido e corrija-o considerando:
130
- - **Endereçamento**: Dirigido ao tribunal competente (ex.: "Tribunal de Justiça do Estado de [Estado]").
131
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
132
- - **Tempestividade**: Verifique se menciona o prazo de 15 dias (Art. 1.003, CPC).
133
- - **Decisão Impugnada**: Avalie se identifica claramente a decisão interlocutória (Art. 1.015, CPC).
134
- - **Risco de Dano**: Confirme se demonstra o risco de dano irreparável ou de difícil reparação.
135
- - **Fundamentação**: Verifique se aponta erro na decisão com base legal e jurisprudencial.
136
- - **Pedidos**: Confirme se solicita efeito suspensivo ou reforma da decisão.
137
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
138
- Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
139
- """,
140
- "Petição de Tutela Provisória": """
141
- Você é um corretor de petições de tutela provisória para prática cível no Brasil, com base no CPC. Analise a petição fornecida e corrija-a considerando:
142
- - **Endereçamento**: Igual ao da ação principal ou ao juiz competente.
143
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
144
- - **Tipo de Tutela**: Verifique se especifica tutela de urgência (cautelar/antecipada) ou de evidência (Art. 294, CPC).
145
- - **Requisitos**: Avalie se demonstra probabilidade do direito e perigo de dano ou risco ao resultado útil do processo (Art. 300, CPC).
146
- - **Fundamentação Jurídica**: Confirme se cita dispositivos do CPC e jurisprudência.
147
- - **Pedidos**: Verifique se solicita medida específica (ex.: liminar para suspender ato).
148
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
149
- Forneça uma análise detalhada com sugestões de melhoria e, se necessário, reescreva trechos. Retorne o resultado em markdown com seções claras.
150
- """
151
  }
152
 
153
- # Estado global
154
  HISTORICO = []
155
  CORRECAO_CACHE = {}
156
 
157
- def preprocess_image_for_ocr(img):
 
 
 
158
  try:
159
- gray = img.convert('L')
160
- np_img = np.array(gray)
161
- _, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
162
  return Image.fromarray(thresh)
163
  except Exception as e:
164
- logger.error(f"Erro no pré-processamento da imagem: {str(e)}")
165
- return f"❌ Erro no pré-processamento: {str(e)}"
166
 
167
  def extract_text_from_pdf(file_path_or_obj):
 
168
  try:
169
- if isinstance(file_path_or_obj, str):
170
- with open(file_path_or_obj, 'rb') as f:
171
- text = ""
172
- with pdfplumber_open(f) as pdf:
173
- for page in pdf.pages:
174
- text += (page.extract_text() or "") + "\n"
175
- if not text.strip():
176
- images = pdfplumber_open(f).pages
177
- for i, image in enumerate(images):
178
- try:
179
- img = image.to_image()
180
- preprocessed_img = preprocess_image_for_ocr(img)
181
- text += pytesseract.image_to_string(preprocessed_img) + "\n"
182
- except Exception as e:
183
- text += f"[Erro na página {i+1}: {str(e)}]\n"
184
- else:
185
- text = ""
186
- with pdfplumber_open(file_path_or_obj) as pdf:
187
- for page in pdf.pages:
188
- text += (page.extract_text() or "") + "\n"
189
- if not text.strip():
190
- images = pdfplumber_open(file_path_or_obj).pages
191
- for i, image in enumerate(images):
192
- try:
193
- img = image.to_image()
194
- preprocessed_img = preprocess_image_for_ocr(img)
195
- text += pytesseract.image_to_string(preprocessed_img) + "\n"
196
- except Exception as e:
197
- text += f"[Erro na página {i+1}: {str(e)}]\n"
198
- return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF."
199
- except Exception as e:
200
- logger.error(f"Erro ao processar o PDF: {str(e)}")
201
- return f"❌ Erro ao processar o PDF: {str(e)}"
202
 
203
  def extract_text_from_docx(file_path_or_obj):
204
  try:
205
- if isinstance(file_path_or_obj, str):
206
- with open(file_path_or_obj, 'rb') as f:
207
- doc = docx.Document(f)
208
- text = ""
209
- for para in doc.paragraphs:
210
- text += para.text + "\n"
211
- for table in doc.tables:
212
- for row in table.rows:
213
- for cell in row.cells:
214
- text += cell.text + " "
215
- text += "\n"
216
- else:
217
- doc = docx.Document(file_path_or_obj)
218
- text = ""
219
- for para in doc.paragraphs:
220
- text += para.text + "\n"
221
- for table in doc.tables:
222
- for row in table.rows:
223
- for cell in row.cells:
224
- text += cell.text + " "
225
- text += "\n"
226
- return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX."
227
- except Exception as e:
228
- logger.error(f"Erro ao processar o DOCX: {str(e)}")
229
- return f"❌ Erro ao processar o DOCX: {str(e)}"
230
 
231
  def extract_text_from_txt(file_path_or_obj):
232
  try:
 
233
  if isinstance(file_path_or_obj, str):
234
- with open(file_path_or_obj, 'rb') as f:
235
- content = f.read()
236
- encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
237
- for encoding in encodings:
238
- try:
239
- return content.decode(encoding)
240
- except UnicodeDecodeError:
241
- continue
242
- return "❌ Não foi possível decodificar o arquivo TXT."
243
- else:
244
- content = file_path_or_obj.read()
245
- encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
246
- for encoding in encodings:
247
  try:
248
- return content.decode(encoding)
249
- except UnicodeDecodeError:
250
- continue
251
- return "❌ Não foi possível decodificar o arquivo TXT."
252
- except Exception as e:
253
- logger.error(f"Erro ao processar o TXT: {str(e)}")
254
- return f"❌ Erro ao processar o TXT: {str(e)}"
 
 
 
 
 
 
255
 
256
  def process_file(file_obj):
257
- if file_obj is None:
258
- return "Nenhum arquivo enviado."
259
  try:
260
- if hasattr(file_obj, 'name'):
261
- file_path = file_obj.name
262
- filename = os.path.basename(file_path).lower()
263
- file_size = os.path.getsize(file_path)
264
- if file_size > 10 * 1024 * 1024:
265
- return "⚠️ O arquivo é muito grande. Máx. 10 MB."
266
- if filename.endswith('.pdf'):
267
- return extract_text_from_pdf(file_path)
268
- elif filename.endswith('.docx'):
269
- return extract_text_from_docx(file_path)
270
- elif filename.endswith('.txt'):
271
- return extract_text_from_txt(file_path)
272
- else:
273
- return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
274
- else:
275
- return "❌ Erro: Objeto de arquivo inválido."
276
- except Exception as e:
277
- logger.error(f"Erro ao processar o arquivo: {str(e)}")
278
- return f"❌ Erro ao processar o arquivo: {str(e)}"
279
-
280
- # NOVO: Função para gerar casos simulados para cada tipo de petição
281
- def gerar_caso_simulado(tipo_peticao):
282
- casos = {
283
- "Petição Inicial": """
284
- João, residente em São Paulo, celebrou contrato de compra de um veículo com a concessionária AutoLux em 01/01/2025. O veículo apresentou defeitos graves em 15/01/2025, e a concessionária recusou o reparo. João deseja rescindir o contrato, receber o valor pago (R$ 50.000) e indenização por danos morais (R$ 10.000).
285
- """,
286
- "Contestação": """
287
- Na ação movida por João contra a AutoLux, a concessionária alega que o defeito no veículo decorre de mau uso por João e que não há prova de falha na fabricação. A AutoLux contesta o pedido de rescisão e danos morais, requerendo a improcedência da ação.
288
- """,
289
- "Reconvenção": """
290
- Na mesma ação, a AutoLux, ao contestar, alega que João deve R$ 5.000 por serviços de manutenção não pagos, requerendo a condenação de João ao pagamento dessa quantia.
291
- """,
292
- "Impugnação à Contestação": """
293
- João, ao responder à contestação da AutoLux, nega o mau uso do veículo e apresenta laudo técnico comprovando defeito de fabricação. Reitera os pedidos da inicial e contesta a reconvenção, negando a dívida de R$ 5.000.
294
- """,
295
- "Embargos de Declaração": """
296
- Em sentença que julgou a ação de João contra a AutoLux, o juiz omitiu-se sobre o pedido de danos morais, apesar de deferir a rescisão contratual. João deseja esclarecer a omissão.
297
- """,
298
- "Recurso de Apelação": """
299
- A sentença na ação de João contra a AutoLux deferiu a rescisão, mas negou os danos morais, sob o argumento de ausência de prova. João apela, alegando que os danos morais decorrem do descaso da concessionária.
300
- """,
301
- "Agravo de Instrumento": """
302
- Na ação de João contra a AutoLux, o juiz indeferiu liminar para suspender a cobrança de parcelas do veículo. João agrava, alegando risco de inclusão de seu nome em cadastros de inadimplentes.
303
- """,
304
- "Petição de Tutela Provisória": """
305
- João, na ação contra a AutoLux, requer tutela de urgência para suspender a cobrança de parcelas do veículo, sob risco de negativação, enquanto se discute a rescisão contratual.
306
- """
307
- }
308
- return casos.get(tipo_peticao, "⚠️ Tipo de petição não suportado.")
309
-
310
- # MODIFICADO: Função para estruturar o prompt, agora com suporte a diferentes tipos de petições
311
  def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
312
- prompt = PROMPTS_PETICOES.get(tipo_peticao, PROMPTS_PETICOES["Petição Inicial"])
313
- base_prompt = f"""
314
- {prompt}
315
- **Caso fornecido**: {caso}
316
- **Petição fornecida**: {peticao}
317
- **Instruções adicionais**:
318
- - Se o caso ou a petição estiverem incompletos, sugira informações plausíveis com base no CPC e nas práticas forenses.
319
- - Em modo de treinamento, forneça exemplos práticos e compare com a petição fornecida.
320
- - Retorne a análise em markdown, com seções claras e sugestões específicas.
321
- """
322
- return base_prompt
323
-
324
- # MODIFICADO: Função principal de análise, agora recebe o tipo de petição
325
- def analisar_peticao(file_obj, tipo_peticao, modo_treinamento):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  try:
327
- # Processa o arquivo da petição
328
- peticao_texto = process_file(file_obj)
329
- if peticao_texto.startswith("❌") or peticao_texto.startswith("⚠️"):
330
- return peticao_texto
331
-
332
- # Gera um caso simulado para o tipo de petição
333
- caso_simulado = gerar_caso_simulado(tipo_peticao)
334
-
335
- # Estrutura o prompt com base no tipo de petição
336
- prompt = estruturar_prompt_analise(caso_simulado, peticao_texto, tipo_peticao, modo_treinamento)
337
-
338
- # Chama a API do Gemini para análise
339
- headers = {"Content-Type": "application/json"}
340
- data = {
341
- "contents": [{"parts": [{"text": prompt}]}],
342
- "generationConfig": {
343
- "temperature": CONFIG["temperatura"],
344
- "maxOutputTokens": CONFIG["max_tokens"]
345
- }
346
- }
347
-
348
- with rate_limiter:
349
- for attempt in range(CONFIG["max_retry_attempts"]):
350
- try:
351
- response = requests.post(GEMINI_API_URL, headers=headers, json=data, timeout=CONFIG["timeout"])
352
- response.raise_for_status()
353
- result = response.json()
354
- if "candidates" in result and result["candidates"]:
355
- correcao = result["candidates"][0]["content"]["parts"][0]["text"]
356
- CORRECAO_CACHE[peticao_texto[:50]] = correcao
357
- HISTORICO.append({
358
- "id": str(uuid.uuid4()),
359
- "timestamp": datetime.now().isoformat(),
360
- "tipo_peticao": tipo_peticao,
361
- "peticao": peticao_texto[:500],
362
- "correcao": correcao
363
- })
364
- return correcao
365
- else:
366
- logger.error("Nenhuma resposta válida retornada pela API.")
367
- return "❌ Nenhuma resposta válida retornada pela API."
368
- except requests.exceptions.RequestException as e:
369
- logger.error(f"Tentativa {attempt + 1} falhou: {str(e)}")
370
- if attempt < CONFIG["max_retry_attempts"] - 1:
371
- time.sleep(CONFIG["retry_delay"])
372
- else:
373
- return f"❌ Erro após {CONFIG['max_retry_attempts']} tentativas: {str(e)}"
374
- except Exception as e:
375
- logger.error(f"Erro ao analisar a petição: {str(e)}")
376
- return f"❌ Erro ao analisar a petição: {str(e)}"
377
-
378
- # MODIFICADO: Interface do Gradio com dropdown para tipo de petição
379
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
380
- gr.Markdown("# Corretor de Petições Jurídicas")
381
- gr.Markdown("Faça o upload de uma petição em PDF, DOCX ou TXT e selecione o tipo de petição para análise detalhada.")
382
-
383
- with gr.Row():
384
- tipo_peticao = gr.Dropdown(
385
- choices=list(PROMPTS_PETICOES.keys()),
386
- label="Tipo de Petição",
387
- value="Petição Inicial"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  )
389
- file_input = gr.File(label="Upload da Petição (PDF, DOCX, TXT)", file_types=[".pdf", ".docx", ".txt"])
390
-
391
- modo_treinamento = gr.Checkbox(label="Modo de Treinamento (inclui exemplos práticos)", value=False)
392
-
393
- with gr.Row():
394
- submit_button = gr.Button("Analisar Petição", variant="primary")
395
-
396
- output = gr.Markdown(label="Resultado da Análise")
397
-
398
- submit_button.click(
399
- fn=analisar_peticao,
400
- inputs=[file_input, tipo_peticao, modo_treinamento],
401
- outputs=output
402
- )
403
 
404
  if __name__ == "__main__":
405
- demo.launch()
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
  import gradio as gr
3
+ import google.generativeai as genai
4
+ from google.api_core import exceptions as google_exceptions
5
  import os
6
  import json
 
7
  import docx
8
  import io
9
  import time
 
12
  from pdfplumber import open as pdfplumber_open
13
  from PIL import Image
14
  import pytesseract
15
+ try:
16
+ import cv2
17
+ import numpy as np
18
+ CV2_AVAILABLE = True
19
+ except ImportError:
20
+ CV2_AVAILABLE = False
21
+ print("Aviso: OpenCV (cv2) não encontrado.")
22
  from datetime import datetime
23
  from ratelimiter import RateLimiter
24
  import difflib
25
  from html import escape
26
  import random
27
  import logging
28
+ import traceback
29
+ import re
30
 
31
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s')
 
32
  logger = logging.getLogger(__name__)
33
 
 
34
  load_dotenv()
35
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") or os.environ.get("GEMINI_API_KEY")
36
  if not GEMINI_API_KEY:
37
+ raise ValueError("❌ Chave GEMINI_API_KEY não encontrada.")
38
 
39
+ try:
40
+ genai.configure(api_key=GEMINI_API_KEY)
41
+ logger.info("Biblioteca google-generativeai configurada.")
42
+ except Exception as e:
43
+ raise ValueError(f"❌ Erro config google-generativeai: {e}")
44
 
45
+ GEMINI_MODEL_NAME = "gemini-1.5-flash-latest"
46
+ CONFIG = {"max_retry_attempts": 3, "retry_delay": 5, "timeout_api": 120, "temperatura": 0.3, "max_tokens": 8192}
47
+ rate_limiter = RateLimiter(max_calls=15, period=60)
 
 
 
 
 
48
 
49
+ SAFETY_SETTINGS = [
50
+ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
51
+ {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
52
+ {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
53
+ {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
54
+ ]
55
 
56
+ 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"]
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ DICAS_OAB_MELHORADO = {
59
+ "Clareza e Concisão": "Direto, evite jargões.",
60
+ "Fundamentação Legal": "Cite leis, súmulas, jurisprudência.",
61
+ "Revisão Ortográfica": "Evite erros de português.",
62
+ "Endereçamento (Inicial)": "Verifique competência.",
63
+ "Pedidos (Inicial)": "Certos, determinados, líquidos.",
64
+ "Preliminares (Contestação)": "Art. 337 CPC antes do mérito.",
65
+ "Impugnação Específica (Contestação)": "Conteste cada fato (Art. 341)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
 
 
68
  HISTORICO = []
69
  CORRECAO_CACHE = {}
70
 
71
+ def preprocess_image_for_ocr(img_pil):
72
+ if not CV2_AVAILABLE:
73
+ logger.warning("OpenCV indisponível, usando conversão simples.")
74
+ return img_pil.convert('L')
75
  try:
76
+ img_cv = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2GRAY)
77
+ _, thresh = cv2.threshold(img_cv, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
 
78
  return Image.fromarray(thresh)
79
  except Exception as e:
80
+ logger.error(f"Erro pré-processamento OpenCV: {e}")
81
+ return img_pil.convert('L')
82
 
83
  def extract_text_from_pdf(file_path_or_obj):
84
+ text = ""; ocr_text_parts = []
85
  try:
86
+ logger.info(f"Abrindo PDF: {getattr(file_path_or_obj, 'name', file_path_or_obj)}")
87
+ with pdfplumber_open(file_path_or_obj) as pdf:
88
+ logger.info(f"PDF com {len(pdf.pages)} páginas.")
89
+ for i, page in enumerate(pdf.pages):
90
+ page_text = page.extract_text(x_tolerance=2, y_tolerance=2)
91
+ if page_text and len(page_text.strip()) > 20: text += page_text + "\n"
92
+ else:
93
+ logger.warning(f"Pág {i+1} sem texto, tentando OCR.")
94
+ try:
95
+ img = page.to_image(resolution=200)
96
+ preprocessed_img = preprocess_image_for_ocr(img.original)
97
+ page_ocr = pytesseract.image_to_string(preprocessed_img, lang='por', config='--psm 6')
98
+ if page_ocr and page_ocr.strip(): ocr_text_parts.append(page_ocr)
99
+ else: logger.warning(f"OCR vazio pág {i+1}."); ocr_text_parts.append(f"[OCR P{i+1}: Vazio]")
100
+ except Exception as e: logger.error(f"Erro OCR pág {i+1}: {e}"); ocr_text_parts.append(f"[Erro OCR P{i+1}: {e}]")
101
+ ocr_text = "\n".join(ocr_text_parts)
102
+ len_text, len_ocr = len(text.strip()), len(ocr_text.strip())
103
+ if len_text > len_ocr * 0.7: logger.info(f"Usando texto PDF ({len_text} chars)."); return text
104
+ elif len_ocr > 0: logger.info(f"Usando OCR PDF ({len_ocr} chars)."); return ocr_text
105
+ elif len_text > 0: logger.info("Usando texto curto PDF."); return text
106
+ else: logger.warning("PDF sem texto."); return "⚠️ PDF sem texto (extração/OCR falharam)."
107
+ except Exception as e: logger.error(f"Erro PDF: {e}", exc_info=True); return f"❌ Erro PDF: {e}"
 
 
 
 
 
 
 
 
 
 
 
108
 
109
  def extract_text_from_docx(file_path_or_obj):
110
  try:
111
+ doc = docx.Document(file_path_or_obj)
112
+ text = "\n".join([p.text for p in doc.paragraphs if p.text])
113
+ return text if text.strip() else "⚠️ DOCX vazio."
114
+ except Exception as e: logger.error(f"Erro DOCX: {e}", exc_info=True); return f"❌ Erro DOCX: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  def extract_text_from_txt(file_path_or_obj):
117
  try:
118
+ content = b''; encodings = ['utf-8', 'latin-1', 'cp1252']
119
  if isinstance(file_path_or_obj, str):
120
+ with open(file_path_or_obj, 'rb') as f: content = f.read()
121
+ elif hasattr(file_path_or_obj, 'read'):
122
+ if hasattr(file_path_or_obj, 'seek') and callable(file_path_or_obj.seek):
 
 
 
 
 
 
 
 
 
 
123
  try:
124
+ file_path_or_obj.seek(0)
125
+ except Exception as seek_e:
126
+ logger.warning(f"Não foi possível resetar ponteiro TXT: {seek_e}")
127
+ content = file_path_or_obj.read()
128
+ else: return "❌ Input TXT inválido."
129
+ for enc in encodings:
130
+ try:
131
+ decoded = content.decode(enc)
132
+ logger.info(f"TXT decodificado: {enc}.")
133
+ return decoded if decoded.strip() else "⚠️ TXT vazio."
134
+ except UnicodeDecodeError: continue
135
+ return "❌ Não foi possível decodificar TXT."
136
+ except Exception as e: logger.error(f"Erro TXT: {e}", exc_info=True); return f"❌ Erro TXT: {e}"
137
 
138
  def process_file(file_obj):
139
+ if file_obj is None: return "⚠️ Nenhum arquivo."
 
140
  try:
141
+ fpath = getattr(file_obj, 'name', None);
142
+ if not fpath: return "❌ Erro: Não identificou arquivo."
143
+ fname = os.path.basename(fpath).lower(); logger.info(f"Processando: {fname}")
144
+ try:
145
+ fsize = os.path.getsize(fpath); logger.info(f"Tamanho: {fsize} bytes")
146
+ if fsize > 15*1024*1024: return "⚠️ Arquivo grande (Máx 15MB)."
147
+ except OSError as e: logger.warning(f"Erro tamanho arquivo: {e}")
148
+ if fname.endswith('.pdf'): return extract_text_from_pdf(fpath)
149
+ elif fname.endswith('.docx'): return extract_text_from_docx(fpath)
150
+ elif fname.endswith('.txt'): return extract_text_from_txt(fpath)
151
+ else: return "⚠️ Formato não suportado (PDF, DOCX, TXT)."
152
+ except Exception as e: logger.error(f"Erro process_file: {e}", exc_info=True); return f"❌ Erro processar arquivo: {e}"
153
+
154
+ def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
155
+ base = """
156
+ **Estrutura:** 1.Análise Geral. 2.Nota Final(0-10). 3.Pontos Positivos. 4.Pontos a Melhorar(Desc, Fund., Impacto, Sug.). 5.Análise Requisitos.
157
+ **Critérios:** Coerência, Fundamentação, Linguagem, Pedidos. NÃO INVENTAR FATOS(-3a-5). NÃO SE IDENTIFICAR(Nota ZERO)."""
158
+ if modo_treinamento: base += "\n**Treinamento:** Detalhar Pontos a Melhorar(a-e). Destacar inconsistências. Sugerir estratégias."
159
+ return base
160
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
162
+ instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento)
163
+ prompt = f"## Análise: {tipo_peticao}\n**Caso:**\n```\n{caso}\n```\n**Peça Aluno:**\n```\n{peticao}\n```\n{instrucoes_gerais}\n**Requisitos({tipo_peticao}):**\n"
164
+ if tipo_peticao == "Petição Inicial": prompt += "-End. Comp(319,I;CDC101,I).\n-Qualif(319,II).\n-Fatos.\n-Fundam(319,III).\n-Pedidos(319,IV).\n-Vlr Causa(291-2,319,V).\n-Provas(319,VI).\n-Aud(319,VII).\n-Citação.\n-Docs essenc.\n-Fecho.\n**Adic:**'XXX'.Falta custas/JG(-1).Vírgula pede defer.(-0.2)."
165
+ elif tipo_peticao == "Contestação": prompt += "-End. Qualif réu. Proc nº. Temp.\n-Prelim(337):TODAS.\n-Impug.Esp Fatos(341):PONTOxPONTO.\n-Mérito:Versão réu;defesa dir/indir.\n-Fundam defesa.\n-Pedidos:Acolher prelim;IMPROC TOTAL;Sucumb(85).\n-Provas.\n**Adic:**Prelim preclui?Falha impug(-2a-4).Sem ped improc(-1.5)."
166
+ elif tipo_peticao == "Reconvenção": prompt += "-NA MESMA PEÇA(343).End. Proc nº.Qualif.\n-Conexão(343).\n-Estrut PI:Fatos,Fund,Ped,Vlr,Provas(reconv).\n-Ped intim(343§1).\n**Adic:**S/conex(-2).Falta ped/vlr/prov(-1/item).Peça separada(-3)."
167
+ elif tipo_peticao == "Impugnação à Contestação": prompt += "-End.Proc nº.Autor.Temp(15d-350/1).\n-Resp Prelim.\n-Resp Fatos Novos(350).\n-Resp Docs(437).\n-Ratific Inicial.\n-Espec Provas.\n-Peds:Rej prelim;refutar def;proced inicial.\n**Adic:**Ñ resp prelim(-1.5).Ñ manif fato novo(-2).Ñ ratificar(-0.5)."
168
+ elif tipo_peticao == "Embargos de Declaração": prompt += "-Contra Decisão Espec.End juiz prolator.Proc nº.Partes.Temp(5d-1023).\n-Cabim(1022):Apontar PRECISAMENTE:Omissão/Contrad/Obscur/ErroMat.\n-NÃO REJULGAR.\n-Ped:Conhec e ACOLHER p/ sanar vício.\n**Adic:**Rediscutir mérito(-4).Ñ apontar vício(-3)."
169
+ elif tipo_peticao == "Recurso de Apelação": prompt += "-Interpos:Juízo a quo.Proc nº.Partes.Pressup(cabim 1009;temp 15d;prep/JG).Ped receb/remessa.\n-Razões:TRIBUNAL.Relato.Fundam Reforma/Anul(1010):Error in proc/judic.DIALETICIDADE.\n-Peds:Conhec;PROVIM(anul/ref);Inv sucumb.\n**Adic:**Falta peça(-10).End razões(-1).S/dialetic(-3a-5)."
170
+ elif tipo_peticao == "Agravo de Instrumento": prompt += "-Peça única TRIBUNAL.Proc origem.Partes+Advs(end 1016).Cópia Decisão(1017,I).\n-Cabim(1015/mitig).Temp(15d).Prep/JG.\n-Peças Obrig(1017,I):Dec,Intim,Proc.Facult(III).\n-Razões(1016):Motivos ref/inval.\n-Efeito Susp/Tut Antecip Rec(1019,I)(se risco).\n-Peds Finais:Conhec;Efeito/Tut;PROVIM;Comunic juiz.\n**Adic:**Falta peça obrig(-4).S/cabim(-3)."
171
+ elif tipo_peticao == "Petição de Tutela Provisória": prompt += "-Tipo(Urg/Evid).Caráter(Antec/Incid).End.Qualif.Proc(se incid).\n-Urg(300):Fumus Boni Iuris E Periculum in Mora.Reversib(300§3).\n-Evid(311):Hipótese I-IV.NÃO precisa periculum.\n-Ped Específico:Medida liminar CLARA.\n-Se Antec(303/5):Indicar ped final ou aditar.Vlr Causa.\n-Peds:Concessão tutela;cit/intim;indic ação/adit.\n**Adic:**S/Fumus/Peric(-3a-5).Erro Evid(-3).Ped genérico(-2)."
172
+ else: prompt += "-Adequação? Clareza? Fundam? Pedido?"
173
+ prompt += "\n\n**Instruções Finais:** Analise estritamente. Rigoroso e construtivo. Gere **exclusivamente** no formato solicitado."
174
+ return prompt
175
+
176
+ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
177
+ prompt = f"**Caso:**\n```\n{caso}\n```\n**Peça Original ({tipo_peticao}):**\n```\n{peticao_original}\n```\n**Tarefa:** Gere versão corrigida ({tipo_peticao}), seguindo práticas OAB.\n**Instruções ({tipo_peticao}):**\n"
178
+ if tipo_peticao == "Petição Inicial": prompt += "1.End. 2.Qualif(319,II,'XXX'). 3.Fatos. 4.Fundam. 5.Pedidos(quantif). 6.Vlr Causa(292). 7.Provas. 8.Aud(319,VII). 9.Citação(246,I). 10.Custas/JG(NÃO OMITIR). 11.Fecho('Nestes termos...')."
179
+ elif tipo_peticao == "Contestação": prompt += "1.End. 2.ID(proc nº,réu). 3.Temp. 4.Estrut:Síntese;Prelim(337);Mérito(Impug+Defesa);Reconv(se antes peds cont). 5.Peds Cont:Acolher prelim;IMPROC TOTAL;Sucumb. 6.Provas réu. 7.Fecho."
180
+ elif tipo_peticao == "Reconvenção": prompt += "*Parte da Contest.* 1.Após mérito cont, antes peds: 'DA RECONVENÇÃO'. 2.Qualif Recvte/Recvdo. 3.Conexão. 4.Fatos/Dir Recv. 5.Peds Recv. 6.Vlr Causa/Provas Recv. 7.Intim Recvdo. 8.Peds Cont. 9.Fecho."
181
+ elif tipo_peticao == "Impugnação à Contestação": prompt += "1.End,proc,autor,temp. 2.Tópico PRELIM - Rebater. 3.Tópico MÉRITO/FATOS NOVOS - Manifestar. 4.Tópico RATIFICAÇÃO. 5.Tópico PROVAS. 6.Peds:Rej prelim, refutar def, proced inicial. 7.Fecho."
182
+ elif tipo_peticao == "Embargos de Declaração": prompt += "1.End juiz prolator.Proc,Partes.Decisão ID.Temp(5d). 2.Tópico p/ vício(1022):OMISSÃO/CONTRAD/OBSCUR/ERRO MAT. 3.Dentro:Trecho + Explicar vício. Ñ REJULGAR. 4.Ped:Conhec e ACOLHER p/ sanar. 5.Fecho."
183
+ elif tipo_peticao == "Recurso de Apelação": prompt += "1.Interpos:Juízo a quo.Proc,Partes.Pressup(cab,temp,prep/JG).Ped receb/remessa. 2.Razões:Tribunal.Relato.FUND REFORMA/ANUL(Error in proc/judic).DIALETICIDADE. 3.Peds:Conhec;PROVIM(anul/ref);Inv sucumb. 4.Fecho."
184
+ elif tipo_peticao == "Agravo de Instrumento": prompt += "1.Peça única Trib.Proc origem,Partes,Advs(ends).Decisão ID(cópia). 2.CABIM(1015/mitig).TEMP.PREP/JG.PEÇAS OBRIG/FACULT. 3.RAZÕES:Motivos ref/inval. 4.EFEITO SUSP/TUT ANTEC(se cabível). 5.PEDS:Conhec;Efeito/Tut;PROVIM;Comunic juiz. 6.Fecho."
185
+ elif tipo_peticao == "Petição de Tutela Provisória": prompt += "1.TIPO(Urg/Evid) e CARÁTER(Antec/Incid).End.Qualif.Proc(se incid). 2.REQUISITOS:Urg=Fumus+Peric(+Reversib).Evid=Hipót 311. 3.PED LIMINAR:Medida CLARA. 4.Se Antec:Indic ped final/aditar.Vlr Causa. 5.PEDS:Concessão tutela;cit/intim;indic ação/adit. 6.Fecho."
186
+ else: prompt += "1.Estrutura. 2.Args Claros. 3.Ped Claro. 4.Formais OAB."
187
+ prompt += "\n\n**Instrução Final:** Gere **APENAS** texto da peça corrigida (endereçamento até 'OAB/...'). Sem comentários."
188
+ return prompt
189
+
190
+ def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
191
+ if "Nestes termos, pede deferimento" in peticao_corrigida:
192
+ peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
193
+ if tipo_peticao == "Petição Inicial":
194
+ p_lower = peticao_corrigida.lower()
195
+ if "juntada da guia de custas" not in p_lower and "gratuidade de justiça" not in p_lower:
196
+ partes = peticao_corrigida.split("Nestes termos pede deferimento")
197
+ 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]
198
+ else: peticao_corrigida = peticao_corrigida.replace("Local, data.","Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.")
199
+ if "audiência de conciliação" not in p_lower and "audiência de mediação" not in p_lower:
200
+ partes = peticao_corrigida.split("Nestes termos pede deferimento")
201
+ 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]
202
+ 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.")
203
+ linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
204
+ fechamento = ["Local, data.", "ADVOGADO(A)", "OAB/..."]
205
+ while linhas and any(t in linhas[-1] for t in ["ADVOGADO","OAB","Local","data"]): linhas.pop()
206
+ linhas.extend(fechamento)
207
+ return "\n".join(linhas)
208
+
209
+ def chamar_api(prompt):
210
+ logger.info(f"Chamando API Gemini (Modelo: {GEMINI_MODEL_NAME})...")
211
+ model = genai.GenerativeModel(GEMINI_MODEL_NAME, safety_settings=SAFETY_SETTINGS)
212
+ gen_config = genai.types.GenerationConfig(temperature=CONFIG["temperatura"], max_output_tokens=CONFIG["max_tokens"], top_p=0.95)
213
+ for tentativa in range(CONFIG["max_retry_attempts"]):
214
+ try:
215
+ with rate_limiter:
216
+ logger.debug(f"API Tentativa {tentativa + 1}")
217
+ response = model.generate_content(contents=prompt, generation_config=gen_config)
218
+ if response.prompt_feedback.block_reason:
219
+ logger.error(f"Prompt bloqueado: {response.prompt_feedback.block_reason}")
220
+ return f"❌ Prompt bloqueado: {response.prompt_feedback.block_reason}. Reformule."
221
+ if not response.text:
222
+ if response.candidates and response.candidates[0].finish_reason != genai.types.FinishReason.STOP:
223
+ finish_reason = response.candidates[0].finish_reason.name
224
+ logger.error(f"Geração interrompida (s/ texto): {finish_reason}")
225
+ safety = response.candidates[0].safety_ratings
226
+ harms = [r.category.name for r in safety if r.probability.name not in ['NEGLIGIBLE', 'LOW']]
227
+ if harms: return f"❌ Bloqueado segurança: {finish_reason} ({','.join(harms)})."
228
+ else: return f"❌ Geração interrompida: {finish_reason}."
229
+ else: logger.warning("API retornou s/ texto ou bloqueio."); return "⚠️ API retornou resposta vazia."
230
+ logger.info(f"API OK ({len(response.text)} chars).")
231
+ return response.text
232
+ except google_exceptions.ResourceExhausted as e:
233
+ logger.warning(f"Erro 429 - Tentativa {tentativa + 1}: {e}")
234
+ if tentativa < CONFIG["max_retry_attempts"] - 1: wait = CONFIG['retry_delay']*(2**tentativa); logger.info(f"Aguardando {wait}s..."); time.sleep(wait); continue
235
+ return "❌ Erro 429: Muitas requisições."
236
+ except google_exceptions.PermissionDenied as e: logger.error(f"Erro Permissão API: {e}"); return f"❌ Erro Permissão: Verifique API Key/acesso modelo '{GEMINI_MODEL_NAME}'."
237
+ except google_exceptions.InvalidArgument as e: logger.error(f"Erro Argumento API: {e}"); return f"❌ Erro parâmetros API: {e}"
238
+ except genai.types.BlockedPromptException as e: logger.error(f"Prompt bloqueado (exc): {e}"); return f"❌ Prompt bloqueado. Reformule."
239
+ except genai.types.StopCandidateException as e: logger.error(f"Geração interrompida (exc): {e}"); return f"❌ Geração interrompida (segurança/outro)."
240
+ except google_exceptions.GoogleAPIError as e:
241
+ logger.error(f"Erro API Google - Tentativa {tentativa + 1}: {e}")
242
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay']*(tentativa + 1)); continue
243
+ return f"❌ Erro API Google: {e}"
244
+ except Exception as e:
245
+ logger.exception(f"Erro inesperado API - Tentativa {tentativa + 1}: {e}")
246
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay']*(tentativa + 1)); continue
247
+ return f"❌ Erro inesperado API: {e}"
248
+ logger.error("Falha API após todas as tentativas.")
249
+ return "❌ Falha API após múltiplas tentativas."
250
+
251
+ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
252
+ logger.info(f"Correção: Tipo={tipo_peticao}, Treinamento={modo_treinamento}")
253
+ start = time.time()
254
+ analise_fmt, pet_corr, res_diff, crit_diff, html_diff = "", "", "", "", ""
255
+ pet_cont = ""
256
  try:
257
+ if not caso or not caso.strip(): return "⚠️ Forneça caso.", "", "", "", ""
258
+ if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo.", "", "", "", ""
259
+ if peticao_arquivo: pet_cont = process_file(peticao_arquivo)
260
+ elif peticao_texto: pet_cont = peticao_texto
261
+ else: return "⚠️ Forneça petição.", "", "", "", ""
262
+ if pet_cont.startswith(("❌", "⚠️")): return pet_cont, "", "", "", ""
263
+ logger.info(f"Petição recebida ({len(pet_cont)} chars).")
264
+ troll = ["escreva a petição", "faça a petição", "gere a petição"]
265
+ if any(t in pet_cont.lower() for t in troll): return random.choice(["Hummm 😉","🚫 Corrijo, não crio."]), "", "", "", ""
266
+ cache_key = f"{tipo_peticao}|{caso[:100]}|{pet_cont[:100]}|{modo_treinamento}"
267
+ if cache_key in CORRECAO_CACHE:
268
+ logger.info("Cache hit.")
269
+ cached = CORRECAO_CACHE[cache_key]
270
+ return cached["analise_formatada"], cached["peticao_corrigida"], cached["resumo_diff"], cached["criticas_diff"], cached["diff_html"]
271
+ prompt_analise = estruturar_prompt_analise(caso, pet_cont, tipo_peticao, modo_treinamento)
272
+ analise_bruta = chamar_api(prompt_analise)
273
+ if analise_bruta.startswith(("❌", "⏱️", "⚠️")): return analise_bruta, "", "", "", ""
274
+ analise_fmt = formatar_correcao(analise_bruta)
275
+ prompt_peticao = estruturar_prompt_peticao_corrigida(caso, pet_cont, tipo_peticao)
276
+ pet_corr_bruta = chamar_api(prompt_peticao)
277
+ if pet_corr_bruta.startswith(("❌", "⏱️", "⚠️")): pet_corr = f"⚠️ Falha gerar corrigida:\n{pet_corr_bruta}"
278
+ else:
279
+ markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
280
+ start_index = -1
281
+ p_lower = pet_corr_bruta.lower()
282
+ for m in markers:
283
+ idx = p_lower.find(m)
284
+ if idx != -1:
285
+ start_index = idx
286
+ break
287
+ pet_extr = pet_corr_bruta[start_index:] if start_index != -1 else pet_corr_bruta
288
+ pet_corr = pos_corrigir_peticao(pet_extr, tipo_peticao)
289
+ html_diff, res_diff, crit_diff = calcular_diff_interno(pet_cont, pet_corr)
290
+ res_cache = {"analise_formatada":analise_fmt,"peticao_corrigida":pet_corr,"resumo_diff":res_diff,"criticas_diff":crit_diff,"diff_html":html_diff}
291
+ CORRECAO_CACHE[cache_key] = res_cache
292
+ HISTORICO.append({"data":datetime.now().strftime("%Y%m%d_%H%M%S"),"tipo_peticao":tipo_peticao,"caso":caso,"peticao_original":pet_cont,**res_cache})
293
+ if len(HISTORICO) > 10: HISTORICO.pop(0)
294
+ logger.info(f"Correção concluída em {time.time() - start:.2f}s.")
295
+ return analise_fmt, pet_corr, res_diff, crit_diff, html_diff
296
+ except Exception as e: logger.exception("Erro GERAL corrigir_peticao:"); return f"❌ Erro correção: {e}", "", "", "", ""
297
+
298
+ def formatar_correcao(texto):
299
+ texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
300
+ texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
301
+ texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
302
+ texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
303
+ texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica")
304
+ linhas = texto.split('\n'); linhas_fmt = []
305
+ in_list = False
306
+ for l in linhas:
307
+ ls = l.strip()
308
+ if ls.startswith(("1.","2.","3.","4.","5.")): linhas_fmt.append(f"\n**{ls}**"); in_list = False
309
+ elif ls.startswith(("- ","* ")): prefix = " * " if in_list else "* "; linhas_fmt.append(prefix + ls[2:]); in_list = True
310
+ elif ls.startswith(("a)","b)","c)","d)","e)")): linhas_fmt.append(f" * {ls}"); in_list = True
311
+ else: linhas_fmt.append(l); in_list = False if not ls.startswith(("*","- ","a)","b)","c)","d)","e)"," ")) else in_list
312
+ txt_final = "\n".join(linhas_fmt)
313
+ m = re.search(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})", txt_final, re.I)
314
+ if m: nota = m.group(2).replace(",","."); txt_final = re.sub(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})", f"\\1**{nota}/10**", txt_final, count=1, flags=re.I)
315
+ return txt_final
316
+
317
+ def fornecer_dica_contextual(texto_peticao, tipo_peticao):
318
+ if not texto_peticao or not texto_peticao.strip(): return "*Digite para dicas...*"
319
+ dicas = []; txt_l = texto_peticao.lower()
320
+ for k in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]:
321
+ if k in DICAS_OAB_MELHORADO: dicas.append(f"💡 **{k}:** {DICAS_OAB_MELHORADO[k]}")
322
+ kw = { "Petição Inicial": ["endereçamento","qualificação","fatos","pedidos","valor","provas","audiência","custas"],
323
+ "Contestação": ["preliminar","mérito","impugnação","art. 337"], "Embargos de Declaração": ["omissão","contradição","art. 1022"],
324
+ "Recurso de Apelação": ["sentença","reforma","dialeticidade","art. 1009"], "Agravo de Instrumento": ["interlocutória","cabimento","art. 1015"],
325
+ "Petição de Tutela Provisória": ["urgência","evidência","periculum","fumus","art. 300"]}
326
+ if tipo_peticao in kw:
327
+ for k, v in DICAS_OAB_MELHORADO.items():
328
+ cl = k.lower()
329
+ if f"({tipo_peticao.split()[0].lower()})" in cl or any(w in cl for w in kw[tipo_peticao] if w in txt_l):
330
+ fmt = f"💡 **{k}:** {v}"
331
+ if fmt not in dicas: dicas.append(fmt)
332
+ if not dicas: return "*Continue para dicas...*"
333
+ return "\n\n".join(dicas[:4])
334
+
335
+ def calcular_diff_interno(texto_original, texto_corrigido):
336
+ if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")): return "","",""
337
+ orig = texto_original.splitlines(); corr = texto_corrigido.splitlines()
338
+ diff = difflib.ndiff(orig, corr)
339
+ html, crit, rem, add = [], [], 0, 0
340
+ kw_r = ["invenção","identificação","erro","incorreto","prazo"]
341
+ kw_a = ["custas","art. 319","art. 337","art. 1022","art. 1015","art. 300","fumus","periculum","essencial","preliminar","tempestividade","cabimento"]
342
+ for l in diff:
343
+ txt = l[2:]; txt_esc = escape(txt)
344
+ if l.startswith(' '): html.append(f"<div class='linha-igual'>{txt_esc}</div>")
345
+ elif l.startswith('- '):
346
+ rem += 1; html.append(f"<div class='linha-removida'><del>{txt_esc}</del></div>")
347
+ if txt.strip() and any(kw in txt.lower() for kw in kw_r): crit.append(f"<span style='color:red'>🔴 REM:</span> {txt_esc}")
348
+ elif l.startswith('+ '):
349
+ add += 1; html.append(f"<div class='linha-adicionada'><ins>{txt_esc}</ins></div>")
350
+ if txt.strip() and any(kw in txt.lower() for kw in kw_a): crit.append(f"<span style='color:green'>🟢 ADD:</span> {txt_esc}")
351
+ resumo = f"**Resumo:** Rem={rem}, Add={add}, Saldo={add-rem}"
352
+ crit_txt = "### 🚨 Críticas:\n"+"\n".join(crit) if crit else "*Nenhuma crítica.*"
353
+ 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}.linha-removida{background-color:#ffebee;color:#c62828}.linha-adicionada{background-color:#e8f5e9;color:#2e7d32}.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>"
354
+ html_diff = f"{css}<div class='diff-container'>{''.join(html)}</div>"
355
+ return html_diff, resumo, crit_txt
356
+
357
+ def calcular_progresso():
358
+ if not HISTORICO: return "📊 Nenhum progresso."
359
+ notas, tipos = [], set()
360
+ for entry in HISTORICO:
361
+ tipos.add(entry["tipo_peticao"])
362
+ m = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.I)
363
+ if m:
364
+ try: notas.append(float(m.group(1).replace(",", ".")))
365
+ except ValueError: pass
366
+ tot = len(HISTORICO); media = sum(notas)/len(notas) if notas else 0; tend = ""
367
+ if len(notas) > 1: tend = "⬆️" if notas[-1] > notas[-2] else ("⬇️" if notas[-1] < notas[-2] else "↔️")
368
+ return f"**📊 Resumo:** Corrs:{tot}, Tipos:{len(tipos)}, Média:{media:.1f}/10 ({len(notas)} val), Tend:{tend if tend else 'N/A'}"
369
+
370
+ def mostrar_historico():
371
+ if not HISTORICO: return "📜 Histórico vazio."
372
+ md = "### 📜 Histórico (Últimas 10)\n---\n"
373
+ for i, entry in enumerate(reversed(HISTORICO)):
374
+ caso = (entry['caso'][:70] + '...') if len(entry['caso']) > 70 else entry['caso']
375
+ nota = "N/A"; m = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.I)
376
+ if m: nota = f"{m.group(1).replace(',', '.')}/10"
377
+ md += f"**{len(HISTORICO)-i}.** {entry['data']} | **{entry['tipo_peticao']}** | **Nota:{nota}**\n **Caso:** _{caso}_\n---\n"
378
+ return md
379
+
380
+ def salvar_progresso(caso, peticao, tipo_peticao):
381
+ logger.info("Salvando progresso...")
382
+ try:
383
+ if not caso or not peticao or not tipo_peticao: return "", "⚠️ Inputs necessários.", None
384
+ dados = {"tipo_peticao":tipo_peticao,"caso":caso,"peticao":peticao,"timestamp":datetime.now().isoformat()}
385
+ json_cont = json.dumps(dados, ensure_ascii=False, indent=4)
386
+ fname = f"progresso_peticao_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
387
+ fpath = os.path.join(tempfile.gettempdir(), fname)
388
+ with open(fpath, 'w', encoding='utf-8') as f: f.write(json_cont)
389
+ logger.info(f"Progresso salvo: {fpath}")
390
+ msg = f"✅ Salvo! Baixe `{fname}`."
391
+ return json_cont, msg, fpath
392
+ except Exception as e: logger.error(f"Erro salvar: {e}", exc_info=True); return "", f"❌ Erro salvar: {e}", None
393
+
394
+ def carregar_progresso(arquivo_entrada):
395
+ logger.info("Carregando progresso...")
396
+ if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo."
397
+ try:
398
+ fpath = arquivo_entrada.name; logger.info(f"Carregando: {fpath}")
399
+ with open(fpath, 'r', encoding='utf-8') as f: dados = json.load(f)
400
+ caso, peticao, tipo = dados.get("caso"), dados.get("peticao"), dados.get("tipo_peticao")
401
+ if not all([caso, peticao, tipo]): return "", "", "", "❌ Arquivo inválido."
402
+ if tipo not in TIPOS_DE_PETICAO: return caso, peticao, "", f"⚠️ Tipo '{tipo}' inválido."
403
+ logger.info("Progresso carregado."); return tipo, caso, peticao, "✅ Progresso carregado!"
404
+ except Exception as e: logger.error(f"Erro carregar: {e}", exc_info=True); return "", "", "", f"❌ Erro carregar: {e}"
405
+
406
+ def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
407
+ prompt = f"Crie caso fictício **detalhado e realista** p/ peça **'{tipo_peticao}'** (simulado OAB).\n**Geral:**\n1.Complexidade adequada (15-25l).\n2.Dados Essenciais (nomes,locais,datas,valores,relação,conflito).\n3.Coerência.\n4.Foco no Tipo Peça.\n**Específico '{tipo_peticao}':**\n"
408
+ if tipo_peticao == "Petição Inicial": prompt += "* Descreva violação. * Inclua danos. * Dados qualificação. * Tema: Consumo, Contrato, etc."
409
+ elif tipo_peticao == "Contestação": prompt += "* Resumo PI recebida. * Versão Réu (p/ prelim, impug, mérito). * Dados qualif réu."
410
+ elif tipo_peticao == "Reconvenção": prompt += "* Estrutura Contestação + Fatos/Fundamentos p/ pedido PRÓPRIO CONEXO do Réu contra Autor."
411
+ elif tipo_peticao == "Impugnação à Contestação": prompt += "* Resumo PI Autor. * Resumo Contestação Réu (prelim, fatos novos, docs). * Objetivo: Rebater."
412
+ elif tipo_peticao == "Embargos de Declaração": prompt += "* Breve caso. * TRECHO DECISÃO. * INDIQUE VÍCIO (omissão/contradição/obscuridade/erro material)."
413
+ elif tipo_peticao == "Recurso de Apelação": prompt += "* Caso original. * Resumo SENTENÇA. * ELEMENTOS ERRO (in judicando/procedendo). * Indique apelante."
414
+ elif tipo_peticao == "Agravo de Instrumento": prompt += "* Caso andamento. * DECISÃO INTERLOCUTÓRIA. * Enquadrar Art.1015/mitig. * ELEMENTOS ERRO + URGÊNCIA. * Indique agravante."
415
+ elif tipo_peticao == "Petição de Tutela Provisória": prompt += "* Situação URGENTE ou EVIDENTE. * ELEMENTOS: Urg=Fumus+Peric; Evid=Hipót Art.311. * Medida específica. * Antec/Incid."
416
+ else: prompt += "* Crie cenário cível com conflito."
417
+ prompt += f'\n**Finalize:**\n"**Situação-Problema:** Advogado(a) de [Nome Cliente], elabore a peça cabível ({tipo_peticao})."\n**Importante:** Temas comuns.'
418
+ return prompt
419
+
420
+ def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
421
+ logger.info(f"Gerando caso para: {tipo_peticao}")
422
+ try:
423
+ if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo válido."
424
+ prompt = estruturar_prompt_geracao_caso(tipo_peticao)
425
+ caso_gerado = chamar_api(prompt)
426
+ if caso_gerado.startswith(("❌","⏱️","⚠️")): return f"⚠️ Falha gerar caso:\n{caso_gerado}"
427
+ frase = "**Situação-Problema:** Na qualidade de advogado(a) de"
428
+ if frase.lower() not in caso_gerado.lower():
429
+ logger.warning("Frase final não encontrada. Adicionando.")
430
+ nomes = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado)
431
+ cliente = nomes[0] if nomes else "[Cliente]"
432
+ caso_gerado += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente}, elabore a peça processual cabível ({tipo_peticao})."
433
+ logger.info("Caso gerado.")
434
+ return caso_gerado
435
+ except Exception as e: logger.exception("Erro gerar_caso:"); return f"❌ Erro gerar caso: {e}"
436
+
437
+ def atualizar_config(temp, max_tokens):
438
+ CONFIG["temperatura"] = temp
439
+ CONFIG["max_tokens"] = max_tokens
440
+ logger.info(f"Configs: Temp={temp}, Max Tokens={max_tokens}")
441
+ return f"✅ Configs Atualizadas!"
442
+
443
+ def interface_gradio():
444
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
445
+ gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️\nAnalise e aprimore suas peças processuais!")
446
+ with gr.Row():
447
+ with gr.Column(scale=1):
448
+ gr.Markdown("### ⚙️ Configs")
449
+ tipo_peticao_input = gr.Dropdown(label="1. Tipo Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial")
450
+ modo_treinamento = gr.Checkbox(label="🎓 Treinamento", value=False)
451
+ with gr.Accordion("🛠️ Opções IA", open=False):
452
+ temperatura_slider = gr.Slider(0.0, 1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade")
453
+ max_tokens_slider = gr.Slider(2048, 8192, value=CONFIG["max_tokens"], step=256, label="Max Tokens")
454
+ config_update_btn = gr.Button("💾 Salvar Configs")
455
+ config_status = gr.Textbox(label="Status", interactive=False, lines=1)
456
+ with gr.Accordion("📊 Dashboard", open=False):
457
+ progresso_output = gr.Markdown("*Atualize...*")
458
+ progresso_btn = gr.Button("🔄 Progresso")
459
+ with gr.Accordion("📜 Histórico", open=False):
460
+ historico_output = gr.Markdown("*Atualize...*")
461
+ historico_btn = gr.Button("🔄 Histórico")
462
+ with gr.Accordion("💾📂 Salvar/Carregar", open=False):
463
+ salvar_btn = gr.Button("💾 Salvar")
464
+ carregar_btn = gr.Button("📂 Carregar")
465
+ arquivo_progresso_upload = gr.File(label=".json", file_types=[".json"])
466
+ progresso_download_file = gr.File(label="Baixar", interactive=False)
467
+ with gr.Column(scale=3):
468
+ gr.Markdown("### 1. Caso / Contexto")
469
+ with gr.Row():
470
+ caso_input = gr.Textbox(label="📋 Caso", lines=8, placeholder="Insira o texto do caso...")
471
+ caso_btn = gr.Button("🎲 Gerar", variant="secondary", scale=0)
472
+ gr.Markdown("### 2. Sua Peça")
473
+ with gr.Tabs():
474
+ with gr.TabItem("✏️ Texto"):
475
+ peticao_texto_input = gr.Textbox(label="Texto", lines=12, placeholder="Cole ou digite...")
476
+ dica_contextual_output = gr.Markdown("*Dicas...*")
477
+ btn_corrigir = gr.Button("🚀 CORRIGIR!", variant="primary")
478
+ with gr.TabItem("📎 Arquivo"):
479
+ peticao_arquivo_input = gr.File(label="Arquivo (.pdf,.docx,.txt)", file_types=[".pdf",".docx",".txt"])
480
+ arquivo_preview_output = gr.Textbox(label="🔍 Prévia", lines=4, interactive=False)
481
+ btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR ARQUIVO!", variant="primary")
482
+
483
+ processing_message = gr.Markdown("", visible=False)
484
+ gr.Markdown("--- \n ### 3. Resultados")
485
+ with gr.Tabs():
486
+ with gr.TabItem("📊 Análise e Correção"):
487
+ resultado_analise_output = gr.Markdown(label="Análise e Nota")
488
+ resultado_peticao_corrigida_output = gr.Textbox(label="✨ Versão Sugerida", lines=18, show_copy_button=True)
489
+ with gr.TabItem("⚖️ Comparativo"):
490
+ gr.Markdown("### ↔️ Comparativo (Diff)")
491
+ with gr.Row():
492
+ with gr.Column(scale=1): resumo_alteracoes_output = gr.Markdown(label="Resumo"); criticas_output = gr.Markdown(label="Críticas")
493
+ with gr.Column(scale=3): visualizacao_detalhada_output = gr.HTML(label="Detalhes")
494
+ clear_btn = gr.Button("🗑️ Limpar Tudo")
495
+
496
+ caso_btn.click(lambda: gr.update(value="⏳ Gerando...", visible=True), outputs=processing_message).then(
497
+ fn=gerar_caso_ficticio, inputs=[tipo_peticao_input], outputs=[caso_input]
498
+ ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
499
+
500
+ peticao_texto_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output)
501
+ tipo_peticao_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output)
502
+ peticao_arquivo_input.change(process_file, [peticao_arquivo_input], [arquivo_preview_output])
503
+
504
+ btn_corrigir.click(lambda: gr.update(value="⏳ Processando texto...", visible=True), outputs=processing_message).then(
505
+ fn=corrigir_peticao,
506
+ inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento],
507
+ outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
508
+ concurrency_limit=1
509
+ ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
510
+
511
+ btn_corrigir_arquivo.click(lambda: gr.update(value="⏳ Processando arquivo...", visible=True), outputs=processing_message).then(
512
+ fn=corrigir_peticao,
513
+ inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento],
514
+ outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
515
+ concurrency_limit=1
516
+ ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
517
+
518
+ config_update_btn.click(atualizar_config, [temperatura_slider, max_tokens_slider], config_status)
519
+ progresso_btn.click(calcular_progresso, outputs=progresso_output)
520
+ historico_btn.click(mostrar_historico, outputs=historico_output)
521
+
522
+ salvar_btn.click(lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message).then(
523
+ fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
524
+ outputs=[gr.Textbox(visible=False), config_status, progresso_download_file]
525
+ ).then(lambda x: gr.update(visible=bool(x)), [progresso_download_file], [progresso_download_file]
526
+ ).then(lambda: gr.update(value="", visible=False), None, processing_message)
527
+
528
+ carregar_btn.click(lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message).then(
529
+ fn=carregar_progresso, inputs=[arquivo_progresso_upload],
530
+ outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status]
531
+ ).then(lambda: gr.update(value="", visible=False), None, processing_message)
532
+
533
+ clear_btn.click(
534
+ fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas...*", "", "", "", "", "", None, None, ""],
535
+ outputs=[tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, peticao_arquivo_input,
536
+ arquivo_preview_output, dica_contextual_output, resultado_analise_output, resultado_peticao_corrigida_output,
537
+ resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output,
538
+ arquivo_progresso_upload, progresso_download_file, config_status]
539
  )
540
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
541
 
542
  if __name__ == "__main__":
543
+ logger.info("Iniciando a aplicação Gradio...")
544
+ app = interface_gradio()
545
+ app.launch()
546
+ logger.info("Aplicação Gradio encerrada.")