GabrielGD commited on
Commit
c7a1af4
·
verified ·
1 Parent(s): bf9a754

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +236 -618
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import gradio as gr
2
  import requests
3
  import os
@@ -94,7 +95,6 @@ def extract_text_from_pdf(file_path_or_obj):
94
  ocr_text_parts.append(f"[Erro OCR Página {i+1}: {str(ocr_e)}]")
95
  pdf_reader.close()
96
  ocr_text = "\n".join(ocr_text_parts)
97
-
98
  if text.strip() and len(text) > len(ocr_text) * 0.7:
99
  logger.info(f"Usando texto extraído do PDF ({len(text)} chars).")
100
  return text
@@ -133,7 +133,6 @@ def extract_text_from_txt(file_path_or_obj):
133
  content = file_path_or_obj.read()
134
  else:
135
  return "❌ Tipo de entrada TXT inválido."
136
-
137
  encodings = ['utf-8', 'latin-1', 'cp1252']
138
  for encoding in encodings:
139
  try:
@@ -167,13 +166,9 @@ def process_file(file_obj):
167
  return "⚠️ Arquivo muito grande (Máx 15MB)."
168
  except OSError as size_e:
169
  logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}")
170
-
171
- if filename.endswith('.pdf'):
172
- return extract_text_from_pdf(file_path)
173
- elif filename.endswith('.docx'):
174
- return extract_text_from_docx(file_path)
175
- elif filename.endswith('.txt'):
176
- return extract_text_from_txt(file_path)
177
  else:
178
  logger.warning(f"Formato de arquivo não suportado: {filename}")
179
  return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
@@ -184,267 +179,60 @@ def process_file(file_obj):
184
  def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
185
  base = """
186
  **Estrutura da Avaliação:**
187
- 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.
188
- 2. **Nota Final:** 0 a 10 (uma casa decimal), com justificativa BREVE dos principais fatores.
189
- 3. **Pontos Positivos:** Liste pelo menos 2-3 aspectos bem executados, com justificativa (legal/técnica).
190
- 4. **Pontos a Melhorar:** Liste **TODOS** os problemas em ordem de gravidade (erros crassos primeiro). Para cada ponto:
191
- * Descrição clara do erro/omissão.
192
- * Fundamentação legal/doutrinária do porquê é um erro.
193
- * **Impacto estimado na nota** (ex: -0.5, -1.0, -2.0).
194
- * Sugestão de correção ou como deveria ter sido feito.
195
- 5. **Análise Específica dos Requisitos:** Avalie os itens chave da peça (listados abaixo para cada tipo).
196
-
197
- **Critérios Fundamentais (Aplicáveis a Quase Todos):**
198
- * **Coerência:** A peça responde ao caso/situação processual? Os argumentos são lógicos?
199
- * **Fundamentação:** Usou a lei, doutrina e jurisprudência corretamente? Citou artigos relevantes?
200
- * **Linguagem:** Clareza, objetividade, correção gramatical e formalidade adequada?
201
- * **Pedidos:** São claros, certos, determinados e coerentes com a fundamentação?
202
- * **NÃO INVENTAR FATOS:** Penalidade severa (-3.0 a -5.0) se criar fatos não presentes no caso.
203
- * **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/...".
204
- """
205
  if modo_treinamento:
206
  base += """
207
- **Modo de Treinamento:**
208
- * Para cada Ponto a Melhorar, detalhe AINDA MAIS:
209
- a) Explicação aprofundada do erro e sua consequência prática.
210
- b) Base legal/doutrinária específica.
211
- c) **Exemplo de Redação Corrigida** para aquele trecho específico.
212
- d) Dica prática relevante (use o banco de dicas se aplicável).
213
- e) Possível estratégia alternativa.
214
- * Destaque inconsistências entre a peça e o caso com citações diretas.
215
- * Sugira estratégias jurídicas adicionais ou alternativas viáveis.
216
- """
217
  return base
218
 
219
  def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
220
  instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento)
221
- prompt_especifico = f"""
222
- ## Análise de Peça Processual: {tipo_peticao} (Critérios OAB Simulados)
223
- **Caso/Contexto Fornecido:**
224
- ```
225
- {caso}
226
- ```
227
- **Peça do Aluno ({tipo_peticao}):**
228
- ```
229
- {peticao}
230
- ```
231
- {instrucoes_gerais}
232
- **Requisitos Específicos para {tipo_peticao}:**
233
- """
234
  if tipo_peticao == "Petição Inicial":
235
- prompt_especifico += """
236
- - Endereçamento e competência (Art. 319, I, CPC; leis especiais como CDC Art. 101, I).
237
- - Qualificação completa das partes (Art. 319, II, CPC).
238
- - Exposição clara e lógica dos fatos (causa de pedir remota).
239
- - Fundamentação jurídica pertinente (causa de pedir próxima - Art. 319, III, CPC).
240
- - Pedidos certos, determinados e líquidos (se possível) (Art. 319, IV, CPC).
241
- - Valor da causa correto e justificado (Art. 291-292, 319, V, CPC).
242
- - Indicação das provas pretendidas (Art. 319, VI, CPC).
243
- - Opção pela audiência de conciliação/mediação (Art. 319, VII, CPC).
244
- - Pedido de citação do réu (forma específica se necessário).
245
- - Requerimento de juntada de documentos essenciais (procuração, custas/JG).
246
- - Fechamento formal ("Nestes termos pede deferimento", Local, data, Advogado, OAB).
247
- **Critérios Adicionais (Inicial):**
248
- * Uso de "XXX" para dados ausentes. Penalizar invenção (-0.5 por ocorrência).
249
- * Falta de pedido de juntada de custas/JG: -1.0 ponto.
250
- * Erro em "Nestes termos, pede deferimento" (com vírgula): -0.2 ponto.
251
- * 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.
252
- """
253
  elif tipo_peticao == "Contestação":
254
- prompt_especifico += """
255
- - Endereçamento ao juízo correto onde tramita a ação.
256
- - Qualificação completa do réu (se já não constante nos autos, referenciar).
257
- - Indicação do processo (número).
258
- - Tempestividade (menção implícita ou explícita de que está no prazo).
259
- - **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).
260
- - **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!
261
- - **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).
262
- - Fundamentação jurídica da defesa.
263
- - **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).
264
- - Indicação das provas que pretende produzir.
265
- - Fechamento formal.
266
- **Critérios Adicionais (Contestação):**
267
- * A não alegação de preliminar arguível (exceto incompetência absoluta, conexão, convenção de arbitragem) preclui? Avaliar impacto.
268
- * Falha grave na impugnação específica: -2.0 a -4.0 pontos.
269
- * Ausência de pedido de improcedência: -1.5 pontos.
270
- """
271
  elif tipo_peticao == "Reconvenção":
272
- prompt_especifico += """
273
- - Apresentada NA MESMA PEÇA da Contestação (Art. 343, CPC).
274
- - Endereçamento ao mesmo juízo.
275
- - Indica�o do processo.
276
- - Qualificação completa do réu-reconvinte e do autor-reconvindo (podem ser referenciadas se já completas).
277
- - **Conexão com a Ação Principal ou Fundamento de Defesa:** O pedido da reconvenção deve ser conexo (Art. 343, caput).
278
- - **Estrutura Similar à Petição Inicial:** Fatos, Fundamentação, Pedidos específicos, Valor da causa, Provas.
279
- - Pedido de intimação do autor-reconvindo para contestar a reconvenção (na pessoa de seu advogado - Art. 343, §1º).
280
- - Fechamento formal (pode ser conjunto com a contestação).
281
- **Critérios Adicionais (Reconvenção):**
282
- * Ausência de conexão clara: -2.0 pontos. Falta de pedido/valor da causa/provas específicos: -1.0 por item. Apresentada em peça separada (erro formal grave): -3.0 pontos.
283
- """
284
  elif tipo_peticao == "Impugnação à Contestação":
285
- prompt_especifico += """
286
- - Endereçamento ao juízo correto. Indicação do processo. Qualificação do autor (impugnante). Tempestividade (15 dias - Art. 350/351 CPC).
287
- - **Resposta às Preliminares:** Rebater cada preliminar arguida.
288
- - **Resposta a Fatos Impeditivos/Modificativos/Extintivos:** Manifestar-se sobre fatos novos (Art. 350).
289
- - **Resposta a Documentos Novos:** Manifestar-se sobre documentos juntados (Art. 437 CPC).
290
- - **Ratificação da Inicial:** Reiterar os termos e pedidos da inicial.
291
- - **Especificação de Provas:** Indicar provas para contrapor defesa indireta/preliminares (Art. 350/351).
292
- - Pedidos: Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial. Fechamento formal.
293
- **Critérios Adicionais (Impugnação):**
294
- * Não responder a preliminar relevante: -1.5 pontos. Não se manifestar sobre fato novo: -2.0 pontos. Não ratificar a inicial: -0.5 pontos.
295
- """
296
  elif tipo_peticao == "Embargos de Declaração":
297
- prompt_especifico += """
298
- - **Contra Decisão Específica:** Indicar claramente qual decisão está sendo embargada. Endereçamento ao PRÓPRIO juízo prolator. Indicação do processo. Qualificação das partes. Tempestividade (5 dias - Art. 1023 CPC).
299
- - **Cabimento (Art. 1022, CPC):** Apontar **PRECISAMENTE** e **FUNDAMENTADAMENTE** a ocorrência de: Omissão, Contradição, Obscuridade, Erro Material.
300
- - **NÃO PODE REJULGAR A CAUSA:** Objetivo é sanar vício, não modificar o mérito (salvo efeitos infringentes excepcionais).
301
- - **Pedido:** Requerer o conhecimento e ACOLHIMENTO para sanar o vício (com ou sem efeitos infringentes, se for o caso). Fechamento formal.
302
- **Critérios Adicionais (Embargos):**
303
- * Usar para rediscutir mérito (erro grave): -4.0 pontos. Não apontar vício claramente: -3.0 pontos. Endereçar ao tribunal (erro): -1.0 ponto.
304
- """
305
  elif tipo_peticao == "Recurso de Apelação":
306
- prompt_especifico += """
307
- - **Peça de Interposição:** Endereçamento ao juízo a quo. Indicação do processo. Qualificação das partes. Pressupostos (cabimento - Art. 1009; tempestividade - 15 dias Art. 1003, §5º; preparo/JG; regularidade). Pedido de recebimento e remessa ao Tribunal.
308
- - **Razões Recursais:** Endereçamento ao TRIBUNAL competente. Exposição dos fatos e da sentença. **Fundamentos para Reforma/Anulação (Art. 1010, II e III):** Error in procedendo (anulação) ou Error in judicando (reforma). **Princípio da Dialeticidade:** Impugnar ESPECIFICAMENTE os fundamentos da sentença.
309
- - **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão da sucumbência. Fechamento formal.
310
- **Critérios Adicionais (Apelação):**
311
- * Falta de interposição ou razões: Nota zero. Endereçamento incorreto das razões: -1.0. Ausência de dialeticidade: -3.0 a -5.0. Não pedir anulação/reforma: -1.5. Falta de preparo/JG: -2.0.
312
- """
313
  elif tipo_peticao == "Agravo de Instrumento":
314
- prompt_especifico += """
315
- - **Peça Única:** Endereçamento direto ao TRIBUNAL. Indicação processo origem. Qualificação partes e advogados (com endereços - Art. 1016, IV). Cópia da decisão agravada (OBRIGATÓRIA - Art. 1017, I).
316
- - **Cabimento (Art. 1015 ou taxatividade mitigada):** Demonstrar hipótese legal ou urgência (Tema 988 STJ). Tempestividade (15 dias). Preparo/JG.
317
- - **Peças Obrigatórias (Art. 1017, I):** Decisão agravada, certidão de intimação, procurações. Peças Facultativas (Art. 1017, III).
318
- - **Razões (Art. 1016, II e III):** Exposição do fato e direito; razões para reforma/invalidação da decisão interlocutória.
319
- - **Pedido de Efeito Suspensivo ou Tutela Antecipada Recursal (Art. 1019, I):** Se houver risco, pedir fundamentadamente.
320
- - **Pedidos Finais:** Conhecimento; (se aplicável) efeito suspensivo/tutela antecipada; PROVIMENTO para reformar/invalidar; comunicação ao juiz (Art. 1018 - opcional se eletrônico). Fechamento formal.
321
- **Critérios Adicionais (Agravo):**
322
- * Ausência de peça obrigatória: Inadmissibilidade (-4.0). Não demonstrar cabimento: -3.0. Endereçar ao juiz a quo: -1.0. Não indicar advogados: -0.5.
323
- """
324
  elif tipo_peticao == "Petição de Tutela Provisória":
