DHEIVER commited on
Commit
1b63e6c
·
verified ·
1 Parent(s): 315daea

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +411 -0
app.py ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # config.py
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ class Config:
8
+ DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
9
+ LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
10
+ MODELS_CACHE_DIR = os.getenv('MODELS_CACHE_DIR', './models')
11
+ HISTORY_FILE = os.getenv('HISTORY_FILE', 'learning_path_history.json')
12
+ MAX_AUDIO_LENGTH = int(os.getenv('MAX_AUDIO_LENGTH', '600')) # segundos
13
+ MAX_TEXT_LENGTH = int(os.getenv('MAX_TEXT_LENGTH', '1000'))
14
+ SUPPORTED_AUDIO_FORMATS = ['.wav', '.mp3', '.ogg', '.flac']
15
+
16
+ # Configurações de visualização
17
+ MAX_TOPICS = int(os.getenv('MAX_TOPICS', '10'))
18
+ MAX_SUBTOPICS = int(os.getenv('MAX_SUBTOPICS', '5'))
19
+ FIGURE_DPI = int(os.getenv('FIGURE_DPI', '300'))
20
+
21
+ # Configurações de modelo
22
+ MODEL_TRANSCRIBER = os.getenv('MODEL_TRANSCRIBER', 'openai/whisper-base')
23
+ MODEL_GENERATOR = os.getenv('MODEL_GENERATOR', 'gpt2')
24
+
25
+ # Configurações de retry
26
+ MAX_RETRIES = int(os.getenv('MAX_RETRIES', '3'))
27
+ RETRY_DELAY = int(os.getenv('RETRY_DELAY', '1'))
28
+
29
+ # utils.py
30
+ import logging
31
+ import json
32
+ from typing import Dict, Any, Optional, List, Tuple
33
+ import os
34
+ from config import Config
35
+
36
+ class Utils:
37
+ @staticmethod
38
+ def setup_logging() -> logging.Logger:
39
+ logger = logging.getLogger("LearningPathGenerator")
40
+ logger.setLevel(getattr(logging, Config.LOG_LEVEL))
41
+
42
+ # Configuração do arquivo de log
43
+ handler = logging.FileHandler("app.log")
44
+ formatter = logging.Formatter(
45
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
46
+ )
47
+ handler.setFormatter(formatter)
48
+ logger.addHandler(handler)
49
+
50
+ return logger
51
+
52
+ @staticmethod
53
+ def save_json(data: Dict[str, Any], filename: str) -> bool:
54
+ try:
55
+ with open(filename, 'w', encoding='utf-8') as f:
56
+ json.dump(data, f, ensure_ascii=False, indent=2)
57
+ return True
58
+ except Exception as e:
59
+ logging.error(f"Erro ao salvar JSON: {str(e)}")
60
+ return False
61
+
62
+ @staticmethod
63
+ def load_json(filename: str) -> Optional[Dict[str, Any]]:
64
+ try:
65
+ with open(filename, 'r', encoding='utf-8') as f:
66
+ return json.load(f)
67
+ except Exception as e:
68
+ logging.error(f"Erro ao carregar JSON: {str(e)}")
69
+ return None
70
+
71
+ # validators.py
72
+ import os
73
+ import soundfile as sf
74
+ from typing import Tuple, Optional
75
+ from config import Config
76
+
77
+ class Validators:
78
+ @staticmethod
79
+ def validate_audio_file(file_path: str) -> Tuple[bool, Optional[str]]:
80
+ try:
81
+ if not os.path.exists(file_path):
82
+ return False, "Arquivo não encontrado"
83
+
84
+ # Verifica extensão
85
+ ext = os.path.splitext(file_path)[1].lower()
86
+ if ext not in Config.SUPPORTED_AUDIO_FORMATS:
87
+ return False, f"Formato não suportado. Use: {Config.SUPPORTED_AUDIO_FORMATS}"
88
+
89
+ # Verifica conteúdo
90
+ data, samplerate = sf.read(file_path)
91
+ duration = len(data) / samplerate
92
+
93
+ if duration > Config.MAX_AUDIO_LENGTH:
94
+ return False, f"Áudio muito longo. Máximo: {Config.MAX_AUDIO_LENGTH}s"
95
+
96
+ return True, None
97
+
98
+ except Exception as e:
99
+ return False, f"Erro na validação: {str(e)}"
100
+
101
+ @staticmethod
102
+ def validate_path_name(name: str) -> Tuple[bool, Optional[str]]:
103
+ if not name:
104
+ return True, None
105
+
106
+ if len(name) > 100:
107
+ return False, "Nome muito longo. Máximo: 100 caracteres"
108
+
109
+ if not all(c.isprintable() for c in name):
110
+ return False, "Nome contém caracteres inválidos"
111
+
112
+ return True, None
113
+
114
+ # models.py
115
+ from transformers import pipeline
116
+ import torch
117
+ from typing import Dict, Optional
118
+ import logging
119
+ from config import Config
120
+
121
+ class ModelManager:
122
+ def __init__(self):
123
+ self.logger = logging.getLogger("ModelManager")
124
+ self.models: Dict[str, Any] = {}
125
+ self._initialize_models()
126
+
127
+ def _initialize_models(self):
128
+ try:
129
+ # Verifica GPU
130
+ device = 0 if torch.cuda.is_available() else -1
131
+
132
+ self.models["transcriber"] = pipeline(
133
+ "automatic-speech-recognition",
134
+ model=Config.MODEL_TRANSCRIBER,
135
+ device=device
136
+ )
137
+
138
+ self.models["generator"] = pipeline(
139
+ "text-generation",
140
+ model=Config.MODEL_GENERATOR,
141
+ device=device
142
+ )
143
+
144
+ except Exception as e:
145
+ self.logger.error(f"Erro ao inicializar modelos: {str(e)}")
146
+ raise
147
+
148
+ def get_model(self, name: str):
149
+ return self.models.get(name)
150
+
151
+ # visualization.py
152
+ import networkx as nx
153
+ import matplotlib.pyplot as plt
154
+ from io import BytesIO
155
+ import base64
156
+ from typing import Dict, List
157
+ from config import Config
158
+
159
+ class Visualizer:
160
+ @staticmethod
161
+ def create_mind_map(topics: List[str],
162
+ subtopics: Dict[str, List[str]]) -> Optional[str]:
163
+ try:
164
+ plt.figure(figsize=(15, 10), dpi=Config.FIGURE_DPI)
165
+ G = nx.DiGraph()
166
+
167
+ # Adiciona nós e arestas
168
+ for i, topic in enumerate(topics[:Config.MAX_TOPICS]):
169
+ G.add_node(topic, level=i)
170
+
171
+ if topic in subtopics:
172
+ for subtopic in subtopics[topic][:Config.MAX_SUBTOPICS]:
173
+ subtopic_name = f"{topic}:\n{subtopic}"
174
+ G.add_node(subtopic_name, level=i)
175
+ G.add_edge(topic, subtopic_name)
176
+
177
+ if i > 0:
178
+ G.add_edge(topics[i-1], topic)
179
+
180
+ # Layout e estilo
181
+ pos = nx.spring_layout(G, k=2, iterations=50)
182
+
183
+ # Desenha nós principais
184
+ nx.draw_networkx_nodes(G, pos,
185
+ nodelist=[n for n in G.nodes() if ":" not in n],
186
+ node_color='lightblue',
187
+ node_size=3000)
188
+
189
+ # Desenha subtópicos
190
+ nx.draw_networkx_nodes(G, pos,
191
+ nodelist=[n for n in G.nodes() if ":" in n],
192
+ node_color='lightgreen',
193
+ node_size=2000)
194
+
195
+ # Desenha arestas e labels
196
+ nx.draw_networkx_edges(G, pos, edge_color='gray', arrows=True)
197
+ nx.draw_networkx_labels(G, pos, font_size=8, font_weight='bold')
198
+
199
+ # Salva imagem
200
+ buf = BytesIO()
201
+ plt.savefig(buf, format='png', bbox_inches='tight')
202
+ buf.seek(0)
203
+ plt.close()
204
+
205
+ return f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode()}"
206
+
207
+ except Exception as e:
208
+ logging.error(f"Erro na visualização: {str(e)}")
209
+ return None
210
+
211
+ # main.py
212
+ import gradio as gr
213
+ from typing import Dict, Any
214
+ import sys
215
+ import logging
216
+ from config import Config
217
+ from utils import Utils
218
+ from validators import Validators
219
+ from models import ModelManager
220
+ from visualization import Visualizer
221
+
222
+ class LearningPathGenerator:
223
+ def __init__(self):
224
+ self.logger = Utils.setup_logging()
225
+ self.model_manager = ModelManager()
226
+ self.history_file = Config.HISTORY_FILE
227
+
228
+ # Inicialização do histórico
229
+ if not os.path.exists(self.history_file):
230
+ Utils.save_json([], self.history_file)
231
+
232
+ def process_audio(self,
233
+ audio_path: str,
234
+ path_name: str = "",
235
+ difficulty: str = "intermediate",
236
+ include_resources: bool = True) -> Dict[str, Any]:
237
+ try:
238
+ # Validação do áudio
239
+ valid_audio, audio_error = Validators.validate_audio_file(audio_path)
240
+ if not valid_audio:
241
+ return self._error_response(audio_error)
242
+
243
+ # Validação do nome
244
+ valid_name, name_error = Validators.validate_path_name(path_name)
245
+ if not valid_name:
246
+ return self._error_response(name_error)
247
+
248
+ # Transcrição
249
+ transcriber = self.model_manager.get_model("transcriber")
250
+ transcription = transcriber(audio_path)["text"]
251
+
252
+ # Geração da análise
253
+ generator = self.model_manager.get_model("generator")
254
+ analysis = self._generate_analysis(generator, transcription, difficulty)
255
+
256
+ # Criação do mapa mental
257
+ topics, subtopics = self._extract_topics(analysis)
258
+ mind_map = Visualizer.create_mind_map(topics, subtopics)
259
+
260
+ # Salva no histórico
261
+ if path_name:
262
+ self._save_to_history(transcription, analysis, path_name)
263
+
264
+ return {
265
+ "transcription": transcription,
266
+ "analysis": analysis,
267
+ "mind_map": mind_map
268
+ }
269
+
270
+ except Exception as e:
271
+ self.logger.error(f"Erro no processamento: {str(e)}")
272
+ return self._error_response(str(e))
273
+
274
+ def create_interface(self):
275
+ with gr.Blocks(theme=gr.themes.Soft()) as app:
276
+ gr.Markdown("""
277
+ # 🎓 Gerador de Trilha de Aprendizado
278
+
279
+ Carregue um arquivo de áudio descrevendo seus objetivos de aprendizado
280
+ e receba uma trilha personalizada com recursos!
281
+ """)
282
+
283
+ with gr.Tab("Gerar Trilha"):
284
+ with gr.Row():
285
+ with gr.Column(scale=2):
286
+ audio_input = gr.Audio(
287
+ type="filepath",
288
+ label="Upload do Áudio",
289
+ description="Grave ou faça upload de um áudio descrevendo seus objetivos"
290
+ )
291
+
292
+ with gr.Row():
293
+ path_name = gr.Textbox(
294
+ label="Nome da Trilha",
295
+ placeholder="Dê um nome para sua trilha (opcional)"
296
+ )
297
+ difficulty = gr.Dropdown(
298
+ choices=["iniciante", "intermediário", "avançado"],
299
+ value="intermediário",
300
+ label="Nível de Dificuldade"
301
+ )
302
+
303
+ with gr.Row():
304
+ include_resources = gr.Checkbox(
305
+ label="Incluir Recursos Recomendados",
306
+ value=True
307
+ )
308
+ process_btn = gr.Button(
309
+ "Gerar Trilha de Aprendizado",
310
+ variant="primary"
311
+ )
312
+
313
+ with gr.Row():
314
+ text_output = gr.Textbox(
315
+ label="Transcrição do Áudio",
316
+ lines=4
317
+ )
318
+
319
+ with gr.Row():
320
+ analysis_output = gr.Textbox(
321
+ label="Análise e Trilha de Aprendizado",
322
+ lines=10
323
+ )
324
+
325
+ with gr.Row():
326
+ mind_map_output = gr.Image(
327
+ label="Mapa Mental da Trilha",
328
+ elem_id="mind_map"
329
+ )
330
+
331
+ with gr.Tab("Histórico"):
332
+ gr.Markdown("Trilhas Anteriores")
333
+ history_table = gr.Dataframe(
334
+ headers=["Data", "Nome", "Transcrição", "Análise"],
335
+ label="Histórico de Trilhas"
336
+ )
337
+ refresh_btn = gr.Button("Atualizar Histórico")
338
+
339
+ # Event handlers
340
+ process_btn.click(
341
+ fn=self.process_audio,
342
+ inputs=[audio_input, path_name, difficulty, include_resources],
343
+ outputs={
344
+ "transcription": text_output,
345
+ "analysis": analysis_output,
346
+ "mind_map": mind_map_output
347
+ }
348
+ )
349
+
350
+ refresh_btn.click(
351
+ fn=self._load_history,
352
+ outputs=[history_table]
353
+ )
354
+
355
+ return app
356
+
357
+ def _generate_analysis(self,
358
+ generator,
359
+ text: str,
360
+ difficulty: str) -> str:
361
+ prompt = f"""
362
+ Com base no seguinte texto, crie uma trilha de aprendizado detalhada
363
+ para nível {difficulty}:
364
+ {text[:Config.MAX_TEXT_LENGTH]}
365
+
366
+ Trilha de aprendizado:
367
+ """
368
+
369
+ response = generator(
370
+ prompt,
371
+ max_length=300,
372
+ num_return_sequences=1
373
+ )[0]["generated_text"]
374
+
375
+ if include_resources:
376
+ response += self._generate_resources()
377
+
378
+ return response
379
+
380
+ def _generate_resources(self) -> str:
381
+ return """
382
+ Recursos Recomendados:
383
+
384
+ 1. Livros:
385
+ - "Guia Essencial do Tema"
386
+ - "Técnicas Avançadas"
387
+
388
+ 2. Cursos Online:
389
+ - Coursera: "Especialização no Tema"
390
+ - edX: "Curso Avançado"
391
+
392
+ 3. Recursos Práticos:
393
+ - Tutoriais interativos
394
+ - Exercícios práticos
395
+ - Projetos do mundo real
396
+ """
397
+
398
+ def _error_response(self, error_msg: str) -> Dict[str, Any]:
399
+ return {
400
+ "transcription": f"Erro: {error_msg}",
401
+ "analysis": "Não foi possível gerar a análise devido a um erro.",
402
+ "mind_map": None
403
+ }
404
+
405
+ # Execução do app
406
+ if __name__ == "__main__":
407
+ try:
408
+ generator = LearningPathGenerator()
409
+ app = generator.create_interface()
410
+ app.launch(debug=Config.DEBUG)
411
+ except Exception as e: