GabrielGD commited on
Commit
4cb1279
·
verified ·
1 Parent(s): 58c871a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +490 -343
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,388 +12,534 @@ 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
  load_dotenv()
29
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
30
  if not GEMINI_API_KEY:
31
- raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a no arquivo .env ou como Secret.")
32
 
33
- GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}"
 
 
 
 
34
 
35
- CONFIG = {
36
- "max_retry_attempts": 3,
37
- "retry_delay": 5,
38
- "timeout": 60,
39
- "temperatura": 0.2,
40
- "max_tokens": 12882
41
- }
42
 
43
- rate_limiter = RateLimiter(max_calls=60, period=60)
 
 
 
 
 
44
 
45
- # Dicas gerais para petições
46
- DICAS_OAB_MELHORADO = {
47
- "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]'.",
48
- "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.",
49
- "Fatos": "Descreva os fatos cronologicamente, sem inventar informações. Exemplo: 'Em [data], o autor celebrou contrato com o réu para [descrição]'.",
50
- "Pedidos": "Seja específico nos pedidos (ex.: 'condenação ao pagamento de R$ X'). Inclua danos morais e materiais, se aplicável.",
51
- "Custas": "Inclua 'Requer a juntada da guia de custas devidamente recolhida'. Nunca omita para evitar extinção do processo.",
52
- "Fundamentação": "Baseie-se em artigos do CPC ou CDC e súmulas do STJ (ex.: Súmula 387 para danos morais).",
53
- "Fechamento": "Finalize com 'Nestes termos pede deferimento', sem vírgula. Evite erros de pontuação.",
54
- "Estratégia Jurídica": "Utilize precedentes e súmulas para fortalecer a argumentação.",
55
- "Provas": "Especifique as provas a produzir. Inclua testemunhas, documentos e perícias, se necessário.",
56
- "Contra-argumentação": "Antecipar defesas da parte contrária e preparar respostas."
57
- }
58
 
59
- # Prompts específicos para cada tipo de petição
60
- PROMPTS_PETICOES = {
61
- "Petição Inicial": """
62
- 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:
63
- - **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).
64
- - **Qualificação**: Confirme se inclui nome, estado civil, CPF/CNPJ, endereço e representação legal (se aplicável).
65
- - **Fatos**: Avalie se os fatos estão claros, cronológicos e suficientes para embasar o pedido.
66
- - **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).
67
- - **Pedidos**: Confirme se são específicos (ex.: "condenação ao pagamento de R$ X") e incluem tutela provisória, se cabível.
68
- - **Provas**: Avalie se especifica documentos, testemunhas ou perícias.
69
- - **Valor da Causa**: Verifique se está presente e correto (Art. 292, CPC).
70
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
71
- 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).
72
- """,
73
- "Contestação": """
74
- 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:
75
- - **Endereçamento**: Igual ao da petição inicial, mas com menção à ação principal (ex.: "Processo nº [número]").
76
- - **Qualificação**: Confirme se o réu está corretamente qualificado.
77
- - **Preliminares**: Verifique se aponta ilegitimidade, incompetência ou outras defesas processuais (Art. 337, CPC).
78
- - **Mérito**: Avalie se refuta os fatos da inicial e apresenta defesa sólida, com base legal (ex.: Arts. CC, CDC).
79
- - **Reconvenção**: Se presente, confirme se está vinculada à ação principal e bem fundamentada (Art. 343, CPC).
80
- - **Provas**: Avalie se especifica provas a produzir (ex.: documentos, testemunhas).
81
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
82
- 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.
83
- """,
84
- "Reconvenção": """
85
- 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:
86
- - **Endereçamento**: Igual à contestação, com menção ao processo principal.
87
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
88
- - **Vinculação**: Verifique se o pedido está relacionado à ação principal (Art. 343, CPC).
89
- - **Fatos**: Avalie se os fatos são claros e suficientes para embasar o pedido reconvencional.
90
- - **Fundamentação Jurídica**: Confirme se cita dispositivos legais e jurisprudência.
91
- - **Pedidos**: Verifique se são específicos e compatíveis com a reconvenção.
92
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
93
- 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.
94
- """,
95
- "Impugnação à Contestação": """
96
- 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:
97
- - **Endereçamento**: Igual ao da ação principal.
98
- - **Qualificação**: Confirme se as partes estão corretamente mencionadas.
99
- - **Refutação**: Avalie se contesta ponto a ponto as alegações da contestação, com argumentos claros.
100
- - **Fundamentação Jurídica**: Verifique se reforça os argumentos da inicial com base legal e jurisprudência.
101
- - **Provas**: Confirme se reitera ou adiciona provas relevantes.
102
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
103
- 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.
104
- """,
105
- "Embargos de Declaração": """
106
- 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:
107
- - **Endereçamento**: Dirigido ao juiz ou tribunal da decisão embargada.
108
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
109
- - **Objetivo**: Verifique se aponta obscuridade, contradição, omissão ou erro material (Art. 1.022, CPC).
110
- - **Fundamentação**: Avalie se explica claramente o vício apontado, sem rediscutir o mérito.
111
- - **Pedidos**: Confirme se solicita esclarecimento ou correção da decisão.
112
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
113
- 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.
114
- """,
115
- "Recurso de Apelação": """
116
- 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:
117
- - **Endereçamento**: Dirigido ao tribunal competente (ex.: "Tribunal de Justiça do Estado de [Estado]").
118
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
119
- - **Tempestividade**: Verifique se menciona o prazo de 15 dias (Art. 1.003, CPC).
120
- - **Razões**: Avalie se aponta erros de fato ou de direito na sentença, com fundamentação legal e jurisprudencial.
121
- - **Pedidos**: Confirme se solicita reforma ou anulação da sentença.
122
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
123
- 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.
124
- """,
125
- "Agravo de Instrumento": """
126
- 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:
127
- - **Endereçamento**: Dirigido ao tribunal competente (ex.: "Tribunal de Justiça do Estado de [Estado]").
128
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
129
- - **Tempestividade**: Verifique se menciona o prazo de 15 dias (Art. 1.003, CPC).
130
- - **Decisão Impugnada**: Avalie se identifica claramente a decisão interlocutória (Art. 1.015, CPC).
131
- - **Risco de Dano**: Confirme se demonstra o risco de dano irreparável ou de difícil reparação.
132
- - **Fundamentação**: Verifique se aponta erro na decisão com base legal e jurisprudencial.
133
- - **Pedidos**: Confirme se solicita efeito suspensivo ou reforma da decisão.
134
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
135
- 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.
136
- """,
137
- "Petição de Tutela Provisória": """
138
- 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:
139
- - **Endereçamento**: Igual ao da ação principal ou ao juiz competente.
140
- - **Qualificação**: Confirme se as partes estão corretamente qualificadas.
141
- - **Tipo de Tutela**: Verifique se especifica tutela de urgência (cautelar/antecipada) ou de evidência (Art. 294, CPC).
142
- - **Requisitos**: Avalie se demonstra probabilidade do direito e perigo de dano ou risco ao resultado útil do processo (Art. 300, CPC).
143
- - **Fundamentação Jurídica**: Confirme se cita dispositivos do CPC e jurisprudência.
144
- - **Pedidos**: Verifique se solicita medida específica (ex.: liminar para suspender ato).
145
- - **Fechamento**: Deve conter "Nestes termos, pede deferimento" e assinatura.
146
- 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.
147
- """
148
  }