325
- prompt_especifico += """
326
- - **Tipo (Urgência ou Evidência):** Deixar claro. Antecedente ou incidental. Endereçamento (competente ou da causa). Qualificação partes. Indicação processo (se incidental).
327
- - **Tutela de Urgência (Art. 300):** **Fumus Boni Iuris** (probabilidade do direito) E **Periculum in Mora** (perigo de dano/risco ao resultado útil). **Reversibilidade** (Art. 300, §3º - se possível).
328
- - **Tutela de Evidência (Art. 311):** Demonstrar uma das hipóteses (I a IV). **NÃO precisa periculum in mora.**
329
- - **Pedido Específico:** Indicar CLARAMENTE a medida liminar solicitada.
330
- - **Se Antecedente (Art. 303 ou 305):** Indicar pedido final ou aditar/apresentar inicial no prazo. Valor da Causa.
331
- - **Pedidos:** Concessão da tutela (liminar/justificação prévia); citação/intimação; (se antecedente) indicação da ação principal/aditamento. Fechamento formal.
332
- **Critérios Adicionais (Tutela):**
333
- * Não demonstrar Fumus/Periculum (Urgência): -3.0 a -5.0. Não enquadrar na Evidência: -3.0. Pedido genérico: -2.0. Confundir requisitos: -1.5.
334
- """
335
- else:
336
- prompt_especifico += """
337
- - **Adequação ao Tipo:** A peça corresponde à estrutura e finalidade do tipo?
338
- - **Clareza:** É possível entender a pretensão?
339
- - **Fundamentação Mínima:** Tentativa de basear em lei ou fatos?
340
- - **Pedido:** Existe um pedido?
341
- """
342
- prompt_especifico += "\n\n**Instruções Finais:** Analise a peça do aluno estritamente com base no caso/contexto fornecido e nos critérios acima. Seja rigoroso como um examinador da OAB, mas forneça feedback construtivo e detalhado. Gere a resposta **exclusivamente** no formato solicitado."
343
  return prompt_especifico
344
 
345
  def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
346
- prompt = f"""
347
- **Caso/Contexto Fornecido:**
348
- ```
349
- {caso}
350
- ```
351
- **Peça Original do Aluno ({tipo_peticao}):**
352
- ```
353
- {peticao_original}
354
- ```
355
- **Tarefa:** Com base no caso e nos problemas da peça original, **gere uma versão corrigida e aprimorada** da peça ({tipo_peticao}). Siga **rigorosamente** as melhores práticas e requisitos formais/materiais para este tipo de peça no exame da OAB.
356
- **Instruções Específicas para a Peça Corrigida ({tipo_peticao}):**
357
- """
358
  if tipo_peticao == "Petição Inicial":
359
- prompt += """
360
- 1. **Endereçamento:** Correto e fundamentado (Art. 46 CPC ou Art. 101 CDC se aplicável).
361
- 2. **Qualificação:** Completa (Art. 319, II), usando "XXX" para dados ausentes.
362
- 3. **Fatos:** Narre de forma cronológica, clara e fiel ao caso.
363
- 4. **Fundamentação:** Cite artigos pertinentes (CC, CDC, CPC) e súmulas/jurisprudência, se aplicável.
364
- 5. **Pedidos:** Claros, certos, determinados (quantificar danos se possível). Cumulados se cabível.
365
- 6. **Valor da Causa:** Correto (soma dos benefícios - Art. 292).
366
- 7. **Provas:** Indicar ("protesta provar o alegado por todos os meios...").
367
- 8. **Audiência:** Indicar opção (Art. 319, VII).
368
- 9. **Citação:** Pedido de citação (ex: "via postal, Art. 246, I, CPC").
369
- 10. **Custas:** Requerimento expresso de juntada da guia OU pedido de gratuidade (com pedido de declaração). **NUNCA OMITIR.**
370
- 11. **Fechamento:** "Nestes termos pede deferimento." (SEM vírgula). Seguido de "Local, data.", "ADVOGADO(A)", "OAB/...". **NUNCA identificar.**
371
- """
372
  elif tipo_peticao == "Contestação":
373
- prompt += """
374
- 1. **Endereçamento:** Ao juízo da ação.
375
- 2. **Identificação:** Número do processo, qualificação do réu.
376
- 3. **Tempestividade:** Mencionar ("A presente contestação é tempestiva...").
377
- 4. **Estrutura:** Síntese da Inicial; Preliminares (Art. 337, fundamentadas); Mérito (Impugnação Específica + Defesa de Mérito Direta/Indireta com fundamentos); Reconvenção (Se houver, ANTES dos pedidos da contestação).
378
- 5. **Pedidos da Contestação:** Acolhimento das preliminares (com consequência); **IMPROCEDÊNCIA TOTAL** dos pedidos do autor; Condenação em custas e honorários.
379
- 6. **Provas:** Indicar as provas do réu.
380
- 7. **Fechamento:** Padrão OAB.
381
- """
382
  elif tipo_peticao == "Reconvenção":
383
- prompt += """
384
- **IMPORTANTE:** Gere a Reconvenção como parte da Contestação.
385
- 1. **Após a defesa de mérito da Contestação e ANTES dos pedidos da Contestação**, insira: "DA RECONVENÇÃO".
386
- 2. **Qualificação:** Réu-Reconvinte e Autor-Reconvindo.
387
- 3. **Conexão:** Justifique brevemente.
388
- 4. **Fatos e Direito Reconvencionais:** Narre e fundamente o pedido do reconvinte.
389
- 5. **Pedidos da Reconvenção:** Claros e específicos contra o autor-reconvindo.
390
- 6. **Valor da Causa e Provas da Reconvenção:** Específicos.
391
- 7. **Intimação:** Peça a intimação do autor-reconvindo para contestar.
392
- 8. **Continue com os Pedidos da Contestação**.
393
- 9. **Fechamento:** Padrão OAB.
394
- """
395
  elif tipo_peticao == "Impugnação à Contestação":
396
- prompt += """
397
- 1. Endereçamento, processo, qualificação autor, tempestividade.
398
- 2. Tópico: **DA(S) PRELIMINAR(ES) ARGUIDA(S)** - Rebater cada uma.
399
- 3. Tópico: **DO MÉRITO E DOS FATOS NOVOS** - Manifestar-se sobre fatos impeditivos/modificativos/extintivos e documentos.
400
- 4. Tópico: **DA RATIFICAÇÃO DA INICIAL** - Reiterar termos e pedidos.
401
- 5. Tópico: **DAS PROVAS** - Especificar provas contrapostas.
402
- 6. **Pedidos:** Rejeição das preliminares, refutação da defesa, procedência dos pedidos da inicial.
403
- 7. Fechamento padrão OAB.
404
- """
405
  elif tipo_peticao == "Embargos de Declaração":
406
- prompt += """
407
- 1. Endereçamento ao juízo prolator. Processo, Embargante, Embargado. Decisão embargada identificada. Tempestividade (5 dias).
408
- 2. Tópico para cada vício (Art. 1022): **DA OMISSÃO / DA CONTRADIÇÃO / DA OBSCURIDADE / DO ERRO MATERIAL**.
409
- 3. Dentro de cada tópico: **Transcrever trecho relevante da decisão** e explicar **objetivamente** o vício. NÃO REJULGAR.
410
- 4. **Pedido:** Conhecimento e ACOLHIMENTO para sanar o(s) vício(s) apontado(s), especificando o que se espera. Pedir efeitos infringentes se cabível e fundamentado.
411
- 5. Fechamento padrão OAB.
412
- """
413
  elif tipo_peticao == "Recurso de Apelação":
414
- prompt += """
415
- 1. **Peça de Interposição:** Endereçamento ao juízo a quo. Processo, Apelante, Apelado. Pressupostos (cabimento, tempestividade, preparo/JG). Pedido de recebimento e remessa.
416
- 2. **Razões Recursais:** Endereçamento ao Tribunal. Breve relato. **DOS FUNDAMENTOS PARA REFORMA/ANULAÇÃO:** Error in procedendo ou Error in judicando. Impugnar especificamente a sentença (dialeticidade).
417
- 3. **Pedidos:** Conhecimento; PROVIMENTO para ANULAR ou REFORMAR (especificar); inversão sucumbência.
418
- 4. Fechamento padrão OAB.
419
- """
420
  elif tipo_peticao == "Agravo de Instrumento":
421
- prompt += """
422
- 1. Peça única ao Tribunal. Processo origem, Agravante, Agravado, Advogados (com endereços). Decisão agravada identificada (juntar cópia).
423
- 2. **DO CABIMENTO:** Demonstrar hipótese Art. 1015 ou mitigação. **DA TEMPESTIVIDADE.** **DO PREPARO/JG.** **DAS PEÇAS OBRIGATÓRIAS E FACULTATIVAS.**
424
- 3. **DAS RAZÕES RECURSAIS:** Exposição fato/direito, motivos para reforma/invalidação da interlocutória.
425
- 4. **DO PEDIDO DE EFEITO SUSPENSIVO / TUTELA ANTECIPADA RECURSAL** (se cabível e fundamentado).
426
- 5. **Pedidos:** Conhecimento; (se aplicável) efeito suspensivo/tutela; PROVIMENTO para reformar/invalidar; comunicação ao juiz.
427
- 6. Fechamento padrão OAB.
428
- """
429
  elif tipo_peticao == "Petição de Tutela Provisória":
430
- prompt += """
431
- 1. Indicar TIPO (Urgência/Evidência) e CARÁTER (Antecedente/Incidental). Endereçamento. Qualificação. Processo (se incidental).
432
- 2. **DOS REQUISITOS:**
433
- * **Urgência:** Tópico **DA PROBABILIDADE DO DIREITO (FUMUS BONI IURIS)** e tópico **DO PERIGO DE DANO OU RISCO AO RESULTADO ÚTIL (PERICULUM IN MORA)**. Se possível, **DA REVERSIBILIDADE**.
434
- * **Evidência:** Tópico **DA HIPÓTESE DE EVIDÊNCIA (ART. 311, INCISO __)** - Demonstrar o enquadramento.
435
- 3. **DO PEDIDO LIMINAR:** Descrever CLARAMENTE a medida solicitada.
436
- 4. **Se Antecedente:** Indicar pedido final ou aditamento/inicial completa. Valor da Causa.
437
- 5. **Pedidos:** Concessão da tutela; citação/intimação; (se antecedente) indicação ação principal/aditamento.
438
- 6. Fechamento padrão OAB.
439
- """
440
- else:
441
- prompt += """
442
- 1. Estrutura Lógica: Começo, meio, fim.
443
- 2. Argumentos Claros.
444
- 3. Pedido Claro.
445
- 4. Formalidades: Linguagem adequada, fechamento OAB.
446
- """
447
- 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."
448
  return prompt
449
 
450
  def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
@@ -454,24 +242,17 @@ def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
454
  peticao_lower = peticao_corrigida.lower()
455
  if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower:
456
  partes = peticao_corrigida.split("Nestes termos pede deferimento")
457
- if len(partes) == 2:
458
- peticao_corrigida = partes[0].strip() + "\nRequer, ainda, a juntada da guia de custas devidamente recolhida.\nNestes termos pede deferimento" + partes[1]
459
- else:
460
- peticao_corrigida = peticao_corrigida.replace("Local, data.", "Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.")
461
  if "audiência de conciliação" not in peticao_lower and "audiência de mediação" not in peticao_lower:
462
  partes = peticao_corrigida.split("Nestes termos pede deferimento")
463
- if len(partes) == 2:
464
- 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]
465
- else:
466
- 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.")
467
-
468
  linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
469
  fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."]
470
- while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]):
471
- linhas.pop()
472
  linhas.extend(fechamento_padrao)
473
- peticao_corrigida = "\n".join(linhas)
474
- return peticao_corrigida
475
 
476
  def chamar_api(prompt):
477
  payload = {"contents": [{"parts": [{"text": prompt}]}],"generationConfig": {"temperature": CONFIG["temperatura"],"maxOutputTokens": CONFIG["max_tokens"],"topP": 0.95,}}
@@ -483,71 +264,58 @@ def chamar_api(prompt):
483
  logger.debug(f"API Call - Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}")
484
  response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"])
485
  logger.debug(f"API Response - Status: {response.status_code}")
486
-
487
  if response.status_code == 429:
488
  retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1)))
489
- logger.warning(f"Erro 429 (Too Many Requests). Aguardando {retry_after}s...")
490
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(retry_after); continue
491
- else: return "❌ Erro 429: Muitas requisições. Tente novamente mais tarde."
492
-
493
  if response.status_code != 200:
494
- error_msg = "Erro desconhecido na API"
495
- try:
496
- error_info = response.json()
497
- error_msg = error_info.get("error", {}).get("message", error_msg)
498
- except json.JSONDecodeError:
499
- error_msg = f"Erro {response.status_code}: {response.text[:200]}" # Mostra início do erro não-JSON
500
- logger.error(f"API Error (Status {response.status_code}): {error_msg}")
501
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
502
- return f"❌ Erro da API ({response.status_code}): {error_msg}"
503
-
504
  response_data = response.json()
505
-
506
  if not response_data.get("candidates"):
507
  prompt_feedback = response_data.get("promptFeedback", {})
508
  block_reason = prompt_feedback.get("blockReason")
509
  logger.error(f"API Response sem 'candidates'. Block Reason: {block_reason}")
510
- if block_reason: return f"❌ A solicitação foi bloqueada pela API. Motivo: {block_reason}. Tente reformular."
511
- else: return "❌ Resposta vazia ou inválida da API (sem 'candidates')."
512
-
513
  candidate = response_data["candidates"][0]
514
  finish_reason = candidate.get("finishReason")
