Update app.py
Browse files
app.py
CHANGED
|
@@ -32,194 +32,31 @@ CONFIG = {
|
|
| 32 |
"retry_delay": 5,
|
| 33 |
"timeout": 60,
|
| 34 |
"temperatura": 0.2,
|
| 35 |
-
"max_tokens":
|
| 36 |
}
|
| 37 |
|
| 38 |
rate_limiter = RateLimiter(max_calls=60, period=60)
|
| 39 |
|
| 40 |
DICAS_OAB_MELHORADO = {
|
| 41 |
-
"Endereçamento": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). "
|
| 42 |
-
|
| 43 |
-
"
|
| 44 |
-
|
| 45 |
-
"
|
| 46 |
-
|
| 47 |
-
"
|
| 48 |
-
|
| 49 |
-
"
|
| 50 |
-
|
| 51 |
-
"Fundamentação": "Baseie-se em artigos do CPC ou CDC e, se possível, em súmulas do STJ (ex.: Súmula 387 para danos morais). "
|
| 52 |
-
"Exemplo: 'Conforme o artigo 186 do Código Civil, a responsabilidade civil pode ser caracterizada'.",
|
| 53 |
-
"Fechamento": "Finalize com 'Nestes termos pede deferimento', sem vírgula. "
|
| 54 |
-
"Dica: Evite erros de pontuação que podem comprometer a formalidade do documento.",
|
| 55 |
-
"Estratégia Jurídica": "Utilize precedentes e súmulas para fortalecer sua argumentação. "
|
| 56 |
-
"Exemplo: 'Conforme a Súmula 387 do STJ, os danos morais devem ser comprovados e quantificados'.",
|
| 57 |
-
"Provas": "Especifique as provas que pretende produzir. "
|
| 58 |
-
"Dica: Inclua testemunhas, documentos e perícias, se necessário, para comprovar os fatos alegados.",
|
| 59 |
-
"Contra-argumentação": "Antecipe possíveis defesas da parte contrária e prepare respostas. "
|
| 60 |
-
"Dica: Considere os argumentos que a outra parte pode usar e esteja preparado para refutá-los."
|
| 61 |
}
|
| 62 |
|
| 63 |
HISTORICO = []
|
| 64 |
CORRECAO_CACHE = {}
|
| 65 |
|
| 66 |
-
|
| 67 |
-
try:
|
| 68 |
-
gray = img.convert('L')
|
| 69 |
-
np_img = np.array(gray)
|
| 70 |
-
_, thresh = cv2.threshold(np_img, 150, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
| 71 |
-
return Image.fromarray(thresh)
|
| 72 |
-
except Exception as e:
|
| 73 |
-
return f"❌ Erro no pré-processamento: {str(e)}"
|
| 74 |
-
|
| 75 |
-
def extract_text_from_pdf(file_path_or_obj):
|
| 76 |
-
try:
|
| 77 |
-
if isinstance(file_path_or_obj, str):
|
| 78 |
-
with open(file_path_or_obj, 'rb') as f:
|
| 79 |
-
text = ""
|
| 80 |
-
with pdfplumber_open(f) as pdf:
|
| 81 |
-
for page in pdf.pages:
|
| 82 |
-
text += (page.extract_text() or "") + "\n"
|
| 83 |
-
if not text.strip():
|
| 84 |
-
images = pdfplumber_open(f).pages
|
| 85 |
-
for i, image in enumerate(images):
|
| 86 |
-
try:
|
| 87 |
-
img = image.to_image()
|
| 88 |
-
preprocessed_img = preprocess_image_for_ocr(img)
|
| 89 |
-
text += pytesseract.image_to_string(preprocessed_img) + "\n"
|
| 90 |
-
except Exception as e:
|
| 91 |
-
text += f"[Erro na página {i+1}: {str(e)}]\n"
|
| 92 |
-
else:
|
| 93 |
-
text = ""
|
| 94 |
-
with pdfplumber_open(file_path_or_obj) as pdf:
|
| 95 |
-
for page in pdf.pages:
|
| 96 |
-
text += (page.extract_text() or "") + "\n"
|
| 97 |
-
if not text.strip():
|
| 98 |
-
images = pdfplumber_open(file_path_or_obj).pages
|
| 99 |
-
for i, image in enumerate(images):
|
| 100 |
-
try:
|
| 101 |
-
img = image.to_image()
|
| 102 |
-
preprocessed_img = preprocess_image_for_ocr(img)
|
| 103 |
-
text += pytesseract.image_to_string(preprocessed_img) + "\n"
|
| 104 |
-
except Exception as e:
|
| 105 |
-
text += f"[Erro na página {i+1}: {str(e)}]\n"
|
| 106 |
-
return text if text.strip() else "⚠️ Não foi possível extrair texto do PDF."
|
| 107 |
-
except Exception as e:
|
| 108 |
-
return f"❌ Erro ao processar o PDF: {str(e)}"
|
| 109 |
-
|
| 110 |
-
def extract_text_from_docx(file_path_or_obj):
|
| 111 |
-
try:
|
| 112 |
-
if isinstance(file_path_or_obj, str):
|
| 113 |
-
with open(file_path_or_obj, 'rb') as f:
|
| 114 |
-
doc = docx.Document(f)
|
| 115 |
-
text = ""
|
| 116 |
-
for para in doc.paragraphs:
|
| 117 |
-
text += para.text + "\n"
|
| 118 |
-
for table in doc.tables:
|
| 119 |
-
for row in table.rows:
|
| 120 |
-
for cell in row.cells:
|
| 121 |
-
text += cell.text + " "
|
| 122 |
-
text += "\n"
|
| 123 |
-
else:
|
| 124 |
-
doc = docx.Document(file_path_or_obj)
|
| 125 |
-
text = ""
|
| 126 |
-
for para in doc.paragraphs:
|
| 127 |
-
text += para.text + "\n"
|
| 128 |
-
for table in doc.tables:
|
| 129 |
-
for row in table.rows:
|
| 130 |
-
for cell in row.cells:
|
| 131 |
-
text += cell.text + " "
|
| 132 |
-
text += "\n"
|
| 133 |
-
return text if text.strip() else "⚠️ Não foi possível extrair texto do DOCX."
|
| 134 |
-
except Exception as e:
|
| 135 |
-
return f"❌ Erro ao processar o DOCX: {str(e)}"
|
| 136 |
|
| 137 |
-
def
|
| 138 |
-
|
| 139 |
-
if isinstance(file_path_or_obj, str):
|
| 140 |
-
with open(file_path_or_obj, 'rb') as f:
|
| 141 |
-
content = f.read()
|
| 142 |
-
encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
|
| 143 |
-
for encoding in encodings:
|
| 144 |
-
try:
|
| 145 |
-
return content.decode(encoding)
|
| 146 |
-
except UnicodeDecodeError:
|
| 147 |
-
continue
|
| 148 |
-
return "❌ Não foi possível decodificar o arquivo TXT."
|
| 149 |
-
else:
|
| 150 |
-
content = file_path_or_obj.read()
|
| 151 |
-
encodings = ['utf-8', 'latin-1', 'cp1252', 'iso-8859-1']
|
| 152 |
-
for encoding in encodings:
|
| 153 |
-
try:
|
| 154 |
-
return content.decode(encoding)
|
| 155 |
-
except UnicodeDecodeError:
|
| 156 |
-
continue
|
| 157 |
-
return "❌ Não foi possível decodificar o arquivo TXT."
|
| 158 |
-
except Exception as e:
|
| 159 |
-
return f"❌ Erro ao processar o TXT: {str(e)}"
|
| 160 |
-
|
| 161 |
-
def process_file(file_obj):
|
| 162 |
-
if file_obj is None:
|
| 163 |
-
return "Nenhum arquivo enviado."
|
| 164 |
-
try:
|
| 165 |
-
if hasattr(file_obj, 'name'):
|
| 166 |
-
file_path = file_obj.name
|
| 167 |
-
filename = os.path.basename(file_path).lower()
|
| 168 |
-
file_size = os.path.getsize(file_path)
|
| 169 |
-
if file_size > 10 * 1024 * 1024:
|
| 170 |
-
return "⚠️ O arquivo é muito grande. Máx. 10 MB."
|
| 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 |
-
return "⚠️ Formato de arquivo não suportado. Use PDF, DOCX ou TXT."
|
| 179 |
-
else:
|
| 180 |
-
return "❌ Erro: Objeto de arquivo inválido."
|
| 181 |
-
except Exception as e:
|
| 182 |
-
return f"❌ Erro ao processar o arquivo: {str(e)}"
|
| 183 |
-
|
| 184 |
-
def calcular_pontuacao(enderecamento, fundamentos, pedidos, fechamento):
|
| 185 |
-
tabela = [
|
| 186 |
-
("**Endereçamento**", ""),
|
| 187 |
-
("1. Correto endereçamento ao juízo competente", f"{enderecamento[0]:.2f} / 0,10"),
|
| 188 |
-
("2. Qualificação das partes (Art. 319, II, CPC)", f"{enderecamento[1]:.2f} / 0,20"),
|
| 189 |
-
("**Fundamentos**", ""),
|
| 190 |
-
("3. Exposição clara dos fatos (Art. 319, III, CPC)", f"{fundamentos[0]:.2f} / 0,20"),
|
| 191 |
-
("4. Fundamentação jurídica adequada", f"{fundamentos[1]:.2f} / 0,25"),
|
| 192 |
-
("5. Demonstração de ilícito e dano (Art. 186, CC)", f"{fundamentos[2]:.2f} / 0,25"),
|
| 193 |
-
("**Pedidos**", ""),
|
| 194 |
-
("6. Pedidos claros e específicos (Art. 319, IV, CPC)", f"{pedidos[0]:.2f} / 0,30"),
|
| 195 |
-
("7. Valor da causa indicado (Art. 319, V, CPC)", f"{pedidos[1]:.2f} / 0,10"),
|
| 196 |
-
("8. Provas pretendidas (Art. 319, VI, CPC)", f"{pedidos[2]:.2f} / 0,10"),
|
| 197 |
-
("9. Opção sobre audiência (Art. 319, VII, CPC)", f"{pedidos[3]:.2f} / 0,10"),
|
| 198 |
-
("10. Pedido de citação do réu", f"{pedidos[4]:.2f} / 0,10"),
|
| 199 |
-
("11. Juntada da guia de custas", f"{pedidos[5]:.2f} / 0,10"),
|
| 200 |
-
("**Fechamento**", ""),
|
| 201 |
-
("12. Fechamento formal adequado", f"{fechamento[0]:.2f} / 0,10"),
|
| 202 |
-
]
|
| 203 |
-
nota_final = sum(enderecamento) + sum(fundamentos) + sum(pedidos) + sum(fechamento)
|
| 204 |
-
tabela_formatada = "\n".join([f"| {linha[0].ljust(60)} | {linha[1].rjust(10)} |" for linha in tabela])
|
| 205 |
-
return f"{tabela_formatada}\n\n**Nota Final:** {nota_final:.2f} / 10,00"
|
| 206 |
-
|
| 207 |
-
def validar_peticao_basica(peticao):
|
| 208 |
-
problemas = []
|
| 209 |
-
if "EXCELENTÍSSIMO" not in peticao.upper():
|
| 210 |
-
problemas.append("⚠️ Endereçamento ausente ou incompleto (Art. 319, I, CPC).")
|
| 211 |
-
if "Art. 319" not in peticao and "CPC" not in peticao:
|
| 212 |
-
problemas.append("⚠️ Fundamentação jurídica não menciona o CPC (Art. 319).")
|
| 213 |
-
if "Dá-se à causa o valor" not in peticao:
|
| 214 |
-
problemas.append("⚠️ Valor da causa não especificado (Art. 319, V, CPC).")
|
| 215 |
-
if "juntada da guia de custas" not in peticao.lower():
|
| 216 |
-
problemas.append("⚠️ Pedido de juntada da guia de custas ausente (-1,0 ponto).")
|
| 217 |
-
if "Nestes termos" not in peticao:
|
| 218 |
-
problemas.append("⚠️ Fechamento formal ausente.")
|
| 219 |
-
return problemas
|
| 220 |
-
|
| 221 |
-
def estruturar_prompt_correcao(caso, peticao, modo_treinamento=False):
|
| 222 |
-
"""Estrutura um prompt avançado e especializado para correção de petições."""
|
| 223 |
base_prompt = f"""
|
| 224 |
## Correção Avançada de Petição Inicial Cível (Critérios Realistas da OAB com Análise Especializada)
|
| 225 |
**Caso Fornecido:**
|
|
@@ -282,9 +119,30 @@ def estruturar_prompt_correcao(caso, peticao, modo_treinamento=False):
|
|
| 282 |
**Fechamento** | | |
|
| 283 |
12. Fechamento formal adequado (0,10) | / 0,10 | [Explique erro ou acerto] |
|
| 284 |
**Nota Final:** Calcule a soma dos pontos, aplicando penalizações cumulativas conforme os critérios fundamentais; justifique qualquer desconto adicional.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
### Petição Corrigida
|
| 287 |
-
Certifique-se de incluir esta seção exatamente como '### Petição Corrigida'. Forneça a petição do aluno reescrita com todas as correções aplicadas, incluindo partes omitidas (como pedidos ou custas) e ajustando erros identificados
|
| 288 |
1. Endereçamento correto (com foro justificado por CPC ou CDC).
|
| 289 |
2. Qualificação completa (Art. 319, II, CPC; use "XXX" para dados ausentes).
|
| 290 |
3. Fatos claros e fiéis ao caso (Art. 319, III, CPC).
|
|
@@ -297,48 +155,11 @@ def estruturar_prompt_correcao(caso, peticao, modo_treinamento=False):
|
|
| 297 |
10. Juntada da guia de custas com redação precisa.
|
| 298 |
11. Fechamento formal com "Local e data.\nADVOGADO.\nOAB." sem identificação específica.
|
| 299 |
"""
|
| 300 |
-
if modo_treinamento:
|
| 301 |
-
base_prompt += f"""
|
| 302 |
-
**Modo de Treinamento Interativo Avançado:**
|
| 303 |
-
- Para cada item com pontuação inferior ao máximo, forneça:
|
| 304 |
-
a) Explicação detalhada (mínimo 3 frases) com base legal (ex.: Art. 319, CPC) e análise do impacto processual.
|
| 305 |
-
b) Sugestão de redação corrigida baseada no caso, com linguagem técnica exemplar.
|
| 306 |
-
c) Exemplo prático em outro contexto (ex.: "Em ação consumerista, ‘Requer a inversão do ônus da prova, nos termos do Art. 6º, VIII, CDC.’").
|
| 307 |
-
d) Dica prática do banco ({json.dumps(DICAS_OAB_MELHORADO, ensure_ascii=False)}) ou personalizada, com enfoque pedagógico.
|
| 308 |
-
e) Estratégia jurídica avançada (ex.: "Cite o Art. 944, CC para quantificar danos ou peça tutela antecipada com base no Art. 300, CPC").
|
| 309 |
-
- Verifique a coerência entre os fatos do caso e a petição, listando todas as inconsistências com trechos específicos.
|
| 310 |
-
- Sugira estratégias adicionais baseadas no caso (ex.: cumulação de pedidos, Art. 327, CPC; ou danos morais com Súmula 387/STJ).
|
| 311 |
-
- Detalhe cada erro pedagogicamente, com referências a leis, súmulas (ex.: Súmula 54/STJ para lucros cessantes), e doutrina (ex.: Nelson Nery Jr. sobre Art. 319, CPC).
|
| 312 |
-
- Avalie a viabilidade processual da petição (ex.: risco de indeferimento por inépcia, Art. 330, CPC).
|
| 313 |
-
"""
|
| 314 |
-
return base_prompt
|
| 315 |
-
|
| 316 |
-
def estruturar_prompt_geracao_caso():
|
| 317 |
-
return """
|
| 318 |
-
Crie um caso fictício detalhado para uma petição inicial cível, similar ao exemplo:
|
| 319 |
-
"Ana, modelo profissional, residente em Manaus, viajou para São Paulo, para o casamento de sua filha. Para lavar, pintar seus cabelos e realizar um penteado para o casamento, Ana procurou os serviços de João Macedo, cabeleireiro e dono do salão de beleza 'Hair', sediado na cidade de São Paulo, que lhe cobrou R$500,00 (quinhentos reais) pela prestação do serviço. Após lavar os cabelos de Ana, João aplicou-lhe uma tintura da marca francesa ABC, importada pela empresa Brasil Connection Ltda, sediada na cidade de Curitiba (PR). Meia hora após a aplicação da tintura, Ana sofreu uma reação alérgica, que demandou atendimento médico-hospitalar, no valor de R$1.000,00 (um mil reais), bem como dois dias de absoluto repouso que impossibilitou sua presença no casamento de sua filha. Além disso, perdeu grande parte de seu cabelo, tendo permanecido com manchas em seu rosto por dois meses, perdendo um ensaio fotográfico, para o qual já havia sido contratada, pelo valor de R$50.000,00 (cinquenta mil reais). Posteriormente constatou-se que a tintura utilizada continha substâncias químicas extremamente perigosas à vida e à saúde das pessoas e a fabricante ABC já havia sido condenada pela justiça francesa a encerrar a fabricação e comercialização do produto. Indignada com os danos sofridos, Ana procura um advogado para pleitear o devido ressarcimento."
|
| 320 |
-
O caso deve:
|
| 321 |
-
1. Conter uma situação clara de violação de direitos que justifique uma ação cível
|
| 322 |
-
2. Incluir valores específicos de danos materiais e/ou morais
|
| 323 |
-
3. Mencionar nomes completos, cidades e informações suficientes para identificar as partes
|
| 324 |
-
4. Estabelecer relação entre as partes (contratual, extracontratual, consumo, etc.)
|
| 325 |
-
5. Ter complexidade adequada para uma petição inicial de nível OAB
|
| 326 |
-
6. Ter entre 15-25 linhas de texto
|
| 327 |
-
7. Focar em um dos seguintes temas (escolha APENAS UM aleatoriamente):
|
| 328 |
-
- Responsabilidade civil por acidente de consumo
|
| 329 |
-
- Inadimplemento contratual com danos
|
| 330 |
-
- Responsabilidade civil por dano ambiental
|
| 331 |
-
- Violação de direitos da personalidade
|
| 332 |
-
- Relação de consumo com vício de produto/serviço
|
| 333 |
-
- Reparação por danos patrimoniais
|
| 334 |
-
Termine com: "Situação problema: Como advogado(a) de [NOME DO CLIENTE], promova a demanda cabível."
|
| 335 |
-
"""
|
| 336 |
|
| 337 |
-
def
|
| 338 |
-
prompt = estruturar_prompt_geracao_caso()
|
| 339 |
payload = {
|
| 340 |
"contents": [{"parts": [{"text": prompt}]}],
|
| 341 |
-
"generationConfig": {"temperature":
|
| 342 |
}
|
| 343 |
headers = {"Content-Type": "application/json"}
|
| 344 |
for tentativa in range(CONFIG["max_retry_attempts"]):
|
|
@@ -358,8 +179,7 @@ def gerar_caso_ficticio():
|
|
| 358 |
time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
|
| 359 |
continue
|
| 360 |
return "❌ A API retornou uma resposta vazia. Tente novamente."
|
| 361 |
-
|
| 362 |
-
return f"## 📝 Caso Fictício Gerado\n\n{caso_gerado}"
|
| 363 |
except requests.Timeout:
|
| 364 |
if tentativa < CONFIG["max_retry_attempts"] - 1:
|
| 365 |
time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
|
|
@@ -392,66 +212,34 @@ def corrigir_peticao(caso, peticao_texto=None, peticao_arquivo=None, modo_treina
|
|
| 392 |
if cache_key in CORRECAO_CACHE:
|
| 393 |
return aviso_validacao + CORRECAO_CACHE[cache_key]
|
| 394 |
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
# Debug: Inspeciona a resposta se a seção esperada não estiver presente
|
| 420 |
-
if "### Petição Corrigida" not in correcao_completa:
|
| 421 |
-
print("Debug: Resposta da API não contém '### Petição Corrigida'. Resposta completa (primeiros 1000 caracteres):")
|
| 422 |
-
print(correcao_completa[:1000])
|
| 423 |
-
if "### Petição Corrigida" in correcao_completa:
|
| 424 |
-
analise_fim = correcao_completa.index("### Petição Corrigida")
|
| 425 |
-
analise = correcao_completa[:analise_fim].strip()
|
| 426 |
-
else:
|
| 427 |
-
analise = correcao_completa.strip()
|
| 428 |
-
correcao_formatada = formatar_correcao(analise)
|
| 429 |
-
HISTORICO.append({
|
| 430 |
-
"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 431 |
-
"caso": caso,
|
| 432 |
-
"peticao": peticao_conteudo,
|
| 433 |
-
"correcao_completa": correcao_completa
|
| 434 |
-
})
|
| 435 |
-
if len(HISTORICO) > 10: # Limite de histórico
|
| 436 |
-
HISTORICO.pop(0)
|
| 437 |
-
CORRECAO_CACHE[cache_key] = correcao_formatada
|
| 438 |
-
return aviso_validacao + correcao_formatada
|
| 439 |
-
except requests.Timeout:
|
| 440 |
-
if tentativa < CONFIG["max_retry_attempts"] - 1:
|
| 441 |
-
time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
|
| 442 |
-
continue
|
| 443 |
-
return aviso_validacao + "⏱️ A resposta demorou muito. Tente novamente mais tarde."
|
| 444 |
-
except requests.RequestException as e:
|
| 445 |
-
if tentativa < CONFIG["max_retry_attempts"] - 1:
|
| 446 |
-
time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
|
| 447 |
-
continue
|
| 448 |
-
return aviso_validacao + f"❌ Erro ao chamar a API: {e}"
|
| 449 |
-
except Exception as e:
|
| 450 |
-
return aviso_validacao + f"❌ Erro inesperado: {str(e)}"
|
| 451 |
|
| 452 |
def formatar_correcao(texto):
|
| 453 |
linhas = texto.split('\n')
|
| 454 |
-
texto_formatado = ""
|
| 455 |
for linha in linhas:
|
| 456 |
if "Análise Geral" in linha:
|
| 457 |
texto_formatado += f"### ℹ️ {linha.strip()}\n\n"
|
|
@@ -480,7 +268,6 @@ def formatar_correcao(texto):
|
|
| 480 |
|
| 481 |
def extrair_peticao_corrigida(correcao_completa):
|
| 482 |
try:
|
| 483 |
-
# Tenta encontrar a seção exata
|
| 484 |
if "### Petição Corrigida" in correcao_completa:
|
| 485 |
inicio = correcao_completa.index("### Petição Corrigida") + len("### Petição Corrigida")
|
| 486 |
peticao_texto = correcao_completa[inicio:].strip()
|
|
@@ -488,11 +275,10 @@ def extrair_peticao_corrigida(correcao_completa):
|
|
| 488 |
peticao_texto = peticao_texto[:fim].strip()
|
| 489 |
return peticao_texto
|
| 490 |
else:
|
| 491 |
-
# Fallback: tenta identificar o início da petição corrigida por padrões comuns
|
| 492 |
linhas = correcao_completa.split('\n')
|
| 493 |
inicio_peticao = -1
|
| 494 |
for i, linha in enumerate(linhas):
|
| 495 |
-
if "EXCELENTÍSSIMO" in linha.upper() and i > len(linhas) // 2:
|
| 496 |
inicio_peticao = i
|
| 497 |
break
|
| 498 |
if inicio_peticao != -1:
|
|
@@ -502,127 +288,7 @@ def extrair_peticao_corrigida(correcao_completa):
|
|
| 502 |
except Exception as e:
|
| 503 |
return f"❌ Erro ao extrair petição corrigida: {str(e)}"
|
| 504 |
|
| 505 |
-
|
| 506 |
-
dicas = [f"💡 {chave}: {dica}" for chave, dica in DICAS_OAB_MELHORADO.items() if chave.lower() in texto.lower()]
|
| 507 |
-
return "\n".join(dicas) if dicas else "💡 Escreva mais para receber dicas."
|
| 508 |
-
|
| 509 |
-
def calcular_progresso():
|
| 510 |
-
if not HISTORICO:
|
| 511 |
-
return "📊 Nenhum progresso registrado."
|
| 512 |
-
notas = []
|
| 513 |
-
for entry in HISTORICO:
|
| 514 |
-
for line in entry["correcao_completa"].split("\n"):
|
| 515 |
-
if "Nota Final" in line:
|
| 516 |
-
try:
|
| 517 |
-
nota = float(line.split()[-1].split("/")[0].replace(",", "."))
|
| 518 |
-
notas.append(nota)
|
| 519 |
-
except:
|
| 520 |
-
continue
|
| 521 |
-
media = sum(notas) / len(notas) if notas else 0
|
| 522 |
-
tendencia = "⬆️ Melhorando" if len(notas) > 1 and notas[-1] > notas[-2] else "⬇️ Estável ou piorando"
|
| 523 |
-
return f"📊 Progresso: Média {media:.1f}/10 | Tentativas: {len(notas)} | Tendência: {tendencia}"
|
| 524 |
-
|
| 525 |
-
def comparar_peticao_melhorada(caso, peticao_texto, correcao):
|
| 526 |
-
if not caso:
|
| 527 |
-
return "⚠️ Forneça um caso antes de comparar.", "", "", "", ""
|
| 528 |
-
peticao = peticao_texto if peticao_texto else (HISTORICO[-1]["peticao"] if HISTORICO else "")
|
| 529 |
-
if not peticao:
|
| 530 |
-
return "⚠️ Forneça uma petição para comparar ou corrija uma antes.", "", "", "", ""
|
| 531 |
-
if not correcao:
|
| 532 |
-
return "⚠️ Corrija a petição antes de comparar.", "", "", "", ""
|
| 533 |
-
ultima_correcao = HISTORICO[-1]["correcao_completa"] if HISTORICO else ""
|
| 534 |
-
if not ultima_correcao:
|
| 535 |
-
return "⚠️ Nenhuma correção disponível no histórico.", "", "", "", ""
|
| 536 |
-
|
| 537 |
-
peticao_corrigida = extrair_peticao_corrigida(ultima_correcao)
|
| 538 |
-
if "⚠️" in peticao_corrigida or "❌" in peticao_corrigida:
|
| 539 |
-
return peticao_corrigida, "", "", "", ""
|
| 540 |
-
|
| 541 |
-
orig_lines = peticao.split('\n')
|
| 542 |
-
corr_lines = peticao_corrigida.split('\n')
|
| 543 |
-
start_orig = next((i for i, line in enumerate(orig_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0)
|
| 544 |
-
start_corr = next((i for i, line in enumerate(corr_lines) if "AÇÃO" in line.upper() or "EXCELENTÍSSIMO" in line.upper()), 0)
|
| 545 |
-
|
| 546 |
-
orig_body = "\n".join(orig_lines[start_orig:]).strip()
|
| 547 |
-
corr_body = "\n".join(corr_lines[start_corr:]).strip()
|
| 548 |
-
|
| 549 |
-
diff = difflib.ndiff(orig_body.splitlines(), corr_body.splitlines())
|
| 550 |
-
|
| 551 |
-
resultado_diff = []
|
| 552 |
-
criticas = []
|
| 553 |
-
for linha in diff:
|
| 554 |
-
if linha.startswith(' '):
|
| 555 |
-
texto = linha[2:]
|
| 556 |
-
resultado_diff.append(f"<div class='linha-igual'>{escape(texto)}</div>")
|
| 557 |
-
elif linha.startswith('- '):
|
| 558 |
-
texto = linha[2:]
|
| 559 |
-
if any(kw in texto.lower() for kw in ["invenção", "custas", "art. 319"]):
|
| 560 |
-
criticas.append(f"🔴 REMOVIDO: {texto} (inconsistente ou incorreto)")
|
| 561 |
-
resultado_diff.append(f"<div class='linha-removida'><del>{escape(texto)}</del></div>")
|
| 562 |
-
elif linha.startswith('+ '):
|
| 563 |
-
texto = linha[2:]
|
| 564 |
-
if any(kw in texto.lower() for kw in ["custas", "art. 319"]):
|
| 565 |
-
criticas.append(f"🟢 ADICIONADO: {texto} (essencial para conformidade)")
|
| 566 |
-
resultado_diff.append(f"<div class='linha-adicionada'><ins>{escape(texto)}</ins></div>")
|
| 567 |
-
elif linha.startswith('? '):
|
| 568 |
-
continue
|
| 569 |
-
|
| 570 |
-
total_removidas = len([linha for linha in resultado_diff if "linha-removida" in linha])
|
| 571 |
-
total_adicionadas = len([linha for linha in resultado_diff if "linha-adicionada" in linha])
|
| 572 |
-
|
| 573 |
-
resumo_estatistico = f"""
|
| 574 |
-
Linhas Removidas: {total_removidas}
|
| 575 |
-
Linhas Adicionadas: {total_adicionadas}
|
| 576 |
-
Diferença Líquida: {total_adicionadas - total_removidas} linhas
|
| 577 |
-
"""
|
| 578 |
-
|
| 579 |
-
criticas_texto = "\n".join(criticas) if criticas else "Nenhuma mudança crítica detectada."
|
| 580 |
-
|
| 581 |
-
visualizacao_detalhada = f"""
|
| 582 |
-
<style>
|
| 583 |
-
.linha-igual {{ color: #000000; margin: 2px 0; }}
|
| 584 |
-
.linha-removida {{ color: #ffffff; background-color: #b30000; margin: 2px 0; padding: 2px; }}
|
| 585 |
-
.linha-adicionada {{ color: #ffffff; background-color: #006600; margin: 2px 0; padding: 2px; }}
|
| 586 |
-
</style>
|
| 587 |
-
{"".join(resultado_diff)}
|
| 588 |
-
"""
|
| 589 |
-
return peticao, peticao_corrigida, resumo_estatistico, criticas_texto, visualizacao_detalhada
|
| 590 |
-
|
| 591 |
-
def exportar_diferencas_html(caso, peticao_texto, correcao):
|
| 592 |
-
_, _, resumo_estatistico, criticas_texto, visualizacao_detalhada = comparar_peticao_melhorada(caso, peticao_texto, correcao)
|
| 593 |
-
html_content = f"""
|
| 594 |
-
<html>
|
| 595 |
-
<head>
|
| 596 |
-
<title>Diferenças na Petição</title>
|
| 597 |
-
<style>
|
| 598 |
-
body {{ font-family: Arial, sans-serif; margin: 20px; }}
|
| 599 |
-
.linha-igual {{ color: #000000; margin: 2px 0; }}
|
| 600 |
-
.linha-removida {{ color: #ffffff; background-color: #b30000; margin: 2px 0; padding: 2px; }}
|
| 601 |
-
.linha-adicionada {{ color: #ffffff; background-color: #006600; margin: 2px 0; padding: 2px; }}
|
| 602 |
-
h2 {{ color: #333; }}
|
| 603 |
-
pre {{ white-space: pre-wrap; }}
|
| 604 |
-
</style>
|
| 605 |
-
</head>
|
| 606 |
-
<body>
|
| 607 |
-
<h2>Estatísticas</h2>
|
| 608 |
-
<pre>{resumo_estatistico}</pre>
|
| 609 |
-
<h2>Mudanças Críticas</h2>
|
| 610 |
-
<pre>{criticas_texto}</pre>
|
| 611 |
-
<h2>Alterações Detalhadas</h2>
|
| 612 |
-
{visualizacao_detalhada}
|
| 613 |
-
</body>
|
| 614 |
-
</html>
|
| 615 |
-
"""
|
| 616 |
-
try:
|
| 617 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
|
| 618 |
-
temp_path = tmp.name
|
| 619 |
-
HTML(string=html_content).write_pdf(temp_path)
|
| 620 |
-
return temp_path
|
| 621 |
-
except Exception as e:
|
| 622 |
-
return f"❌ Erro ao gerar PDF: {str(e)}"
|
| 623 |
-
finally:
|
| 624 |
-
if 'temp_path' in locals() and os.path.exists(temp_path):
|
| 625 |
-
os.unlink(temp_path)
|
| 626 |
|
| 627 |
def interface_gradio():
|
| 628 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
|
|
| 32 |
"retry_delay": 5,
|
| 33 |
"timeout": 60,
|
| 34 |
"temperatura": 0.2,
|
| 35 |
+
"max_tokens": 8000 # Mantido em 8000, mas dividido em duas chamadas
|
| 36 |
}
|
| 37 |
|
| 38 |
rate_limiter = RateLimiter(max_calls=60, period=60)
|
| 39 |
|
| 40 |
DICAS_OAB_MELHORADO = {
|
| 41 |
+
"Endereçamento": "Em relações de consumo, prefira o foro do domicílio do autor (Art. 101, I, CDC). Exemplo: 'Excelentíssimo Senhor Juiz de Direito da Vara Cível da Comarca de [Cidade do Autor]'.",
|
| 42 |
+
"Qualificação": "Mencione nome, estado civil (se informado), endereço (use 'Rua XXX' se não fornecido). Dica: Sempre verifique se todos os dados das partes estão corretos e completos.",
|
| 43 |
+
"Fatos": "Descreva os fatos cronologicamente, sem inventar informações. Exemplo: 'Em [data], o autor celebrou contrato com o réu para a prestação de serviços de [descrição]'.",
|
| 44 |
+
"Pedidos": "Seja específico nos pedidos (ex.: 'condenação ao pagamento de R$ X'). Dica: Inclua pedidos de danos morais e materiais, se aplicável, com base em precedentes.",
|
| 45 |
+
"Custas": "Inclua 'Requer a juntada da guia de custas devidamente recolhida'. Dica: Nunca se esqueça de mencionar o recolhimento das custas para evitar a extinção do processo.",
|
| 46 |
+
"Fundamentação": "Baseie-se em artigos do CPC ou CDC e, se possível, em súmulas do STJ (ex.: Súmula 387 para danos morais). Exemplo: 'Conforme o artigo 186 do Código Civil, a responsabilidade civil pode ser caracterizada'.",
|
| 47 |
+
"Fechamento": "Finalize com 'Nestes termos pede deferimento', sem vírgula. Dica: Evite erros de pontuação que podem comprometer a formalidade do documento.",
|
| 48 |
+
"Estratégia Jurídica": "Utilize precedentes e súmulas para fortalecer sua argumentação. Exemplo: 'Conforme a Súmula 387 do STJ, os danos morais devem ser comprovados e quantificados'.",
|
| 49 |
+
"Provas": "Especifique as provas que pretende produzir. Dica: Inclua testemunhas, documentos e perícias, se necessário, para comprovar os fatos alegados.",
|
| 50 |
+
"Contra-argumentação": "Antecipe possíveis defesas da parte contrária e prepare respostas. Dica: Considere os argumentos que a outra parte pode usar e esteja preparado para refutá-los."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
|
| 53 |
HISTORICO = []
|
| 54 |
CORRECAO_CACHE = {}
|
| 55 |
|
| 56 |
+
# Funções auxiliares (processamento de arquivos) permanecem iguais, omitidas aqui por brevidade
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
+
def estruturar_prompt_analise(caso, peticao, modo_treinamento=False):
|
| 59 |
+
"""Estrutura o prompt apenas para a análise da petição."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
base_prompt = f"""
|
| 61 |
## Correção Avançada de Petição Inicial Cível (Critérios Realistas da OAB com Análise Especializada)
|
| 62 |
**Caso Fornecido:**
|
|
|
|
| 119 |
**Fechamento** | | |
|
| 120 |
12. Fechamento formal adequado (0,10) | / 0,10 | [Explique erro ou acerto] |
|
| 121 |
**Nota Final:** Calcule a soma dos pontos, aplicando penalizações cumulativas conforme os critérios fundamentais; justifique qualquer desconto adicional.
|
| 122 |
+
"""
|
| 123 |
+
if modo_treinamento:
|
| 124 |
+
base_prompt += f"""
|
| 125 |
+
**Modo de Treinamento Interativo Avançado:**
|
| 126 |
+
- Para cada item com pontuação inferior ao máximo, forneça:
|
| 127 |
+
a) Explicação detalhada (máximo 2 frases curtas) com base legal (ex.: Art. 319, CPC) e impacto processual.
|
| 128 |
+
b) Sugestão de redação corrigida baseada no caso, com linguagem técnica.
|
| 129 |
+
c) Exemplo prático em outro contexto (máximo 1 frase).
|
| 130 |
+
d) Dica prática do banco ({json.dumps(DICAS_OAB_MELHORADO, ensure_ascii=False)}) ou personalizada, curta.
|
| 131 |
+
e) Estratégia jurídica avançada (máximo 1 frase).
|
| 132 |
+
- Verifique a coerência entre os fatos do caso e a petição, listando inconsistências com trechos específicos.
|
| 133 |
+
- Sugira estratégias adicionais baseadas no caso (ex.: cumulação de pedidos, Art. 327, CPC).
|
| 134 |
+
"""
|
| 135 |
+
return base_prompt
|
| 136 |
|
| 137 |
+
def estruturar_prompt_peticao_corrigida(caso, peticao):
|
| 138 |
+
"""Estrutura o prompt apenas para a petição corrigida."""
|
| 139 |
+
return f"""
|
| 140 |
+
**Caso Fornecido:**
|
| 141 |
+
{caso}
|
| 142 |
+
**Petição do Aluno:**
|
| 143 |
+
{peticao}
|
| 144 |
### Petição Corrigida
|
| 145 |
+
Certifique-se de incluir esta seção exatamente como '### Petição Corrigida'. Forneça a petição do aluno reescrita com todas as correções aplicadas, incluindo partes omitidas (como pedidos ou custas) e ajustando erros identificados com base nos seguintes critérios:
|
| 146 |
1. Endereçamento correto (com foro justificado por CPC ou CDC).
|
| 147 |
2. Qualificação completa (Art. 319, II, CPC; use "XXX" para dados ausentes).
|
| 148 |
3. Fatos claros e fiéis ao caso (Art. 319, III, CPC).
|
|
|
|
| 155 |
10. Juntada da guia de custas com redação precisa.
|
| 156 |
11. Fechamento formal com "Local e data.\nADVOGADO.\nOAB." sem identificação específica.
|
| 157 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
|
| 159 |
+
def chamar_api(prompt):
|
|
|
|
| 160 |
payload = {
|
| 161 |
"contents": [{"parts": [{"text": prompt}]}],
|
| 162 |
+
"generationConfig": {"temperature": CONFIG["temperatura"], "maxOutputTokens": CONFIG["max_tokens"], "topP": 0.95}
|
| 163 |
}
|
| 164 |
headers = {"Content-Type": "application/json"}
|
| 165 |
for tentativa in range(CONFIG["max_retry_attempts"]):
|
|
|
|
| 179 |
time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
|
| 180 |
continue
|
| 181 |
return "❌ A API retornou uma resposta vazia. Tente novamente."
|
| 182 |
+
return response_data["candidates"][0]["content"]["parts"][0]["text"].strip()
|
|
|
|
| 183 |
except requests.Timeout:
|
| 184 |
if tentativa < CONFIG["max_retry_attempts"] - 1:
|
| 185 |
time.sleep(CONFIG["retry_delay"] * (tentativa + 1))
|
|
|
|
| 212 |
if cache_key in CORRECAO_CACHE:
|
| 213 |
return aviso_validacao + CORRECAO_CACHE[cache_key]
|
| 214 |
|
| 215 |
+
# Primeira chamada: análise
|
| 216 |
+
prompt_analise = estruturar_prompt_analise(caso, peticao_conteudo, modo_treinamento)
|
| 217 |
+
analise = chamar_api(prompt_analise)
|
| 218 |
+
if "❌" in analise or "⏱️" in analise:
|
| 219 |
+
return aviso_validacao + analise
|
| 220 |
+
analise_formatada = formatar_correcao(analise)
|
| 221 |
+
|
| 222 |
+
# Segunda chamada: petição corrigida
|
| 223 |
+
prompt_peticao = estruturar_prompt_peticao_corrigida(caso, peticao_conteudo)
|
| 224 |
+
peticao_corrigida = chamar_api(prompt_peticao)
|
| 225 |
+
if "❌" in peticao_corrigida or "⏱️" in peticao_corrigida:
|
| 226 |
+
return aviso_validacao + analise_formatada + "\n\n" + peticao_corrigida
|
| 227 |
+
|
| 228 |
+
correcao_completa = analise + "\n\n" + peticao_corrigida
|
| 229 |
+
HISTORICO.append({
|
| 230 |
+
"data": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 231 |
+
"caso": caso,
|
| 232 |
+
"peticao": peticao_conteudo,
|
| 233 |
+
"correcao_completa": correcao_completa
|
| 234 |
+
})
|
| 235 |
+
if len(HISTORICO) > 10:
|
| 236 |
+
HISTORICO.pop(0)
|
| 237 |
+
CORRECAO_CACHE[cache_key] = analise_formatada
|
| 238 |
+
return aviso_validacao + analise_formatada
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
def formatar_correcao(texto):
|
| 241 |
linhas = texto.split('\n')
|
| 242 |
+
texto_formatado = ""
|
| 243 |
for linha in linhas:
|
| 244 |
if "Análise Geral" in linha:
|
| 245 |
texto_formatado += f"### ℹ️ {linha.strip()}\n\n"
|
|
|
|
| 268 |
|
| 269 |
def extrair_peticao_corrigida(correcao_completa):
|
| 270 |
try:
|
|
|
|
| 271 |
if "### Petição Corrigida" in correcao_completa:
|
| 272 |
inicio = correcao_completa.index("### Petição Corrigida") + len("### Petição Corrigida")
|
| 273 |
peticao_texto = correcao_completa[inicio:].strip()
|
|
|
|
| 275 |
peticao_texto = peticao_texto[:fim].strip()
|
| 276 |
return peticao_texto
|
| 277 |
else:
|
|
|
|
| 278 |
linhas = correcao_completa.split('\n')
|
| 279 |
inicio_peticao = -1
|
| 280 |
for i, linha in enumerate(linhas):
|
| 281 |
+
if "EXCELENTÍSSIMO" in linha.upper() and i > len(linhas) // 2:
|
| 282 |
inicio_peticao = i
|
| 283 |
break
|
| 284 |
if inicio_peticao != -1:
|
|
|
|
| 288 |
except Exception as e:
|
| 289 |
return f"❌ Erro ao extrair petição corrigida: {str(e)}"
|
| 290 |
|
| 291 |
+
# Outras funções (comparar_peticao_melhorada, interface_gradio, etc.) permanecem iguais, omitidas por brevidade
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
|
| 293 |
def interface_gradio():
|
| 294 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|