149
 
150
  HISTORICO = []
151
  CORRECAO_CACHE = {}
152
 
153
- def preprocess_image_for_ocr(img):
 
 
 
154
  try:
155
- gray = img.convert('L')
156
- np_img = np.array(gray)
157
- _, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
158
  return Image.fromarray(thresh)
159
  except Exception as e:
160
- logger.error(f"Erro no pré-processamento da imagem: {str(e)}")
161
- return f"❌ Erro no pré-processamento: {str(e)}"
162
 
163
  def extract_text_from_pdf(file_path_or_obj):
 
164
  try:
165
- if isinstance(file_path_or_obj, str):
166
- with open(file_path_or_obj, 'rb') as f:
167
- text = ""
168
- with pdfplumber_open(f) as pdf:
169
- for page in pdf.pages:
170
- text += (page.extract_text() or "") + "\n"
171
- if not text.strip():
172
- images = pdfplumber_open(f).pages
173
- for i, image in enumerate(images):
174
- try:
175
- img = image.to_image()
176
- preprocessed_img = preprocess_image_for_ocr(img)
177
- text += pytesseract.image_to_string(preprocessed_img) + "\n"
178
- except Exception as e:
179
- text += f"[Erro na página {i+1}: {str(e)}]\n"
180
- else:
181
- text = ""
182
- with pdfplumber_open(file_path_or_obj) as pdf:
183
- for page in pdf.pages:
184
- text += (page.extract_text() or "") + "\n"
185
- if not text.strip():
186
- images = pdfplumber_open(file_path_or_obj).pages
187
- for i, image in enumerate(images):
188
- try:
189
- img = image.to_image()
190
- preprocessed_img = preprocess_image_for_ocr(img)
191
- text += pytesseract.image_to_string(preprocessed_img) + "\n"
192
- except Exception as e:
193
- text += f"[Erro na página {i+1}: {str(e)}]\n"
194
- return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF."
195
- except Exception as e:
196
- logger.error(f"Erro ao processar o PDF: {str(e)}")
197
- return f"❌ Erro ao processar o PDF: {str(e)}"
198
 
199
  def extract_text_from_docx(file_path_or_obj):
200
  try:
201
- if isinstance(file_path_or_obj, str):
202
- with open(file_path_or_obj, 'rb') as f:
203
- doc = docx.Document(f)
204
- text = ""
205
- for para in doc.paragraphs:
206
- text += para.text + "\n"
207
- for table in doc.tables:
208
- for row in table.rows:
209
- for cell in row.cells:
210
- text += cell.text + " "
211
- text += "\n"
212
- else:
213
- doc = docx.Document(file_path_or_obj)
214
- text = ""
215
- for para in doc.paragraphs:
216
- text += para.text + "\n"
217
- for table in doc.tables:
218
- for row in table.rows:
219
- for cell in row.cells:
220
- text += cell.text + " "
221
- text += "\n"
222
- return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX."
223
- except Exception as e:
224
- logger.error(f"Erro ao processar o DOCX: {str(e)}")
225
- return f"❌ Erro ao processar o DOCX: {str(e)}"
226
 
227
  def extract_text_from_txt(file_path_or_obj):
228
  try:
 
229
  if isinstance(file_path_or_obj, str):
230
- with open(file_path_or_obj, 'rb') as f:
231
- content = f.read()
232
- encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
233
- for encoding in encodings:
234
- try:
235
- return content.decode(encoding)
236
- except UnicodeDecodeError:
237
- continue
238
- return "❌ Não foi possível decodificar o arquivo TXT."
239
- else:
240
  content = file_path_or_obj.read()
241
- encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
242
- for encoding in encodings:
243
- try:
244
- return content.decode(encoding)
245
- except UnicodeDecodeError:
246
- continue
247
- return "❌ Não foi possível decodificar o arquivo TXT."
248
- except Exception as e:
249
- logger.error(f"Erro ao processar o TXT: {str(e)}")
250
- return f"❌ Erro ao processar o TXT: {str(e)}"
251
 
252
  def process_file(file_obj):
253
- if file_obj is None:
254
- return "Nenhum arquivo enviado."
255
  try:
256
- if hasattr(file_obj, 'name'):
257
- file_path = file_obj.name
258
- filename = os.path.basename(file_path).lower()
259
- file_size = os.path.getsize(file_path)
260
- if file_size > 10 * 1024 * 1024:
261
- return "⚠️ O arquivo é muito grande. Máx. 10 MB."
262
- if filename.endswith('.pdf'):
263
- return extract_text_from_pdf(file_path)
264
- elif filename.endswith('.docx'):
265
- return extract_text_from_docx(file_path)
266
- elif filename.endswith('.txt'):
267
- return extract_text_from_txt(file_path)
268
- else:
269
- return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
270
- else:
271
- return "❌ Erro: Objeto de arquivo inválido."
272
- except Exception as e:
273
- logger.error(f"Erro ao processar o arquivo: {str(e)}")
274
- return f"❌ Erro ao processar o arquivo: {str(e)}"
275
-
276
- def gerar_caso_simulado(tipo_peticao):
277
- casos = {
278
- "Petição Inicial": """
279
- 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).
280
- """,
281
- "Contestação": """
282
- 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.
283
- """,
284
- "Reconvenção": """
285
- 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.
286
- """,
287
- "Impugnação à Contestação": """
288
- 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.
289
- """,
290
- "Embargos de Declaração": """
291
- 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.
292
- """,
293
- "Recurso de Apelação": """
294
- 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.
295
- """,
296
- "Agravo de Instrumento": """
297
- 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.
298
- """,
299
- "Petição de Tutela Provisória": """
300
- 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.
301
- """
302
- }
303
- return casos.get(tipo_peticao, "⚠️ Tipo de petição não suportado.")
304
 
305
  def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
306
- prompt = PROMPTS_PETICOES.get(tipo_peticao, PROMPTS_PETICOES["Petição Inicial"])
307
- base_prompt = f"""
308
- {prompt}
309
- **Caso fornecido**: {caso}
310
- **Petição fornecida**: {peticao}
311
- **Instruções adicionais**:
312
- - Se o caso ou a petição estiverem incompletos, sugira informações plausíveis com base no CPC e nas práticas forenses.
313
- - Em modo de treinamento, forneça exemplos práticos e compare com a petição fornecida.
314
- - Retorne a análise em markdown, com seções claras e sugestões específicas.
315
- """
316
- return base_prompt
317
-
318
- def analisar_peticao(file_obj, tipo_peticao, modo_treinamento):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  try:
320
- # Processa o arquivo da petição
321
- peticao_texto = process_file(file_obj)
322
- if peticao_texto.startswith("❌") or peticao_texto.startswith("⚠️"):
323
- return peticao_texto
324
-
325
- # Gera um caso simulado para o tipo de petição
326
- caso_simulado = gerar_caso_simulado(tipo_peticao)
327
-
328
- # Estrutura o prompt com base no tipo de petição
329
- prompt = estruturar_prompt_analise(caso_simulado, peticao_texto, tipo_peticao, modo_treinamento)
330
-
331
- # Chama a API do Gemini para análise
332
- headers = {"Content-Type": "application/json"}
333
- data = {
334
- "contents": [{"parts": [{"text": prompt}]}],
335
- "generationConfig": {
336
- "temperature": CONFIG["temperatura"],
337
- "maxOutputTokens": CONFIG["max_tokens"]
338
- }
339
- }
340
-
341
- with rate_limiter:
342
- for attempt in range(CONFIG["max_retry_attempts"]):
343
- try:
344
- response = requests.post(GEMINI_API_URL, headers=headers, json=data, timeout=CONFIG["timeout"])
345
- response.raise_for_status()
346
- result = response.json()
347
- if "candidates" in result and result["candidates"]:
348
- correcao = result["candidates"][0]["content"]["parts"][0]["text"]
349
- CORRECAO_CACHE[peticao_texto[:50]] = correcao
350
- HISTORICO.append({
351
- "id": str(uuid.uuid4()),
352
- "timestamp": datetime.now().isoformat(),
353
- "tipo_peticao": tipo_peticao,
354
- "peticao": peticao_texto[:500],
355
- "correcao": correcao
356
- })
357
- return correcao
358
- else:
359
- logger.error("Nenhuma resposta válida retornada pela API.")
360
- return "❌ Nenhuma resposta válida retornada pela API."
361
- except requests.exceptions.RequestException as e:
362
- logger.error(f"Tentativa {attempt + 1} falhou: {str(e)}")
363
- if attempt < CONFIG["max_retry_attempts"] - 1:
364
- time.sleep(CONFIG["retry_delay"])
365
- else:
366
- return f"❌ Erro após {CONFIG['max_retry_attempts']} tentativas: {str(e)}"
367
- except Exception as e:
368
- logger.error(f"Erro ao analisar a petição: {str(e)}")
369
- return f"❌ Erro ao analisar a petição: {str(e)}"
370
-
371
- # Interface do Gradio
372
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
373
- gr.Markdown("# Corretor de Petições Jurídicas")
374
- 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.")
375
-
376
- with gr.Row():
377
- tipo_peticao = gr.Dropdown(
378
- choices=list(PROMPTS_PETICOES.keys()),
379
- label="Tipo de Petição",
380
- value="Petição Inicial"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  )
382
- file_input = gr.File(label="Upload da Petição (PDF, DOCX, TXT)", file_types=[".pdf", ".docx", ".txt"])
383
-
384
- modo_treinamento = gr.Checkbox(label="Modo de Treinamento (inclui exemplos práticos)", value=False)
385
-
386
- with gr.Row():
387
- submit_button = gr.Button("Analisar Petição", variant="primary")
388
-
389
- output = gr.Markdown(label="Resultado da Análise")
390
-
391
- submit_button.click(
392
- fn=analisar_peticao,
393
- inputs=[file_input, tipo_peticao, modo_treinamento],
394
- outputs=output
395
- )
396
 
397
  if __name__ == "__main__":
398
- 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: file_path_or_obj.seek(0)
124
+ except Exception as seek_e: logger.warning(f"Não foi possível resetar ponteiro TXT: {seek_e}")
 
 
 
 
 
125
  content = file_path_or_obj.read()
126
+ else: return "❌ Input TXT inválido."
127
+ for enc in encodings:
128
+ try:
129
+ decoded = content.decode(enc)
130
+ logger.info(f"TXT decodificado: {enc}.")
131
+ return decoded if decoded.strip() else "⚠️ TXT vazio."
132
+ except UnicodeDecodeError: continue
133
+ return "❌ Não foi possível decodificar TXT."
134
+ except Exception as e: logger.error(f"Erro TXT: {e}", exc_info=True); return f"❌ Erro TXT: {e}"
 
135
 
136
  def process_file(file_obj):
137
+ if file_obj is None: return "⚠️ Nenhum arquivo."
 
138
  try:
139
+ fpath = getattr(file_obj, 'name', None);
140
+ if not fpath: return "❌ Erro: Não identificou arquivo."
141
+ fname = os.path.basename(fpath).lower(); logger.info(f"Processando: {fname}")
142
+ try:
143
+ fsize = os.path.getsize(fpath); logger.info(f"Tamanho: {fsize} bytes")
144
+ if fsize > 15*1024*1024: return "⚠️ Arquivo grande (Máx 15MB)."
145
+ except OSError as e: logger.warning(f"Erro tamanho arquivo: {e}")
146
+ if fname.endswith('.pdf'): return extract_text_from_pdf(fpath)
147
+ elif fname.endswith('.docx'): return extract_text_from_docx(fpath)
148
+ elif fname.endswith('.txt'): return extract_text_from_txt(fpath)
149
+ else: return "⚠️ Formato não suportado (PDF, DOCX, TXT)."
150
+ except Exception as e: logger.error(f"Erro process_file: {e}", exc_info=True); return f"❌ Erro processar arquivo: {e}"
151
+
152
+ def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
153
+ base = """
154
+ **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.
155
+ **Critérios:** Coerência, Fundamentação, Linguagem, Pedidos. NÃO INVENTAR FATOS(-3a-5). NÃO SE IDENTIFICAR(Nota ZERO)."""
156
+ if modo_treinamento: base += "\n**Treinamento:** Detalhar Pontos a Melhorar(a-e). Destacar inconsistências. Sugerir estratégias."
157
+ return base
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
160
+ instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento)
161
+ 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"
162
+ 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)."
163
+ 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)."
164
+ 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)."
165
+ 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)."
166
+ 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)."
167
+ 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)."
168
+ 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)."
169
+ 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)."
170
+ else: prompt += "-Adequação? Clareza? Fundam? Pedido?"
171
+ prompt += "\n\n**Instruções Finais:** Analise estritamente. Rigoroso e construtivo. Gere **exclusivamente** no formato solicitado."
172
+ return prompt
173
+
174
+ def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
175
+ 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"
176
+ 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...')."
177
+ 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."
178
+ 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."
179
+ 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."
180
+ 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."
181
+ 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."
182
+ 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."
183
+ 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."
184
+ else: prompt += "1.Estrutura. 2.Args Claros. 3.Ped Claro. 4.Formais OAB."
185
+ prompt += "\n\n**Instrução Final:** Gere **APENAS** texto da peça corrigida (endereçamento até 'OAB/...'). Sem comentários."
186
+ return prompt
187
+
188
+ def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
189
+ if "Nestes termos, pede deferimento" in peticao_corrigida:
190
+ peticao_corrigida = peticao_corrigida.replace("Nestes termos, pede deferimento", "Nestes termos pede deferimento")
191
+ if tipo_peticao == "Petição Inicial":
192
+ p_lower = peticao_corrigida.lower()
193
+ if "juntada da guia de custas" not in p_lower and "gratuidade de justiça" not in p_lower:
194
+ partes = peticao_corrigida.split("Nestes termos pede deferimento")
195
+ 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]
196
+ else: peticao_corrigida = peticao_corrigida.replace("Local, data.","Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.")
197
+ if "audiência de conciliação" not in p_lower and "audiência de mediação" not in p_lower:
198
+ partes = peticao_corrigida.split("Nestes termos pede deferimento")
199
+ 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]
200
+ 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.")
201
+ linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
202
+ fechamento = ["Local, data.", "ADVOGADO(A)", "OAB/..."]
203
+ while linhas and any(t in linhas[-1] for t in ["ADVOGADO","OAB","Local","data"]): linhas.pop()
204
+ linhas.extend(fechamento)
205
+ return "\n".join(linhas)
206
+
207
+ def chamar_api(prompt):
208
+ logger.info(f"Chamando API Gemini (Modelo: {GEMINI_MODEL_NAME})...")
209
+ model = genai.GenerativeModel(GEMINI_MODEL_NAME, safety_settings=SAFETY_SETTINGS)
210
+ gen_config = genai.types.GenerationConfig(temperature=CONFIG["temperatura"], max_output_tokens=CONFIG["max_tokens"], top_p=0.95)
211
+ for tentativa in range(CONFIG["max_retry_attempts"]):
212
+ try:
213
+ with rate_limiter:
214
+ logger.debug(f"API Tentativa {tentativa + 1}")
215
+ response = model.generate_content(contents=prompt, generation_config=gen_config)
216
+ if response.prompt_feedback.block_reason:
217
+ logger.error(f"Prompt bloqueado: {response.prompt_feedback.block_reason}")
218
+ return f"❌ Prompt bloqueado: {response.prompt_feedback.block_reason}. Reformule."
219
+ if not response.text:
220
+ if response.candidates and response.candidates[0].finish_reason != genai.types.FinishReason.STOP:
221
+ finish_reason = response.candidates[0].finish_reason.name
222
+ logger.error(f"Geração interrompida (s/ texto): {finish_reason}")
223
+ safety = response.candidates[0].safety_ratings
224
+ harms = [r.category.name for r in safety if r.probability.name not in ['NEGLIGIBLE', 'LOW']]
225
+ if harms: return f"❌ Bloqueado segurança: {finish_reason} ({','.join(harms)})."
226
+ else: return f"❌ Geração interrompida: {finish_reason}."
227
+ else: logger.warning("API retornou s/ texto ou bloqueio."); return "⚠️ API retornou resposta vazia."
228
+ logger.info(f"API OK ({len(response.text)} chars).")
229
+ return response.text
230
+ except google_exceptions.ResourceExhausted as e:
231
+ logger.warning(f"Erro 429 - Tentativa {tentativa + 1}: {e}")
232
+ if tentativa < CONFIG["max_retry_attempts"] - 1: wait = CONFIG['retry_delay']*(2**tentativa); logger.info(f"Aguardando {wait}s..."); time.sleep(wait); continue
233
+ return "❌ Erro 429: Muitas requisições."
234
+ 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}'."
235
+ except google_exceptions.InvalidArgument as e: logger.error(f"Erro Argumento API: {e}"); return f"❌ Erro parâmetros API: {e}"
236
+ except genai.types.BlockedPromptException as e: logger.error(f"Prompt bloqueado (exc): {e}"); return f"❌ Prompt bloqueado. Reformule."
237
+ except genai.types.StopCandidateException as e: logger.error(f"Geração interrompida (exc): {e}"); return f"❌ Geração interrompida (segurança/outro)."
238
+ except google_exceptions.GoogleAPIError as e:
239
+ logger.error(f"Erro API Google - Tentativa {tentativa + 1}: {e}")
240
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay']*(tentativa + 1)); continue
241
+ return f"❌ Erro API Google: {e}"
242
+ except Exception as e:
243
+ logger.exception(f"Erro inesperado API - Tentativa {tentativa + 1}: {e}")
244
+ if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay']*(tentativa + 1)); continue
245
+ return f"❌ Erro inesperado API: {e}"
246
+ logger.error("Falha API após todas as tentativas.")
247
+ return "❌ Falha API após múltiplas tentativas."
248
+
249
+ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
250
+ logger.info(f"Correção: Tipo={tipo_peticao}, Treinamento={modo_treinamento}")
251
+ start = time.time()
252
+ analise_fmt, pet_corr, res_diff, crit_diff, html_diff = "", "", "", "", ""
253
+ pet_cont = ""
254
  try:
255
+ if not caso or not caso.strip(): return "⚠️ Forneça caso.", "", "", "", ""
256
+ if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo.", "", "", "", ""
257
+ if peticao_arquivo: pet_cont = process_file(peticao_arquivo)
258
+ elif peticao_texto: pet_cont = peticao_texto
259
+ else: return "⚠️ Forneça petição.", "", "", "", ""
260
+ if pet_cont.startswith(("❌", "⚠️")): return pet_cont, "", "", "", ""
261
+ logger.info(f"Petição recebida ({len(pet_cont)} chars).")
262
+ troll = ["escreva a petição", "faça a petição", "gere a petição"]
263
+ if any(t in pet_cont.lower() for t in troll): return random.choice(["Hummm 😉","🚫 Corrijo, não crio."]), "", "", "", ""
264
+ cache_key = f"{tipo_peticao}|{caso[:100]}|{pet_cont[:100]}|{modo_treinamento}"
265
+ if cache_key in CORRECAO_CACHE:
266
+ logger.info("Cache hit.")
267
+ cached = CORRECAO_CACHE[cache_key]
268
+ return cached["analise_formatada"], cached["peticao_corrigida"], cached["resumo_diff"], cached["criticas_diff"], cached["diff_html"]
269
+ prompt_analise = estruturar_prompt_analise(caso, pet_cont, tipo_peticao, modo_treinamento)
270
+ analise_bruta = chamar_api(prompt_analise)
271
+ if analise_bruta.startswith(("❌", "⏱️", "⚠️")): return analise_bruta, "", "", "", ""
272
+ analise_fmt = formatar_correcao(analise_bruta)
273
+ prompt_peticao = estruturar_prompt_peticao_corrigida(caso, pet_cont, tipo_peticao)
274
+ pet_corr_bruta = chamar_api(prompt_peticao)
275
+ if pet_corr_bruta.startswith(("❌", "⏱️", "⚠️")): pet_corr = f"⚠️ Falha gerar corrigida:\n{pet_corr_bruta}"
276
+ else:
277
+ markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
278
+ start_index = -1
279
+ p_lower = pet_corr_bruta.lower()
280
+ # CORRIGIDO: Bloco for com indentação correta
281
+ for m in markers:
282
+ idx = p_lower.find(m)
283
+ if idx != -1:
284
+ start_index = idx
285
+ break
286
+ pet_extr = pet_corr_bruta[start_index:] if start_index != -1 else pet_corr_bruta
287
+ pet_corr = pos_corrigir_peticao(pet_extr, tipo_peticao)
288
+ html_diff, res_diff, crit_diff = calcular_diff_interno(pet_cont, pet_corr)
289
+ res_cache = {"analise_formatada":analise_fmt,"peticao_corrigida":pet_corr,"resumo_diff":res_diff,"criticas_diff":crit_diff,"diff_html":html_diff}
290
+ CORRECAO_CACHE[cache_key] = res_cache
291
+ HISTORICO.append({"data":datetime.now().strftime("%Y%m%d_%H%M%S"),"tipo_peticao":tipo_peticao,"caso":caso,"peticao_original":pet_cont,**res_cache})
292
+ if len(HISTORICO) > 10: HISTORICO.pop(0)
293
+ logger.info(f"Correção concluída em {time.time() - start:.2f}s.")
294
+ return analise_fmt, pet_corr, res_diff, crit_diff, html_diff
295
+ except Exception as e: logger.exception("Erro GERAL corrigir_peticao:"); return f"❌ Erro correção: {e}", "", "", "", ""
296
+
297
+ def formatar_correcao(texto):
298
+ texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
299
+ texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
300
+ texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
301
+ texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
302
+ texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica")
303
+ linhas = texto.split('\n'); linhas_fmt = []
304
+ in_list = False
305
+ for l in linhas:
306
+ ls = l.strip()
307
+ if ls.startswith(("1.","2.","3.","4.","5.")): linhas_fmt.append(f"\n**{ls}**"); in_list = False
308
+ elif ls.startswith(("- ","* ")): prefix = " * " if in_list else "* "; linhas_fmt.append(prefix + ls[2:]); in_list = True
309
+ elif ls.startswith(("a)","b)","c)","d)","e)")): linhas_fmt.append(f" * {ls}"); in_list = True
310
+ else: linhas_fmt.append(l); in_list = False if not ls.startswith(("*","- ","a)","b)","c)","d)","e)"," ")) else in_list
311
+ txt_final = "\n".join(linhas_fmt)
312
+ m = re.search(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})", txt_final, re.I)
313
+ 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)
314
+ return txt_final
315
+
316
+ def fornecer_dica_contextual(texto_peticao, tipo_peticao):
317
+ if not texto_peticao or not texto_peticao.strip(): return "*Digite para dicas...*"
318
+ dicas = []; txt_l = texto_peticao.lower()
319
+ for k in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]:
320
+ if k in DICAS_OAB_MELHORADO: dicas.append(f"💡 **{k}:** {DICAS_OAB_MELHORADO[k]}")
321
+ kw = { "Petição Inicial": ["endereçamento","qualificação","fatos","pedidos","valor","provas","audiência","custas"],
322
+ "Contestação": ["preliminar","mérito","impugnação","art. 337"], "Embargos de Declaração": ["omissão","contradição","art. 1022"],
323
+ "Recurso de Apelação": ["sentença","reforma","dialeticidade","art. 1009"], "Agravo de Instrumento": ["interlocutória","cabimento","art. 1015"],
324
+ "Petição de Tutela Provisória": ["urgência","evidência","periculum","fumus","art. 300"]}
325
+ if tipo_peticao in kw:
326
+ for k, v in DICAS_OAB_MELHORADO.items():
327
+ cl = k.lower()
328
+ 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):
329
+ fmt = f"💡 **{k}:** {v}"
330
+ if fmt not in dicas: dicas.append(fmt)
331
+ if not dicas: return "*Continue para dicas...*"
332
+ return "\n\n".join(dicas[:4])
333
+
334
+ def calcular_diff_interno(texto_original, texto_corrigido):
335
+ if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")): return "","",""
336
+ orig = texto_original.splitlines(); corr = texto_corrigido.splitlines()
337
+ diff = difflib.ndiff(orig, corr)
338
+ html, crit, rem, add = [], [], 0, 0
339
+ kw_r = ["invenção","identificação","erro","incorreto","prazo"]
340
+ kw_a = ["custas","art. 319","art. 337","art. 1022","art. 1015","art. 300","fumus","periculum","essencial","preliminar","tempestividade","cabimento"]
341
+ for l in diff:
342
+ txt = l[2:]; txt_esc = escape(txt)
343
+ if l.startswith(' '): html.append(f"<div class='linha-igual'>{txt_esc}</div>")
344
+ elif l.startswith('- '):
345
+ rem += 1; html.append(f"<div class='linha-removida'><del>{txt_esc}</del></div>")
346
+ 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}")
347
+ elif l.startswith('+ '):
348
+ add += 1; html.append(f"<div class='linha-adicionada'><ins>{txt_esc}</ins></div>")
349
+ 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}")
350
+ resumo = f"**Resumo:** Rem={rem}, Add={add}, Saldo={add-rem}"
351
+ crit_txt = "### 🚨 Críticas:\n"+"\n".join(crit) if crit else "*Nenhuma crítica.*"
352
+ 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>"
353
+ html_diff = f"{css}<div class='diff-container'>{''.join(html)}</div>"
354
+ return html_diff, resumo, crit_txt
355
+
356
+ def calcular_progresso():
357
+ if not HISTORICO: return "📊 Nenhum progresso."
358
+ notas, tipos = [], set()
359
+ for entry in HISTORICO:
360
+ tipos.add(entry["tipo_peticao"])
361
+ m = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.I)
362
+ if m:
363
+ try: notas.append(float(m.group(1).replace(",", ".")))
364
+ except ValueError: pass
365
+ tot = len(HISTORICO); media = sum(notas)/len(notas) if notas else 0; tend = ""
366
+ if len(notas) > 1: tend = "⬆️" if notas[-1] > notas[-2] else ("⬇️" if notas[-1] < notas[-2] else "↔️")
367
+ return f"**📊 Resumo:** Corrs:{tot}, Tipos:{len(tipos)}, Média:{media:.1f}/10 ({len(notas)} val), Tend:{tend if tend else 'N/A'}"
368
+
369
+ def mostrar_historico():
370
+ if not HISTORICO: return "📜 Histórico vazio."
371
+ md = "### 📜 Histórico (Últimas 10)\n---\n"
372
+ for i, entry in enumerate(reversed(HISTORICO)):
373
+ caso = (entry['caso'][:70] + '...') if len(entry['caso']) > 70 else entry['caso']
374
+ 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)
375
+ if m: nota = f"{m.group(1).replace(',', '.')}/10"
376
+ md += f"**{len(HISTORICO)-i}.** {entry['data']} | **{entry['tipo_peticao']}** | **Nota:{nota}**\n **Caso:** _{caso}_\n---\n"
377
+ return md
378
+
379
+ def salvar_progresso(caso, peticao, tipo_peticao):
380
+ logger.info("Salvando progresso...")
381
+ try:
382
+ if not caso or not peticao or not tipo_peticao: return "", "⚠️ Inputs necessários.", None
383
+ dados = {"tipo_peticao":tipo_peticao,"caso":caso,"peticao":peticao,"timestamp":datetime.now().isoformat()}
384
+ json_cont = json.dumps(dados, ensure_ascii=False, indent=4)
385
+ fname = f"progresso_peticao_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
386
+ fpath = os.path.join(tempfile.gettempdir(), fname)
387
+ with open(fpath, 'w', encoding='utf-8') as f: f.write(json_cont)
388
+ logger.info(f"Progresso salvo: {fpath}")
389
+ msg = f"✅ Salvo! Baixe `{fname}`."
390
+ return json_cont, msg, fpath
391
+ except Exception as e: logger.error(f"Erro salvar: {e}", exc_info=True); return "", f"❌ Erro salvar: {e}", None
392
+
393
+ def carregar_progresso(arquivo_entrada):
394
+ logger.info("Carregando progresso...")
395
+ if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo."
396
+ try:
397
+ fpath = arquivo_entrada.name; logger.info(f"Carregando: {fpath}")
398
+ with open(fpath, 'r', encoding='utf-8') as f: dados = json.load(f)
399
+ caso, peticao, tipo = dados.get("caso"), dados.get("peticao"), dados.get("tipo_peticao")
400
+ if not all([caso, peticao, tipo]): return "", "", "", "❌ Arquivo inválido."
401
+ if tipo not in TIPOS_DE_PETICAO: return caso, peticao, "", f"⚠️ Tipo '{tipo}' inválido."
402
+ logger.info("Progresso carregado."); return tipo, caso, peticao, "✅ Progresso carregado!"
403
+ except Exception as e: logger.error(f"Erro carregar: {e}", exc_info=True); return "", "", "", f"❌ Erro carregar: {e}"
404
+
405
+ def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
406
+ 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"
407
+ if tipo_peticao == "Petição Inicial": prompt += "* Descreva violação. * Inclua danos. * Dados qualificação. * Tema: Consumo, Contrato, etc."
408
+ elif tipo_peticao == "Contestação": prompt += "* Resumo PI recebida. * Versão Réu (p/ prelim, impug, mérito). * Dados qualif réu."
409
+ elif tipo_peticao == "Reconvenção": prompt += "* Estrutura Contestação + Fatos/Fundamentos p/ pedido PRÓPRIO CONEXO do Réu contra Autor."
410
+ elif tipo_peticao == "Impugnação à Contestação": prompt += "* Resumo PI Autor. * Resumo Contestação Réu (prelim, fatos novos, docs). * Objetivo: Rebater."
411
+ elif tipo_peticao == "Embargos de Declaração": prompt += "* Breve caso. * TRECHO DECISÃO. * INDIQUE VÍCIO (omissão/contradição/obscuridade/erro material)."
412
+ elif tipo_peticao == "Recurso de Apelação": prompt += "* Caso original. * Resumo SENTENÇA. * ELEMENTOS ERRO (in judicando/procedendo). * Indique apelante."
413
+ elif tipo_peticao == "Agravo de Instrumento": prompt += "* Caso andamento. * DECISÃO INTERLOCUTÓRIA. * Enquadrar Art.1015/mitig. * ELEMENTOS ERRO + URGÊNCIA. * Indique agravante."
414
+ 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."
415
+ else: prompt += "* Crie cenário cível com conflito."
416
+ 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.'
417
+ return prompt
418
+
419
+ def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
420
+ logger.info(f"Gerando caso para: {tipo_peticao}")
421
+ try:
422
+ if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo válido."
423
+ prompt = estruturar_prompt_geracao_caso(tipo_peticao)
424
+ caso_gerado = chamar_api(prompt)
425
+ if caso_gerado.startswith(("❌","⏱️","⚠️")): return f"⚠️ Falha gerar caso:\n{caso_gerado}"
426
+ frase = "**Situação-Problema:** Na qualidade de advogado(a) de"
427
+ if frase.lower() not in caso_gerado.lower():
428
+ logger.warning("Frase final não encontrada. Adicionando.")
429
+ nomes = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado)
430
+ cliente = nomes[0] if nomes else "[Cliente]"
431
+ caso_gerado += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente}, elabore a peça processual cabível ({tipo_peticao})."
432
+ logger.info("Caso gerado.")
433
+ return caso_gerado
434
+ except Exception as e: logger.exception("Erro gerar_caso:"); return f"❌ Erro gerar caso: {e}"
435
+
436
+ def atualizar_config(temp, max_tokens):
437
+ CONFIG["temperatura"] = temp
438
+ CONFIG["max_tokens"] = max_tokens
439
+ logger.info(f"Configs: Temp={temp}, Max Tokens={max_tokens}")
440
+ return f"✅ Configs Atualizadas!"
441
+
442
+ def interface_gradio():
443
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
444
+ gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️\nAnalise e aprimore suas peças processuais!")
445
+ with gr.Row():
446
+ with gr.Column(scale=1):
447
+ gr.Markdown("### ⚙️ Configs")
448
+ tipo_peticao_input = gr.Dropdown(label="1. Tipo Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial")
449
+ modo_treinamento = gr.Checkbox(label="🎓 Treinamento", value=False)
450
+ with gr.Accordion("🛠️ Opções IA", open=False):
451
+ temperatura_slider = gr.Slider(0.0, 1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade")
452
+ max_tokens_slider = gr.Slider(2048, 8192, value=CONFIG["max_tokens"], step=256, label="Max Tokens")
453
+ config_update_btn = gr.Button("💾 Salvar Configs")
454
+ config_status = gr.Textbox(label="Status", interactive=False, lines=1)
455
+ with gr.Accordion("📊 Dashboard", open=False):
456
+ progresso_output = gr.Markdown("*Atualize...*")
457
+ progresso_btn = gr.Button("🔄 Progresso")
458
+ with gr.Accordion("📜 Histórico", open=False):
459
+ historico_output = gr.Markdown("*Atualize...*")
460
+ historico_btn = gr.Button("🔄 Histórico")
461
+ with gr.Accordion("💾📂 Salvar/Carregar", open=False):
462
+ salvar_btn = gr.Button("💾 Salvar")
463
+ carregar_btn = gr.Button("📂 Carregar")
464
+ arquivo_progresso_upload = gr.File(label=".json", file_types=[".json"])
465
+ progresso_download_file = gr.File(label="Baixar", interactive=False)
466
+ with gr.Column(scale=3):
467
+ gr.Markdown("### 1. Caso / Contexto")
468
+ with gr.Row():
469
+ caso_input = gr.Textbox(label="📋 Caso", lines=8, placeholder="Insira o texto do caso...")
470
+ caso_btn = gr.Button("🎲 Gerar", variant="secondary", scale=0)
471
+ gr.Markdown("### 2. Sua Peça")
472
+ with gr.Tabs():
473
+ with gr.TabItem("✏️ Texto"):
474
+ peticao_texto_input = gr.Textbox(label="Texto", lines=12, placeholder="Cole ou digite...")
475
+ dica_contextual_output = gr.Markdown("*Dicas...*")
476
+ btn_corrigir = gr.Button("🚀 CORRIGIR!", variant="primary")
477
+ with gr.TabItem("📎 Arquivo"):
478
+ peticao_arquivo_input = gr.File(label="Arquivo (.pdf,.docx,.txt)", file_types=[".pdf",".docx",".txt"])
479
+ arquivo_preview_output = gr.Textbox(label="🔍 Prévia", lines=4, interactive=False)
480
+ btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR ARQUIVO!", variant="primary")
481
+
482
+ processing_message = gr.Markdown("", visible=False)
483
+ gr.Markdown("--- \n ### 3. Resultados")
484
+ with gr.Tabs():
485
+ with gr.TabItem("📊 Análise e Correção"):
486
+ resultado_analise_output = gr.Markdown(label="Análise e Nota")
487
+ resultado_peticao_corrigida_output = gr.Textbox(label="✨ Versão Sugerida", lines=18, show_copy_button=True)
488
+ with gr.TabItem("⚖️ Comparativo"):
489
+ gr.Markdown("### ↔️ Comparativo (Diff)")
490
+ with gr.Row():
491
+ with gr.Column(scale=1): resumo_alteracoes_output = gr.Markdown(label="Resumo"); criticas_output = gr.Markdown(label="Críticas")
492
+ with gr.Column(scale=3): visualizacao_detalhada_output = gr.HTML(label="Detalhes")
493
+ clear_btn = gr.Button("🗑️ Limpar Tudo")
494
+
495
+ caso_btn.click(lambda: gr.update(value="⏳ Gerando...", visible=True), outputs=processing_message).then(
496
+ fn=gerar_caso_ficticio, inputs=[tipo_peticao_input], outputs=[caso_input]
497
+ ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
498
+
499
+ peticao_texto_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output)
500
+ tipo_peticao_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output)
501
+ peticao_arquivo_input.change(process_file, [peticao_arquivo_input], [arquivo_preview_output])
502
+
503
+ btn_corrigir.click(lambda: gr.update(value="⏳ Processando texto...", visible=True), outputs=processing_message).then(
504
+ fn=corrigir_peticao,
505
+ inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento],
506
+ outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
507
+ concurrency_limit=1
508
+ ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
509
+
510
+ btn_corrigir_arquivo.click(lambda: gr.update(value="⏳ Processando arquivo...", visible=True), outputs=processing_message).then(
511
+ fn=corrigir_peticao,
512
+ inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento],
513
+ outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
514
+ concurrency_limit=1
515
+ ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
516
+
517
+ config_update_btn.click(atualizar_config, [temperatura_slider, max_tokens_slider], config_status)
518
+ progresso_btn.click(calcular_progresso, outputs=progresso_output)
519
+ historico_btn.click(mostrar_historico, outputs=historico_output)
520
+
521
+ salvar_btn.click(lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message).then(
522
+ fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
523
+ outputs=[gr.Textbox(visible=False), config_status, progresso_download_file]
524
+ ).then(lambda x: gr.update(visible=bool(x)), [progresso_download_file], [progresso_download_file]
525
+ ).then(lambda: gr.update(value="", visible=False), None, processing_message)
526
+
527
+ carregar_btn.click(lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message).then(
528
+ fn=carregar_progresso, inputs=[arquivo_progresso_upload],
529
+ outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status]
530
+ ).then(lambda: gr.update(value="", visible=False), None, processing_message)
531
+
532
+ clear_btn.click(
533
+ fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas...*", "", "", "", "", "", None, None, ""],
534
+ outputs=[tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, peticao_arquivo_input,
535
+ arquivo_preview_output, dica_contextual_output, resultado_analise_output, resultado_peticao_corrigida_output,
536
+ resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output,
537
+ arquivo_progresso_upload, progresso_download_file, config_status]
538
  )
539
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
540
 
541
  if __name__ == "__main__":
542
+ logger.info("Iniciando a aplicação Gradio...")
543
+ app = interface_gradio()
544
+ app.launch()
545
+ logger.info("Aplicação Gradio encerrada.")