515
-
516
  if finish_reason and finish_reason not in ["STOP", "MAX_TOKENS", "FINISH_REASON_UNSPECIFIED"]:
517
- logger.error(f"Geração interrompida pela API. Motivo: {finish_reason}")
518
  safety_ratings = candidate.get("safetyRatings", [])
519
  harm_categories = [r['category'] for r in safety_ratings if r['probability'] not in ['NEGLIGIBLE', 'LOW']]
520
- if harm_categories: return f"❌ Resposta bloqueada por segurança. Motivo: {finish_reason} ({', '.join(harm_categories)})."
521
- else: return f"❌ Geração interrompida. Motivo: {finish_reason}."
522
-
523
  if not candidate.get("content") or not candidate["content"].get("parts"):
524
- logger.error("Estrutura de resposta inesperada: Faltando content/parts.")
525
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
526
- return "❌ Resposta inválida da API (estrutura inesperada)."
527
-
528
  texto_gerado = candidate["content"]["parts"][0]["text"].strip()
529
- logger.info(f"API retornou com sucesso ({len(texto_gerado)} chars). Finish Reason: {finish_reason}")
530
  return texto_gerado
531
-
532
  except requests.Timeout:
533
- logger.warning(f"Timeout na API - Tentativa {tentativa + 1}.")
534
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
535
- return "⏱️ Tempo esgotado na comunicação com a API."
536
  except requests.RequestException as e:
537
- logger.error(f"Erro de Rede/Requisição API - Tentativa {tentativa + 1}: {e}")
538
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
539
- return f"❌ Erro de conexão com a API: {e}"
540
  except json.JSONDecodeError as e:
541
- logger.error(f"Erro ao decodificar JSON da API: {e}")
542
  logger.debug(f"Resposta não-JSON: {response.text[:500]}")
543
- return f"❌ Erro ao processar a resposta da API (JSON inválido)."
544
  except Exception as e:
545
- logger.exception(f"Erro inesperado na chamada da API - Tentativa {tentativa + 1}: {str(e)}")
546
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
547
- return f"❌ Erro inesperado no processamento da API: {str(e)}"
548
-
549
- logger.error("Falha ao chamar a API após todas as tentativas.")
550
- return "❌ Falha ao comunicar com a API após múltiplas tentativas."
551
 
552
  def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
553
  logger.info(f"Iniciando correção. Tipo={tipo_peticao}, Treinamento={modo_treinamento}")
@@ -555,60 +323,32 @@ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=Non
555
  analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html = "", "", "", "", ""
556
  peticao_conteudo = ""
557
  try:
558
- if not caso or not caso.strip():
559
- logger.warning("Caso não fornecido.")
560
- return "⚠️ Forneça o caso/contexto da peça.", "", "", "", ""
561
- if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO:
562
- logger.warning(f"Tipo de petição inválido ou não fornecido: {tipo_peticao}")
563
- return "⚠️ Selecione um tipo de petição válido.", "", "", "", ""
564
-
565
  if peticao_arquivo is not None:
566
- logger.info("Processando arquivo de petição...")
567
  peticao_conteudo = process_file(peticao_arquivo)
568
- if peticao_conteudo.startswith("❌") or peticao_conteudo.startswith("⚠️"):
569
- logger.error(f"Erro ao processar arquivo: {peticao_conteudo}")
570
- return peticao_conteudo, "", "", "", ""
571
- elif peticao_texto and peticao_texto.strip():
572
- peticao_conteudo = peticao_texto
573
- else:
574
- logger.warning("Nenhuma petição fornecida (texto ou arquivo).")
575
- return "⚠️ Forneça a petição (em texto ou arquivo).", "", "", "", ""
576
  logger.info(f"Petição recebida ({len(peticao_conteudo)} chars).")
577
-
578
  troll_triggers = ["escreva a petição", "faça a petição", "redija a petição", "elabore a petição", "crie a petição", "gere a petição", "faça para mim", "faça por mim"]
579
  if any(trigger in peticao_conteudo.lower() for trigger in troll_triggers):
580
  logger.info("Detectado pedido para escrever petição.")
581
- return random.choice([
582
- "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?",
583
- "🚫 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!",
584
- ]), "", "", "", ""
585
-
586
  cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}"
587
  if cache_key in CORRECAO_CACHE:
588
  logger.info("Correção encontrada no cache.")
589
- cached_data = CORRECAO_CACHE[cache_key]
590
- return cached_data["analise_formatada"], cached_data["peticao_corrigida"], cached_data["resumo_diff"], cached_data["criticas_diff"], cached_data["diff_html"]
591
-
592
- logger.info("Estruturando prompt de análise...")
593
  prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento)
594
- logger.info("Chamando API para análise...")
595
  analise_bruta = chamar_api(prompt_analise)
596
- if analise_bruta.startswith(("❌", "⏱️", "⚠️")):
597
- logger.error(f"Erro retornado pela API (Análise): {analise_bruta}")
598
- return analise_bruta, "", "", "", ""
599
- logger.info("Análise recebida. Formatando...")
600
  analise_formatada = formatar_correcao(analise_bruta)
601
-
602
- logger.info("Estruturando prompt para peça corrigida...")
603
  prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao)
604
- logger.info("Chamando API para gerar peça corrigida...")
605
  peticao_corrigida_bruta = chamar_api(prompt_peticao)
606
-
607
  if peticao_corrigida_bruta.startswith(("❌", "⏱️", "⚠️")):
608
- logger.error(f"Erro retornado pela API (Correção): {peticao_corrigida_bruta}")
609
- peticao_corrigida_final = f"⚠️ Falha ao gerar a versão corrigida:\n{peticao_corrigida_bruta}"
610
  else:
611
- logger.info("Peça corrigida recebida. Aplicando pós-correção...")
612
  start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
613
  start_index = -1
614
  peticao_lower_corr = peticao_corrigida_bruta.lower()
@@ -617,390 +357,268 @@ def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=Non
617
  if idx != -1: start_index = idx; break
618
  peticao_extraida = peticao_corrigida_bruta[start_index:] if start_index != -1 else peticao_corrigida_bruta
619
  peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao)
620
- logger.info("Pós-correção aplicada.")
621
-
622
- logger.info("Calculando diferenças...")
623
  diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final)
624
- logger.info("Diferenças calculadas.")
625
-
626
  resultado_cache = {"analise_formatada": analise_formatada,"peticao_corrigida": peticao_corrigida_final,"resumo_diff": resumo_diff,"criticas_diff": criticas_diff,"diff_html": diff_html}
627
  CORRECAO_CACHE[cache_key] = resultado_cache
628
  HISTORICO.append({"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"tipo_peticao": tipo_peticao,"caso": caso,"peticao_original": peticao_conteudo,**resultado_cache})
629
  if len(HISTORICO) > 10: HISTORICO.pop(0)
630
-
631
  end_time = time.time()
632
  logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.")
633
  return analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html
634
-
635
  except Exception as e:
636
  logger.exception("Erro GERAL inesperado dentro de corrigir_peticao:")
637
- error_message = f"❌ Erro inesperado na função de correção: {str(e)}\nConsulte os logs para detalhes."
638
- return error_message, "", "", "", ""
639
 
640
  def formatar_correcao(texto):
641
  texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
642
  texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
643
  texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
644
  texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
645
- texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica dos Requisitos")
646
- linhas = texto.split('\n')
647
- linhas_formatadas = []
648
  in_list = False
649
  for linha in linhas:
650
- linha_strip = linha.strip()
651
- if linha_strip.startswith(("1.","2.","3.","4.","5.")):
652
- linhas_formatadas.append(f"\n**{linha_strip}**")
653
- in_list = False
654
- elif linha_strip.startswith(("- ","* ")):
655
- prefix = " * " if in_list else "* "
656
- linhas_formatadas.append(prefix + linha_strip[2:])
657
- in_list = True
658
- elif linha_strip.startswith(("a)","b)","c)","d)","e)")):
659
- linhas_formatadas.append(f" * {linha_strip}")
660
- in_list = True
661
- else:
662
- linhas_formatadas.append(linha)
663
- if not linha_strip.startswith(("*", "- ", "a)", "b)", "c)", "d)", "e)"," ")):
664
- in_list = False
665
  texto_final = "\n".join(linhas_formatadas)
666
- nota_pattern = r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})"
667
- match = re.search(nota_pattern, texto_final, re.IGNORECASE)
668
- if match:
669
- nota = match.group(2).replace(",",".")
670
- texto_final = re.sub(nota_pattern, f"\\1**{nota}/10**", texto_final, count=1, flags=re.IGNORECASE)
671
  return texto_final
672
 
673
  def fornecer_dica_contextual(texto_peticao, tipo_peticao):
674
- if not texto_peticao or not texto_peticao.strip():
675
- return "*Digite algo na peça para receber dicas...*"
676
  dicas_relevantes = []
677
  texto_lower = texto_peticao.lower()
678
  for chave in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]:
679
- if chave in DICAS_OAB_MELHORADO:
680
- dicas_relevantes.append(f"💡 **{chave}:** {DICAS_OAB_MELHORADO[chave]}")
681
  keywords_por_tipo = {
682
- "Petição Inicial": ["endereçamento", "qualificação", "fatos", "fundamentos", "pedidos", "valor", "provas", "audiência", "citação", "custas"],
683
- "Contestação": ["preliminar", "mérito", "impugnação", "improcedência", "honorários", "art. 337", "art. 341"],
684
- "Reconvenção": ["reconvinte", "reconvindo", "conexão", "art. 343"],
685
- "Impugnação à Contestação": ["réplica", "preliminar", "fatos novos", "documentos", "ratifica", "art. 350"],
686
- "Embargos de Declaração": ["omissão", "contradição", "obscuridade", "erro material", "art. 1022"],
687
- "Recurso de Apelação": ["sentença", "reforma", "anulação", "dialeticidade", "preparo", "tribunal", "art. 1009"],
688
- "Agravo de Instrumento": ["interlocutória", "cabimento", "urgência", "efeito suspensivo", "art. 1015", "art. 1017"],
689
- "Petição de Tutela Provisória": ["urgência", "evidência", "liminar", "periculum", "fumus boni", "art. 300", "art. 311"],
690
  }
691
  if tipo_peticao in keywords_por_tipo:
692
  for chave, dica in DICAS_OAB_MELHORADO.items():
693
- chave_lower = chave.lower()
694
- if f"({tipo_peticao.split()[0].lower()})" in chave_lower or \
695
- any(kw in chave_lower for kw in keywords_por_tipo[tipo_peticao] if kw in texto_lower):
696
- dica_formatada = f"💡 **{chave}:** {dica}"
697
- if dica_formatada not in dicas_relevantes:
698
- dicas_relevantes.append(dica_formatada)
699
- if not dicas_relevantes:
700
- return "*Continue escrevendo ou selecione o tipo de peça para dicas mais específicas.*"
701
  return "\n\n".join(dicas_relevantes[:4])
702
 
703
  def calcular_diff_interno(texto_original, texto_corrigido):
704
- if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")):
705
- return "","",""
706
- orig_lines = texto_original.splitlines()
707
- corr_lines = texto_corrigido.splitlines()
708
  diff = difflib.ndiff(orig_lines, corr_lines)
709
- resultado_diff_html = []
710
- criticas = []
711
- linhas_removidas = 0
712
- linhas_adicionadas = 0
713
- keywords_criticas_removidas = ["invenção", "identificação", "erro crasso", "incorreto", "prazo"]
714
- keywords_criticas_adicionadas = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "essencial", "preliminar", "tempestividade", "cabimento"]
715
  for linha in diff:
716
- texto = linha[2:]
717
- texto_escaped = escape(texto)
718
- if linha.startswith(' '): resultado_diff_html.append(f"<div class='linha-igual'>{texto_escaped}</div>")
719
- elif linha.startswith('- '):
720
- linhas_removidas += 1
721
- resultado_diff_html.append(f"<div class='linha-removida' title='Removido'><del>{texto_escaped}</del></div>")
722
- if texto.strip() and any(kw in texto.lower() for kw in keywords_criticas_removidas): criticas.append(f"<span style='color: red;'>🔴 REMOVIDO:</span> {texto_escaped}")
723
- elif linha.startswith('+ '):
724
- linhas_adicionadas += 1
725
- resultado_diff_html.append(f"<div class='linha-adicionada' title='Adicionado'><ins>{texto_escaped}</ins></div>")
726
- if texto.strip() and any(kw in texto.lower() for kw in keywords_criticas_adicionadas): criticas.append(f"<span style='color: green;'>🟢 ADICIONADO:</span> {texto_escaped}")
727
- resumo_estatistico = f"**Resumo:**\n* Removidas: {linhas_removidas}\n* Adicionadas: {linhas_adicionadas}\n* **Saldo:** {linhas_adicionadas - linhas_removidas} linhas"
728
- criticas_texto = "### 🚨 Mudanças Críticas / Essenciais:\n" + "\n".join(criticas) if criticas else "*Nenhuma mudança crítica/essencial detectada.*"
729
- css = """<style>.diff-container { font-family: monospace; white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; background-color: #f9f9f9; max-height: 600px; overflow-y: auto;} .linha-igual { color: #555; margin: 1px 0; } .linha-removida { background-color: #ffebee; color: #c62828; margin: 1px 0; } .linha-adicionada { background-color: #e8f5e9; color: #2e7d32; margin: 1px 0; } .linha-removida del, .linha-adicionada ins { text-decoration: none; padding: 0 2px; border-radius: 2px; } .linha-removida del { background-color: #ffcdd2; } .linha-adicionada ins { background-color: #c8e6c9; }</style>"""
730
- visualizacao_detalhada_html = f"{css}<div class='diff-container'>{''.join(resultado_diff_html)}</div>"
731
- return visualizacao_detalhada_html, resumo_estatistico, criticas_texto
732
 
