Spaces:
Sleeping
Sleeping
| 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() |