Spaces:
Sleeping
Sleeping
# 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: | |
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 | |
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 | |
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: | |
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)}" | |
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: | |
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: |