733
  def calcular_progresso():
734
- if not HISTORICO: return "📊 Nenhum progresso registrado ainda."
735
- notas = []
736
- tipos_corrigidos = set()
737
  for entry in HISTORICO:
738
- tipos_corrigidos.add(entry["tipo_peticao"])
739
- match = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.IGNORECASE)
740
- if match:
741
- try: notas.append(float(match.group(1).replace(",", ".")))
742
- except ValueError: pass
743
- total_corridas = len(HISTORICO)
744
- media = sum(notas) / len(notas) if notas else 0
745
- tendencia = ""
746
- if len(notas) > 1:
747
- if notas[-1] > notas[-2]: tendencia = "⬆️ Melhorando"
748
- elif notas[-1] < notas[-2]: tendencia = "⬇️ Piorando"
749
- else: tendencia = "↔️ Estável"
750
- progresso_texto = f"**📊 Resumo:**\n* Correções: {total_corridas}\n* Tipos: {', '.join(sorted(list(tipos_corrigidos)))}\n* Média: {media:.1f}/10 ({len(notas)} válidas)\n* Tendência: {tendencia if tendencia else 'N/A'}"
751
- return progresso_texto
752
 
753
  def mostrar_historico():
754
  if not HISTORICO: return "📜 Histórico vazio."
755
- historico_md = "### 📜 Histórico (Últimas 10)\n---\n"
756
  for i, entry in enumerate(reversed(HISTORICO)):
757
- caso_resumo = (entry['caso'][:80] + '...') if len(entry['caso']) > 80 else entry['caso']
758
- nota_str = "N/A"
759
- match = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.IGNORECASE)
760
- if match: nota_str = f"{match.group(1).replace(',', '.')}/10"
761
- historico_md += f"**{len(HISTORICO)-i}.** {entry['data']} | **Tipo:** {entry['tipo_peticao']} | **Nota:** {nota_str}\n **Caso:** _{caso_resumo}_\n---\n"
762
- return historico_md
763
 
764
  def salvar_progresso(caso, peticao, tipo_peticao):
765
- logger.info("Iniciando salvamento de progresso...")
766
  try:
767
- if not caso or not peticao or not tipo_peticao:
768
- logger.warning("Inputs insuficientes para salvar progresso.")
769
- return "", "⚠️ Caso, Petição e Tipo são necessários para salvar.", None
770
- dados_progresso = {"tipo_peticao": tipo_peticao, "caso": caso, "peticao": peticao, "timestamp": datetime.now().isoformat()}
771
- json_content = json.dumps(dados_progresso, ensure_ascii=False, indent=4)
772
- timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
773
- filename = f"progresso_peticao_{timestamp_str}.json"
774
- temp_dir = tempfile.gettempdir()
775
- temp_file_path = os.path.join(temp_dir, filename)
776
- with open(temp_file_path, 'w', encoding='utf-8') as f: f.write(json_content)
777
- logger.info(f"Progresso salvo com sucesso em: {temp_file_path}")
778
- success_message = f"✅ Progresso salvo! Baixe `{filename}`."
779
- return json_content, success_message, temp_file_path
780
- except Exception as e:
781
- logger.error(f"Erro ao salvar progresso: {str(e)}", exc_info=True)
782
- return "", f"❌ Erro ao salvar: {str(e)}", None
783
 
784
  def carregar_progresso(arquivo_entrada):
785
- logger.info("Iniciando carregamento de progresso...")
786
- if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo selecionado."
787
  try:
788
- file_path = arquivo_entrada.name
789
- logger.info(f"Carregando progresso do arquivo: {file_path}")
790
- with open(file_path, 'r', encoding='utf-8') as f: dados = json.load(f)
791
- caso = dados.get("caso")
792
- peticao = dados.get("peticao")
793
- tipo_peticao = dados.get("tipo_peticao")
794
- if caso is None or peticao is None or tipo_peticao is None:
795
- logger.warning(f"Arquivo JSON carregado não contém chaves esperadas: {dados.keys()}")
796
- return "", "", "", "❌ Erro: Arquivo de progresso inválido."
797
- if tipo_peticao not in TIPOS_DE_PETICAO:
798
- logger.warning(f"Tipo de petição '{tipo_peticao}' do arquivo não é válido.")
799
- return caso, peticao, "", f"⚠️ Tipo '{tipo_peticao}' inválido. Selecione manualmente."
800
- logger.info("Progresso carregado com sucesso.")
801
- return tipo_peticao, caso, peticao, "✅ Progresso carregado!"
802
- except json.JSONDecodeError:
803
- logger.error(f"Erro ao decodificar JSON do arquivo: {file_path}")
804
- return "", "", "", "❌ Erro: Arquivo não é JSON válido."
805
- except Exception as e:
806
- logger.error(f"Erro ao carregar o progresso: {str(e)}", exc_info=True)
807
- return "", "", "", f"❌ Erro inesperado ao carregar: {str(e)}"
808
 
809
  def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
810
- prompt = f"""
811
- Crie um caso fictício **detalhado e realista** para um exercício prático de elaboração de uma peça do tipo **"{tipo_peticao}"**, simulando um cenário de Exame de Ordem (OAB).
812
- **Instruções Gerais:**
813
- 1. Complexidade Adequada (15-25 linhas).
814
- 2. Dados Essenciais: Nomes, locais, datas, valores, relação jurídica, conflito.
815
- 3. Coerência: Fatos lógicos justificando a peça.
816
- 4. Foco no Tipo de Peça: Cenário deve exigir a **{tipo_peticao}**.
817
- **Instruções Específicas para "{tipo_peticao}":**
818
- """
819
- if tipo_peticao == "Petição Inicial":
820
- prompt += """* Descreva violação de direitos (contrato descumprido, dano, etc.) onde cliente busca tutela inicial. * Inclua elementos para danos materiais/morais. * Forneça dados para qualificação das partes. * **Exemplo Tema:** Consumo, inadimplemento, cobrança indevida, despejo."""
821
- elif tipo_peticao == "Contestação":
822
- prompt += """* **PRIMEIRO:** Resumo da Petição Inicial recebida pelo réu (autor, alegações, pedidos). * **DEPOIS:** Versão dos fatos do RÉU, incluindo informações para: Alegar preliminares; Impugnar fatos; Apresentar defesa de mérito (pagamento, prescrição, etc.). * Forneça dados qualificação réu."""
823
- elif tipo_peticao == "Reconvenção":
824
- prompt += """* Siga estrutura **Contestação**: Resumo Petição Inicial + Versão Réu. * **ADICIONE fatos/fundamentos** para RÉU fazer pedido PRÓPRIO contra AUTOR ORIGINAL (CONEXO com ação/defesa). * **Exemplo:** Cobrança -> Réu contesta e reconvém pedindo indenização por danos no mesmo negócio."""
825
- elif tipo_peticao == "Impugnação à Contestação":
826
- prompt += """* Resumo Petição Inicial do Autor. * **Resumo Contestação** do réu, destacando: Preliminares arguidas; Fatos novos (impeditivos/modificativos/extintivos); Documentos juntados. * Objetivo: fornecer elementos para Autor rebater contestação."""
827
- elif tipo_peticao == "Embargos de Declaração":
828
- prompt += """* Breve descrição caso original. * **Apresente DISPOSITIVO ou TRECHO FUNDAMENTAL de DECISÃO JUDICIAL** (sentença/acórdão). * **INDIQUE CLARAMENTE vício** (omissão, contradição, obscuridade, erro material) NESSA DECISÃO, dando contexto. * **Exemplo:** "Sentença julgou X, mas omissa quanto a Y." OU "Acórdão afirmou X e concluiu Y (contradição)." """
829
- elif tipo_peticao == "Recurso de Apelação":
830
- prompt += """* Descrição caso original. * **Resumo da SENTENÇA** (procedente/improcedente/parcial, fundamentos juiz). * **FORNEÇA ELEMENTOS indicando ERRO na sentença** (in judicando/in procedendo) que justifiquem reforma/anulação. * **Exemplo:** "Sentença ignorou prova X." OU "Juiz indeferiu testemunha chave Y (cerceamento)." * Indique cliente (apelante)."""
831
- elif tipo_peticao == "Agravo de Instrumento":
832
- prompt += """* Descrição caso em andamento. * **Apresente DECISÃO INTERLOCUTÓRIA específica**. * Decisão DEVE se enquadrar no Art. 1015 ou justificar mitigação. * **FORNEÇA ELEMENTOS indicando ERRO** na decisão E, se possível, **URGÊNCIA**. * **Exemplo:** "Juiz indeferiu JG apesar de prova." OU "Deferiu liminar sem comprovação mora." * Indique cliente (agravante)."""
833
- elif tipo_peticao == "Petição de Tutela Provisória":
834
- prompt += """* Situação de conflito com **urgência** (risco iminente) ou direito **evidente**. * **FORNEÇA ELEMENTOS claros demonstrando:** **Urgência:** Probabilidade do direito (fumus) E Perigo de dano (periculum). **Evidência:** Hipótese do Art. 311 (abuso defesa, repetitivo, etc.). * Indique medida específica desejada. * Especifique se antecedente ou incidental."""
835
- else:
836
- prompt += "* Crie cenário jurídico cível com conflito claro."
837
- prompt += """
838
- **Finalize o caso com:**
839
- "**Situação-Problema:** Na qualidade de advogado(a) de [Nome do Cliente], elabore a peça processual cabível ({tipo_peticao})."
840
- **Importante:** Evite casos muito específicos. Foque em temas comuns. Seja criativo!
841
- """
842
  return prompt
843
 
844
  def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
845
- logger.info(f"Solicitada geração de caso para: {tipo_peticao}")
846
  try:
847
- if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO:
848
- logger.warning(f"Tentativa de gerar caso com tipo inválido: {tipo_peticao}")
849
- return "⚠️ Erro: Selecione um tipo de petição válido antes de gerar o caso."
850
- logger.info("Estruturando prompt para geração de caso...")
851
  prompt = estruturar_prompt_geracao_caso(tipo_peticao)
852
- logger.info("Chamando API para gerar caso...")
853
  caso_gerado = chamar_api(prompt)
854
- if caso_gerado.startswith(("❌","⏱️","⚠️")):
855
- logger.error(f"Erro da API ao gerar caso: {caso_gerado}")
856
- return f"⚠️ Falha ao gerar caso fictício:\n{caso_gerado}"
857
- frase_final_padrao = f"**Situação-Problema:** Na qualidade de advogado(a) de"
858
- if frase_final_padrao.lower() not in caso_gerado.lower():
859
- logger.warning("Frase 'Situação-Problema' não encontrada. Adicionando manualmente.")
860
- nomes_possiveis = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado)
861
- cliente_nome = nomes_possiveis[0] if nomes_possiveis else "[Cliente]"
862
- 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})."
863
- logger.info("Caso fictício gerado com sucesso.")
864
  return caso_gerado
865
- except Exception as e:
866
- logger.exception("Erro inesperado dentro de gerar_caso_ficticio:")
867
- return f"❌ Erro inesperado ao gerar o caso: {str(e)}"
 
 
 
 
 
 
868
 
869
  def interface_gradio():
870
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
871
- gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️")
872
- gr.Markdown("Analise e aprimore suas peças processuais!")
873
  with gr.Row():
874
  with gr.Column(scale=1):
875
  gr.Markdown("### ⚙️ Configurações")
876
- tipo_peticao_input = gr.Dropdown(label="1. Selecione o Tipo de Peça", choices=TIPOS_DE_PETICAO, value="Petição Inicial")
877
  modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento", value=False)
878
- with gr.Accordion("🛠️ Opções Avançadas da IA", open=False):
879
- temperatura_slider = gr.Slider(minimum=0.0, maximum=1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade")
880
- timeout_slider = gr.Slider(minimum=30, maximum=180, value=CONFIG["timeout"], step=10, label="Timeout API (seg)")
881
- max_tokens_slider = gr.Slider(minimum=2048, maximum=8192, value=CONFIG["max_tokens"], step=256, label="Max Resposta (Tokens)")
882
  config_update_btn = gr.Button("💾 Salvar Configs")
883
  config_status = gr.Textbox(label="Status", interactive=False, lines=1)
884
  with gr.Accordion("📊 Dashboard", open=False):
885
- progresso_output = gr.Markdown("*Atualize para ver estatísticas.*")
886
  progresso_btn = gr.Button("🔄 Atualizar Progresso")
887
  with gr.Accordion("📜 Histórico", open=False):
888
- historico_output = gr.Markdown("*Atualize para ver histórico.*")
889
  historico_btn = gr.Button("🔄 Ver Histórico")
890
  with gr.Accordion("💾📂 Salvar/Carregar", open=False):
891
- salvar_btn = gr.Button("💾 Salvar Caso/Peça")
892
- carregar_btn = gr.Button("📂 Carregar Caso/Peça")
893
- arquivo_progresso_upload = gr.File(label="Selecione .json", file_types=[".json"], scale=2)
894
  progresso_download_file = gr.File(label="Baixar Progresso", interactive=False)
895
  with gr.Column(scale=3):
896
- gr.Markdown("### 1. Forneça o Caso / Contexto")
897
  with gr.Row():
898
- caso_input = gr.Textbox(label="📋 Caso ou Situação", lines=8, placeholder="Insira o texto COMPLETO do caso...")
899
- caso_btn = gr.Button("🎲 Gerar Caso", variant="secondary", scale=0)
900
- gr.Markdown("### 2. Insira Sua Peça")
901
  with gr.Tabs():
902
- with gr.TabItem("✏️ Digitar Texto"):
903
- peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=12, placeholder="Cole ou digite sua peça aqui...")
904
- dica_contextual_output = gr.Markdown(value="*Dicas aparecerão aqui...*")
905
- btn_corrigir = gr.Button("🚀 CORRIGIR E ANALISAR!", variant="primary")
906
- with gr.TabItem("📎 Anexar Arquivo"):
907
- peticao_arquivo_input = gr.File(label="Arquivo (.pdf, .docx, .txt - Máx 15MB)", file_types=[".pdf", ".docx", ".txt"])
908
- arquivo_preview_output = gr.Textbox(label="🔍 Prévia do Arquivo", lines=4, interactive=False)
909
- btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR E ANALISAR ARQUIVO!", variant="primary")
910
 
911
  processing_message = gr.Markdown("", visible=False)
912
-
913
- gr.Markdown("--- \n ### 3. Resultados da Correção")
914
  with gr.Tabs():
915
- with gr.TabItem("📊 Análise e Correção Sugerida"):
916
- resultado_analise_output = gr.Markdown(label="Análise Detalhada e Nota")
917
  resultado_peticao_corrigida_output = gr.Textbox(label="✨ Versão Sugerida", lines=18, show_copy_button=True)
918
- with gr.TabItem("⚖️ Comparativo (Diff)"):
919
- gr.Markdown("### ↔️ Comparativo: Sua Peça vs. Versão Sugerida")
920
  with gr.Row():
921
- with gr.Column(scale=1):
922
- resumo_alteracoes_output = gr.Markdown(label="Resumo")
923
- criticas_output = gr.Markdown(label="Mudanças Críticas")
924
- with gr.Column(scale=3):
925
- visualizacao_detalhada_output = gr.HTML(label="Alterações Detalhadas")
926
-
927
- clear_btn = gr.Button("🗑️ Limpar Campos e Resultados")
928
 
929
- caso_btn.click(
930
- fn=lambda: gr.update(value="⏳ Gerando caso...", visible=True), outputs=processing_message
931
- ).then(
932
  fn=gerar_caso_ficticio, inputs=[tipo_peticao_input], outputs=[caso_input]
933
- ).then(
934
- fn=lambda: gr.update(value="", visible=False), outputs=processing_message
935
- )
936
 
937
- peticao_texto_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output)
938
- tipo_peticao_input.change(fn=fornecer_dica_contextual, inputs=[peticao_texto_input, tipo_peticao_input], outputs=dica_contextual_output)
939
- peticao_arquivo_input.change(fn=process_file, inputs=[peticao_arquivo_input], outputs=[arquivo_preview_output])
940
 
941
- btn_corrigir.click(
942
- fn=lambda: gr.update(value="⏳ Processando texto... Gerando análise e correção...", visible=True),
943
- outputs=processing_message
944
- ).then(
945
  fn=corrigir_peticao,
946
  inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento],
947
- outputs=[resultado_analise_output, resultado_peticao_corrigida_output,
948
- resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
949
  concurrency_limit=1
950
- ).then(
951
- fn=lambda: gr.update(value="", visible=False),
952
- outputs=processing_message
953
- )
954
 
955
- btn_corrigir_arquivo.click(
956
- fn=lambda: gr.update(value="⏳ Processando arquivo... Gerando análise e correção...", visible=True),
957
- outputs=processing_message
958
- ).then(
959
  fn=corrigir_peticao,
960
  inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento],
961
- outputs=[resultado_analise_output, resultado_peticao_corrigida_output,
962
- resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
963
  concurrency_limit=1
964
- ).then(
965
- fn=lambda: gr.update(value="", visible=False),
966
- outputs=processing_message
967
- )
968
 
969
- config_update_btn.click(atualizar_config, inputs=[temperatura_slider, timeout_slider, max_tokens_slider], outputs=config_status)
970
- progresso_btn.click(fn=calcular_progresso, outputs=progresso_output)
971
- historico_btn.click(fn=mostrar_historico, outputs=historico_output)
972
 
973
- salvar_btn.click(
974
- fn=lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message
975
- ).then(
976
  fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
977
  outputs=[gr.Textbox(visible=False), config_status, progresso_download_file]
978
- ).then(
979
- fn=lambda x: gr.update(visible=bool(x)), inputs=[progresso_download_file], outputs=[progresso_download_file]
980
- ).then(
981
- lambda: gr.update(value="", visible=False), None, processing_message
982
- )
983
 
984
- carregar_btn.click(
985
- fn=lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message
986
- ).then(
987
  fn=carregar_progresso, inputs=[arquivo_progresso_upload],
988
  outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status]
989
- ).then(
990
- lambda: gr.update(value="", visible=False), None, processing_message
991
- )
992
 
993
  clear_btn.click(
994
- fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas aparecerão aqui...*", "", "", "", "", "", None, None, ""],
995
- outputs=[
996
- tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input,
997
- peticao_arquivo_input, arquivo_preview_output, dica_contextual_output,
998
- resultado_analise_output, resultado_peticao_corrigida_output,
999
- resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output,
1000
- arquivo_progresso_upload, progresso_download_file, config_status
1001
- ]
1002
  )
1003
-
1004
  return demo
1005
 
1006
  if __name__ == "__main__":
 
1
+ # -*- coding: utf-8 -*-
2
  import gradio as gr
3
  import requests
4
  import os
 
95
  ocr_text_parts.append(f"[Erro OCR Página {i+1}: {str(ocr_e)}]")
96
  pdf_reader.close()
97
  ocr_text = "\n".join(ocr_text_parts)
 
98
  if text.strip() and len(text) > len(ocr_text) * 0.7:
99
  logger.info(f"Usando texto extraído do PDF ({len(text)} chars).")
100
  return text
 
133
  content = file_path_or_obj.read()
134
  else:
135
  return "❌ Tipo de entrada TXT inválido."
 
136
  encodings = ['utf-8', 'latin-1', 'cp1252']
137
  for encoding in encodings:
138
  try:
 
166
  return "⚠️ Arquivo muito grande (Máx 15MB)."
167
  except OSError as size_e:
168
  logger.warning(f"Não foi possível verificar o tamanho do arquivo: {size_e}")
169
+ if filename.endswith('.pdf'): return extract_text_from_pdf(file_path)
170
+ elif filename.endswith('.docx'): return extract_text_from_docx(file_path)
171
+ elif filename.endswith('.txt'): return extract_text_from_txt(file_path)
 
 
 
 
172
  else:
173
  logger.warning(f"Formato de arquivo não suportado: {filename}")
174
  return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
 
179
  def get_instrucoes_gerais_avaliacao(modo_treinamento=False):
180
  base = """
181
  **Estrutura da Avaliação:**
182
+ 1. **Análise Geral:** Visão geral concisa (2-3 linhas).
183
+ 2. **Nota Final:** 0 a 10 (uma casa decimal), com justificativa BREVE.
184
+ 3. **Pontos Positivos:** Liste 2-3 aspectos bem executados, com justificativa.
185
+ 4. **Pontos a Melhorar:** Liste **TODOS** os problemas em ordem de gravidade. Para cada: Descrição, Fundamentação legal, Impacto na nota, Sugestão.
186
+ 5. **Análise Específica dos Requisitos:** Avalie os itens chave da peça.
187
+ **Critérios Fundamentais:** Coerência, Fundamentação, Linguagem, Pedidos. **NÃO INVENTAR FATOS** (-3 a -5). **NÃO SE IDENTIFICAR** (Nota ZERO)."""
 
 
 
 
 
 
 
 
 
 
 
 
188
  if modo_treinamento:
189
  base += """
190
+ **Modo de Treinamento:** Para cada Ponto a Melhorar: a) Explicação aprofundada. b) Base legal. c) Exemplo Redação Corrigida. d) Dica prática. e) Estratégia alternativa. Destaque inconsistências. Sugira estratégias adicionais."""
 
 
 
 
 
 
 
 
 
191
  return base
192
 
193
  def estruturar_prompt_analise(caso, peticao, tipo_peticao, modo_treinamento=False):
194
  instrucoes_gerais = get_instrucoes_gerais_avaliacao(modo_treinamento)
195
+ prompt_especifico = f"## Análise: {tipo_peticao}\n**Caso:**\n```\n{caso}\n```\n**Peça Aluno:**\n```\n{peticao}\n```\n{instrucoes_gerais}\n**Requisitos Específicos ({tipo_peticao}):**\n"
 
 
 
 
 
 
 
 
 
 
 
 
196
  if tipo_peticao == "Petição Inicial":
197
+ prompt_especifico += "- Endereçamento/competência (Art. 319, I; CDC 101, I).\n- Qualificação (Art. 319, II).\n- Fatos.\n- Fundamentação (Art. 319, III).\n- Pedidos (Art. 319, IV).\n- Valor causa (Art. 291-292, 319, V).\n- Provas (Art. 319, VI).\n- Audiência (Art. 319, VII).\n- Citação.\n- Docs essenciais (procuração, custas/JG).\n- Fechamento.\n**Critérios Adicionais:** Usar 'XXX'. Falta custas/JG (-1.0). Vírgula em 'pede deferimento' (-0.2). Foro consumidor OK."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  elif tipo_peticao == "Contestação":
199
+ prompt_especifico += "- Endereçamento. Qualificação réu. Processo nº. Tempestividade.\n- **Preliminares (Art. 337):** TODAS aplicáveis.\n- **Impugnação Específica Fatos (Art. 341):** PONTO A PONTO.\n- **Mérito:** Versão réu; defesa direta/indireta (fatos impeditivos/modificativos/extintivos).\n- Fundamentação defesa.\n- **Pedidos:** Acolher preliminares; IMPROCEDÊNCIA TOTAL; Condenação sucumbência (Art. 85).\n- Provas. Fechamento.\n**Critérios Adicionais:** Preliminar não arguida preclui? Falha grave impugnação (-2 a -4). Ausência pedido improcedência (-1.5)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  elif tipo_peticao == "Reconvenção":
201
+ prompt_especifico += "- NA MESMA PEÇA da Contestação (Art. 343). Endereçamento. Processo nº.\n- Qualificação réu-reconvinte e autor-reconvindo.\n- **Conexão** (Art. 343).\n- **Estrutura PI:** Fatos, Fundamentos, Pedidos específicos, Valor causa, Provas (da reconvenção).\n- Pedido intimação autor-reconvindo (Art. 343, §1º).\n**Critérios Adicionais:** Sem conexão (-2.0). Falta pedido/valor/provas reconv. (-1.0/item). Peça separada (-3.0)."
 
 
 
 
 
 
 
 
 
 
 
202
  elif tipo_peticao == "Impugnação à Contestação":
203
+ prompt_especifico += "- Endereçamento. Processo nº. Qualificação autor. Tempestividade (15d - Art. 350/351).\n- **Resposta Preliminares.**\n- **Resposta Fatos Novos** (Art. 350).\n- **Resposta Documentos** (Art. 437).\n- **Ratificação Inicial.**\n- **Especificação Provas** (contra defesa indireta/preliminares).\n- Pedidos: Rejeitar preliminares; refutar defesa; proceder inicial.\n**Critérios Adicionais:** Não responder preliminar (-1.5). Não manifestar fato novo (-2.0). Não ratificar (-0.5)."
 
 
 
 
 
 
 
 
 
 
204
  elif tipo_peticao == "Embargos de Declaração":
205
+ prompt_especifico += "- **Contra Decisão Específica.** Endereçamento ao juízo prolator. Processo nº. Partes. Tempestividade (5d - Art. 1023).\n- **Cabimento (Art. 1022):** Apontar PRECISAMENTE: Omissão, Contradição, Obscuridade ou Erro Material.\n- **NÃO REJULGAR CAUSA.**\n- **Pedido:** Conhecimento e ACOLHIMENTO para sanar vício.\n**Critérios Adicionais:** Rediscutir mérito (-4.0). Não apontar vício (-3.0). Endereçar tribunal (-1.0)."
 
 
 
 
 
 
 
206
  elif tipo_peticao == "Recurso de Apelação":
207
+ prompt_especifico += "- **Interposição:** Ao juízo a quo. Processo nº. Partes. Pressupostos (cabimento Art. 1009; tempestividade 15d; preparo/JG). Pedido recebimento/remessa.\n- **Razões:** Ao TRIBUNAL. Relato. **Fundamentos Reforma/Anulação (Art. 1010):** Error in procedendo/judicando. **Dialeticidade.**\n- **Pedidos:** Conhecimento; PROVIMENTO (anular/reformar); Inversão sucumbência.\n**Critérios Adicionais:** Falta peça (-10). Endereço razões (-1.0). Sem dialeticidade (-3 a -5). Sem pedido reforma/anulação (-1.5). Falta preparo/JG (-2.0)."
 
 
 
 
 
 
208
  elif tipo_peticao == "Agravo de Instrumento":
209
+ prompt_especifico += "- Peça única ao TRIBUNAL. Processo origem. Partes e Advogados (endereços Art. 1016, IV). Cópia decisão agravada (Art. 1017, I).\n- **Cabimento (Art. 1015 / mitigada).** Tempestividade (15d). Preparo/JG.\n- **Peças Obrigatórias (Art. 1017, I):** Decisão, Intimação, Procurações. Facultativas (III).\n- **Razões (Art. 1016):** Motivos reforma/invalidação.\n- **Efeito Suspensivo / Tutela Antecipada Recursal (Art. 1019, I)** (se risco).\n- **Pedidos Finais:** Conhecimento; Efeito/Tutela (se aplicável); PROVIMENTO; Comunicação juiz.\n**Critérios Adicionais:** Falta peça obrigatória (-4.0). Sem cabimento (-3.0). Endereço juiz (-1.0). Sem advogados (-0.5)."
 
 
 
 
 
 
 
 
 
210
  elif tipo_peticao == "Petição de Tutela Provisória":
211
+ prompt_especifico += "- Tipo (Urgência/Evidência). Caráter (Antecedente/Incidental). Endereçamento. Qualificação. Processo (se incidental).\n- **Tutela Urgência (Art. 300):** **Fumus Boni Iuris** E **Periculum in Mora**. **Reversibilidade** (Art. 300, §3º).\n- **Tutela Evidência (Art. 311):** Hipótese I a IV. **NÃO precisa periculum.**\n- **Pedido Específico:** Medida liminar CLARA.\n- **Se Antecedente (Art. 303/305):** Indicar pedido final ou aditar/apresentar inicial. Valor Causa.\n- **Pedidos:** Concessão tutela; citação/intimação; indicação ação/aditamento.\n**Critérios Adicionais:** Sem Fumus/Periculum (-3 a -5). Erro hipótese Evidência (-3.0). Pedido genérico (-2.0)."
212
+ else: prompt_especifico += "- Adequação ao tipo? Clareza? Fundamentação Mínima? Pedido?"
213
+ prompt_especifico += "\n\n**Instruções Finais:** Analise estritamente com base no caso e critérios. Seja rigoroso mas construtivo. Gere **exclusivamente** no formato solicitado."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  return prompt_especifico
215
 
216
  def estruturar_prompt_peticao_corrigida(caso, peticao_original, tipo_peticao):
217
+ prompt = f"**Caso:**\n```\n{caso}\n```\n**Peça Original ({tipo_peticao}):**\n```\n{peticao_original}\n```\n**Tarefa:** Gere versão corrigida da peça ({tipo_peticao}), seguindo rigorosamente práticas OAB.\n**Instruções Específicas ({tipo_peticao}):**\n"
 
 
 
 
 
 
 
 
 
 
 
218
  if tipo_peticao == "Petição Inicial":
219
+ prompt += "1.Endereçamento correto/fundamentado. 2.Qualificação completa (Art.319,II,'XXX'). 3.Fatos cronológicos/fiéis. 4.Fundamentação (artigos, súmulas). 5.Pedidos certos/determinados (quantificar). 6.Valor causa (Art.292). 7.Provas ('protesta provar...'). 8.Audiência (Art.319,VII). 9.Citação (Art.246,I). 10.Custas/JG (NÃO OMITIR). 11.Fechamento ('Nestes termos pede deferimento.', Local, data., ADVOGADO(A), OAB/...). NUNCA identificar."
 
 
 
 
 
 
 
 
 
 
 
 
220
  elif tipo_peticao == "Contestação":
221
+ prompt += "1.Endereçamento juízo ação. 2.Identificação (proc. nº, réu). 3.Tempestividade. 4.Estrutura: Síntese Inicial; Preliminares (Art.337, fund.); Mérito (Impug. Específica + Defesa fund.); Reconvenção (se houver, antes pedidos cont.). 5.Pedidos Cont.: Acolher prelim.; IMPROCEDÊNCIA TOTAL; Sucumbência. 6.Provas réu. 7.Fechamento OAB."
 
 
 
 
 
 
 
 
222
  elif tipo_peticao == "Reconvenção":
223
+ prompt += "**IMPORTANTE:** Gere como parte da Contestação.\n1.Após defesa mérito cont., ANTES pedidos cont.: Tópico 'DA RECONVENÇÃO'. 2.Qualificação Reconvinte/Reconvindo. 3.Conexão. 4.Fatos e Direito Reconvencionais. 5.Pedidos Reconv.: Claros contra autor-reconvindo. 6.Valor Causa e Provas Reconv. 7.Intimação autor-reconvindo. 8.Continue com Pedidos Contestação. 9.Fechamento OAB."
 
 
 
 
 
 
 
 
 
 
 
224
  elif tipo_peticao == "Impugnação à Contestação":
225
+ prompt += "1.Endereçamento, proc, autor, tempestividade. 2.Tópico: DA(S) PRELIMINAR(ES) - Rebater. 3.Tópico: DO MÉRITO E FATOS NOVOS - Manifestar. 4.Tópico: DA RATIFICAÇÃO INICIAL. 5.Tópico: DAS PROVAS - Especificar. 6.Pedidos: Rejeitar prelim., refutar defesa, proceder inicial. 7.Fechamento OAB."
 
 
 
 
 
 
 
 
226
  elif tipo_peticao == "Embargos de Declaração":
227
+ prompt += "1.Endereçamento juízo prolator. Proc, Partes. Decisão embargada ID. Tempestividade (5d). 2.Tópico para cada vício (Art.1022): DA OMISSÃO/CONTRADIÇÃO/OBSCURIDADE/ERRO MATERIAL. 3.Dentro: Transcrever trecho + explicar vício OBJETIVAMENTE. NÃO REJULGAR. 4.Pedido: Conhecimento e ACOLHIMENTO p/ sanar vício(s). 5.Fechamento OAB."
 
 
 
 
 
 
228
  elif tipo_peticao == "Recurso de Apelação":
229
+ prompt += "1.Interposição: Juízo a quo. Proc, Partes. Pressupostos (cabim., temp., prep./JG). Pedido receb./remessa. 2.Razões: Tribunal. Relato. FUNDAMENTOS REFORMA/ANULAÇÃO (Error in proc./judic.). DIALETICIDADE. 3.Pedidos: Conhec.; PROVIMENTO (anular/reformar); Inversão sucumb. 4.Fechamento OAB."
 
 
 
 
 
230
  elif tipo_peticao == "Agravo de Instrumento":
231
+ prompt += "1.Peça única Tribunal. Proc. origem, Partes, Advogados (endereços). Decisão agravada ID (juntar cópia). 2.CABIMENTO (Art.1015/mitig.). TEMPESTIVIDADE. PREPARO/JG. PEÇAS OBRIG./FACULT. 3.RAZÕES RECURSAIS: Motivos reforma/invalidação. 4.EFEITO SUSPENSIVO/TUTELA ANTECIPADA (se cabível). 5.PEDIDOS: Conhec.; Efeito/Tutela; PROVIMENTO; Comunicação juiz. 6.Fechamento OAB."
 
 
 
 
 
 
 
232
  elif tipo_peticao == "Petição de Tutela Provisória":
233
+ prompt += "1.Indicar TIPO(Urg/Evid) e CARÁTER(Antec/Incid). Endereç. Qualif. Proc(se incid). 2.REQUISITOS: Urgência=Fumus+Periculum(+Reversib.). Evidência=Hipótese Art.311. 3.PEDIDO LIMINAR: Medida CLARA. 4.Se Antec.: Indicar pedido final/aditar. Valor Causa. 5.PEDIDOS: Concessão tutela; citação/intim.; indic. ação/adit. 6.Fechamento OAB."
234
+ else: prompt += "1.Estrutura Lógica. 2.Argumentos Claros. 3.Pedido Claro. 4.Formalidades OAB."
235
+ 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/...'. Sem comentários extras."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  return prompt
237
 
238
  def pos_corrigir_peticao(peticao_corrigida, tipo_peticao):
 
242
  peticao_lower = peticao_corrigida.lower()
243
  if "juntada da guia de custas" not in peticao_lower and "gratuidade de justiça" not in peticao_lower:
244
  partes = peticao_corrigida.split("Nestes termos pede deferimento")
245
+ 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]
246
+ else: peticao_corrigida = peticao_corrigida.replace("Local, data.", "Requer, ainda, a juntada da guia de custas devidamente recolhida.\n\nLocal, data.")
 
 
247
  if "audiência de conciliação" not in peticao_lower and "audiência de mediação" not in peticao_lower:
248
  partes = peticao_corrigida.split("Nestes termos pede deferimento")
249
+ 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]
250
+ 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.")
 
 
 
251
  linhas = [l.strip() for l in peticao_corrigida.strip().split('\n') if l.strip()]
252
  fechamento_padrao = ["Local, data.", "ADVOGADO(A)", "OAB/..."]
253
+ while linhas and any(term in linhas[-1] for term in ["ADVOGADO", "OAB", "Local", "data"]): linhas.pop()
 
254
  linhas.extend(fechamento_padrao)
255
+ return "\n".join(linhas)
 
256
 
257
  def chamar_api(prompt):
258
  payload = {"contents": [{"parts": [{"text": prompt}]}],"generationConfig": {"temperature": CONFIG["temperatura"],"maxOutputTokens": CONFIG["max_tokens"],"topP": 0.95,}}
 
264
  logger.debug(f"API Call - Tentativa {tentativa + 1}/{CONFIG['max_retry_attempts']}")
265
  response = requests.post(GEMINI_API_URL, headers=headers, data=json.dumps(payload), timeout=CONFIG["timeout"])
266
  logger.debug(f"API Response - Status: {response.status_code}")
 
267
  if response.status_code == 429:
268
  retry_after = int(response.headers.get("Retry-After", CONFIG["retry_delay"] * (tentativa + 1)))
269
+ logger.warning(f"Erro 429. Aguardando {retry_after}s...")
270
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(retry_after); continue
271
+ else: return "❌ Erro 429: Muitas requisições."
 
272
  if response.status_code != 200:
273
+ error_msg = f"Erro {response.status_code}"
274
+ try: error_msg = response.json().get("error", {}).get("message", error_msg)
275
+ except json.JSONDecodeError: error_msg = f"Erro {response.status_code}: {response.text[:200]}"
276
+ logger.error(f"API Error: {error_msg}")
 
 
 
277
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
278
+ return f"❌ Erro API ({response.status_code}): {error_msg}"
 
279
  response_data = response.json()
 
280
  if not response_data.get("candidates"):
281
  prompt_feedback = response_data.get("promptFeedback", {})
282
  block_reason = prompt_feedback.get("blockReason")
283
  logger.error(f"API Response sem 'candidates'. Block Reason: {block_reason}")
284
+ if block_reason: return f"❌ Bloqueado pela API: {block_reason}."
285
+ else: return "❌ Resposta API vazia (sem 'candidates')."
 
286
  candidate = response_data["candidates"][0]
287
  finish_reason = candidate.get("finishReason")
 
288
  if finish_reason and finish_reason not in ["STOP", "MAX_TOKENS", "FINISH_REASON_UNSPECIFIED"]:
289
+ logger.error(f"Geração API interrompida: {finish_reason}")
290
  safety_ratings = candidate.get("safetyRatings", [])
291
  harm_categories = [r['category'] for r in safety_ratings if r['probability'] not in ['NEGLIGIBLE', 'LOW']]
292
+ if harm_categories: return f"❌ Bloqueado por segurança: {finish_reason} ({','.join(harm_categories)})."
293
+ else: return f"❌ Geração interrompida: {finish_reason}."
 
294
  if not candidate.get("content") or not candidate["content"].get("parts"):
295
+ logger.error("Estrutura resposta API inesperada.")
296
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
297
+ return "❌ Resposta API inválida (estrutura)."
 
298
  texto_gerado = candidate["content"]["parts"][0]["text"].strip()
299
+ logger.info(f"API retornou com sucesso ({len(texto_gerado)} chars). Finish: {finish_reason}")
300
  return texto_gerado
 
301
  except requests.Timeout:
302
+ logger.warning(f"Timeout API - Tentativa {tentativa + 1}.")
303
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
304
+ return "⏱️ Tempo esgotado API."
305
  except requests.RequestException as e:
306
+ logger.error(f"Erro Rede/Requisição API - Tentativa {tentativa + 1}: {e}")
307
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
308
+ return f"❌ Erro conexão API: {e}"
309
  except json.JSONDecodeError as e:
