# config.py import os from dotenv import load_dotenv load_dotenv() class Config: DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO') MODELS_CACHE_DIR = os.getenv('MODELS_CACHE_DIR', './models') HISTORY_FILE = os.getenv('HISTORY_FILE', 'learning_path_history.json') MAX_AUDIO_LENGTH = int(os.getenv('MAX_AUDIO_LENGTH', '600')) # segundos MAX_TEXT_LENGTH = int(os.getenv('MAX_TEXT_LENGTH', '1000')) SUPPORTED_AUDIO_FORMATS = ['.wav', '.mp3', '.ogg', '.flac'] # Configurações de visualização MAX_TOPICS = int(os.getenv('MAX_TOPICS', '10')) MAX_SUBTOPICS = int(os.getenv('MAX_SUBTOPICS', '5')) FIGURE_DPI = int(os.getenv('FIGURE_DPI', '300')) # Configurações de modelo MODEL_TRANSCRIBER = os.getenv('MODEL_TRANSCRIBER', 'openai/whisper-base') MODEL_GENERATOR = os.getenv('MODEL_GENERATOR', 'gpt2') # Configurações de retry MAX_RETRIES = int(os.getenv('MAX_RETRIES', '3')) RETRY_DELAY = int(os.getenv('RETRY_DELAY', '1')) # utils.py import logging import json from typing import Dict, Any, Optional, List, Tuple import os from config import Config class Utils: @staticmethod def setup_logging() -> logging.Logger: logger = logging.getLogger("LearningPathGenerator") logger.setLevel(getattr(logging, Config.LOG_LEVEL)) # Configuração do arquivo de log handler = logging.FileHandler("app.log") formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) handler.setFormatter(formatter) logger.addHandler(handler) return logger @staticmethod def save_json(data: Dict[str, Any], filename: str) -> bool: try: with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) return True except Exception as e: logging.error(f"Erro ao salvar JSON: {str(e)}") return False @staticmethod def load_json(filename: str) -> Optional[Dict[str, Any]]: try: with open(filename, 'r', encoding='utf-8') as f: return json.load(f) except Exception as e: logging.error(f"Erro ao carregar JSON: {str(e)}") return None # validators.py import os import soundfile as sf from typing import Tuple, Optional from config import Config class Validators: @staticmethod def validate_audio_file(file_path: str) -> Tuple[bool, Optional[str]]: try: if not os.path.exists(file_path): return False, "Arquivo não encontrado" # Verifica extensão ext = os.path.splitext(file_path)[1].lower() if ext not in Config.SUPPORTED_AUDIO_FORMATS: return False, f"Formato não suportado. Use: {Config.SUPPORTED_AUDIO_FORMATS}" # Verifica conteúdo data, samplerate = sf.read(file_path) duration = len(data) / samplerate if duration > Config.MAX_AUDIO_LENGTH: return False, f"Áudio muito longo. Máximo: {Config.MAX_AUDIO_LENGTH}s" return True, None except Exception as e: return False, f"Erro na validação: {str(e)}" @staticmethod def validate_path_name(name: str) -> Tuple[bool, Optional[str]]: if not name: return True, None if len(name) > 100: return False, "Nome muito longo. Máximo: 100 caracteres" if not all(c.isprintable() for c in name): return False, "Nome contém caracteres inválidos" return True, None # models.py from transformers import pipeline import torch from typing import Dict, Optional import logging from config import Config class ModelManager: def __init__(self): self.logger = logging.getLogger("ModelManager") self.models: Dict[str, Any] = {} self._initialize_models() def _initialize_models(self): try: # Verifica GPU device = 0 if torch.cuda.is_available() else -1 self.models["transcriber"] = pipeline( "automatic-speech-recognition", model=Config.MODEL_TRANSCRIBER, device=device ) self.models["generator"] = pipeline( "text-generation", model=Config.MODEL_GENERATOR, device=device ) except Exception as e: self.logger.error(f"Erro ao inicializar modelos: {str(e)}") raise def get_model(self, name: str): return self.models.get(name) # visualization.py import networkx as nx import matplotlib.pyplot as plt from io import BytesIO import base64 from typing import Dict, List from config import Config class Visualizer: @staticmethod def create_mind_map(topics: List[str], subtopics: Dict[str, List[str]]) -> Optional[str]: try: plt.figure(figsize=(15, 10), dpi=Config.FIGURE_DPI) G = nx.DiGraph() # Adiciona nós e arestas for i, topic in enumerate(topics[:Config.MAX_TOPICS]): G.add_node(topic, level=i) if topic in subtopics: for subtopic in subtopics[topic][:Config.MAX_SUBTOPICS]: subtopic_name = f"{topic}:\n{subtopic}" G.add_node(subtopic_name, level=i) G.add_edge(topic, subtopic_name) if i > 0: G.add_edge(topics[i-1], topic) # Layout e estilo pos = nx.spring_layout(G, k=2, iterations=50) # Desenha nós principais nx.draw_networkx_nodes(G, pos, nodelist=[n for n in G.nodes() if ":" not in n], node_color='lightblue', node_size=3000) # Desenha subtópicos nx.draw_networkx_nodes(G, pos, nodelist=[n for n in G.nodes() if ":" in n], node_color='lightgreen', node_size=2000) # Desenha arestas e labels nx.draw_networkx_edges(G, pos, edge_color='gray', arrows=True) nx.draw_networkx_labels(G, pos, font_size=8, font_weight='bold') # Salva imagem buf = BytesIO() plt.savefig(buf, format='png', bbox_inches='tight') buf.seek(0) plt.close() return f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode()}" except Exception as e: logging.error(f"Erro na visualização: {str(e)}") return None # main.py import gradio as gr from typing import Dict, Any import sys import logging from config import Config from utils import Utils from validators import Validators from models import ModelManager from visualization import Visualizer class LearningPathGenerator: def __init__(self): self.logger = Utils.setup_logging() self.model_manager = ModelManager() self.history_file = Config.HISTORY_FILE # Inicialização do histórico if not os.path.exists(self.history_file): Utils.save_json([], self.history_file) def process_audio(self, audio_path: str, path_name: str = "", difficulty: str = "intermediate", include_resources: bool = True) -> Dict[str, Any]: try: # Validação do áudio valid_audio, audio_error = Validators.validate_audio_file(audio_path) if not valid_audio: return self._error_response(audio_error) # Validação do nome valid_name, name_error = Validators.validate_path_name(path_name) if not valid_name: return self._error_response(name_error) # Transcrição transcriber = self.model_manager.get_model("transcriber") transcription = transcriber(audio_path)["text"] # Geração da análise generator = self.model_manager.get_model("generator") analysis = self._generate_analysis(generator, transcription, difficulty) # Criação do mapa mental topics, subtopics = self._extract_topics(analysis) mind_map = Visualizer.create_mind_map(topics, subtopics) # Salva no histórico if path_name: self._save_to_history(transcription, analysis, path_name) return { "transcription": transcription, "analysis": analysis, "mind_map": mind_map } except Exception as e: self.logger.error(f"Erro no processamento: {str(e)}") return self._error_response(str(e)) def create_interface(self): with gr.Blocks(theme=gr.themes.Soft()) as app: gr.Markdown(""" # 🎓 Gerador de Trilha de Aprendizado Carregue um arquivo de áudio descrevendo seus objetivos de aprendizado e receba uma trilha personalizada com recursos! """) with gr.Tab("Gerar Trilha"): with gr.Row(): with gr.Column(scale=2): audio_input = gr.Audio( type="filepath", label="Upload do Áudio", description="Grave ou faça upload de um áudio descrevendo seus objetivos" ) with gr.Row(): path_name = gr.Textbox( label="Nome da Trilha", placeholder="Dê um nome para sua trilha (opcional)" ) difficulty = gr.Dropdown( choices=["iniciante", "intermediário", "avançado"], value="intermediário", label="Nível de Dificuldade" ) with gr.Row(): include_resources = gr.Checkbox( label="Incluir Recursos Recomendados", value=True ) process_btn = gr.Button( "Gerar Trilha de Aprendizado", variant="primary" ) with gr.Row(): text_output = gr.Textbox( label="Transcrição do Áudio", lines=4 ) with gr.Row(): analysis_output = gr.Textbox( label="Análise e Trilha de Aprendizado", lines=10 ) with gr.Row(): mind_map_output = gr.Image( label="Mapa Mental da Trilha", elem_id="mind_map" ) with gr.Tab("Histórico"): gr.Markdown("Trilhas Anteriores") history_table = gr.Dataframe( headers=["Data", "Nome", "Transcrição", "Análise"], label="Histórico de Trilhas" ) refresh_btn = gr.Button("Atualizar Histórico") # Event handlers process_btn.click( fn=self.process_audio, inputs=[audio_input, path_name, difficulty, include_resources], outputs={ "transcription": text_output, "analysis": analysis_output, "mind_map": mind_map_output } ) refresh_btn.click( fn=self._load_history, outputs=[history_table] ) return app def _generate_analysis(self, generator, text: str, difficulty: str) -> str: prompt = f""" Com base no seguinte texto, crie uma trilha de aprendizado detalhada para nível {difficulty}: {text[:Config.MAX_TEXT_LENGTH]} Trilha de aprendizado: """ response = generator( prompt, max_length=300, num_return_sequences=1 )[0]["generated_text"] if include_resources: response += self._generate_resources() return response def _generate_resources(self) -> str: return """ Recursos Recomendados: 1. Livros: - "Guia Essencial do Tema" - "Técnicas Avançadas" 2. Cursos Online: - Coursera: "Especialização no Tema" - edX: "Curso Avançado" 3. Recursos Práticos: - Tutoriais interativos - Exercícios práticos - Projetos do mundo real """ def _error_response(self, error_msg: str) -> Dict[str, Any]: return { "transcription": f"Erro: {error_msg}", "analysis": "Não foi possível gerar a análise devido a um erro.", "mind_map": None } # Execução do app if __name__ == "__main__": try: generator = LearningPathGenerator() app = generator.create_interface() app.launch(debug=Config.DEBUG) except Exception as e: