GabrielGD commited on
Commit
677749e
·
verified ·
1 Parent(s): 65d0ba0

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1409
app.py DELETED
@@ -1,1409 +0,0 @@
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
9
- import tempfile
10
- from pdfplumber import open as pdfplumber_open
11
- from PIL import Image
12
- import pytesseract
13
- import cv2
14
- import numpy as np
15
- from datetime import datetime
16
- from ratelimiter import RateLimiter
17
- import difflib
18
- from html import escape
19
- import random
20
- import logging
21
- import re
22
-
23
- # Configuração do Logging
24
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
25
- logger = logging.getLogger(__name__)
26
-
27
- # Leitura da API Key (usar Secrets no HF)
28
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
29
- if not GEMINI_API_KEY:
30
- raise ValueError("❌ A chave GEMINI_API_KEY não foi encontrada. Configure-a como Secret nas configurações do Space.")
31
-
32
- # API Endpoint
33
- GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={GEMINI_API_KEY}"
34
-
35
- # Configurações
36
- CONFIG = {
37
- "max_retry_attempts": 3,
38
- "retry_delay": 5,
39
- "timeout": 120,
40
- "temperatura": 0.3,
41
- "max_tokens": 8192
42
- }
43
-
44
- # Rate Limiter
45
- rate_limiter = RateLimiter(max_calls=30, period=60)
46
-
47
- # Constantes e Dicionários
48
- TIPOS_DE_PETICAO = [
49
- "Petição Inicial", "Contestação", "Reconvenção", "Impugnação à Contestação",
50
- "Embargos de Declaração", "Recurso de Apelação", "Agravo de Instrumento",
51
- "Petição de Tutela Provisória"
52
- ]
53
- DICAS_OAB_MELHORADO = {
54
- "Clareza e Concisão": "Seja direto e evite jargões excessivos. Facilite a leitura do juiz.",
55
- "Fundamentação Legal": "Sempre cite os artigos de lei, súmulas e jurisprudência pertinentes ao seu argumento.",
56
- "Coerência Argumentativa": "Mantenha uma linha lógica de raciocínio do início ao fim da peça.",
57
- "Revisão Ortográfica e Gramatical": "Erros de português podem prejudicar a credibilidade da sua peça.",
58
- "Formatação": "Utilize parágrafos bem definidos, espaçamento adequado e destaque pontos importantes (negrito, itálico) com moderação.",
59
- "Endereçamento (Inicial)": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). Verifique a competência (material, territorial).",
60
- "Qualificação (Inicial)": "Inclua todos os dados do Art. 319, II, CPC. Use 'XXX' para dados não fornecidos.",
61
- "Fatos (Inicial)": "Narrativa cronológica, clara e objetiva, sem juízo de valor.",
62
- "Pedidos (Inicial)": "Certos, determinados e líquidos (sempre que possível). Cumule pedidos se necessário (Art. 327, CPC).",
63
- "Custas (Inicial)": "Sempre peça a juntada da guia ou a gratuidade de justiça (com declaração).",
64
- "Valor da Causa (Inicial)": "Deve corresponder ao benefício econômico pretendido (Art. 292, CPC).",
65
- "Preliminares (Contestação)": "Alegue todas as matérias do Art. 337 do CPC antes de discutir o mérito.",
66
- "Impugnação Específica (Contestação)": "Conteste cada fato alegado pelo autor, sob pena de presunção de veracidade (Art. 341, CPC).",
67
- "Mérito (Contestação)": "Apresente sua versão dos fatos e a fundamentação jurídica que ampara a improcedência dos pedidos do autor.",
68
- "Pedidos (Contestação)": "Requeira o acolhimento das preliminares e/ou a improcedência total dos pedidos do autor, além da condenação em custas e honorários.",
69
- "Pressupostos Recursais": "Verifique cabimento, tempestividade, preparo (custas recursais), regularidade formal e interesse/legitimidade.",
70
- "Dialeticidade (Recurso)": "Impugne especificamente os fundamentos da decisão recorrida (Art. 1010, II e III, CPC para Apelação).",
71
- "Delimitação da Matéria (Recurso)": "Indique claramente qual parte da decisão você está recorrendo.",
72
- "Fumus Boni Iuris": "Demonstre a plausibilidade do direito alegado com base em provas e argumentos sólidos.",
73
- "Periculum in Mora": "Evidencie o risco de dano irreparável ou de difícil reparação se a medida não for concedida imediatamente.",
74
- "Reversibilidade": "Se possível, argumente que a medida é reversível caso a decisão final seja diferente (Art. 300, §3º, CPC).",
75
- }
76
- HISTORICO = []
77
- CORRECAO_CACHE = {}
78
-
79
- # --- Funções de Extração de Texto ---
80
- def preprocess_image_for_ocr(img):
81
- try:
82
- gray = img.convert('L')
83
- np_img = np.array(gray)
84
- _, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
85
- return Image.fromarray(thresh)
86
- except Exception as e:
87
- logger.error(f"Erro no pré-processamento da imagem: {str(e)}")
88
- return f"❌ Erro no pré-processamento: {str(e)}"
89
-
90
- def extract_text_from_pdf(file_path_or_obj):
91
- text = ""
92
- ocr_text = ""
93
- try:
94
- pdf_reader = pdfplumber_open(file_path_or_obj)
95
- for i, page in enumerate(pdf_reader.pages):
96
- page_text = page.extract_text()
97
- if page_text:
98
- text += page_text + "\n"
99
- else:
100
- logger.warning(f"Página {i+1} sem texto extraível, tentando OCR.")
101
- try:
102
- img = page.to_image(resolution=300)
103
- preprocessed_img = preprocess_image_for_ocr(img.original)
104
- page_ocr_text = pytesseract.image_to_string(preprocessed_img, lang='por')
105
- if page_ocr_text.strip():
106
- ocr_text += page_ocr_text + "\n"
107
- else:
108
- logger.warning(f"OCR falhou na página {i+1}.")
109
- ocr_text += f"[OCR falhou na página {i+1}]\n"
110
- except Exception as ocr_e:
111
- if "Tesseract is not installed or not in your PATH" in str(ocr_e):
112
- logger.error(f"Erro no OCR: Tesseract não encontrado ou não está no PATH. Verifique a instalação e o packages.txt no HF. {str(ocr_e)}")
113
- return "❌ Erro de OCR: Tesseract não encontrado. Verifique a configuração do ambiente."
114
- else:
115
- logger.error(f"Erro no OCR da página {i+1}: {str(ocr_e)}")
116
- ocr_text += f"[Erro no OCR da página {i+1}: {str(ocr_e)}]\n"
117
- pdf_reader.close()
118
-
119
- if "❌ Erro de OCR: Tesseract não encontrado." in ocr_text:
120
- return ocr_text
121
-
122
- if text.strip() and len(text) > len(ocr_text) * 0.5:
123
- logger.info("Usando texto extraído do PDF.")
124
- return text
125
- elif ocr_text.strip():
126
- logger.info("Usando texto do OCR do PDF.")
127
- return ocr_text
128
- else:
129
- logger.warning("Não foi possível extrair texto significativo do PDF.")
130
- return "⚠️ Não foi possível extrair texto do PDF (tentativa de OCR falhou ou texto inexistente)."
131
-
132
- except ImportError as ie:
133
- logger.error(f"Erro de importação ao processar PDF: {str(ie)}. Verifique se 'pdfplumber' está no requirements.txt.")
134
- return f"❌ Erro interno: Falta dependência para PDF ({str(ie)})."
135
- except Exception as e:
136
- logger.error(f"Erro geral ao processar o PDF: {str(e)}", exc_info=True)
137
- try:
138
- logger.info("Tentando fallback com PyPDF2.")
139
- text_fallback = ""
140
- if isinstance(file_path_or_obj, str):
141
- with open(file_path_or_obj, 'rb') as f:
142
- reader = PyPDF2.PdfReader(f)
143
- for page in reader.pages:
144
- text_fallback += page.extract_text() or ""
145
- else:
146
- file_path_or_obj.seek(0)
147
- reader = PyPDF2.PdfReader(file_path_or_obj)
148
- for page in reader.pages:
149
- text_fallback += page.extract_text() or ""
150
-
151
- if text_fallback.strip():
152
- logger.info("Texto extraído com fallback PyPDF2.")
153
- return text_fallback
154
- else:
155
- return f"❌ Erro ao processar o PDF e fallback falhou: {str(e)}"
156
- except Exception as fallback_e:
157
- logger.error(f"Erro no fallback com PyPDF2: {str(fallback_e)}")
158
- return f"❌ Erro crítico ao processar o PDF: {str(e)}"
159
-
160
- def extract_text_from_docx(file_path_or_obj):
161
- try:
162
- doc = docx.Document(file_path_or_obj)
163
- text = ""
164
- for para in doc.paragraphs:
165
- text += para.text + "\n"
166
- return text if text.strip() else "⚠️ Documento DOCX vazio ou sem texto."
167
- except ImportError as ie:
168
- logger.error(f"Erro de importação ao processar DOCX: {str(ie)}. Verifique se 'python-docx' está no requirements.txt.")
169
- return f"❌ Erro interno: Falta dependência para DOCX ({str(ie)})."
170
- except Exception as e:
171
- logger.error(f"Erro ao processar o DOCX: {str(e)}", exc_info=True)
172
- return f"❌ Erro ao processar o DOCX: {str(e)}"
173
-
174
- def extract_text_from_txt(file_path_or_obj):
175
- try:
176
- if isinstance(file_path_or_obj, str):
177
- with open(file_path_or_obj, 'rb') as f:
178
- content = f.read()
179
- elif hasattr(file_path_or_obj, 'read'):
180
- content = file_path_or_obj.read()
181
- if hasattr(file_path_or_obj, 'seek'):
182
- try: file_path_or_obj.seek(0)
183
- except: pass
184
- else:
185
- return "❌ Tipo de entrada TXT inválido."
186
-
187
- encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
188
- for encoding in encodings:
189
- try:
190
- decoded_text = content.decode(encoding)
191
- logger.info(f"Arquivo TXT decodificado com {encoding}.")
192
- return decoded_text if decoded_text.strip() else "⚠️ Arquivo TXT vazio."
193
- except UnicodeDecodeError:
194
- continue
195
- logger.error("Não foi possível decodificar o arquivo TXT com encodings comuns.")
196
- return "❌ Não foi possível decodificar o arquivo TXT. Verifique o encoding."
197
- except Exception as e:
198
- logger.error(f"Erro ao processar o TXT: {str(e)}", exc_info=True)
199
- return f"❌ Erro ao processar o TXT: {str(e)}"
200
-
201
- def process_file(file_obj):
202
- logger.info(">>> PROCESS FILE INICIADO")
203
- if file_obj is None:
204
- logger.warning("--- Nenhum arquivo fornecido para processamento.")
205
- return "⚠️ Nenhum arquivo enviado."
206
- try:
207
- file_path = file_obj.name
208
- filename = os.path.basename(file_path).lower()
209
- logger.info(f"--- Processando arquivo: {filename} (path: {file_path})")
210
-
211
- try:
212
- file_size = os.path.getsize(file_path)
213
- logger.info(f"--- Tamanho do arquivo: {file_size} bytes")
214
- if file_size > 15 * 1024 * 1024:
215
- logger.warning(f"--- Arquivo {filename} excede o limite de tamanho.")
216
- return "⚠️ O arquivo é muito grande. Máx. 15 MB."
217
- except OSError as size_e:
218
- logger.warning(f"--- Não foi possível verificar o tamanho do arquivo: {size_e}")
219
-
220
- result_text = ""
221
- if filename.endswith('.pdf'):
222
- logger.info("--- Chamando extract_text_from_pdf")
223
- result_text = extract_text_from_pdf(file_path)
224
- elif filename.endswith('.docx'):
225
- logger.info("--- Chamando extract_text_from_docx")
226
- result_text = extract_text_from_docx(file_path)
227
- elif filename.endswith('.txt'):
228
- logger.info("--- Chamando extract_text_from_txt")
229
- result_text = extract_text_from_txt(file_path)
230
- else:
231
- logger.warning(f"--- Formato de arquivo não suportado: {filename}")
232
- result_text = "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
233
-
234
- logger.info(f"<<< PROCESS FILE FINALIZADO. Resultado: {result_text[:100]}...")
235
- return result_text
236
-
237
- except Exception as e:
238
- logger.error(f"Erro geral ao processar o arquivo '{getattr(file_obj, 'name', 'N/A')}': {str(e)}", exc_info=True)
239
- logger.info("<<< PROCESS FILE FINALIZADO COM ERRO GERAL")
240
- return f"❌ Erro inesperado ao processar o arquivo: {str(e)}"
241
-
242
- # --- Funções de Estruturação de Prompt ---
243
- def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
244
- base = """
245
- **Estrutura da Avaliação:**
246
- 1. **Análise Geral:** Visão geral concisa (2-3 linhas) sobre a adequação da peça ao caso e tipo, consistência e clareza.
247
- 2. **Nota Final:** 0 a 10 (uma casa decimal), com justificativa BREVE dos principais fatores.
248
- 3. **Pontos Positivos:** Liste pelo menos 2-3 aspectos bem executados, com justificativa (legal/técnica).
249
- 4. **Pontos a Melhorar:** Liste **TODOS** os problemas em ordem de gravidade (erros crassos primeiro). Para cada ponto:
250
- * Descrição clara do erro/omissão.
251
- * Fundamentação legal/doutrinária do porquê é um erro.
252
- * **Impacto estimado na nota** (ex: -0.5, -1.0, -2.0).
253
- * Sugestão de correção ou como deveria ter sido feito.
254
- 5. **Análise Específica dos Requisitos:** Avalie os itens chave da peça (listados abaixo para cada tipo).
255
-
256
- **Critérios Fundamentais (Aplicáveis a Quase Todos):**
257
- * **Coerência:** A peça responde ao caso/situação processual? Os argumentos são lógicos?
258
- * **Fundamentação:** Usou a lei, doutrina e jurisprudência corretamente? Citou artigos relevantes?
259
- * **Linguagem:** Clareza, objetividade, correção gramatical e formalidade adequada?
260
- * **Pedidos:** São claros, certos, determinados e coerentes com a fundamentação?
261
- * **NÃO INVENTAR FATOS:** Penalidade severa (-3.0 a -5.0) se criar fatos não presentes no caso.
262
- * **NÃO SE IDENTIFICAR:** Nota ZERO se houver nome, OAB, CPF, etc., do aluno na peça (exceto nomes das partes do caso). Use "ADVOGADO(A)", "OAB/...".
263
- """
264
- if modo_treinamento:
265
- base += """
266
- **Modo de Treinamento:**
267
- * Para cada Ponto a Melhorar, detalhe AINDA MAIS:
268
- a) Explicação aprofundada do erro e sua consequência prática.
269
- b) Base legal/doutrinária específica.
270
- c) **Exemplo de Redação Corrigida** para aquele trecho específico.
271
- d) Dica prática relevante (use o banco de dicas se aplicável).
272
- e) Possível estratégia alternativa.
273
- * Destaque inconsistências entre a peça e o caso com citações diretas.
274
- * Sugira estratégias jurídicas adicionais ou alternativas viáveis.
275
- """
276
- return base
277
-
278
- def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
279
- instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento)
280
- prompt_especifico = f"""
281
- ## Análise de Peça Processual: {tipo_peticao} (Critérios OAB Simulados)
282
-
283
- **Caso/Contexto Fornecido:**
284
- ```
285
- {caso}
286
- ```
287
-
288
- **Peça do Aluno ({tipo_peticao}):**
289
- ```
290
- {peticao}
291
- ```
292
-
293
- {instrucoes_gerais}
294
-
295
- **Requisitos Específicos para {tipo_peticao}:**
296
- """
297
- if tipo_peticao == "Petição Inicial":
298
- prompt_especifico += """
299
- - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I).
300
- - Qualificação completa das partes (Art. 319, II, CPC).
301
- - Exposição clara e lógica dos fatos (causa de pedir remota).
302
- - Fundamentação jurídica pertinente (causa de pedir próxima - Art. 319, III, CPC).
303
- - Pedidos certos, determinados e líquidos (se possível) (Art. 319, IV, CPC).
304
- - Valor da causa correto e justificado (Art. 291-292, 319, V, CPC).
305
- - Indicação das provas pretendidas (Art. 319, VI, CPC).
306
- - Opção pela audiência de conciliação/mediação (Art. 319, VII, CPC).
307
- - Pedido de citação do réu (forma específica se necessário).
308
- - Requerimento de juntada de documentos essenciais (procuração, custas/JG).
309
- - Fechamento formal ("Nestes termos pede deferimento", Local, data, Advogado, OAB).
310
- **Critérios Adicionais (Inicial):**
311
- * Uso de "XXX" para dados ausentes. Penalizar invenção (-0.5 por ocorrência).
312
- * Falta de pedido de juntada de custas/JG: -1.0 ponto.
313
- * Erro em "Nestes termos, pede deferimento" (com vírgula): -0.2 ponto.
314
- * Analisar foro (Art. 46 CPC vs. Art. 101 CDC): Escolha do foro do consumidor é válida se aplicável; não penalizar, apenas comentar se gerar dificuldades práticas.
315
- """
316
- elif tipo_peticao == "Contestação":
317
- prompt_especifico += """
318
- - Endereçamento ao juízo correto onde tramita a ação.
319
- - Qualificação completa do réu (se já não constante nos autos, referenciar).
320
- - Indicação do processo (número).
321
- - Tempestividade (menção implícita ou explícita de que está no prazo).
322
- - **Preliminares (Art. 337, CPC):** Alegar TODAS as aplicáveis (inexistência/nulidade citação, incompetência, inépcia, perempção, litispendência, coisa julgada, conexão, incapacidade, convenção de arbitragem, ausência de legitimidade/interesse, falta de caução, indevida concessão de JG).
323
- - **Impugnação Específica dos Fatos:** Rebater PONTO A PONTO os fatos alegados na inicial (Art. 341, CPC). A falta de impugnação específica torna o fato incontroverso!
324
- - **Mérito:** Apresentar a versão do réu sobre os fatos; apresentar a defesa de mérito direta (negando direito do autor) e/ou indireta (fatos impeditivos, modificativos, extintivos do direito do autor - ex: pagamento, prescrição, compensação).
325
- - Fundamentação jurídica da defesa.
326
- - **Pedidos:** Requerer acolhimento das preliminares (com extinção ou remessa); NO MÉRITO, requerer a IMPROCEDÊNCIA TOTAL dos pedidos do autor; Requerer a condenação do autor nas custas e honorários advocatícios (Art. 85, CPC).
327
- - Indicação das provas que pretende produzir.
328
- - Fechamento formal.
329
- **Critérios Adicionais (Contestação):**
330
- * A não alegação de preliminar arguível (exceto incompetência absoluta, conexão, convenção de arbitragem) preclui? Avaliar impacto.
331
- * Falha grave na impugnação específica: -2.0 a -4.0 pontos.
332
- * Ausência de pedido de improcedência: -1.5 pontos.
333
- """
334
- elif tipo_peticao == "Reconvenção":
335
- prompt_especifico += """
336
- - Apresentada NA MESMA PEÇA da Contestação (Art. 343, CPC).
337
- - Endereçamento ao mesmo juízo.
338
- - Indicação do processo.
339
- - Qualificação completa do réu-reconvinte e do autor-reconvindo (podem ser referenciadas se já completas).
340
- - **Conexão com a Ação Principal ou Fundamento de Defesa:** O pedido da reconvenção deve ser conexo (Art. 343, caput).
341
- - **Estrutura Similar à Petição Inicial:**
342
- - Fatos que fundamentam a reconvenção.
343
- - Fundamentação jurídica do pedido reconvencional.
344
- - Pedidos específicos da reconvenção (condenação do autor-reconvindo).
345
- - Valor da causa para a reconvenção.
346
- - Indicação de provas para a reconvenção.
347
- - Pedido de intimação do autor-reconvindo para contestar a reconvenção (na pessoa de seu advogado - Art. 343, §1º).
348
- - Fechamento formal (pode ser conjunto com a contestação).
349
- **Critérios Adicionais (Reconvenção):**
350
- * Ausência de conexão clara: -2.0 pontos.
351
- * Falta de pedido/valor da causa/provas específicos para a reconvenção: -1.0 por item.
352
- * Apresentada em peça separada da contestação (erro formal grave): -3.0 pontos.
353
- """
354
- elif tipo_peticao == "Impugnação à Contestação":
355
- prompt_especifico += """
356
- - Endereçamento ao juízo correto.
357
- - Indicação do processo.
358
- - Qualificação do autor (impugnante).
359
- - Tempestividade (prazo de 15 dias - Art. 350/351 CPC).
360
- - **Resposta às Preliminares:** Rebater cada preliminar arguida na contestação.
361
- - **Resposta a Fatos Impeditivos/Modificativos/Extintivos:** Se o réu alegou fatos novos (Art. 350), o autor DEVE se manifestar sobre eles.
362
- - **Resposta a Documentos Novos:** Manifestar-se sobre documentos juntados com a contestação (Art. 437 CPC).
363
- - **Ratificação da Inicial:** Reiterar os termos e pedidos da petição inicial.
364
- - **Especificação de Provas:** Se o réu apresentou defesa de mérito indireta ou preliminares que exigem prova, o autor deve especificar as provas que pretende produzir para contrapor (Art. 350/351).
365
- - Pedidos: Rejeição das preliminares, refutação da defesa de mérito, procedência dos pedidos da inicial.
366
- - Fechamento formal.
367
- **Critérios Adicionais (Impugnação):**
368
- * Não responder a uma preliminar relevante: -1.5 pontos.
369
- * Não se manifestar sobre fato novo impeditivo/modificativo/extintivo alegado: -2.0 pontos.
370
- * Não ratificar a inicial: -0.5 pontos.
371
- """
372
- elif tipo_peticao == "Embargos de Declaração":
373
- prompt_especifico += """
374
- - **Contra Decisão Judicial Específica:** Indicar claramente qual decisão está sendo embargada (sentença, acórdão, decisão interlocutória).
375
- - Endereçamento ao PRÓPRIO juízo que proferiu a decisão.
376
- - Indicação do processo.
377
- - Qualificação das partes (embargante e embargado).
378
- - Tempestividade (prazo de 5 dias - Art. 1023 CPC).
379
- - **Cabimento (Art. 1022, CPC):** Apontar **PRECISAMENTE** e **FUNDAMENTADAMENTE** a ocorrência de:
380
- - **Omissão:** Ponto ou questão sobre o qual o juiz deveria ter se pronunciado e não o fez.
381
- - **Contradição:** Afirmações conflitantes DENTRO da própria decisão.
382
- - **Obscuridade:** Falta de clareza que impede a compreensão da decisão.
383
- - **Erro Material:** Engano manifesto e corrigível de ofício (nome, cálculo, etc.).
384
- - **NÃO PODE REJULGAR A CAUSA:** O objetivo é sanar vício, não modificar o mérito (salvo efeitos infringentes excepcionais).
385
- - **Pedido:** Requerer o conhecimento dos embargos e seu ACOLHIMENTO para sanar o vício apontado (com ou sem efeitos infringentes, se for o caso).
386
- - Fechamento formal.
387
- **Critérios Adicionais (Embargos):**
388
- * Usar Embargos para rediscutir o mérito (erro grave): -4.0 pontos.
389
- * Não apontar claramente o vício (omissão/contradição/obscuridade/erro material): -3.0 pontos.
390
- * Endereçar ao tribunal em vez do juízo prolator (erro): -1.0 ponto.
391
- """
392
- elif tipo_peticao == "Recurso de Apelação":
393
- prompt_especifico += """
394
- - **Peça de Interposição:**
395
- - Endereçamento ao JUIZ que proferiu a sentença (juízo a quo).
396
- - Indicação do processo.
397
- - Qualificação das partes (apelante e apelado).
398
- - Demonstração dos pressupostos de admissibilidade (cabimento - contra sentença Art. 1009; tempestividade - 15 dias Art. 1003, §5º; preparo - juntada das guias ou pedido de JG; regularidade formal).
399
- - Pedido de recebimento do recurso, processamento e remessa ao Tribunal (juízo ad quem).
400
- - **Razões Recursais:**
401
- - Endereçamento ao TRIBUNAL competente (Ex: Egrégio Tribunal de Justiça).
402
- - Breve exposição dos fatos e do ocorrido no processo (resumo da lide e da sentença).
403
- - **Fundamentos de Fato e de Direito para Reforma/Anulação (Art. 1010, II e III):**
404
- - **Error in procedendo:** Erro na condução do processo (ex: cerceamento de defesa). Leva à anulação.
405
- - **Error in judicando:** Erro no julgamento do mérito (má aplicação da lei, má valoração da prova). Leva à reforma.
406
- - **Princípio da Dialeticidade:** Impugnar ESPECIFICAMENTE os fundamentos da sentença recorrida. Não pode ser cópia da inicial/contestação.
407
- - **Pedidos:** Requerer o conhecimento do recurso; o PROVIMENTO para ANULAR ou REFORMAR a sentença (especificar o que se quer: procedência, improcedência, etc.); inversão do ônus da sucumbência.
408
- - Fechamento formal (Local, data, Advogado, OAB).
409
- **Critérios Adicionais (Apelação):**
410
- * Falta da peça de interposição ou das razões: Nota zero.
411
- * Endereçamento incorreto das razões (para o juiz a quo): -1.0 ponto.
412
- * Ausência de impugnação específica aos fundamentos da sentença (violação dialeticidade): -3.0 a -5.0 pontos.
413
- * Não pedir anulação/reforma de forma clara: -1.5 pontos.
414
- * Falta de comprovação do preparo ou pedido de JG: Recurso deserto (na prática). Avaliar como erro grave (-2.0).
415
- """
416
- elif tipo_peticao == "Agravo de Instrumento":
417
- prompt_especifico += """
418
- - **Peça Única (Interposição e Razões):**
419
- - Endereçamento diretamente ao TRIBUNAL competente (Presidente ou Relator sorteado).
420
- - Indicação do processo de origem (número, vara, comarca).
421
- - Qualificação completa das partes (agravante e agravado) e seus advogados (com endereços - Art. 1016, IV).
422
- - Indicação da decisão agravada (cópia integral OBRIGATÓRIA - Art. 1017, I).
423
- - **Cabimento (Art. 1015, CPC ou teses de taxatividade mitigada):** Demonstrar que a decisão interlocutória se encaixa numa das hipóteses legais ou na urgência decorrente da inutilidade futura do julgamento do mérito (Tema 988 STJ).
424
- - **Tempestividade:** Prazo de 15 dias (Art. 1003, §5º).
425
- - **Preparo:** Juntada das guias ou pedido de JG.
426
- - **Peças Obrigatórias (Art. 1017, I):** Cópia da decisão agravada, certidão de intimação (ou outro doc. que comprove tempestividade), procurações das partes.
427
- - **Peças Facultativas (Art. 1017, III):** Outras peças úteis para compreensão da controvérsia.
428
- - **Razões do Pedido de Reforma/Invalidação (Art. 1016, II e III):** Exposição do fato e do direito; razões pelas quais a decisão deve ser alterada. Foco no erro da decisão interlocutória.
429
- - **Pedido de Efeito Suspensivo ou Tutela Antecipada Recursal (Art. 1019, I):** Se houver risco de dano grave, pedir fundamentadamente.
430
- - **Pedidos Finais:** Requerer o conhecimento do recurso; (se aplicável) a concessão de efeito suspensivo/tutela antecipada; o PROVIMENTO do agravo para reformar ou invalidar a decisão agravada; a comunicação ao juiz da causa (Art. 1018 - opcional se processo eletrônico).
431
- - Fechamento formal.
432
- **Critérios Adicionais (Agravo):**
433
- * Ausência de peça obrigatória (decisão, intimação, procurações): Inadmissibilidade (na prática). Erro gravíssimo (-4.0).
434
- * Não demonstrar o cabimento (Art. 1015 ou mitigação): -3.0 pontos.
435
- * Endereçar ao juiz a quo: -1.0 ponto.
436
- * Não indicar o nome e endereço dos advogados: Falha formal (-0.5).
437
- """
438
- elif tipo_peticao == "Petição de Tutela Provisória":
439
- prompt_especifico += """
440
- - **Tipo (Urgência ou Evidência):** A petição deve deixar claro qual modalidade se busca. Pode ser requerida em caráter antecedente ou incidental.
441
- - Endereçamento ao juízo competente (se antecedente) ou ao juízo da causa (se incidental).
442
- - Qualificação completa das partes.
443
- - Indicação do processo (se incidental).
444
- - **Tutela de Urgência (Art. 300 CPC):**
445
- - **Fumus Boni Iuris:** Demonstrar a **probabilidade do direito** alegado. Argumentação e provas pré-constituídas.
446
- - **Periculum in Mora:** Demonstrar o **perigo de dano** ou o risco ao resultado útil do processo se tiver que esperar a decisão final. Urgência concreta.
447
- - **Reversibilidade (Art. 300, §3º):** Argumentar, se possível, que a medida pode ser revertida caso a decisão final seja improcedente.
448
- - **Tutela de Evidência (Art. 311 CPC):**
449
- - Demonstrar uma das hipóteses: I - abuso do direito de defesa/propósito protelatório; II - alegações comprovadas apenas documentalmente + tese firmada em julgamento repetitivo ou súmula vinculante; III - pedido reipersecutório com prova documental adequada do contrato de depósito; IV - inicial instruída com prova documental suficiente + réu não opõe prova capaz de gerar dúvida razoável.
450
- - **NÃO precisa demonstrar periculum in mora.**
451
- - **Pedido Específico:** Indicar CLARAMENTE qual medida liminar está sendo solicitada (obrigação de fazer/não fazer, bloqueio de bens, etc.).
452
- - **Se Antecedente (Art. 303 ou 305):** Indicar o pedido final a ser formulado ou apresentar a petição inicial completa no prazo legal.
453
- - **Valor da Causa:** Se antecedente, estimar. Se incidental, geralmente já existe.
454
- - **Pedidos:** Requerer a concessão da tutela provisória (liminarmente ou após justificação prévia); citação/intimação do réu; (se antecedente) indicação da futura ação principal ou aditamento.
455
- - Fechamento formal.
456
- **Critérios Adicionais (Tutela):**
457
- * Não demonstrar claramente o Fumus Boni Iuris ou o Periculum in Mora (para Urgência): -3.0 a -5.0 pontos.
458
- * Não enquadrar corretamente numa das hipóteses da Tutela de Evidência: -3.0 pontos.
459
- * Pedido genérico ou pouco claro sobre a medida desejada: -2.0 pontos.
460
- * Confundir os requisitos de Urgência e Evidência: -1.5 pontos.
461
- """
462
- else:
463
- prompt_especifico += """
464
- - **Adequação ao Tipo:** A peça corresponde minimamente à estrutura e finalidade do tipo selecionado?
465
- - **Clareza:** É possível entender o que o aluno pretende com a peça?
466
- - **Fundamentação Mínima:** Há alguma tentativa de basear os argumentos em lei ou fatos?
467
- - **Pedido:** Existe um pedido, mesmo que mal formulado?
468
- """
469
-
470
- prompt_especifico += "\n\n**Instruções Finais:** Analise a peça do aluno estritamente com base no caso/contexto fornecido e nos critérios acima. Seja rigoroso como um examinador da OAB, mas forneça feedback construtivo e detalhado, especialmente nos 'Pontos a Melhorar'. Gere a resposta **exclusivamente** no formato solicitado (Análise Geral, Nota, Pontos Positivos, etc.)."
471
- return prompt_especifico
472
-
473
- def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
474
- prompt = f"""
475
- **Caso/Contexto Fornecido:**
476
- ```
477
- {caso}
478
- ```
479
-
480
- **Peça Original do Aluno ({tipo_peticao}):**
481
- ```
482
- {peticao_original}
483
- ```
484
-
485
- **Tarefa:** Com base no caso e nos problemas identificados na peça original (implícitos na sua estrutura/conteúdo), **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Siga **rigorosamente** as melhores práticas e os requisitos formais/materiais para este tipo de peça no exame da OAB.
486
-
487
- **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):**
488
- """
489
- if tipo_peticao == "Petição Inicial":
490
- prompt += """
491
- 1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável e justificado).
492
- 2. **Qualificação:** Completa (Art. 319, II), usando "XXX" para dados ausentes no caso.
493
- 3. **Fatos:** Narre de forma cronológica, clara e fiel ao caso, sem invenções.
494
- 4. **Fundamentação:** Cite artigos de lei pertinentes (ex: CC, CDC, CPC) e, se aplicável, súmulas/jurisprudência relevante.
495
- 5. **Pedidos:** Claros, certos, determinados (quantificar danos se possível com base no caso). Incluir pedidos cumulados se cabível (principal, danos morais, materiais, etc.).
496
- 6. **Valor da Causa:** Correto (soma dos benefícios econômicos - Art. 292).
497
- 7. **Provas:** Indicar as provas de forma genérica mas adequada (ex: "protesta provar o alegado por todos os meios de prova em direito admitidos, em especial documental, testemunhal e pericial").
498
- 8. **Audiência:** Indicar opção pela audiência (Art. 319, VII).
499
- 9. **Citação:** Pedido de citação do réu (ex: "via postal, nos termos do Art. 246, I, CPC").
500
- 10. **Custas:** Requerimento expresso de juntada da guia paga OU pedido de gratuidade de justiça (se o caso sugerir hipossuficiência, com pedido de juntada de declaração). **NUNCA OMITIR.**
501
- 11. **Fechamento:** "Nestes termos pede deferimento." (SEM vírgula). Seguido de "Local, data.", "ADVOGADO(A)", "OAB/...". **NUNCA identificar o advogado.**
502
- """
503
- elif tipo_peticao == "Contestação":
504
- prompt += """
505
- 1. **Endereçamento:** Ao juízo onde a ação tramita.
506
- 2. **Identificação:** Número do processo, qualificação do réu.
507
- 3. **Tempestividade:** Pode mencionar ("A presente contestação é tempestiva...").
508
- 4. **Estrutura:**
509
- * **Síntese da Inicial:** Breve resumo do que o autor pede.
510
- * **Preliminares (Art. 337):** Argua todas as cabíveis, fundamentando cada uma.
511
- * **Mérito:**
512
- * **Impugnação Específica:** Conteste cada fato relevante da inicial.
513
- * **Defesa de Mérito:** Apresente a versão do réu, fatos impeditivos/modificativos/extintivos, e a fundamentação legal para a improcedência.
514
- * **Reconvenção (Se houver no caso):** Incluir como tópico separado ANTES dos pedidos da contestação.
515
- 5. **Pedidos da Contestação:**
516
- * Acolhimento das preliminares (com a consequência - extinção/remessa).
517
- * **IMPROCEDÊNCIA TOTAL** dos pedidos do autor.
518
- * Condenação do autor em custas e honorários.
519
- 6. **Provas:** Indicar as provas que o réu pretende produzir.
520
- 7. **Fechamento:** Padrão OAB (Local, data, Advogado(a), OAB/...).
521
- """
522
- elif tipo_peticao == "Reconvenção":
523
- prompt += """
524
- **IMPORTANTE:** Gere a Reconvenção como parte da Contestação.
525
- 1. **Após a defesa de mérito da Contestação e ANTES dos pedidos da Contestação**, insira um tópico claro: "DA RECONVENÇÃO".
526
- 2. **Qualificação:** Identifique o Réu como Reconvinte e o Autor como Reconvindo.
527
- 3. **Conexão:** Justifique brevemente a conexão com a ação principal ou defesa.
528
- 4. **Fatos Reconvencionais:** Narre os fatos que baseiam o pedido do reconvinte.
529
- 5. **Direito Reconvencional:** Fundamente juridicamente o pedido do reconvinte.
530
- 6. **Pedidos da Reconvenção:** Formule pedidos claros e específicos contra o autor-reconvindo (ex: condenação a pagar X, fazer Y).
531
- 7. **Valor da Causa da Reconvenção:** Atribua um valor específico para a reconvenção.
532
- 8. **Provas da Reconvenção:** Indique as provas para a reconvenção.
533
- 9. **Intimação:** Peça a intimação do autor-reconvindo para contestar a reconvenção.
534
- 10. **Continue com os Pedidos da Contestação** (improcedência da ação original, etc.).
535
- 11. **Fechamento:** Padrão OAB.
536
- """
537
- elif tipo_peticao == "Impugnação à Contestação":
538
- prompt += """
539
- 1. **Endereçamento:** Ao juízo correto.
540
- 2. **Identificação:** Processo, Impugnante.
541
- 3. **Tempestividade:** Mencione o prazo de 15 dias.
542
- 4. **Estrutura:**
543
- * **Resposta às Preliminares:** Rebata cada preliminar arguida.
544
- * **Resposta aos Fatos Impeditivos/Modificativos/Extintivos:** Manifeste-se sobre fatos novos (Art. 350).
545
- * **Resposta aos Documentos:** Manifeste-se sobre documentos juntados (Art. 437).
546
- * **Ratificação da Inicial:** Reitere os termos e pedidos iniciais.
547
- 5. **Pedidos:** Rejeição das preliminares, refutação da defesa, procedência da a��ão.
548
- 6. **Provas:** Especifique provas para contrapor a defesa, se necessário.
549
- 7. **Fechamento:** Padrão OAB.
550
- """
551
- elif tipo_peticao == "Embargos de Declaração":
552
- prompt += """
553
- 1. **Endereçamento:** Ao juízo que proferiu a decisão embargada.
554
- 2. **Identificação:** Processo, Embargante, Embargado.
555
- 3. **Decisão Embargada:** Indique claramente qual decisão (sentença fls. X, acórdão ID Y).
556
- 4. **Tempestividade:** Mencione o prazo de 5 dias.
557
- 5. **Cabimento (Art. 1022):**
558
- * Crie um tópico para cada vício alegado (Da Omissão, Da Contradição, etc.).
559
- * Dentro de cada tópico, **transcreva o trecho relevante da decisão** e explique **objetivamente** onde está o vício. NÃO REFAÇA O JULGAMENTO.
560
- 6. **Pedido:** Peça o conhecimento e ACOLHIMENTO dos embargos para sanar o(s) vício(s) apontado(s), especificando o que se espera (ex: "para que seja suprida a omissão quanto a...", "para que seja corrigida a contradição entre..."). Se cabível e fundamentado, pedir efeitos infringentes.
561
- 7. **Fechamento:** Padrão OAB.
562
- """
563
- elif tipo_peticao == "Recurso de Apelação":
564
- prompt += """
565
- 1. **Peça de Interposição:** Ao juízo a quo (quem proferiu a sentença).
566
- 2. **Identificação:** Processo, Apelante, Apelado.
567
- 3. **Pressupostos:** Cabimento, tempestividade, preparo/JG.
568
- 4. **Pedido (Interposição):** Recebimento e remessa ao Tribunal.
569
- 5. **Razões Recursais:** Ao Tribunal (ad quem).
570
- 6. **Resumo:** Fatos, lide, sentença.
571
- 7. **Fundamentos (Art. 1010):** Error in procedendo/judicando. **Dialeticidade OBRIGATÓRIA**.
572
- 8. **Pedidos (Razões):** Conhecimento, PROVIMENTO para anular/reformar, inversão sucumbência.
573
- 9. **Fechamento:** Padrão OAB.
574
- """
575
- elif tipo_peticao == "Agravo de Instrumento":
576
- prompt += """
577
- 1. **Endereçamento:** Diretamente ao Tribunal.
578
- 2. **Identificação:** Processo de origem, Agravante, Agravado, Advogados (com endereços).
579
- 3. **Decisão Agravada:** Indicar e juntar cópia OBRIGATÓRIA.
580
- 4. **Cabimento:** Demonstrar hipótese do Art. 1015 ou mitigação.
581
- 5. **Pressupostos:** Tempestividade, preparo/JG.
582
- 6. **Peças Obrigatórias (Art. 1017):** Decisão, intimação, procurações. Facultativas: outras úteis.
583
- 7. **Razões:** Exposição do fato e direito para reforma/invalidação.
584
- 8. **Efeito Suspensivo/Tutela Antecipada:** Pedir fundamentadamente se necessário (Art. 1019, I).
585
- 9. **Pedidos Finais:** Conhecimento, efeito suspensivo/tutela (se houver), PROVIMENTO para reformar/invalidar.
586
- 10. **Fechamento:** Padrão OAB.
587
- """
588
- elif tipo_peticao == "Petição de Tutela Provisória":
589
- prompt += """
590
- 1. **Tipo:** Urgência ou Evidência? Antecedente ou Incidental?
591
- 2. **Endereçamento:** Juízo competente ou da causa.
592
- 3. **Identificação:** Partes, processo (se incidental).
593
- 4. **Requisitos:**
594
- * **Urgência (Art. 300):** Fumus Boni Iuris + Periculum in Mora (+ Reversibilidade, se possível).
595
- * **Evidência (Art. 311):** Uma das hipóteses legais (NÃO precisa periculum).
596
- 5. **Pedido Específico:** Qual medida liminar é solicitada?
597
- 6. **Se Antecedente:** Indicar pedido final ou aditar/completar no prazo.
598
- 7. **Pedidos:** Concessão da tutela, citação/intimação.
599
- 8. **Fechamento:** Padrão OAB.
600
- """
601
- else:
602
- prompt += """
603
- 1. **Estrutura Lógica:** Organize a peça com começo, meio e fim.
604
- 2. **Argumentos:** Apresente os argumentos de forma clara.
605
- 3. **Pedidos:** Formule um pedido claro ao final.
606
- 4. **Formalidades:** Use linguagem adequada e o fechamento padrão OAB.
607
- """
608
-
609
- prompt += "\n\n**Instrução Final:** Gere **APENAS** o texto completo da peça corrigida abaixo, começando pelo endereçamento e terminando com 'OAB/...'. Não inclua NENHUM comentário, introdução ou explicação ANTES ou DEPOIS do texto da peça."
610
- return prompt
611
-
612
- # --- Função de Chamada da API ---
613
- def chamar_api(prompt):
614
- payload = {
615
- "contents": [{"parts": [{"text": prompt}]}],
616
- "generationConfig": {
617
- "temperature": CONFIG["temperatura"],
618
- "maxOutputTokens": CONFIG["max_tokens"],
619
- "topP": 0.95,
620
- }
621
- }
622
- headers = {"Content-Type": "application/json"}
623
- logger.info(f"Chamando API Gemini com {len(prompt)} caracteres de prompt. Temperatura: {CONFIG['temperatura']}, Max Tokens: {CONFIG['max_tokens']}")
624
-
625
- for tentativa in range(CONFIG["max_retry_attempts"]):
626
- try:
627
- with rate_limiter:
628
- logger.debug(f"Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}...")
629
- response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"])
630
-
631
- logger.debug(f"Status Code recebido: {response.status_code}")
632
-
633
- if response.status_code == 429:
634
- retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1)))
635
- logger.warning(f"Erro 429 (Too Many Requests). Tentando novamente em {retry_after}s...")
636
- if tentativa < CONFIG["max_retry_attempts"] - 1:
637
- time.sleep(retry_after)
638
- continue
639
- else:
640
- return "❌ Erro 429: Muitas requisições. Tente novamente mais tarde."
641
-
642
- if response.status_code != 200:
643
- error_info = {}
644
- error_msg = f"Erro desconhecido (Status {response.status_code})"
645
- try:
646
- error_info = response.json()
647
- error_msg = error_info.get("error", {}).get("message", error_msg)
648
- except json.JSONDecodeError:
649
- error_msg = f"{error_msg}. Resposta não é JSON: {response.text[:200]}"
650
- logger.error(f"Erro da API (Status {response.status_code}): {error_msg}")
651
- if tentativa < CONFIG["max_retry_attempts"] - 1:
652
- logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
653
- time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
654
- continue
655
- return f"❌ Erro da API ({response.status_code}): {error_msg}"
656
-
657
- response_data = response.json()
658
-
659
- if not response_data.get("candidates"):
660
- prompt_feedback = response_data.get("promptFeedback", {})
661
- block_reason = prompt_feedback.get("blockReason")
662
- safety_ratings = prompt_feedback.get("safetyRatings")
663
- logger.error(f"Resposta da API sem 'candidates'. Block Reason: {block_reason}, Safety Ratings: {safety_ratings}")
664
- if block_reason:
665
- return f"❌ A solicitação foi bloqueada pela API. Motivo: {block_reason}. Tente reformular o caso ou a petição."
666
- else:
667
- return "❌ Resposta vazia ou inválida da API (sem 'candidates'). Verifique o log para detalhes."
668
-
669
- candidate = response_data["candidates"][0]
670
- finish_reason = candidate.get("finishReason")
671
- if finish_reason not in [None, "STOP", "MAX_TOKENS"]:
672
- logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}")
673
- safety_ratings_candidate = candidate.get("safetyRatings", [])
674
- harm_categories = [r['category'] for r in safety_ratings_candidate if r['probability'] not in ['NEGLIGIBLE', 'LOW']]
675
- if harm_categories:
676
- return f"❌ A resposta foi bloqueada por segurança pela API. Motivo: {finish_reason} (Categorias: {', '.join(harm_categories)}). Revise o conteúdo."
677
- else:
678
- return f"❌ A geração da resposta foi interrompida pela API. Motivo: {finish_reason}. Tente novamente ou simplifique a solicitação."
679
-
680
- if "content" not in candidate or "parts" not in candidate["content"] or not candidate["content"]["parts"]:
681
- logger.error("Estrutura de resposta inesperada da API: Faltando content/parts.")
682
- if tentativa < CONFIG["max_retry_attempts"] - 1:
683
- logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
684
- time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
685
- continue
686
- return "❌ Resposta inválida da API (estrutura inesperada)."
687
-
688
- texto_gerado = candidate["content"]["parts"][0]["text"].strip()
689
- logger.info(f"API retornou com sucesso. Tamanho da resposta: {len(texto_gerado)} caracteres.")
690
- return texto_gerado
691
-
692
- except requests.Timeout:
693
- logger.warning(f"Timeout na tentativa {tentativa + 1}.")
694
- if tentativa < CONFIG["max_retry_attempts"] - 1:
695
- logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
696
- time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
697
- continue
698
- return "⏱️ Tempo esgotado após múltiplas tentativas. Verifique a conexão ou aumente o timeout."
699
- except requests.RequestException as e:
700
- logger.error(f"Erro de rede/requisição na tentativa {tentativa + 1}: {e}")
701
- if tentativa < CONFIG["max_retry_attempts"] - 1:
702
- logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
703
- time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
704
- continue
705
- return f"❌ Erro na requisição: {e}"
706
- except json.JSONDecodeError as e:
707
- logger.error(f"Erro ao decodificar JSON da resposta da API: {e}")
708
- logger.debug(f"Resposta que causou erro de JSON: {response.text[:500]}")
709
- return f"❌ Erro ao processar a resposta da API (JSON inválido)."
710
- except Exception as e:
711
- logger.exception(f"Erro inesperado na chamada da API na tentativa {tentativa + 1}: {str(e)}")
712
- if tentativa < CONFIG["max_retry_attempts"] - 1:
713
- logger.info(f"Tentando novamente em {CONFIG['retry_delay'] * (tentativa + 1)}s...")
714
- time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
715
- continue
716
- return f"❌ Erro inesperado: {str(e)}"
717
-
718
- return "❌ Falha ao chamar a API após todas as tentativas."
719
-
720
- # --- Função Principal de Correção ---
721
- def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
722
- logger.info(f">>> CORRIGIR PETICAO INICIADO - Tipo: {tipo_peticao}, Treinamento: {modo_treinamento}")
723
- start_time = time.time()
724
-
725
- if not caso or not caso.strip():
726
- logger.warning("--- Caso não fornecido.")
727
- return "⚠️ Forneça o caso/contexto da peça.", "", "", "", "", ""
728
-
729
- if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO:
730
- logger.warning(f"--- Tipo de petição inválido ou não fornecido: {tipo_peticao}")
731
- return "⚠️ Selecione um tipo de petição válido.", "", "", "", "", ""
732
-
733
- peticao_conteudo = ""
734
- if peticao_arquivo is not None:
735
- logger.info("--- Processando arquivo de petição...")
736
- peticao_conteudo = process_file(peticao_arquivo)
737
- if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"):
738
- logger.error(f"--- Erro ao processar arquivo: {peticao_conteudo}")
739
- return peticao_conteudo, "", "", "", "", ""
740
- logger.info(f"--- Texto extraído do arquivo ({len(peticao_conteudo)} caracteres).")
741
- elif peticao_texto and peticao_texto.strip():
742
- peticao_conteudo = peticao_texto
743
- logger.info(f"--- Usando texto da petição fornecido ({len(peticao_conteudo)} caracteres).")
744
- else:
745
- logger.warning("--- Nenhuma petição fornecida (nem texto, nem arquivo).")
746
- return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", "", ""
747
-
748
- troll_triggers = [
749
- "escreva a petição", "faça a petição", "redija a petição",
750
- "elabore a petição", "crie a petição", "gere a petição",
751
- "3scr3v4 a p3t1çã0", "f4ç4 a p3t1çã0", "r3d1j4 a p3t1çã0",
752
- "3lab0r3 a p3t1çã0", "cr13 a p3t1çã0", "g3r3 a p3t1çã0",
753
- "formule", "produza a petição", "desenvolva a petição",
754
- "faça para mim", "faça por mim", "escreva para mim",
755
- ]
756
- peticao_lower = peticao_conteudo.lower()
757
- if any(trigger in peticao_lower for trigger in troll_triggers):
758
- respostas_troll = [
759
- "Hummm... Parece que você quer que *eu* faça o trabalho, né? 😉 Que tal você tentar escrever a peça e *eu* te ajudo a corrigir? Assim você aprende de verdade!",
760
- "🚫 Opa! Meu trabalho é *corrigir* e *ensinar*, não fazer a peça do zero. Mostre o que você fez e eu te dou o feedback!",
761
- "📚 Lembre-se: a prática leva à perfeição (e à aprovação!). Tente redigir a peça com base no caso, e depois eu analiso para você.",
762
- "🤖 Sou um corretor, não um escritor fantasma! Escreva sua versão que eu aponto os caminhos para melhorar.",
763
- "💡 Dica de ouro: Comece! Esboce a peça, mesmo que imperfeita. Depois a gente lapida juntos. Mande bala!",
764
- "😂 Pedir pra IA fazer a peça é tipo pedir pro GPS dirigir o carro. Ele te guia, mas quem tá no volante é você! Escreva aí!",
765
- ]
766
- logger.info("--- Detectado pedido para escrever petição. Retornando resposta troll.")
767
- return random.choice(respostas_troll), "", "", "", "", ""
768
-
769
- aviso_validacao = ""
770
-
771
- cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}"
772
- if cache_key in CORRECAO_CACHE:
773
- logger.info("--- Correção encontrada no cache.")
774
- cached_data = CORRECAO_CACHE[cache_key]
775
- diff_html_cache, _, _ = calcular_diff_interno(cached_data["peticao_original"], cached_data["peticao_corrigida"])
776
- logger.info("<<< CORRIGIR PETICAO FINALIZADO (CACHE HIT)")
777
- # Retorna 6 elementos para corresponder aos outputs
778
- return cached_data["analise_formatada"], cached_data["peticao_original"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"], diff_html_cache
779
-
780
- logger.info("--- Gerando prompt de análise...")
781
- prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento)
782
- logger.info("--- Enviando para análise da API...")
783
- analise = chamar_api(prompt_analise)
784
- if analise.startswith("❌") or analise.startswith("⏱️") or analise.startswith("⚠️"):
785
- logger.error(f"--- Erro na análise da petição: {analise}")
786
- return aviso_validacao + analise, peticao_conteudo, "", "", "", ""
787
- logger.info("--- Análise recebida da API.")
788
- analise_formatada = formatar_correcao(analise)
789
-
790
- logger.info("--- Gerando prompt para peça corrigida...")
791
- prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao)
792
- logger.info("--- Enviando para geração da peça corrigida API...")
793
- peticao_corrigida_bruta = chamar_api(prompt_peticao)
794
-
795
- peticao_corrigida_final = ""
796
- if peticao_corrigida_bruta.startswith("❌") or peticao_corrigida_bruta.startswith("⏱️") or peticao_corrigida_bruta.startswith("⚠️"):
797
- logger.error(f"--- Erro ao gerar peça corrigida: {peticao_corrigida_bruta}")
798
- peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}"
799
- else:
800
- logger.info("--- Peça corrigida recebida, aplicando pós-correção...")
801
- start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
802
- start_index = -1
803
- peticao_lower_corr = peticao_corrigida_bruta.lower()
804
- for marker in start_markers:
805
- idx = peticao_lower_corr.find(marker)
806
- if idx != -1:
807
- start_index = idx
808
- break
809
-
810
- if start_index != -1:
811
- peticao_extraida = peticao_corrigida_bruta[start_index:]
812
- else:
813
- peticao_extraida = peticao_corrigida_bruta
814
-
815
- peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao)
816
- logger.info("--- Pós-correção aplicada.")
817
-
818
- logger.info("--- Calculando diferenças...")
819
- diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final)
820
- logger.info("--- Diferenças calculadas.")
821
-
822
- correcao_completa_formatada = aviso_validacao + analise_formatada
823
- resultado_historico = {
824
- "data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
825
- "tipo_peticao": tipo_peticao,
826
- "caso": caso,
827
- "peticao_original": peticao_conteudo,
828
- "analise_formatada": correcao_completa_formatada,
829
- "peticao_corrigida": peticao_corrigida_final,
830
- "resumo_diff": resumo_diff,
831
- "criticas_diff": criticas_diff
832
- }
833
- HISTORICO.append(resultado_historico)
834
- if len(HISTORICO) > 10:
835
- HISTORICO.pop(0)
836
-
837
- CORRECAO_CACHE[cache_key] = {
838
- "analise_formatada": correcao_completa_formatada,
839
- "peticao_original": peticao_conteudo,
840
- "peticao_corrigida": peticao_corrigida_final,
841
- "resumo_diff": resumo_diff,
842
- "criticas_diff": criticas_diff,
843
- }
844
-
845
- end_time = time.time()
846
- logger.info(f"<<< CORRIGIR PETICAO FINALIZADO em {end_time - start_time:.2f} segundos.")
847
- # Retorna 6 elementos para corresponder aos outputs
848
- return correcao_completa_formatada, peticao_conteudo, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html
849
-
850
- # --- Funções Auxiliares ---
851
- def formatar_correcao(texto):
852
- texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
853
- texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
854
- texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
855
- texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
856
- texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos")
857
-
858
- linhas = texto.split('\n')
859
- linhas_formatadas = []
860
- in_list = False
861
- for linha in linhas:
862
- linha_strip = linha.strip()
863
- if linha_strip.startswith("1.") or linha_strip.startswith("2.") or linha_strip.startswith("3.") or linha_strip.startswith("4.") or linha_strip.startswith("5."):
864
- linhas_formatadas.append(f"\n**{linha_strip}**")
865
- in_list = False
866
- elif linha_strip.startswith("- ") or linha_strip.startswith("* "):
867
- prefix = " * " if in_list else "* "
868
- linhas_formatadas.append(prefix + linha_strip[2:])
869
- in_list = True
870
- elif linha_strip.startswith("a)") or linha_strip.startswith("b)") or linha_strip.startswith("c)") or linha_strip.startswith("d)") or linha_strip.startswith("e)"):
871
- linhas_formatadas.append(f" * {linha_strip}")
872
- in_list = True
873
- else:
874
- linhas_formatadas.append(linha)
875
- if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")):
876
- in_list = False
877
-
878
- texto_final = "\n".join(linhas_formatadas)
879
- nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})"
880
- match = re.search(nota_pattern, texto_final, re.IGNORECASE)
881
- if match:
882
- nota = match.group(2).replace(",",".")
883
- texto_final = re.sub(nota_pattern, f"\\1**{nota}/10**", texto_final, count=1, flags=re.IGNORECASE)
884
-
885
- return texto_final
886
-
887
- def fornecer_dica_contextual(texto_peticao, tipo_peticao):
888
- if not texto_peticao or not texto_peticao.strip():
889
- return "Digite algo na petição para receber dicas..."
890
-
891
- dicas_relevantes = []
892
- texto_lower = texto_peticao.lower()
893
-
894
- for chave, dica in DICAS_OAB_MELHORADO.items():
895
- if chave in ["Clareza e Concisão", "Fundamentação Legal", "Coerência Argumentativa", "Revisão Ortográfica e Gramatical", "Formatação"]:
896
- dicas_relevantes.append(f"💡 **{chave}:** {dica}")
897
-
898
- keywords_por_tipo = {
899
- "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor da causa", "provas", "audiência", "citação", "custas"],
900
- "Contestação": ["preliminar", "mérito", "impugnação", "fatos", "improcedência", "provas", "honorários"],
901
- "Reconvenção": ["reconvinte", "reconvindo", "conexão", "pedidos reconvencionais"],
902
- "Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica"],
903
- "Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "decisão embargada"],
904
- "Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "razões"],
905
- "Agravo de Instrumento": ["decisão interlocutória", "tribunal", "cabimento", "urgência", "efeito suspensivo", "peças obrigatórias"],
906
- "Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "probabilidade", "risco"],
907
- }
908
-
909
- if tipo_peticao in keywords_por_tipo:
910
- for keyword in keywords_por_tipo[tipo_peticao]:
911
- if keyword in texto_lower:
912
- for chave, dica in DICAS_OAB_MELHORADO.items():
913
- chave_lower = chave.lower()
914
- if f"({tipo_peticao.split()[0].lower()})" in chave_lower or keyword in chave_lower:
915
- dica_formatada = f"💡 **{chave}:** {dica}"
916
- if dica_formatada not in dicas_relevantes:
917
- dicas_relevantes.append(dica_formatada)
918
-
919
- if not dicas_relevantes:
920
- return "💡 Continue escrevendo ou selecione o tipo de peça para dicas mais específicas."
921
-
922
- return "\n\n".join(dicas_relevantes[:5])
923
-
924
- def calcular_diff_interno(texto_original, texto_corrigido):
925
- if not texto_original or not texto_corrigido or texto_corrigido.startswith("⚠️") or texto_corrigido.startswith("❌"):
926
- return "","",""
927
-
928
- orig_lines = texto_original.splitlines()
929
- corr_lines = texto_corrigido.splitlines()
930
-
931
- diff = difflib.ndiff(orig_lines, corr_lines)
932
-
933
- resultado_diff_html = []
934
- criticas = []
935
- linhas_removidas = 0
936
- linhas_adicionadas = 0
937
-
938
- keywords_criticas_removidas = ["invenção", "identificação", "erro crasso", "fundamento incorreto"]
939
- keywords_criticas_adicionadas = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "pedido essencial", "preliminar obrigatória"]
940
-
941
- for linha in diff:
942
- texto = linha[2:]
943
- texto_escaped = escape(texto)
944
- if linha.startswith(' '):
945
- resultado_diff_html.append(f"<div class='linha-igual'>{texto_escaped}</div>")
946
- elif linha.startswith('- '):
947
- linhas_removidas += 1
948
- resultado_diff_html.append(f"<div class='linha-removida' title='Removido'><del>{texto_escaped}</del></div>")
949
- if any(kw in texto.lower() for kw in keywords_criticas_removidas):
950
- criticas.append(f"<span style='color: red;'>🔴 REMOVIDO (Potencialmente Crítico):</span> {texto_escaped}")
951
- elif linha.startswith('+ '):
952
- linhas_adicionadas += 1
953
- resultado_diff_html.append(f"<div class='linha-adicionada' title='Adicionado'><ins>{texto_escaped}</ins></div>")
954
- if any(kw in texto.lower() for kw in keywords_criticas_adicionadas):
955
- criticas.append(f"<span style='color: green;'>🟢 ADICIONADO (Potencialmente Essencial):</span> {texto_escaped}")
956
-
957
- resumo_estatistico = f"""
958
- **Resumo das Mudanças:**
959
- * Linhas Removidas: {linhas_removidas}
960
- * Linhas Adicionadas: {linhas_adicionadas}
961
- * **Diferença Líquida:** {linhas_adicionadas - linhas_removidas} linhas
962
- """
963
-
964
- criticas_texto = "### 🚨 Mudanças Críticas / Essenciais Identificadas:\n" + "\n".join(criticas) if criticas else "Nenhuma mudança crítica/essencial automaticamente detectada."
965
-
966
- css = """
967
- <style>
968
- .diff-container { font-family: monospace; white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; }
969
- .linha-igual { color: #555; }
970
- .linha-removida { background-color: #ffebee; color: #c62828; text-decoration: line-through; }
971
- .linha-adicionada { background-color: #e8f5e9; color: #2e7d32; }
972
- .linha-removida del, .linha-adicionada ins { text-decoration: none; padding: 1px 3px; border-radius: 3px; }
973
- .linha-removida del { background-color: #ffcdd2; }
974
- .linha-adicionada ins { background-color: #c8e6c9; }
975
- .linha-info { color: #0277bd; font-style: italic; }
976
- </style>
977
- """
978
- visualizacao_detalhada_html = f"{css}<div class='diff-container'>{''.join(resultado_diff_html)}</div>"
979
-
980
- return visualizacao_detalhada_html, resumo_estatistico, criticas_texto
981
-
982
- def calcular_progresso():
983
- if not HISTORICO:
984
- return "📊 Nenhum progresso registrado ainda."
985
-
986
- notas = []
987
- tipos_corrigidos = set()
988
- for entry in HISTORICO:
989
- tipos_corrigidos.add(entry["tipo_peticao"])
990
- nota_str = "N/A"
991
- match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"])
992
- if match:
993
- try:
994
- nota = float(match.group(1).replace(",", "."))
995
- notas.append(nota)
996
- nota_str = f"{nota:.1f}"
997
- except ValueError:
998
- pass
999
-
1000
- total_corridas = len(HISTORICO)
1001
- media = sum(notas) / len(notas) if notas else 0
1002
- tendencia = ""
1003
- if len(notas) > 1:
1004
- if notas[-1] > notas[-2]:
1005
- tendencia = "⬆️ Melhorando"
1006
- elif notas[-1] < notas[-2]:
1007
- tendencia = "⬇️ Piorando"
1008
- else:
1009
- tendencia = "↔️ Estável"
1010
-
1011
- progresso_texto = f"""
1012
- **📊 Resumo do Progresso:**
1013
- * **Total de Correções:** {total_corridas}
1014
- * **Tipos de Peças Corrigidas:** {', '.join(sorted(list(tipos_corrigidos)))}
1015
- * **Média das Notas (quando aplicável):** {media:.1f}/10 (de {len(notas)} notas válidas)
1016
- * **Tendência da Última Nota:** {tendencia if tendencia else 'N/A'}
1017
- """
1018
- return progresso_texto
1019
-
1020
- def mostrar_historico():
1021
- if not HISTORICO:
1022
- return "📜 Histórico vazio."
1023
-
1024
- historico_md = "### 📜 Histórico de Correções (Últimas 10)\n\n---\n"
1025
- for i, entry in enumerate(reversed(HISTORICO)):
1026
- caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso']
1027
- nota_str = "N/A"
1028
- match = re.search(r"\*\*(\d{1,2}[,.]\d{1}|\d{1,2})/10\*\*", entry["analise_formatada"])
1029
- if match:
1030
- nota_str = f"{match.group(1).replace(',', '.')}/10"
1031
-
1032
- historico_md += f"**{len(HISTORICO)-i}. Data:** {entry['data']}\n"
1033
- historico_md += f"**Tipo:** {entry['tipo_peticao']}\n"
1034
- historico_md += f"**Nota:** {nota_str}\n"
1035
- historico_md += f"**Caso:** _{caso_resumo}_\n"
1036
- historico_md += "---\n"
1037
- return historico_md
1038
-
1039
- def salvar_progresso(caso, peticao, tipo_peticao):
1040
- logger.info(">>> SALVAR PROGRESSO INICIADO")
1041
- try:
1042
- if not caso or not peticao or not tipo_peticao:
1043
- logger.warning("--- Inputs insuficientes para salvar progresso.")
1044
- return "", "⚠️ Caso, Petição e Tipo de Petição são necessários para salvar.", None
1045
-
1046
- dados_progresso = {
1047
- "tipo_peticao": tipo_peticao,
1048
- "caso": caso,
1049
- "peticao": peticao,
1050
- "timestamp": datetime.now().isoformat()
1051
- }
1052
-
1053
- json_content = json.dumps(dados_progresso, ensure_ascii=False, indent=4)
1054
- timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
1055
- filename = f"progresso_peticao_{timestamp_str}.json"
1056
- temp_dir = tempfile.gettempdir()
1057
- temp_file_path = os.path.join(temp_dir, filename)
1058
-
1059
- with open(temp_file_path, 'w', encoding='utf-8') as f:
1060
- f.write(json_content)
1061
-
1062
- logger.info(f"--- Progresso salvo com sucesso no arquivo temporário: {temp_file_path}")
1063
- success_message = (
1064
- f"✅ Progresso salvo! Baixe o arquivo `{filename}`. "
1065
- "Use o botão 'Carregar Progresso' e selecione este arquivo para restaurar."
1066
- )
1067
- logger.info("<<< SALVAR PROGRESSO FINALIZADO COM SUCESSO")
1068
- # Retorna o path para o componente gr.File poder oferecer o download
1069
- return json_content, success_message, temp_file_path
1070
-
1071
- except Exception as e:
1072
- logger.error(f"Erro ao salvar progresso: {str(e)}", exc_info=True)
1073
- logger.info("<<< SALVAR PROGRESSO FINALIZADO COM ERRO")
1074
- # Retorna None para o path do arquivo em caso de erro
1075
- return "", f"❌ Erro ao salvar o progresso: {str(e)}", None
1076
-
1077
- def carregar_progresso(arquivo_entrada):
1078
- logger.info(">>> CARREGAR PROGRESSO INICIADO")
1079
- if arquivo_entrada is None:
1080
- logger.warning("--- Nenhum arquivo selecionado para carregar.")
1081
- return "", "", "", "⚠️ Nenhum arquivo selecionado para carregar."
1082
-
1083
- try:
1084
- file_path = arquivo_entrada.name
1085
- logger.info(f"--- Carregando progresso do arquivo: {file_path}")
1086
-
1087
- with open(file_path, 'r', encoding='utf-8') as f:
1088
- dados = json.load(f)
1089
-
1090
- caso = dados.get("caso")
1091
- peticao = dados.get("peticao")
1092
- tipo_peticao = dados.get("tipo_peticao")
1093
-
1094
- if caso is None or peticao is None or tipo_peticao is None:
1095
- logger.warning(f"--- Arquivo JSON carregado não contém as chaves esperadas: {dados.keys()}")
1096
- return "", "", "", "❌ Erro: Arquivo de progresso inválido ou incompleto."
1097
-
1098
- if tipo_peticao not in TIPOS_DE_PETICAO:
1099
- logger.warning(f"--- Tipo de petição '{tipo_peticao}' do arquivo não é válido.")
1100
- # Retorna os dados carregados, mas deixa o dropdown vazio para seleção manual
1101
- return gr.update(value=None), caso, peticao, f"⚠️ Tipo de petição '{tipo_peticao}' do arquivo não é mais válido. Selecione manualmente."
1102
-
1103
- logger.info("<<< CARREGAR PROGRESSO FINALIZADO COM SUCESSO")
1104
- # Retorna os valores corretos para atualizar os componentes
1105
- return tipo_peticao, caso, peticao, "✅ Progresso carregado com sucesso!"
1106
-
1107
- except json.JSONDecodeError:
1108
- logger.error(f"Erro ao decodificar JSON do arquivo: {file_path}")
1109
- return "", "", "", "❌ Erro: Arquivo selecionado não é um JSON válido."
1110
- except Exception as e:
1111
- logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True)
1112
- logger.info("<<< CARREGAR PROGRESSO FINALIZADO COM ERRO")
1113
- return "", "", "", f"❌ Erro inesperado ao carregar o progresso: {str(e)}"
1114
-
1115
- # --- Geração de Caso Fictício ---
1116
- def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
1117
- logger.info(f">>> GERAR CASO FICTICIO INICIADO para: {tipo_peticao}")
1118
- if not tipo_peticao:
1119
- logger.error("--- Tipo de petição não selecionado para gerar caso.")
1120
- return "Erro: Tipo de petição não selecionado para gerar caso."
1121
- prompt = estruturar_prompt_geracao_caso(tipo_peticao)
1122
- logger.info("--- Prompt de geração de caso estruturado.")
1123
- caso_gerado = chamar_api(prompt)
1124
- logger.info(f"--- Resultado da chamada API para gerar caso: {caso_gerado[:100]}...")
1125
-
1126
- if caso_gerado.startswith("❌") or caso_gerado.startswith("⏱️") or caso_gerado.startswith("⚠️"):
1127
- logger.error(f"--- Erro ao gerar caso fictício: {caso_gerado}")
1128
- return f"Falha ao gerar caso fictício.\n{caso_gerado}"
1129
-
1130
- frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de"
1131
- if frase_final_padrao.lower() not in caso_gerado.lower():
1132
- logger.warning("--- Frase 'Situação-Problema' não encontrada. Adicionando manualmente.")
1133
- nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado)
1134
- cliente_nome = nomes_possiveis[0] if nomes_possiveis else "[Cliente]"
1135
- caso_gerado += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente_nome}, elabore a peça processual cabível ({tipo_peticao})."
1136
-
1137
- logger.info("<<< GERAR CASO FICTICIO FINALIZADO COM SUCESSO")
1138
- return caso_gerado
1139
-
1140
- # --- Validação Básica ---
1141
- def validar_peticao_basica(peticao):
1142
- problemas = []
1143
- peticao_lower = peticao.lower()
1144
- if "excelentíssimo" not in peticao_lower and "eméritos" not in peticao_lower and "meritíssimo" not in peticao_lower :
1145
- problemas.append("Endereçamento inicial parece ausente ou incompleto.")
1146
- if "valor da causa" not in peticao_lower and "valor à causa" not in peticao_lower:
1147
- problemas.append("Atribuição de Valor da Causa não encontrada (essencial na Inicial).")
1148
- if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower :
1149
- problemas.append("Pedido de juntada de custas ou gratuidade de justiça não encontrado (essencial na Inicial).")
1150
- if "nestes termos" not in peticao_lower and "pede deferimento" not in peticao_lower:
1151
- problemas.append("Fechamento formal padrão ('Nestes termos pede deferimento') parece ausente.")
1152
- return problemas
1153
-
1154
- # --- Interface Gradio ---
1155
- def interface_gradio():
1156
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
1157
- gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️")
1158
- gr.Markdown("Analise e aprimore suas peças processuais! Selecione o tipo, forneça o caso e sua peça (texto ou arquivo).")
1159
-
1160
- # Definir o Textbox oculto aqui para que as saídas dos botões possam referenciá-lo
1161
- peticao_original_hidden = gr.Textbox(visible=False, label="Original Hidden")
1162
-
1163
- with gr.Row():
1164
- with gr.Column(scale=1):
1165
- gr.Markdown("### Configurações")
1166
- tipo_peticao_input = gr.Dropdown(
1167
- label="Selecione o Tipo de Peça Processual",
1168
- choices=TIPOS_DE_PETICAO,
1169
- value="Petição Inicial"
1170
- )
1171
- modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento (Feedback Detalhado)", value=False)
1172
-
1173
- with gr.Accordion("⚙️ Configurações Avançadas", open=False):
1174
- temperatura_slider = gr.Slider(minimum=0.0, maximum=1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade (Temperatura)")
1175
- timeout_slider = gr.Slider(minimum=30, maximum=180, value=CONFIG["timeout"], step=10, label="Timeout da API (segundos)")
1176
- max_tokens_slider = gr.Slider(minimum=2048, maximum=8192, value=CONFIG["max_tokens"], step=256, label="Tamanho Máx. Resposta (Tokens)")
1177
- def atualizar_config(temp, timeout, tokens):
1178
- CONFIG["temperatura"] = temp
1179
- CONFIG["timeout"] = timeout
1180
- CONFIG["max_tokens"] = tokens
1181
- logger.info(f"Configurações atualizadas: Temp={temp}, Timeout={timeout}, Tokens={tokens}")
1182
- return f"✅ Configs Atualizadas!"
1183
- config_update_btn = gr.Button("💾 Salvar Configs da Sessão")
1184
- config_status = gr.Textbox(label="Status Config", interactive=False)
1185
- config_update_btn.click(atualizar_config, inputs=[temperatura_slider, timeout_slider, max_tokens_slider], outputs=config_status)
1186
-
1187
- with gr.Accordion("📊 Dashboard de Progresso", open=False):
1188
- progresso_output = gr.Markdown("Clique em 'Atualizar Progresso' para ver suas estatísticas.")
1189
- progresso_btn = gr.Button("🔄 Atualizar Progresso")
1190
-
1191
- with gr.Accordion("📜 Histórico da Sessão", open=False):
1192
- historico_output = gr.Markdown("Clique em 'Ver Histórico' para ver suas últimas correções.")
1193
- historico_btn = gr.Button("🔄 Ver Histórico")
1194
-
1195
- with gr.Accordion("💾/📂 Salvar/Carregar Progresso Atual", open=False):
1196
- salvar_btn = gr.Button("💾 Salvar Caso/Peça Atual")
1197
- carregar_btn = gr.Button("📂 Carregar Caso/Peça de Arquivo")
1198
- arquivo_progresso_upload = gr.File(label="Selecione o arquivo .json para carregar", file_types=[".json"], scale=2)
1199
- # Definido como interactive=False desde o início
1200
- progresso_download_file = gr.File(label="Baixar Progresso Salvo", interactive=False)
1201
-
1202
- with gr.Column(scale=3):
1203
- gr.Markdown("### 1. Forneça o Caso / Contexto")
1204
- with gr.Row():
1205
- caso_input = gr.Textbox(label="📋 Caso ou Situação Processual", lines=10, placeholder="Insira o texto COMPLETO do caso ou da situação (ex: resumo da inicial para contestação, decisão para embargos/recurso).")
1206
- caso_btn = gr.Button("🎲 Gerar Caso Fictício", variant="secondary", scale=0)
1207
-
1208
- gr.Markdown("### 2. Insira Sua Peça Processual")
1209
- with gr.Tabs():
1210
- with gr.TabItem("✏️ Digitar Texto"):
1211
- peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=15, placeholder="Cole ou digite sua peça processual aqui...")
1212
- dica_contextual_output = gr.Markdown(value="*Dicas aparecerão aqui enquanto você digita...*")
1213
- btn_corrigir = gr.Button("🚀 CORRIGIR E ANALISAR!", variant="primary")
1214
-
1215
- with gr.TabItem("📎 Anexar Arquivo"):
1216
- peticao_arquivo_input = gr.File(label="Arquivo da Peça (.pdf, .docx, .txt - Máx 15MB)", file_types=[".pdf", ".docx", ".txt"], file_count="single")
1217
- arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Conteúdo do Arquivo (Texto Extraído)", lines=5, interactive=False)
1218
- btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary")
1219
-
1220
- processing_message = gr.Markdown("", visible=False)
1221
-
1222
- with gr.Tabs():
1223
- with gr.TabItem("📊 Análise e Correção"):
1224
- gr.Markdown("### 3. Resultados da Análise")
1225
- resultado_analise_output = gr.Markdown(label="Análise Detalhada e Nota")
1226
-
1227
- gr.Markdown("### ✨ Versão Sugerida (Corrigida/Aprimorada)")
1228
- resultado_peticao_corrigida_output = gr.Textbox(label="Peça Processual Corrigida", lines=20, show_copy_button=True)
1229
-
1230
- with gr.TabItem("⚖️ Comparativo (Diff)"):
1231
- gr.Markdown("### ↔️ Comparativo: Sua Peça vs. Versão Sugerida")
1232
- with gr.Row():
1233
- with gr.Column(scale=1):
1234
- resumo_alteracoes_output = gr.Markdown(label="Resumo das Mudanças")
1235
- criticas_output = gr.Markdown(label="Mudanças Críticas/Essenciais")
1236
- with gr.Column(scale=3):
1237
- visualizacao_detalhada_output = gr.HTML(label="📝 Alterações Detalhadas (Diff)")
1238
-
1239
- clear_btn = gr.Button("🗑️ Limpar Tudo")
1240
-
1241
- # --- FUNÇÕES WRAPPER (com try-except) ---
1242
- def corrigir_peticao_wrapper_texto(caso, tipo_peticao, peticao_texto, peticao_arquivo_state):
1243
- logger.info(">>> WRAPPER TEXTO INICIADO")
1244
- is_treinamento = modo_treinamento.value
1245
- logger.info(f"--- Modo Treinamento (Texto): {is_treinamento}")
1246
- try:
1247
- # A função corrigir_peticao retorna 6 valores
1248
- resultado = corrigir_peticao(caso, tipo_peticao, peticao_texto, peticao_arquivo_state, is_treinamento)
1249
- except Exception as e:
1250
- logger.error(f"Erro dentro do wrapper texto chamando corrigir_peticao: {e}", exc_info=True)
1251
- # Retornar tupla com erro para todos os outputs esperados (6 outputs)
1252
- return f"❌ Erro interno no processamento: {e}", "", "", "", "", ""
1253
- logger.info("<<< WRAPPER TEXTO FINALIZADO")
1254
- return resultado
1255
-
1256
- def corrigir_peticao_wrapper_arquivo(caso, tipo_peticao, peticao_texto_state, peticao_arquivo):
1257
- logger.info(">>> WRAPPER ARQUIVO INICIADO")
1258
- is_treinamento = modo_treinamento.value
1259
- logger.info(f"--- Modo Treinamento (Arquivo): {is_treinamento}")
1260
- try:
1261
- # A função corrigir_peticao retorna 6 valores
1262
- resultado = corrigir_peticao(caso, tipo_peticao, peticao_texto_state, peticao_arquivo, is_treinamento)
1263
- except Exception as e:
1264
- logger.error(f"Erro dentro do wrapper arquivo chamando corrigir_peticao: {e}", exc_info=True)
1265
- # Retornar tupla com erro para todos os outputs esperados (6 outputs)
1266
- return f"❌ Erro interno no processamento: {e}", "", "", "", "", ""
1267
- logger.info("<<< WRAPPER ARQUIVO FINALIZADO")
1268
- return resultado
1269
- # --- FIM DAS FUNÇÕES WRAPPER ---
1270
-
1271
-
1272
- # --- LÓGICA DOS BOTÕES E EVENTOS ---
1273
-
1274
- caso_btn.click(
1275
- fn=gerar_caso_ficticio,
1276
- inputs=[tipo_peticao_input],
1277
- outputs=[caso_input],
1278
- show_progress="full"
1279
- )
1280
-
1281
- peticao_texto_input.change(
1282
- fn=fornecer_dica_contextual,
1283
- inputs=[peticao_texto_input, tipo_peticao_input],
1284
- outputs=dica_contextual_output
1285
- )
1286
- tipo_peticao_input.change(
1287
- fn=fornecer_dica_contextual,
1288
- inputs=[peticao_texto_input, tipo_peticao_input],
1289
- outputs=dica_contextual_output
1290
- )
1291
-
1292
- peticao_arquivo_input.change(
1293
- fn=process_file,
1294
- inputs=[peticao_arquivo_input],
1295
- outputs=[arquivo_preview_output],
1296
- show_progress="minimal"
1297
- )
1298
-
1299
- # Botão Corrigir (Texto) - Usando wrapper e output corrigido
1300
- btn_corrigir.click(
1301
- fn=lambda: gr.update(value="⏳ Processando... Gerando análise e correção. Isso pode levar um minuto...", visible=True),
1302
- inputs=None,
1303
- outputs=[processing_message],
1304
- show_progress=False
1305
- ).then(
1306
- fn=corrigir_peticao_wrapper_texto,
1307
- inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None)],
1308
- # 6 outputs correspondendo aos 6 valores retornados por corrigir_peticao
1309
- outputs=[resultado_analise_output, peticao_original_hidden, resultado_peticao_corrigida_output,
1310
- resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
1311
- show_progress="full"
1312
- ).then(
1313
- fn=lambda: gr.update(value="", visible=False),
1314
- inputs=None,
1315
- outputs=[processing_message]
1316
- )
1317
-
1318
- # Botão Corrigir (Arquivo) - Usando wrapper e output corrigido
1319
- btn_corrigir_arquivo.click(
1320
- fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção. Isso pode levar um minuto...", visible=True),
1321
- inputs=None,
1322
- outputs=[processing_message],
1323
- show_progress=False
1324
- ).then(
1325
- fn=corrigir_peticao_wrapper_arquivo,
1326
- inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input],
1327
- # 6 outputs correspondendo aos 6 valores retornados por corrigir_peticao
1328
- outputs=[resultado_analise_output, peticao_original_hidden, resultado_peticao_corrigida_output,
1329
- resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
1330
- show_progress="full"
1331
- ).then(
1332
- fn=lambda: gr.update(value="", visible=False),
1333
- inputs=None,
1334
- outputs=[processing_message]
1335
- )
1336
-
1337
- # Botão Limpar - Usando gr.update()
1338
- clear_btn.click(
1339
- fn=lambda: [
1340
- gr.update(value="Petição Inicial"),
1341
- gr.update(value=False),
1342
- gr.update(value=None),
1343
- gr.update(value=None),
1344
- gr.update(value=None),
1345
- gr.update(value=""),
1346
- gr.update(value="", visible=False),
1347
- gr.update(value=""),
1348
- gr.update(value=""),
1349
- gr.update(value=""),
1350
- gr.update(value=""),
1351
- gr.update(value=""),
1352
- gr.update(value="*Dicas aparecerão aqui enquanto você digita...*"),
1353
- gr.update(value=None),
1354
- gr.update(value=None) # Limpa o valor do arquivo de download
1355
- ],
1356
- inputs=None,
1357
- outputs=[
1358
- tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, peticao_arquivo_input,
1359
- arquivo_preview_output, processing_message, resultado_analise_output,
1360
- resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output,
1361
- visualizacao_detalhada_output, dica_contextual_output, arquivo_progresso_upload,
1362
- progresso_download_file # Mantém o componente na lista de outputs
1363
- ]
1364
- )
1365
-
1366
- progresso_btn.click(fn=calcular_progresso, outputs=progresso_output)
1367
- historico_btn.click(fn=mostrar_historico, outputs=historico_output)
1368
-
1369
- # Salvar/Carregar Progresso - Lógica de visibilidade do botão de download removida
1370
- salvar_btn.click(
1371
- fn=lambda: gr.update(value="⏳ Salvando progresso atual...", visible=True),
1372
- inputs=None, outputs=processing_message, show_progress=False
1373
- ).then(
1374
- fn=salvar_progresso,
1375
- inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
1376
- # Saída para gr.File (terceiro output) atualiza o valor do componente progresso_download_file
1377
- outputs=[gr.Textbox(visible=False), config_status, progresso_download_file],
1378
- show_progress="minimal"
1379
- # REMOVIDO o .then() que mexia com a visibilidade
1380
- ).then(
1381
- lambda: gr.update(value="", visible=False), None, processing_message
1382
- )
1383
-
1384
- carregar_btn.click(
1385
- fn=lambda: gr.update(value="⏳ Carregando progresso do arquivo...", visible=True),
1386
- inputs=None, outputs=processing_message, show_progress=False
1387
- ).then(
1388
- fn=carregar_progresso,
1389
- inputs=[arquivo_progresso_upload],
1390
- outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status],
1391
- show_progress="minimal"
1392
- ).then(
1393
- lambda: gr.update(value="", visible=False), None, processing_message
1394
- )
1395
-
1396
- return demo
1397
-
1398
- # --- Bloco de Execução Principal ---
1399
- if __name__ == "__main__":
1400
- logger.info("Iniciando a aplicação Gradio...")
1401
- try:
1402
- version = pytesseract.get_tesseract_version()
1403
- logger.info(f"Tesseract versão {version} detectado.")
1404
- except Exception as e:
1405
- logger.warning(f"Tesseract não encontrado ou erro ao verificar versão: {e}. OCR de PDFs pode falhar. Verifique packages.txt.")
1406
-
1407
- app = interface_gradio()
1408
- app.launch()
1409
- logger.info("Aplicação Gradio encerrada.")