310
+ logger.error(f"Erro JSON API: {e}")
311
  logger.debug(f"Resposta não-JSON: {response.text[:500]}")
312
+ return f"❌ Erro processar resposta API (JSON inválido)."
313
  except Exception as e:
314
+ logger.exception(f"Erro inesperado API - Tentativa {tentativa + 1}: {str(e)}")
315
  if tentativa < CONFIG["max_retry_attempts"] - 1: time.sleep(CONFIG['retry_delay'] * (tentativa + 1)); continue
316
+ return f"❌ Erro inesperado API: {str(e)}"
317
+ logger.error("Falha API após todas as tentativas.")
318
+ return "Falha API após múltiplas tentativas."
 
319
 
320
  def corrigir_peticao(caso, tipo_peticao, peticao_texto=None, peticao_arquivo=None, modo_treinamento=False):
321
  logger.info(f"Iniciando correção. Tipo={tipo_peticao}, Treinamento={modo_treinamento}")
 
323
  analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html = "", "", "", "", ""
324
  peticao_conteudo = ""
325
  try:
326
+ if not caso or not caso.strip(): return "⚠️ Forneça o caso.", "", "", "", ""
327
+ if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo.", "", "", "", ""
 
 
 
 
 
328
  if peticao_arquivo is not None:
 
329
  peticao_conteudo = process_file(peticao_arquivo)
330
+ if peticao_conteudo.startswith(("❌", "⚠️")): return peticao_conteudo, "", "", "", ""
331
+ elif peticao_texto and peticao_texto.strip(): peticao_conteudo = peticao_texto
332
+ else: return "⚠️ Forneça a petição.", "", "", "", ""
 
 
 
 
 
333
  logger.info(f"Petição recebida ({len(peticao_conteudo)} chars).")
 
334
  troll_triggers = ["escreva a petição", "faça a petição", "redija a petição", "elabore a petição", "crie a petição", "gere a petição", "faça para mim", "faça por mim"]
335
  if any(trigger in peticao_conteudo.lower() for trigger in troll_triggers):
336
  logger.info("Detectado pedido para escrever petição.")
337
+ return random.choice(["Hummm... 😉", "🚫 Opa! Corrijo, não crio."]), "", "", "", ""
 
 
 
 
338
  cache_key = f"{tipo_peticao}|{caso[:100]}|{peticao_conteudo[:100]}|{modo_treinamento}"
339
  if cache_key in CORRECAO_CACHE:
340
  logger.info("Correção encontrada no cache.")
341
+ cached = CORRECAO_CACHE[cache_key]
342
+ return cached["analise_formatada"], cached["peticao_corrigida"], cached["resumo_diff"], cached["criticas_diff"], cached["diff_html"]
 
 
343
  prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, tipo_peticao, modo_treinamento)
 
344
  analise_bruta = chamar_api(prompt_analise)
345
+ if analise_bruta.startswith(("❌", "⏱️", "⚠️")): return analise_bruta, "", "", "", ""
 
 
 
346
  analise_formatada = formatar_correcao(analise_bruta)
 
 
347
  prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo, tipo_peticao)
 
348
  peticao_corrigida_bruta = chamar_api(prompt_peticao)
 
349
  if peticao_corrigida_bruta.startswith(("❌", "⏱️", "⚠️")):
350
+ peticao_corrigida_final = f"⚠️ Falha ao gerar versão corrigida:\n{peticao_corrigida_bruta}"
 
351
  else:
 
352
  start_markers = ["excelentíssimo", "eméritos julgadores", "meritíssimo juízo"]
353
  start_index = -1
354
  peticao_lower_corr = peticao_corrigida_bruta.lower()
 
357
  if idx != -1: start_index = idx; break
358
  peticao_extraida = peticao_corrigida_bruta[start_index:] if start_index != -1 else peticao_corrigida_bruta
359
  peticao_corrigida_final = pos_corrigir_peticao(peticao_extraida, tipo_peticao)
 
 
 
360
  diff_html, resumo_diff, criticas_diff = calcular_diff_interno(peticao_conteudo, peticao_corrigida_final)
 
 
361
  resultado_cache = {"analise_formatada": analise_formatada,"peticao_corrigida": peticao_corrigida_final,"resumo_diff": resumo_diff,"criticas_diff": criticas_diff,"diff_html": diff_html}
362
  CORRECAO_CACHE[cache_key] = resultado_cache
363
  HISTORICO.append({"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),"tipo_peticao": tipo_peticao,"caso": caso,"peticao_original": peticao_conteudo,**resultado_cache})
364
  if len(HISTORICO) > 10: HISTORICO.pop(0)
 
365
  end_time = time.time()
366
  logger.info(f"Correção concluída em {end_time - start_time:.2f} segundos.")
367
  return analise_formatada, peticao_corrigida_final, resumo_diff, criticas_diff, diff_html
 
368
  except Exception as e:
369
  logger.exception("Erro GERAL inesperado dentro de corrigir_peticao:")
370
+ return f"❌ Erro inesperado correção: {str(e)}", "", "", "", ""
 
371
 
372
  def formatar_correcao(texto):
373
  texto = texto.replace("### Análise Geral", "## 📝 Análise Geral")
374
  texto = texto.replace("### Nota Final", "## ⭐ Nota Final")
375
  texto = texto.replace("### Pontos Positivos", "## ✅ Pontos Positivos")
376
  texto = texto.replace("### Pontos a Melhorar", "## ⚠️ Pontos a Melhorar")
377
+ texto = texto.replace("### Análise Específica dos Requisitos", "## 🔎 Análise Específica")
378
+ linhas = texto.split('\n'); linhas_formatadas = []
 
379
  in_list = False
380
  for linha in linhas:
381
+ ls = linha.strip()
382
+ if ls.startswith(("1.","2.","3.","4.","5.")): linhas_formatadas.append(f"\n**{ls}**"); in_list = False
383
+ elif ls.startswith(("- ","* ")): prefix = " * " if in_list else "* "; linhas_formatadas.append(prefix + ls[2:]); in_list = True
384
+ elif ls.startswith(("a)","b)","c)","d)","e)")): linhas_formatadas.append(f" * {ls}"); in_list = True
385
+ else: linhas_formatadas.append(linha); in_list = False if not ls.startswith(("*","- ","a)","b)","c)","d)","e)"," ")) else in_list
 
 
 
 
 
 
 
 
 
 
386
  texto_final = "\n".join(linhas_formatadas)
387
+ m = re.search(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})", texto_final, re.I)
388
+ if m: nota = m.group(2).replace(",","."); texto_final = re.sub(r"(\*\*Nota Final:?\*\*\s*)(\d{1,2}[,.]\d{1}|\d{1,2})", f"\\1**{nota}/10**", texto_final, count=1, flags=re.I)
 
 
 
389
  return texto_final
390
 
391
  def fornecer_dica_contextual(texto_peticao, tipo_peticao):
392
+ if not texto_peticao or not texto_peticao.strip(): return "*Digite para dicas...*"
 
393
  dicas_relevantes = []
394
  texto_lower = texto_peticao.lower()
395
  for chave in ["Clareza e Concisão", "Fundamentação Legal", "Revisão Ortográfica"]:
396
+ if chave in DICAS_OAB_MELHORADO: dicas_relevantes.append(f"💡 **{chave}:** {DICAS_OAB_MELHORADO[chave]}")
 
397
  keywords_por_tipo = {
398
+ "Petição Inicial": ["endereçamento","qualificação","fatos","fundamentos","pedidos","valor","provas","audiência","citação","custas"],
399
+ "Contestação": ["preliminar","mérito","impugnação","improcedência","honorários","art. 337","art. 341"],
400
+ "Embargos de Declaração": ["omissão","contradição","obscuridade","erro material","art. 1022"],
401
+ "Recurso de Apelação": ["sentença","reforma","anulação","dialeticidade","preparo","tribunal","art. 1009"],
402
+ "Agravo de Instrumento": ["interlocutória","cabimento","urgência","efeito suspensivo","art. 1015"],
403
+ "Petição de Tutela Provisória": ["urgência","evidência","liminar","periculum","fumus boni","art. 300"],
 
 
404
  }
405
  if tipo_peticao in keywords_por_tipo:
406
  for chave, dica in DICAS_OAB_MELHORADO.items():
407
+ cl = chave.lower()
408
+ if f"({tipo_peticao.split()[0].lower()})" in cl or any(kw in cl for kw in keywords_por_tipo[tipo_peticao] if kw in texto_lower):
409
+ dica_fmt = f"💡 **{chave}:** {dica}"
410
+ if dica_fmt not in dicas_relevantes: dicas_relevantes.append(dica_fmt)
411
+ if not dicas_relevantes: return "*Continue escrevendo para dicas...*"
 
 
 
412
  return "\n\n".join(dicas_relevantes[:4])
413
 
414
  def calcular_diff_interno(texto_original, texto_corrigido):
415
+ if not texto_original or not texto_corrigido or texto_corrigido.startswith(("⚠️","❌")): return "","",""
416
+ orig_lines = texto_original.splitlines(); corr_lines = texto_corrigido.splitlines()
 
 
417
  diff = difflib.ndiff(orig_lines, corr_lines)
418
+ res_html, crit, l_rem, l_add = [], [], 0, 0
419
+ kw_rem = ["invenção", "identificação", "erro crasso", "incorreto", "prazo"]
420
+ kw_add = ["custas", "art. 319", "art. 337", "art. 1022", "art. 1015", "art. 300", "fumus", "periculum", "essencial", "preliminar", "tempestividade", "cabimento"]
 
 
 
421
  for linha in diff:
422
+ txt = linha[2:]; txt_esc = escape(txt)
423
+ if linha.startswith(' '): res_html.append(f"<div class='linha-igual'>{txt_esc}</div>")
424
+ elif linha.startswith('- '): l_rem += 1; res_html.append(f"<div class='linha-removida'><del>{txt_esc}</del></div>"); \
425
+ if txt.strip() and any(kw in txt.lower() for kw in kw_rem): crit.append(f"<span style='color:red'>🔴 REMOVIDO:</span> {txt_esc}")
426
+ elif linha.startswith('+ '): l_add += 1; res_html.append(f"<div class='linha-adicionada'><ins>{txt_esc}</ins></div>"); \
427
+ if txt.strip() and any(kw in txt.lower() for kw in kw_add): crit.append(f"<span style='color:green'>🟢 ADICIONADO:</span> {txt_esc}")
428
+ resumo = f"**Resumo:** Rem={l_rem}, Add={l_add}, Saldo={l_add - l_rem}"
429
+ crit_txt = "### 🚨 Mudanças Críticas:\n" + "\n".join(crit) if crit else "*Nenhuma mudança crítica detectada.*"
430
+ 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>"
431
+ html_diff = f"{css}<div class='diff-container'>{''.join(res_html)}</div>"
432
+ return html_diff, resumo, crit_txt
 
 
 
 
 
433
 
434
  def calcular_progresso():
435
+ if not HISTORICO: return "📊 Nenhum progresso."
436
+ notas, tipos = [], set()
 
437
  for entry in HISTORICO:
438
+ tipos.add(entry["tipo_peticao"])
439
+ m = re.search(r"Nota Final:?\s*\**(\d{1,2}[,.]\d{1}|\d{1,2})\s*/\s*10", entry["analise_formatada"], re.I)
440
+ if m: try: notas.append(float(m.group(1).replace(",", "."))) except ValueError: pass
441
+ tot = len(HISTORICO); media = sum(notas)/len(notas) if notas else 0
442
+ tend = ""
443
+ if len(notas) > 1: tend = "⬆️" if notas[-1] > notas[-2] else ("⬇️" if notas[-1] < notas[-2] else "↔️")
444
+ return f"**📊 Resumo:** Corrs:{tot}, Tipos:{len(tipos)}, Média:{media:.1f}/10 ({len(notas)} val), Tend:{tend if tend else 'N/A'}"
 
 
 
 
 
 
 
445
 
446
  def mostrar_historico():
447
  if not HISTORICO: return "📜 Histórico vazio."
448
+ md = "### 📜 Histórico (Últimas 10)\n---\n"
449
  for i, entry in enumerate(reversed(HISTORICO)):
450
+ caso = (entry['caso'][:70] + '...') if len(entry['caso']) > 70 else entry['caso']
451
+ 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)
452
+ if m: nota = f"{m.group(1).replace(',', '.')}/10"
453
+ md += f"**{len(HISTORICO)-i}.** {entry['data']} | **{entry['tipo_peticao']}** | **Nota:{nota}**\n **Caso:** _{caso}_\n---\n"
454
+ return md
 
455
 
456
  def salvar_progresso(caso, peticao, tipo_peticao):
457
+ logger.info("Salvando progresso...")
458
  try:
459
+ if not caso or not peticao or not tipo_peticao: return "", "⚠️ Inputs necessários.", None
460
+ dados = {"tipo_peticao": tipo_peticao, "caso": caso, "peticao": peticao, "timestamp": datetime.now().isoformat()}
461
+ json_cont = json.dumps(dados, ensure_ascii=False, indent=4)
462
+ fname = f"progresso_peticao_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
463
+ fpath = os.path.join(tempfile.gettempdir(), fname)
464
+ with open(fpath, 'w', encoding='utf-8') as f: f.write(json_cont)
465
+ logger.info(f"Progresso salvo: {fpath}")
466
+ msg = f"✅ Salvo! Baixe `{fname}`."
467
+ return json_cont, msg, fpath
468
+ except Exception as e: logger.error(f"Erro salvar: {e}", exc_info=True); return "", f"❌ Erro salvar: {e}", None
 
 
 
 
 
 
469
 
