amauricunha commited on
Commit
0e43b7e
·
verified ·
1 Parent(s): 7198799

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +52 -0
  2. app.py +50 -0
  3. flask_app_hf.py +366 -0
  4. requirements.txt +3 -0
README.md ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: English Helper
3
+ emoji: 🎓
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ app_file: app.py
8
+ pinned: false
9
+ license: mit
10
+ ---
11
+
12
+ # English Helper - HF Spaces
13
+
14
+ Sistema simplificado de aprendizado de inglês para Hugging Face Spaces.
15
+
16
+ ## 🚀 Funcionalidades
17
+
18
+ - **Seleção de Usuário**: Escolha ou crie usuário (sem autenticação)
19
+ - **Text-to-Speech**: Áudio dos textos com gTTS
20
+ - **Flashcards**: Criação automática com IA
21
+ - **Chat**: Conversação básica
22
+ - **Admin Panel**: Gestão de usuários em `/admin`
23
+ - **Status Page**: Monitoramento em `/status`
24
+
25
+ ## 🏗️ Arquitetura Simplificada
26
+
27
+ - **Storage**: Arquivos JSON em `hf_data/`
28
+ - **No Database**: Sistema baseado em arquivos
29
+ - **No Authentication**: Seleção de usuário apenas
30
+ - **Minimal Dependencies**: Flask + requests + gTTS
31
+
32
+ ## 📁 Estrutura
33
+
34
+ ```
35
+ app.py # Entry point HF
36
+ flask_app_hf.py # Flask simplificado
37
+ requirements.txt # Dependências mínimas
38
+ templates/ # Interface HTML
39
+ ├── index.html # Interface principal
40
+ ├── admin.html # Painel admin
41
+ └── status.html # Status do sistema
42
+ ```
43
+
44
+ ## 🎯 Deploy
45
+
46
+ 1. Faça upload de todos os arquivos desta pasta para seu HF Space
47
+ 2. O app roda automaticamente na porta 7860
48
+ 3. Acesse a interface principal, admin (/admin) e status (/status)
49
+
50
+ ---
51
+
52
+ **Versão simplificada para máxima compatibilidade com HF Spaces**
app.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ English Helper - HF Spaces Version (Simplified)
4
+ Sistema de aprendizado de inglês sem autenticação para máxima compatibilidade
5
+ """
6
+
7
+ import os
8
+ import json
9
+ from datetime import datetime
10
+
11
+ def init_simple_storage():
12
+ """Inicializar armazenamento simples baseado em arquivos"""
13
+ try:
14
+ os.makedirs('hf_data', exist_ok=True)
15
+ os.makedirs('hf_data/flashcards', exist_ok=True)
16
+ os.makedirs('hf_data/conversations', exist_ok=True)
17
+ os.makedirs('hf_data/analytics', exist_ok=True)
18
+ print("✅ Sistema de armazenamento HF inicializado")
19
+ return True
20
+ except Exception as e:
21
+ print(f"❌ Erro na inicialização: {e}")
22
+ return False
23
+
24
+ # Executar aplicação
25
+ if __name__ == "__main__":
26
+ print("🎯 English Helper HF - Versão Simplificada")
27
+
28
+ # Inicializar sistema
29
+ if not init_simple_storage():
30
+ print("❌ Falha na inicialização")
31
+ exit(1)
32
+
33
+ try:
34
+ # Importar Flask app simplificado
35
+ import flask_app_hf
36
+ app = flask_app_hf.app
37
+
38
+ print("🚀 Executando Flask na porta 7860 (HF Spaces)")
39
+ app.run(
40
+ host="0.0.0.0",
41
+ port=7860,
42
+ debug=False,
43
+ threaded=True,
44
+ use_reloader=False
45
+ )
46
+
47
+ except Exception as e:
48
+ print(f"❌ Erro: {e}")
49
+ import traceback
50
+ traceback.print_exc()
flask_app_hf.py ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- STUDY PLANNER (PLANO DE ESTUDOS) ---
2
+
3
+ STUDY_PLAN_PATH = 'hf_data/study_plan.json'
4
+
5
+ @app.route('/study-plan', methods=['GET', 'POST'])
6
+ def study_plan():
7
+ """Salvar ou carregar plano de estudos global (sem autenticação)"""
8
+ if request.method == 'POST':
9
+ try:
10
+ data = request.get_json()
11
+ with open(STUDY_PLAN_PATH, 'w', encoding='utf-8') as f:
12
+ json.dump(data, f, ensure_ascii=False, indent=2)
13
+ return jsonify({'success': True, 'message': 'Study plan saved'})
14
+ except Exception as e:
15
+ print(f"Erro ao salvar study plan: {e}")
16
+ return jsonify({'success': False, 'message': 'Failed to save study plan'}), 500
17
+ else:
18
+ try:
19
+ if os.path.exists(STUDY_PLAN_PATH):
20
+ with open(STUDY_PLAN_PATH, 'r', encoding='utf-8') as f:
21
+ plan = json.load(f)
22
+ return jsonify({'success': True, 'plan': plan})
23
+ else:
24
+ return jsonify({'success': True, 'plan': None})
25
+ except Exception as e:
26
+ print(f"Erro ao carregar study plan: {e}")
27
+ return jsonify({'success': False, 'message': 'Failed to load study plan'}), 500
28
+ # flask_app_hf.py - English Helper HF Spaces (Sem Autenticação)
29
+ import os
30
+ import io
31
+ import json
32
+ import base64
33
+ from datetime import datetime
34
+ from flask import Flask, request, jsonify, send_file, render_template_string, redirect, url_for, Response
35
+ from gtts import gTTS
36
+ from groq import Groq
37
+ import google.generativeai as genai
38
+ from google.generativeai.types import GenerationConfig
39
+
40
+ # --- CONFIGURAÇÃO INICIAL ---
41
+ app = Flask(__name__)
42
+
43
+ # Configuração simplificada para HF Spaces
44
+ app.config['SECRET_KEY'] = 'hf-simple-key-no-auth'
45
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
46
+
47
+ print(f"✅ Flask HF app inicializado")
48
+ print(f"✅ Working directory: {os.getcwd()}")
49
+
50
+ # --- UTILITÁRIOS DE ARMAZENAMENTO ---
51
+
52
+ def save_user_data(user_id, data_type, data):
53
+ """Salvar dados do usuário em arquivos JSON"""
54
+ try:
55
+ user_dir = f"hf_data/{data_type}"
56
+ os.makedirs(user_dir, exist_ok=True)
57
+
58
+ file_path = f"{user_dir}/{user_id}.json"
59
+
60
+ # Carregar dados existentes
61
+ existing_data = []
62
+ if os.path.exists(file_path):
63
+ with open(file_path, 'r', encoding='utf-8') as f:
64
+ existing_data = json.load(f)
65
+
66
+ # Adicionar novos dados
67
+ data['timestamp'] = datetime.now().isoformat()
68
+ existing_data.append(data)
69
+
70
+ # Manter apenas os últimos 100 itens
71
+ if len(existing_data) > 100:
72
+ existing_data = existing_data[-100:]
73
+
74
+ # Salvar
75
+ with open(file_path, 'w', encoding='utf-8') as f:
76
+ json.dump(existing_data, f, ensure_ascii=False, indent=2)
77
+
78
+ return True
79
+ except Exception as e:
80
+ print(f"Erro ao salvar dados: {e}")
81
+ return False
82
+
83
+ def load_user_data(user_id, data_type):
84
+ """Carregar dados do usuário"""
85
+ try:
86
+ file_path = f"hf_data/{data_type}/{user_id}.json"
87
+ if os.path.exists(file_path):
88
+ with open(file_path, 'r', encoding='utf-8') as f:
89
+ return json.load(f)
90
+ return []
91
+ except Exception as e:
92
+ print(f"Erro ao carregar dados: {e}")
93
+ return []
94
+
95
+ def get_all_users():
96
+ """Obter lista de todos os usuários"""
97
+ users = set()
98
+ for data_type in ['flashcards', 'conversations', 'analytics']:
99
+ data_dir = f"hf_data/{data_type}"
100
+ if os.path.exists(data_dir):
101
+ for filename in os.listdir(data_dir):
102
+ if filename.endswith('.json'):
103
+ users.add(filename[:-5]) # Remove .json
104
+ return sorted(list(users))
105
+
106
+ # --- CONFIGURAÇÃO DE APIs ---
107
+
108
+ # Configurar APIs
109
+ groq_client = None
110
+ genai_client = None
111
+
112
+ try:
113
+ groq_api_key = os.environ.get('GROQ_API_KEY')
114
+ if groq_api_key:
115
+ groq_client = Groq(api_key=groq_api_key)
116
+ print("✅ Groq API configurada")
117
+ except Exception as e:
118
+ print(f"⚠️ Groq API não configurada: {e}")
119
+
120
+ try:
121
+ gemini_api_key = os.environ.get('GEMINI_API_KEY')
122
+ if gemini_api_key:
123
+ genai.configure(api_key=gemini_api_key)
124
+ genai_client = genai
125
+ print("✅ Gemini API configurada")
126
+ except Exception as e:
127
+ print(f"⚠️ Gemini API não configurada: {e}")
128
+
129
+ # --- ROTAS PRINCIPAIS ---
130
+
131
+ @app.route('/')
132
+ def index():
133
+ return send_file('../templates_hf/index.html')
134
+
135
+ @app.route('/admin')
136
+ def admin():
137
+ return send_file('../templates_hf/admin.html')
138
+
139
+ @app.route('/status')
140
+ def status():
141
+ return send_file('../templates_hf/status.html')
142
+
143
+ # --- API ENDPOINTS ---
144
+
145
+ @app.route('/list-models', methods=['GET'])
146
+ def list_models():
147
+ """Listar modelos disponíveis"""
148
+ available_models = []
149
+
150
+ if groq_client:
151
+ available_models.extend([
152
+ {"name": "Llama 3.2 90B (Ultra Fast)", "value": "groq:llama-3.2-90b-text-preview"},
153
+ {"name": "Llama 3.2 11B Vision (Fast)", "value": "groq:llama-3.2-11b-vision-preview"},
154
+ {"name": "Llama 3.1 70B (Fast)", "value": "groq:llama-3.1-70b-versatile"},
155
+ {"name": "Mixtral 8x7B (Fast)", "value": "groq:mixtral-8x7b-32768"}
156
+ ])
157
+
158
+ if genai_client:
159
+ available_models.extend([
160
+ {"name": "Gemini 2.5 Flash (Recommended)", "value": "gemini:gemini-2.5-flash-latest"},
161
+ {"name": "Gemini 2.5 Pro Experimental", "value": "gemini:gemini-2.5-pro-exp"},
162
+ {"name": "Gemini 1.5 Flash", "value": "gemini:gemini-1.5-flash-latest"},
163
+ {"name": "Gemini 1.5 Pro", "value": "gemini:gemini-1.5-pro-latest"}
164
+ ])
165
+
166
+ return jsonify(available_models)
167
+
168
+ # Cache de áudio TTS em memória
169
+ tts_cache = {}
170
+
171
+ @app.route('/tts-proxy', methods=['POST'])
172
+ def tts_proxy():
173
+ data = request.get_json()
174
+ text = data.get('text', '')
175
+ tld = data.get('tld', 'co.uk')
176
+ if not text: return jsonify({"error": "No text provided"}), 400
177
+
178
+ if len(text) > 10000:
179
+ return jsonify({"error": "Text is too long. Maximum 10,000 characters allowed."}), 400
180
+
181
+ import hashlib
182
+ cache_key = hashlib.md5(f"{text}_{tld}".encode()).hexdigest()
183
+
184
+ try:
185
+ if cache_key in tts_cache:
186
+ print(f"🎵 TTS Cache HIT: {len(text)} chars")
187
+ cached_audio = tts_cache[cache_key]
188
+ audio_fp = io.BytesIO(cached_audio)
189
+ return send_file(audio_fp, mimetype='audio/mpeg', as_attachment=False)
190
+
191
+ print(f"🎵 TTS Cache MISS: Gerando áudio para {len(text)} chars, tld: {tld}")
192
+
193
+ tts = gTTS(text=text, lang='en', tld=tld)
194
+ mp3_fp = io.BytesIO()
195
+ tts.write_to_fp(mp3_fp)
196
+ mp3_fp.seek(0)
197
+
198
+ audio_data = mp3_fp.read()
199
+ tts_cache[cache_key] = audio_data
200
+
201
+ if len(tts_cache) > 50:
202
+ oldest_key = next(iter(tts_cache))
203
+ del tts_cache[oldest_key]
204
+
205
+ audio_fp = io.BytesIO(audio_data)
206
+ return send_file(audio_fp, mimetype='audio/mpeg', as_attachment=False)
207
+
208
+ except Exception as e:
209
+ print(f"❌ TTS Error: {e}")
210
+ return jsonify({"error": f"Failed to generate audio via gTTS: {e}"}), 500
211
+
212
+ @app.route('/explain-proxy', methods=['POST'])
213
+ def explain_proxy():
214
+ """Gerar explicação/flashcard"""
215
+ data = request.get_json()
216
+ selected_text = data.get('selectedText', '')
217
+ model_info = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1)
218
+
219
+ if len(model_info) != 2:
220
+ return jsonify({"error": "Invalid model format"}), 400
221
+
222
+ model_provider, model_name = model_info
223
+
224
+ if not selected_text:
225
+ return jsonify({"error": "No text selected"}), 400
226
+
227
+ try:
228
+ prompt = f"""
229
+ Create a comprehensive flashcard for the English term/phrase: "{selected_text}"
230
+
231
+ Provide:
232
+ 1. Clear definition in English
233
+ 2. Translation to Portuguese
234
+ 3. Example sentence using the term
235
+ 4. Same sentence with the term replaced by "____" for practice
236
+
237
+ Return as JSON with keys: definition, translation, context_sentence, gapped_sentence
238
+ """
239
+
240
+ if model_provider == 'gemini' and genai_client:
241
+ model = genai_client.GenerativeModel(model_name)
242
+ response = model.generate_content(prompt)
243
+
244
+ # Extrair JSON da resposta
245
+ response_text = response.text
246
+ if '```json' in response_text:
247
+ json_start = response_text.find('```json') + 7
248
+ json_end = response_text.find('```', json_start)
249
+ response_text = response_text[json_start:json_end].strip()
250
+
251
+ result = json.loads(response_text)
252
+ result['term'] = selected_text
253
+
254
+ return jsonify(result)
255
+
256
+ elif model_provider == 'groq' and groq_client:
257
+ response = groq_client.chat.completions.create(
258
+ messages=[{"role": "user", "content": prompt}],
259
+ model=model_name,
260
+ temperature=0.3
261
+ )
262
+
263
+ response_text = response.choices[0].message.content
264
+ if '```json' in response_text:
265
+ json_start = response_text.find('```json') + 7
266
+ json_end = response_text.find('```', json_start)
267
+ response_text = response_text[json_start:json_end].strip()
268
+
269
+ result = json.loads(response_text)
270
+ result['term'] = selected_text
271
+
272
+ return jsonify(result)
273
+
274
+ else:
275
+ return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured"}), 503
276
+
277
+ except Exception as e:
278
+ print(f"AI ANALYSIS ERROR: {e}")
279
+ return jsonify({"error": str(e)}), 500
280
+
281
+ # --- ROUTES DE DADOS (SEM AUTENTICAÇÃO) ---
282
+
283
+ @app.route('/users', methods=['GET'])
284
+ def get_users():
285
+ """Obter lista de usuários"""
286
+ users = get_all_users()
287
+ return jsonify({'users': users, 'total': len(users)})
288
+
289
+ @app.route('/user/<user_id>/flashcards', methods=['GET', 'POST'])
290
+ def user_flashcards(user_id):
291
+ """Gerenciar flashcards do usuário"""
292
+ if request.method == 'POST':
293
+ data = request.get_json()
294
+ if save_user_data(user_id, 'flashcards', data):
295
+ return jsonify({'success': True, 'message': 'Flashcard saved'})
296
+ else:
297
+ return jsonify({'success': False, 'message': 'Failed to save flashcard'}), 500
298
+ else:
299
+ flashcards = load_user_data(user_id, 'flashcards')
300
+ return jsonify({'flashcards': flashcards})
301
+
302
+ @app.route('/user/<user_id>/conversations', methods=['GET', 'POST'])
303
+ def user_conversations(user_id):
304
+ """Gerenciar conversas do usuário"""
305
+ if request.method == 'POST':
306
+ data = request.get_json()
307
+ if save_user_data(user_id, 'conversations', data):
308
+ return jsonify({'success': True, 'message': 'Conversation saved'})
309
+ else:
310
+ return jsonify({'success': False, 'message': 'Failed to save conversation'}), 500
311
+ else:
312
+ conversations = load_user_data(user_id, 'conversations')
313
+ return jsonify({'conversations': conversations})
314
+
315
+ @app.route('/user/<user_id>/analytics', methods=['GET'])
316
+ def user_analytics(user_id):
317
+ """Obter analytics do usuário"""
318
+ analytics = load_user_data(user_id, 'analytics')
319
+ flashcards = load_user_data(user_id, 'flashcards')
320
+ conversations = load_user_data(user_id, 'conversations')
321
+
322
+ return jsonify({
323
+ 'total_flashcards': len(flashcards),
324
+ 'total_conversations': len(conversations),
325
+ 'total_sessions': len(analytics),
326
+ 'recent_activity': analytics[-10:] if analytics else []
327
+ })
328
+
329
+ # --- STATUS E ADMIN ---
330
+
331
+ @app.route('/admin/stats', methods=['GET'])
332
+ def admin_stats():
333
+ """Estatísticas do sistema"""
334
+ users = get_all_users()
335
+ stats = {
336
+ 'total_users': len(users),
337
+ 'users': []
338
+ }
339
+
340
+ for user_id in users:
341
+ flashcards = len(load_user_data(user_id, 'flashcards'))
342
+ conversations = len(load_user_data(user_id, 'conversations'))
343
+ stats['users'].append({
344
+ 'user_id': user_id,
345
+ 'flashcards': flashcards,
346
+ 'conversations': conversations
347
+ })
348
+
349
+ return jsonify(stats)
350
+
351
+ @app.route('/system/status', methods=['GET'])
352
+ def system_status():
353
+ """Status do sistema"""
354
+ return jsonify({
355
+ 'status': 'running',
356
+ 'version': 'HF-Simplified-1.0',
357
+ 'apis': {
358
+ 'groq': groq_client is not None,
359
+ 'gemini': genai_client is not None
360
+ },
361
+ 'storage': 'file_based',
362
+ 'auth': 'disabled'
363
+ })
364
+
365
+ if __name__ == '__main__':
366
+ app.run(host='0.0.0.0', port=7860, debug=True)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ flask==2.3.3
2
+ requests==2.31.0
3
+ gtts==2.4.0