import gradio as gr import os import json import logging from dotenv import load_dotenv import google.generativeai as genai from fpdf import FPDF from datetime import datetime import time import re # Configuração de Logs logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # Carregar API Key load_dotenv() GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") if GEMINI_API_KEY: genai.configure(api_key=GEMINI_API_KEY) # --- CONFIGURAÇÃO DA IA --- # Usar Flash é o mais rápido e barato MODEL_NAME = "gemini-3-flash-preview" GENERATION_CONFIG = { "temperature": 0.4, # Temperatura mais baixa = menos alucinação e JSON mais estável "max_output_tokens": 15000, "response_mime_type": "application/json" } # --- MATRIZ DE MATÉRIAS --- # Blocos mantidos, mas a estratégia de chamada muda BLOCOS_PROVA = { 1: [("Ética Profissional", 8), ("Filosofia", 2), ("Constitucional", 7), ("Humanos", 2), ("Internacional", 1)], 2: [("Tributário", 5), ("Administrativo", 6), ("Ambiental", 2), ("Civil", 6), ("ECA", 2)], 3: [("Consumidor", 2), ("Empresarial", 5), ("Processo Civil", 7), ("Penal", 6)], 4: [("Processo Penal", 6), ("Trabalho", 6), ("Processo do Trabalho", 5), ("Previdenciário", 2)] } # --- PROMPTS OTIMIZADOS --- def get_prompt_bloco(numero_bloco, lista_materias): materias_str = ", ".join([f"{qtd} de {nome}" for nome, qtd in lista_materias]) return f""" Você é a BANCA FGV (OAB 1ª FASE). Gere o BLOCO {numero_bloco}/4 da prova. ITENS A GERAR (Total 20): {materias_str}. REGRAS RÍGIDAS DE FORMATAÇÃO JSON: 1. Retorne APENAS UM JSON VÁLIDO (Lista de Objetos). 2. NÃO use Markdown (sem ```json no inicio). 3. Enunciados longos (estilo caso concreto). MODELO DE RESPOSTA: [ {{ "materia": "Nome da Matéria", "enunciado": "João da Silva, residente em...", "alternativas": {{"A": "Opção A", "B": "Opção B", "C": "Opção C", "D": "Opção D"}}, "correta": "A", "justificativa": "Conforme Art. X da Lei Y..." }} ] """ # --- PARSER ROBUSTO --- def extrair_json_seguro(texto): """Tenta limpar e extrair JSON mesmo se a IA mandar lixo junto""" try: # Limpeza agressiva texto = texto.strip() texto = re.sub(r'^```json', '', texto, flags=re.MULTILINE) texto = re.sub(r'^```', '', texto, flags=re.MULTILINE) # Tenta parse direto return json.loads(texto) except: # Tenta achar a lista [] try: inicio = texto.find('[') fim = texto.rfind(']') + 1 if inicio != -1 and fim != -1: return json.loads(texto[inicio:fim]) except Exception as e: return [] return [] # --- PDF ENGINE --- class ProvaOAB_PDF(FPDF): def header(self): self.set_font('Arial', 'B', 12) self.cell(0, 10, 'SIMULADO OAB 1a FASE - PADRAO FGV', 0, 1, 'C') self.ln(5) def footer(self): self.set_y(-15) self.set_font('Arial', 'I', 8) self.cell(0, 10, f'Pagina {self.page_no()}', 0, 0, 'C') def safe_text(text): if not text: return "" text = str(text).replace('–', '-').replace('“', '"').replace('”', '"').replace("’", "'") return text.encode('latin-1', 'replace').decode('latin-1') def gerar_pdf_prova(questoes): pdf = ProvaOAB_PDF() pdf.set_auto_page_break(auto=True, margin=15) pdf.add_page() pdf.set_font('Arial', '', 10) pdf.multi_cell(0, 5, "CADERNO DE QUESTOES.\nTempo de prova: 5 horas.") pdf.ln(10) num = 1 for q in questoes: pdf.set_font('Arial', 'B', 11) pdf.set_fill_color(240, 240, 240) titulo = safe_text(f"Questao {num} - {q.get('materia', 'Geral')}") pdf.cell(0, 8, titulo, 0, 1, 'L', 1) pdf.ln(2) pdf.set_font('Arial', '', 10) enunciado = safe_text(q.get('enunciado', '...')) pdf.multi_cell(0, 5, enunciado) pdf.ln(3) alts = q.get('alternativas', {}) for letra in ['A', 'B', 'C', 'D']: texto_alt = safe_text(f"({letra}) {alts.get(letra, '...')}") pdf.multi_cell(0, 5, texto_alt) pdf.ln(1) pdf.ln(5) num += 1 filename = f"Prova_OAB_{datetime.now().strftime('%H%M')}.pdf" pdf.output(filename) return filename def gerar_pdf_gabarito(questoes): pdf = ProvaOAB_PDF() pdf.add_page() pdf.set_font('Arial', 'B', 14) pdf.cell(0, 10, 'GABARITO COMENTADO', 0, 1, 'C') pdf.ln(10) num = 1 for q in questoes: pdf.set_font('Arial', 'B', 10) header = safe_text(f"{num}) Gabarito: {q.get('correta', '?')}") pdf.cell(0, 6, header, 0, 1) pdf.set_font('Arial', '', 10) just = safe_text(f"Justificativa: {q.get('justificativa', 'Sem comentario.')}") pdf.multi_cell(0, 5, just) pdf.ln(3) num += 1 filename = f"Gabarito_OAB_{datetime.now().strftime('%H%M')}.pdf" pdf.output(filename) return filename # --- LOOP COM CONTROLE DE RATE LIMIT --- def gerar_simulado_completo(progress=gr.Progress()): todas_questoes = [] log_erros = "" if not GEMINI_API_KEY: return None, None, "❌ ERRO: API Key não configurada." model = genai.GenerativeModel(model_name=MODEL_NAME, generation_config=GENERATION_CONFIG) # Processar 4 Blocos for i in range(1, 5): progress((i-1)/4, f"Gerando Bloco {i}/4... (Aguardando resfriamento da API)") # === ESTRATÉGIA ANTI-429 (RATE LIMIT) === # Se não for o primeiro bloco, espera 20 segundos OBRIGATORIAMENTE para a cota resetar if i > 1: time.sleep(20) materias = BLOCOS_PROVA[i] prompt = get_prompt_bloco(i, materias) sucesso_bloco = False tentativas = 0 while not sucesso_bloco and tentativas < 3: try: response = model.generate_content(prompt) questoes = extrair_json_seguro(response.text) if questoes and isinstance(questoes, list) and len(questoes) > 0: todas_questoes.extend(questoes) log_erros += f"✅ Bloco {i}: {len(questoes)} questões geradas.\n" sucesso_bloco = True else: raise ValueError("JSON vazio ou inválido") except Exception as e: erro_str = str(e) log_erros += f"⚠️ Erro Bloco {i} (Tentativa {tentativas+1}): {erro_str[:100]}...\n" tentativas += 1 # Se for erro de cota (429), espera mais tempo (30s) if "429" in erro_str or "quota" in erro_str.lower(): log_erros += "⏳ Cota excedida. Esperando 30 segundos...\n" time.sleep(30) else: time.sleep(5) # Erro genérico, espera curta if not sucesso_bloco: log_erros += f"‼️ FALHA CRÍTICA NO BLOCO {i}. Pulando...\n" progress(0.9, "Gerando PDFs...") if len(todas_questoes) == 0: return None, None, f"FALHA TOTAL:\n{log_erros}" try: file_prova = gerar_pdf_prova(todas_questoes) file_gabarito = gerar_pdf_gabarito(todas_questoes) return file_prova, file_gabarito, f"SUCESSO!\nTotal: {len(todas_questoes)} questões.\n\nLog:\n{log_erros}" except Exception as e: return None, None, f"Erro PDF: {str(e)}\nLog:\n{log_erros}" # --- INTERFACE --- def interface(): with gr.Blocks(theme=gr.themes.Soft(), title="JusTutor 1ª Fase") as demo: gr.Markdown("# 🏛️ JusTutor: Simulador OAB 1ª Fase (Modo Econômico)") gr.Markdown("Gerador otimizado para evitar erros de cota (429). O processo é mais lento propositalmente.") with gr.Row(): btn_gerar = gr.Button("🚀 Gerar Simulado (Pode levar 3 a 4 min)", variant="primary") with gr.Row(): status = gr.Textbox(label="Log Detalhado", lines=10) with gr.Row(): down_prova = gr.File(label="📄 Baixar Prova") down_gabarito = gr.File(label="📄 Baixar Gabarito") btn_gerar.click(gerar_simulado_completo, inputs=[], outputs=[down_prova, down_gabarito, status]) return demo if __name__ == "__main__": app = interface() app.launch()