470
  def carregar_progresso(arquivo_entrada):
471
+ logger.info("Carregando progresso...")
472
+ if arquivo_entrada is None: return "", "", "", "⚠️ Nenhum arquivo."
473
  try:
474
+ fpath = arquivo_entrada.name
475
+ logger.info(f"Carregando: {fpath}")
476
+ with open(fpath, 'r', encoding='utf-8') as f: dados = json.load(f)
477
+ caso, peticao, tipo = dados.get("caso"), dados.get("peticao"), dados.get("tipo_peticao")
478
+ if not all([caso, peticao, tipo]): return "", "", "", "❌ Arquivo inválido."
479
+ if tipo not in TIPOS_DE_PETICAO: return caso, peticao, "", f"⚠️ Tipo '{tipo}' inválido."
480
+ logger.info("Progresso carregado.")
481
+ return tipo, caso, peticao, "✅ Progresso carregado!"
482
+ except Exception as e: logger.error(f"Erro carregar: {e}", exc_info=True); return "", "", "", f"❌ Erro carregar: {e}"
 
 
 
 
 
 
 
 
 
 
 
483
 
484
  def estruturar_prompt_geracao_caso(tipo_peticao="Petição Inicial"):
485
+ 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"
486
+ if tipo_peticao == "Petição Inicial": prompt += "* Descreva violação direitos. * Inclua danos. * Dados qualificação. * Tema: Consumo, Contrato, etc."
487
+ elif tipo_peticao == "Contestação": prompt += "* Resumo PI recebida. * Versão Réu (p/ preliminares, impugnação, mérito). * Dados qualificação réu."
488
+ elif tipo_peticao == "Reconvenção": prompt += "* Estrutura Contestação + Fatos/Fundamentos p/ pedido PRÓPRIO CONEXO do Réu contra Autor."
489
+ elif tipo_peticao == "Impugnação à Contestação": prompt += "* Resumo PI Autor. * Resumo Contestação Réu (preliminares, fatos novos, docs). * Objetivo: Rebater contestação."
490
+ elif tipo_peticao == "Embargos de Declaração": prompt += "* Breve caso. * TRECHO DECISÃO JUDICIAL. * INDIQUE VÍCIO (omissão/contradição/obscuridade/erro material) NESSA DECISÃO."
491
+ elif tipo_peticao == "Recurso de Apelação": prompt += "* Caso original. * Resumo SENTENÇA. * ELEMENTOS indicando ERRO (in judicando/procedendo). * Indique cliente (apelante)."
492
+ elif tipo_peticao == "Agravo de Instrumento": prompt += "* Caso em andamento. * DECISÃO INTERLOCUTÓRIA. * Enquadrar Art.1015/mitigação. * ELEMENTOS indicando ERRO + URGÊNCIA. * Indique cliente (agravante)."
493
+ elif tipo_peticao == "Petição de Tutela Provisória": prompt += "* Situação URGENTE ou EVIDENTE. * ELEMENTOS: Urgência=Fumus+Periculum; Evidência=Hipótese Art.311. * Medida específica. * Antecedente ou Incidental."
494
+ else: prompt += "* Crie cenário cível com conflito claro."
495
+ 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. Seja criativo!'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  return prompt
497
 
498
  def gerar_caso_ficticio(tipo_peticao="Petição Inicial"):
499
+ logger.info(f"Gerando caso para: {tipo_peticao}")
500
  try:
501
+ if not tipo_peticao or tipo_peticao not in TIPOS_DE_PETICAO: return "⚠️ Selecione tipo válido."
 
 
 
502
  prompt = estruturar_prompt_geracao_caso(tipo_peticao)
 
503
  caso_gerado = chamar_api(prompt)
504
+ if caso_gerado.startswith(("❌","⏱️","⚠️")): return f"⚠️ Falha gerar caso:\n{caso_gerado}"
505
+ frase_final = "**Situação-Problema:** Na qualidade de advogado(a) de"
506
+ if frase_final.lower() not in caso_gerado.lower():
507
+ logger.warning("Frase final não encontrada. Adicionando.")
508
+ nomes = re.findall(r"[A-Z][a-z]+(?:\s[A-Z][a-z]+)+", caso_gerado)
509
+ cliente = nomes[0] if nomes else "[Cliente]"
510
+ caso_gerado += f"\n\n**Situação-Problema:** Na qualidade de advogado(a) de {cliente}, elabore a peça processual cabível ({tipo_peticao})."
511
+ logger.info("Caso gerado.")
 
 
512
  return caso_gerado
513
+ except Exception as e: logger.exception("Erro gerar_caso:"); return f"❌ Erro gerar caso: {e}"
514
+
515
+ # Função para atualizar config (faltante no traceback anterior)
516
+ def atualizar_config(temp, timeout, tokens):
517
+ CONFIG["temperatura"] = temp
518
+ CONFIG["timeout"] = timeout
519
+ CONFIG["max_tokens"] = tokens
520
+ logger.info(f"Configs atualizadas: Temp={temp}, Timeout={timeout}, Tokens={tokens}")
521
+ return "✅ Configs Atualizadas!"
522
 
523
  def interface_gradio():
524
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="orange")) as demo:
525
+ gr.Markdown("# ⚖️ Corretor Jurídico OAB Master ⚖️\nAnalise e aprimore suas peças processuais!")
 
526
  with gr.Row():
527
  with gr.Column(scale=1):
528
  gr.Markdown("### ⚙️ Configurações")
529
+ tipo_peticao_input = gr.Dropdown(label="1. Selecione o Tipo", choices=TIPOS_DE_PETICAO, value="Petição Inicial")
530
  modo_treinamento = gr.Checkbox(label="🎓 Modo Treinamento", value=False)
531
+ with gr.Accordion("🛠️ Opções Avançadas", open=False):
532
+ temperatura_slider = gr.Slider(0.0, 1.0, value=CONFIG["temperatura"], step=0.1, label="Criatividade")
533
+ timeout_slider = gr.Slider(30, 180, value=CONFIG["timeout"], step=10, label="Timeout (s)")
534
+ max_tokens_slider = gr.Slider(2048, 8192, value=CONFIG["max_tokens"], step=256, label="Max Tokens")
535
  config_update_btn = gr.Button("💾 Salvar Configs")
536
  config_status = gr.Textbox(label="Status", interactive=False, lines=1)
537
  with gr.Accordion("📊 Dashboard", open=False):
538
+ progresso_output = gr.Markdown("*Atualize para ver.*")
539
  progresso_btn = gr.Button("🔄 Atualizar Progresso")
540
  with gr.Accordion("📜 Histórico", open=False):
541
+ historico_output = gr.Markdown("*Atualize para ver.*")
542
  historico_btn = gr.Button("🔄 Ver Histórico")
543
  with gr.Accordion("💾📂 Salvar/Carregar", open=False):
544
+ salvar_btn = gr.Button("💾 Salvar")
545
+ carregar_btn = gr.Button("📂 Carregar")
546
+ arquivo_progresso_upload = gr.File(label="Selecione .json", file_types=[".json"])
547
  progresso_download_file = gr.File(label="Baixar Progresso", interactive=False)
548
  with gr.Column(scale=3):
549
+ gr.Markdown("### 1. Caso / Contexto")
550
  with gr.Row():
551
+ caso_input = gr.Textbox(label="📋 Caso", lines=8, placeholder="Insira o texto do caso...")
552
+ caso_btn = gr.Button("🎲 Gerar", variant="secondary", scale=0)
553
+ gr.Markdown("### 2. Sua Peça")
554
  with gr.Tabs():
555
+ with gr.TabItem("✏️ Texto"):
556
+ peticao_texto_input = gr.Textbox(label="Texto da Peça", lines=12, placeholder="Cole ou digite...")
557
+ dica_contextual_output = gr.Markdown("*Dicas...*")
558
+ btn_corrigir = gr.Button("🚀 CORRIGIR!", variant="primary")
559
+ with gr.TabItem("📎 Arquivo"):
560
+ peticao_arquivo_input = gr.File(label="Arquivo (.pdf,.docx,.txt)", file_types=[".pdf",".docx",".txt"])
561
+ arquivo_preview_output = gr.Textbox(label="🔍 Prévia", lines=4, interactive=False)
562
+ btn_corrigir_arquivo = gr.Button("🚀 CORRIGIR ARQUIVO!", variant="primary")
563
 
564
  processing_message = gr.Markdown("", visible=False)
565
+ gr.Markdown("--- \n ### 3. Resultados")
 
566
  with gr.Tabs():
567
+ with gr.TabItem("📊 Análise e Correção"):
568
+ resultado_analise_output = gr.Markdown(label="Análise e Nota")
569
  resultado_peticao_corrigida_output = gr.Textbox(label="✨ Versão Sugerida", lines=18, show_copy_button=True)
570
+ with gr.TabItem("⚖️ Comparativo"):
571
+ gr.Markdown("### ↔️ Comparativo (Diff)")
572
  with gr.Row():
573
+ with gr.Column(scale=1): resumo_alteracoes_output = gr.Markdown(label="Resumo"); criticas_output = gr.Markdown(label="Críticas")
574
+ with gr.Column(scale=3): visualizacao_detalhada_output = gr.HTML(label="Detalhes")
575
+ clear_btn = gr.Button("🗑️ Limpar Tudo")
 
 
 
 
576
 
577
+ # --- Eventos ---
578
+ caso_btn.click(lambda: gr.update(value="⏳ Gerando...", visible=True), outputs=processing_message).then(
 
579
  fn=gerar_caso_ficticio, inputs=[tipo_peticao_input], outputs=[caso_input]
580
+ ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
 
 
581
 
582
+ peticao_texto_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output)
583
+ tipo_peticao_input.change(fornecer_dica_contextual, [peticao_texto_input, tipo_peticao_input], dica_contextual_output)
584
+ peticao_arquivo_input.change(process_file, [peticao_arquivo_input], [arquivo_preview_output])
585
 
586
+ btn_corrigir.click(lambda: gr.update(value="⏳ Processando texto...", visible=True), outputs=processing_message).then(
 
 
 
587
  fn=corrigir_peticao,
588
  inputs=[caso_input, tipo_peticao_input, peticao_texto_input, gr.State(None), modo_treinamento],
589
+ outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
 
590
  concurrency_limit=1
591
+ ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
 
 
 
592
 
593
+ btn_corrigir_arquivo.click(lambda: gr.update(value="⏳ Processando arquivo...", visible=True), outputs=processing_message).then(
 
 
 
594
  fn=corrigir_peticao,
595
  inputs=[caso_input, tipo_peticao_input, gr.State(None), peticao_arquivo_input, modo_treinamento],
596
+ outputs=[resultado_analise_output, resultado_peticao_corrigida_output, resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output],
 
597
  concurrency_limit=1
598
+ ).then(lambda: gr.update(value="", visible=False), outputs=processing_message)
 
 
 
599
 
600
+ config_update_btn.click(atualizar_config, [temperatura_slider, timeout_slider, max_tokens_slider], config_status)
601
+ progresso_btn.click(calcular_progresso, outputs=progresso_output)
602
+ historico_btn.click(mostrar_historico, outputs=historico_output)
603
 
604
+ salvar_btn.click(lambda: gr.update(value="⏳ Salvando...", visible=True), outputs=processing_message).then(
 
 
605
  fn=salvar_progresso, inputs=[caso_input, peticao_texto_input, tipo_peticao_input],
606
  outputs=[gr.Textbox(visible=False), config_status, progresso_download_file]
607
+ ).then(lambda x: gr.update(visible=bool(x)), [progresso_download_file], [progresso_download_file]
608
+ ).then(lambda: gr.update(value="", visible=False), None, processing_message)
 
 
 
609
 
610
+ carregar_btn.click(lambda: gr.update(value="⏳ Carregando...", visible=True), outputs=processing_message).then(
 
 
611
  fn=carregar_progresso, inputs=[arquivo_progresso_upload],
612
  outputs=[tipo_peticao_input, caso_input, peticao_texto_input, config_status]
613
+ ).then(lambda: gr.update(value="", visible=False), None, processing_message)
 
 
614
 
615
  clear_btn.click(
616
+ fn=lambda: ["Petição Inicial", False, None, None, None, None, "*Dicas...*", "", "", "", "", "", None, None, ""],
617
+ outputs=[tipo_peticao_input, modo_treinamento, caso_input, peticao_texto_input, peticao_arquivo_input,
618
+ arquivo_preview_output, dica_contextual_output, resultado_analise_output, resultado_peticao_corrigida_output,
619
+ resumo_alteracoes_output, criticas_output, visualizacao_detalhada_output,
620
+ arquivo_progresso_upload, progresso_download_file, config_status]
 
 
 
621
  )
 
622
  return demo
623
 
624
  if __name__ == "__main__":