amauricunha commited on
Commit
0143084
Β·
verified Β·
1 Parent(s): 3c0a9b5

Upload 4 files

Browse files
Files changed (4) hide show
  1. app.py +49 -49
  2. flask_app_hf.py +360 -360
  3. requirements.txt +15 -15
  4. study_plan.py +203 -203
app.py CHANGED
@@ -1,50 +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()
 
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 CHANGED
@@ -1,361 +1,361 @@
1
- # flask_app_hf.py - English Helper HF Spaces (Sem AutenticaΓ§Γ£o)
2
- import os
3
- import io
4
- import json
5
- import base64
6
- from datetime import datetime
7
- from flask import Flask, request, jsonify, send_file, render_template_string, redirect, url_for, Response
8
- from gtts import gTTS
9
- from groq import Groq
10
- import google.generativeai as genai
11
- from google.generativeai.types import GenerationConfig
12
-
13
- # --- CONFIGURAÇÃO INICIAL ---
14
- app = Flask(__name__)
15
-
16
- # ConfiguraΓ§Γ£o simplificada para HF Spaces
17
- app.config['SECRET_KEY'] = 'hf-simple-key-no-auth'
18
- app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
19
-
20
- print(f"βœ… Flask HF app inicializado")
21
- print(f"βœ… Working directory: {os.getcwd()}")
22
-
23
- # --- UTILITÁRIOS DE ARMAZENAMENTO ---
24
-
25
- def save_user_data(user_id, data_type, data):
26
- """Salvar dados do usuΓ‘rio em arquivos JSON"""
27
- try:
28
- user_dir = f"hf_data/{data_type}"
29
- os.makedirs(user_dir, exist_ok=True)
30
-
31
- file_path = f"{user_dir}/{user_id}.json"
32
-
33
- # Carregar dados existentes
34
- existing_data = []
35
- if os.path.exists(file_path):
36
- with open(file_path, 'r', encoding='utf-8') as f:
37
- existing_data = json.load(f)
38
-
39
- # Adicionar novos dados
40
- data['timestamp'] = datetime.now().isoformat()
41
- existing_data.append(data)
42
-
43
- # Manter apenas os ΓΊltimos 100 itens
44
- if len(existing_data) > 100:
45
- existing_data = existing_data[-100:]
46
-
47
- # Salvar
48
- with open(file_path, 'w', encoding='utf-8') as f:
49
- json.dump(existing_data, f, ensure_ascii=False, indent=2)
50
-
51
- return True
52
- except Exception as e:
53
- print(f"Erro ao salvar dados: {e}")
54
- return False
55
-
56
- def load_user_data(user_id, data_type):
57
- """Carregar dados do usuΓ‘rio"""
58
- try:
59
- file_path = f"hf_data/{data_type}/{user_id}.json"
60
- if os.path.exists(file_path):
61
- with open(file_path, 'r', encoding='utf-8') as f:
62
- return json.load(f)
63
- return []
64
- except Exception as e:
65
- print(f"Erro ao carregar dados: {e}")
66
- return []
67
-
68
- def get_all_users():
69
- """Obter lista de todos os usuΓ‘rios"""
70
- users = set()
71
- for data_type in ['flashcards', 'conversations', 'analytics']:
72
- data_dir = f"hf_data/{data_type}"
73
- if os.path.exists(data_dir):
74
- for filename in os.listdir(data_dir):
75
- if filename.endswith('.json'):
76
- users.add(filename[:-5]) # Remove .json
77
- return sorted(list(users))
78
-
79
- # --- CONFIGURAÇÃO DE APIs ---
80
-
81
- # Configurar APIs
82
- groq_client = None
83
- genai_client = None
84
-
85
- try:
86
- groq_api_key = os.environ.get('GROQ_API_KEY')
87
- if groq_api_key:
88
- groq_client = Groq(api_key=groq_api_key)
89
- print("βœ… Groq API configurada")
90
- except Exception as e:
91
- print(f"⚠️ Groq API não configurada: {e}")
92
-
93
- try:
94
- gemini_api_key = os.environ.get('GEMINI_API_KEY')
95
- if gemini_api_key:
96
- genai.configure(api_key=gemini_api_key)
97
- genai_client = genai
98
- print("βœ… Gemini API configurada")
99
- except Exception as e:
100
- print(f"⚠️ Gemini API não configurada: {e}")
101
-
102
- # --- ROTAS PRINCIPAIS ---
103
-
104
- @app.route('/')
105
- def index():
106
- return send_file('templates/index.html')
107
-
108
- @app.route('/admin')
109
- def admin():
110
- return send_file('templates/admin.html')
111
-
112
- @app.route('/status')
113
- def status():
114
- return send_file('templates/status.html')
115
-
116
- # --- API ENDPOINTS ---
117
-
118
- @app.route('/list-models', methods=['GET'])
119
- def list_models():
120
- """Listar modelos disponΓ­veis"""
121
- available_models = []
122
-
123
- if groq_client:
124
- available_models.extend([
125
- {"name": "Llama 3.2 90B (Ultra Fast)", "value": "groq:llama-3.2-90b-text-preview"},
126
- {"name": "Llama 3.2 11B Vision (Fast)", "value": "groq:llama-3.2-11b-vision-preview"},
127
- {"name": "Llama 3.1 70B (Fast)", "value": "groq:llama-3.1-70b-versatile"},
128
- {"name": "Mixtral 8x7B (Fast)", "value": "groq:mixtral-8x7b-32768"}
129
- ])
130
-
131
- if genai_client:
132
- available_models.extend([
133
- {"name": "Gemini 2.5 Flash (Recommended)", "value": "gemini:gemini-2.5-flash-latest"},
134
- {"name": "Gemini 2.5 Pro Experimental", "value": "gemini:gemini-2.5-pro-exp"},
135
- {"name": "Gemini 1.5 Flash", "value": "gemini:gemini-1.5-flash-latest"},
136
- {"name": "Gemini 1.5 Pro", "value": "gemini:gemini-1.5-pro-latest"}
137
- ])
138
-
139
- return jsonify(available_models)
140
-
141
- # Cache de Γ‘udio TTS em memΓ³ria
142
- tts_cache = {}
143
-
144
- @app.route('/tts-proxy', methods=['POST'])
145
- def tts_proxy():
146
- data = request.get_json()
147
- text = data.get('text', '')
148
- tld = data.get('tld', 'co.uk')
149
- if not text: return jsonify({"error": "No text provided"}), 400
150
-
151
- if len(text) > 10000:
152
- return jsonify({"error": "Text is too long. Maximum 10,000 characters allowed."}), 400
153
-
154
- import hashlib
155
- cache_key = hashlib.md5(f"{text}_{tld}".encode()).hexdigest()
156
-
157
- try:
158
- if cache_key in tts_cache:
159
- print(f"🎡 TTS Cache HIT: {len(text)} chars")
160
- cached_audio = tts_cache[cache_key]
161
- audio_fp = io.BytesIO(cached_audio)
162
- return send_file(audio_fp, mimetype='audio/mpeg', as_attachment=False)
163
-
164
- print(f"🎡 TTS Cache MISS: Gerando Ñudio para {len(text)} chars, tld: {tld}")
165
-
166
- tts = gTTS(text=text, lang='en', tld=tld)
167
- mp3_fp = io.BytesIO()
168
- tts.write_to_fp(mp3_fp)
169
- mp3_fp.seek(0)
170
-
171
- audio_data = mp3_fp.read()
172
- tts_cache[cache_key] = audio_data
173
-
174
- if len(tts_cache) > 50:
175
- oldest_key = next(iter(tts_cache))
176
- del tts_cache[oldest_key]
177
-
178
- audio_fp = io.BytesIO(audio_data)
179
- return send_file(audio_fp, mimetype='audio/mpeg', as_attachment=False)
180
-
181
- except Exception as e:
182
- print(f"❌ TTS Error: {e}")
183
- return jsonify({"error": f"Failed to generate audio via gTTS: {e}"}), 500
184
-
185
- @app.route('/explain-proxy', methods=['POST'])
186
- def explain_proxy():
187
- """Gerar explicaΓ§Γ£o/flashcard"""
188
- data = request.get_json()
189
- selected_text = data.get('selectedText', '')
190
- model_info = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1)
191
-
192
- if len(model_info) != 2:
193
- return jsonify({"error": "Invalid model format"}), 400
194
-
195
- model_provider, model_name = model_info
196
-
197
- if not selected_text:
198
- return jsonify({"error": "No text selected"}), 400
199
-
200
- try:
201
- prompt = f"""
202
- Create a comprehensive flashcard for the English term/phrase: "{selected_text}"
203
-
204
- Provide:
205
- 1. Clear definition in English
206
- 2. Translation to Portuguese
207
- 3. Example sentence using the term
208
- 4. Same sentence with the term replaced by "____" for practice
209
-
210
- Return as JSON with keys: definition, translation, context_sentence, gapped_sentence
211
- """
212
-
213
- if model_provider == 'gemini' and genai_client:
214
- model = genai_client.GenerativeModel(model_name)
215
- response = model.generate_content(prompt)
216
-
217
- # Extrair JSON da resposta
218
- response_text = response.text
219
- if '```json' in response_text:
220
- json_start = response_text.find('```json') + 7
221
- json_end = response_text.find('```', json_start)
222
- response_text = response_text[json_start:json_end].strip()
223
-
224
- result = json.loads(response_text)
225
- result['term'] = selected_text
226
-
227
- return jsonify(result)
228
-
229
- elif model_provider == 'groq' and groq_client:
230
- response = groq_client.chat.completions.create(
231
- messages=[{"role": "user", "content": prompt}],
232
- model=model_name,
233
- temperature=0.3
234
- )
235
-
236
- response_text = response.choices[0].message.content
237
- if '```json' in response_text:
238
- json_start = response_text.find('```json') + 7
239
- json_end = response_text.find('```', json_start)
240
- response_text = response_text[json_start:json_end].strip()
241
-
242
- result = json.loads(response_text)
243
- result['term'] = selected_text
244
-
245
- return jsonify(result)
246
-
247
- else:
248
- return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured"}), 503
249
-
250
- except Exception as e:
251
- print(f"AI ANALYSIS ERROR: {e}")
252
- return jsonify({"error": str(e)}), 500
253
-
254
- # --- ROUTES DE DADOS (SEM AUTENTICAÇÃO) ---
255
-
256
- @app.route('/users', methods=['GET'])
257
- def get_users():
258
- """Obter lista de usuΓ‘rios"""
259
- users = get_all_users()
260
- return jsonify({'users': users, 'total': len(users)})
261
-
262
- @app.route('/user/<user_id>/flashcards', methods=['GET', 'POST'])
263
- def user_flashcards(user_id):
264
- """Gerenciar flashcards do usuΓ‘rio"""
265
- if request.method == 'POST':
266
- data = request.get_json()
267
- if save_user_data(user_id, 'flashcards', data):
268
- return jsonify({'success': True, 'message': 'Flashcard saved'})
269
- else:
270
- return jsonify({'success': False, 'message': 'Failed to save flashcard'}), 500
271
- else:
272
- flashcards = load_user_data(user_id, 'flashcards')
273
- return jsonify({'flashcards': flashcards})
274
-
275
- @app.route('/user/<user_id>/conversations', methods=['GET', 'POST'])
276
- def user_conversations(user_id):
277
- """Gerenciar conversas do usuΓ‘rio"""
278
- if request.method == 'POST':
279
- data = request.get_json()
280
- if save_user_data(user_id, 'conversations', data):
281
- return jsonify({'success': True, 'message': 'Conversation saved'})
282
- else:
283
- return jsonify({'success': False, 'message': 'Failed to save conversation'}), 500
284
- else:
285
- conversations = load_user_data(user_id, 'conversations')
286
- return jsonify({'conversations': conversations})
287
-
288
- @app.route('/user/<user_id>/analytics', methods=['GET'])
289
- def user_analytics(user_id):
290
- """Obter analytics do usuΓ‘rio"""
291
- analytics = load_user_data(user_id, 'analytics')
292
- flashcards = load_user_data(user_id, 'flashcards')
293
- conversations = load_user_data(user_id, 'conversations')
294
-
295
- return jsonify({
296
- 'total_flashcards': len(flashcards),
297
- 'total_conversations': len(conversations),
298
- 'total_sessions': len(analytics),
299
- 'recent_activity': analytics[-10:] if analytics else []
300
- })
301
-
302
- # --- STATUS E ADMIN ---
303
-
304
- @app.route('/admin/stats', methods=['GET'])
305
- def admin_stats():
306
- """EstatΓ­sticas do sistema"""
307
- users = get_all_users()
308
- stats = {
309
- 'total_users': len(users),
310
- 'users': []
311
- }
312
-
313
- for user_id in users:
314
- flashcards = len(load_user_data(user_id, 'flashcards'))
315
- conversations = len(load_user_data(user_id, 'conversations'))
316
- stats['users'].append({
317
- 'user_id': user_id,
318
- 'flashcards': flashcards,
319
- 'conversations': conversations
320
- })
321
-
322
- return jsonify(stats)
323
-
324
- @app.route('/system/status', methods=['GET'])
325
- def system_status():
326
- """Status do sistema"""
327
- return jsonify({
328
- 'status': 'running',
329
- 'version': 'HF-Simplified-1.0',
330
- 'apis': {
331
- 'groq': groq_client is not None,
332
- 'gemini': genai_client is not None
333
- },
334
- 'storage': 'file_based',
335
- 'auth': 'disabled'
336
- })
337
-
338
- from study_plan import save_study_plan, load_study_plan, generate_study_plan
339
-
340
- @app.route('/study-plan', methods=['GET', 'POST'])
341
- def study_plan():
342
- """Salvar ou carregar plano de estudos global (sem autenticaΓ§Γ£o)"""
343
- if request.method == 'POST':
344
- try:
345
- data = request.get_json()
346
- plan = generate_study_plan(data)
347
- save_study_plan(plan)
348
- return jsonify({'success': True, 'message': 'Study plan generated and saved', 'plan': plan})
349
- except Exception as e:
350
- print(f"Erro ao salvar study plan: {e}")
351
- return jsonify({'success': False, 'message': 'Failed to save study plan'}), 500
352
- else:
353
- try:
354
- plan = load_study_plan()
355
- return jsonify({'success': True, 'plan': plan})
356
- except Exception as e:
357
- print(f"Erro ao carregar study plan: {e}")
358
- return jsonify({'success': False, 'message': 'Failed to load study plan'}), 500
359
-
360
- if __name__ == '__main__':
361
  app.run(host='0.0.0.0', port=7860, debug=True)
 
1
+ # flask_app_hf.py - English Helper HF Spaces (Sem AutenticaΓ§Γ£o)
2
+ import os
3
+ import io
4
+ import json
5
+ import base64
6
+ from datetime import datetime
7
+ from flask import Flask, request, jsonify, send_file, render_template, render_template_string, redirect, url_for, Response
8
+ from gtts import gTTS
9
+ from groq import Groq
10
+ import google.generativeai as genai
11
+ from google.generativeai.types import GenerationConfig
12
+
13
+ # --- CONFIGURAÇÃO INICIAL ---
14
+ app = Flask(__name__)
15
+
16
+ # ConfiguraΓ§Γ£o simplificada para HF Spaces
17
+ app.config['SECRET_KEY'] = 'hf-simple-key-no-auth'
18
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
19
+
20
+ print(f"βœ… Flask HF app inicializado")
21
+ print(f"βœ… Working directory: {os.getcwd()}")
22
+
23
+ # --- UTILITÁRIOS DE ARMAZENAMENTO ---
24
+
25
+ def save_user_data(user_id, data_type, data):
26
+ """Salvar dados do usuΓ‘rio em arquivos JSON"""
27
+ try:
28
+ user_dir = f"hf_data/{data_type}"
29
+ os.makedirs(user_dir, exist_ok=True)
30
+
31
+ file_path = f"{user_dir}/{user_id}.json"
32
+
33
+ # Carregar dados existentes
34
+ existing_data = []
35
+ if os.path.exists(file_path):
36
+ with open(file_path, 'r', encoding='utf-8') as f:
37
+ existing_data = json.load(f)
38
+
39
+ # Adicionar novos dados
40
+ data['timestamp'] = datetime.now().isoformat()
41
+ existing_data.append(data)
42
+
43
+ # Manter apenas os ΓΊltimos 100 itens
44
+ if len(existing_data) > 100:
45
+ existing_data = existing_data[-100:]
46
+
47
+ # Salvar
48
+ with open(file_path, 'w', encoding='utf-8') as f:
49
+ json.dump(existing_data, f, ensure_ascii=False, indent=2)
50
+
51
+ return True
52
+ except Exception as e:
53
+ print(f"Erro ao salvar dados: {e}")
54
+ return False
55
+
56
+ def load_user_data(user_id, data_type):
57
+ """Carregar dados do usuΓ‘rio"""
58
+ try:
59
+ file_path = f"hf_data/{data_type}/{user_id}.json"
60
+ if os.path.exists(file_path):
61
+ with open(file_path, 'r', encoding='utf-8') as f:
62
+ return json.load(f)
63
+ return []
64
+ except Exception as e:
65
+ print(f"Erro ao carregar dados: {e}")
66
+ return []
67
+
68
+ def get_all_users():
69
+ """Obter lista de todos os usuΓ‘rios"""
70
+ users = set()
71
+ for data_type in ['flashcards', 'conversations', 'analytics']:
72
+ data_dir = f"hf_data/{data_type}"
73
+ if os.path.exists(data_dir):
74
+ for filename in os.listdir(data_dir):
75
+ if filename.endswith('.json'):
76
+ users.add(filename[:-5]) # Remove .json
77
+ return sorted(list(users))
78
+
79
+ # --- CONFIGURAÇÃO DE APIs ---
80
+
81
+ # Configurar APIs
82
+ groq_client = None
83
+ genai_client = None
84
+
85
+ try:
86
+ groq_api_key = os.environ.get('GROQ_API_KEY')
87
+ if groq_api_key:
88
+ groq_client = Groq(api_key=groq_api_key)
89
+ print("βœ… Groq API configurada")
90
+ except Exception as e:
91
+ print(f"⚠️ Groq API não configurada: {e}")
92
+
93
+ try:
94
+ gemini_api_key = os.environ.get('GEMINI_API_KEY')
95
+ if gemini_api_key:
96
+ genai.configure(api_key=gemini_api_key)
97
+ genai_client = genai
98
+ print("βœ… Gemini API configurada")
99
+ except Exception as e:
100
+ print(f"⚠️ Gemini API não configurada: {e}")
101
+
102
+ # --- ROTAS PRINCIPAIS ---
103
+
104
+ @app.route('/')
105
+ def index():
106
+ return render_template('index.html')
107
+
108
+ @app.route('/admin')
109
+ def admin():
110
+ return render_template('admin.html')
111
+
112
+ @app.route('/status')
113
+ def status():
114
+ return render_template('status.html')
115
+
116
+ # --- API ENDPOINTS ---
117
+
118
+ @app.route('/list-models', methods=['GET'])
119
+ def list_models():
120
+ """Listar modelos disponΓ­veis"""
121
+ available_models = []
122
+
123
+ if groq_client:
124
+ available_models.extend([
125
+ {"name": "Llama 3.2 90B (Ultra Fast)", "value": "groq:llama-3.2-90b-text-preview"},
126
+ {"name": "Llama 3.2 11B Vision (Fast)", "value": "groq:llama-3.2-11b-vision-preview"},
127
+ {"name": "Llama 3.1 70B (Fast)", "value": "groq:llama-3.1-70b-versatile"},
128
+ {"name": "Mixtral 8x7B (Fast)", "value": "groq:mixtral-8x7b-32768"}
129
+ ])
130
+
131
+ if genai_client:
132
+ available_models.extend([
133
+ {"name": "Gemini 2.5 Flash (Recommended)", "value": "gemini:gemini-2.5-flash-latest"},
134
+ {"name": "Gemini 2.5 Pro Experimental", "value": "gemini:gemini-2.5-pro-exp"},
135
+ {"name": "Gemini 1.5 Flash", "value": "gemini:gemini-1.5-flash-latest"},
136
+ {"name": "Gemini 1.5 Pro", "value": "gemini:gemini-1.5-pro-latest"}
137
+ ])
138
+
139
+ return jsonify(available_models)
140
+
141
+ # Cache de Γ‘udio TTS em memΓ³ria
142
+ tts_cache = {}
143
+
144
+ @app.route('/tts-proxy', methods=['POST'])
145
+ def tts_proxy():
146
+ data = request.get_json()
147
+ text = data.get('text', '')
148
+ tld = data.get('tld', 'co.uk')
149
+ if not text: return jsonify({"error": "No text provided"}), 400
150
+
151
+ if len(text) > 10000:
152
+ return jsonify({"error": "Text is too long. Maximum 10,000 characters allowed."}), 400
153
+
154
+ import hashlib
155
+ cache_key = hashlib.md5(f"{text}_{tld}".encode()).hexdigest()
156
+
157
+ try:
158
+ if cache_key in tts_cache:
159
+ print(f"🎡 TTS Cache HIT: {len(text)} chars")
160
+ cached_audio = tts_cache[cache_key]
161
+ audio_fp = io.BytesIO(cached_audio)
162
+ return send_file(audio_fp, mimetype='audio/mpeg', as_attachment=False)
163
+
164
+ print(f"🎡 TTS Cache MISS: Gerando Ñudio para {len(text)} chars, tld: {tld}")
165
+
166
+ tts = gTTS(text=text, lang='en', tld=tld)
167
+ mp3_fp = io.BytesIO()
168
+ tts.write_to_fp(mp3_fp)
169
+ mp3_fp.seek(0)
170
+
171
+ audio_data = mp3_fp.read()
172
+ tts_cache[cache_key] = audio_data
173
+
174
+ if len(tts_cache) > 50:
175
+ oldest_key = next(iter(tts_cache))
176
+ del tts_cache[oldest_key]
177
+
178
+ audio_fp = io.BytesIO(audio_data)
179
+ return send_file(audio_fp, mimetype='audio/mpeg', as_attachment=False)
180
+
181
+ except Exception as e:
182
+ print(f"❌ TTS Error: {e}")
183
+ return jsonify({"error": f"Failed to generate audio via gTTS: {e}"}), 500
184
+
185
+ @app.route('/explain-proxy', methods=['POST'])
186
+ def explain_proxy():
187
+ """Gerar explicaΓ§Γ£o/flashcard"""
188
+ data = request.get_json()
189
+ selected_text = data.get('selectedText', '')
190
+ model_info = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1)
191
+
192
+ if len(model_info) != 2:
193
+ return jsonify({"error": "Invalid model format"}), 400
194
+
195
+ model_provider, model_name = model_info
196
+
197
+ if not selected_text:
198
+ return jsonify({"error": "No text selected"}), 400
199
+
200
+ try:
201
+ prompt = f"""
202
+ Create a comprehensive flashcard for the English term/phrase: "{selected_text}"
203
+
204
+ Provide:
205
+ 1. Clear definition in English
206
+ 2. Translation to Portuguese
207
+ 3. Example sentence using the term
208
+ 4. Same sentence with the term replaced by "____" for practice
209
+
210
+ Return as JSON with keys: definition, translation, context_sentence, gapped_sentence
211
+ """
212
+
213
+ if model_provider == 'gemini' and genai_client:
214
+ model = genai_client.GenerativeModel(model_name)
215
+ response = model.generate_content(prompt)
216
+
217
+ # Extrair JSON da resposta
218
+ response_text = response.text
219
+ if '```json' in response_text:
220
+ json_start = response_text.find('```json') + 7
221
+ json_end = response_text.find('```', json_start)
222
+ response_text = response_text[json_start:json_end].strip()
223
+
224
+ result = json.loads(response_text)
225
+ result['term'] = selected_text
226
+
227
+ return jsonify(result)
228
+
229
+ elif model_provider == 'groq' and groq_client:
230
+ response = groq_client.chat.completions.create(
231
+ messages=[{"role": "user", "content": prompt}],
232
+ model=model_name,
233
+ temperature=0.3
234
+ )
235
+
236
+ response_text = response.choices[0].message.content
237
+ if '```json' in response_text:
238
+ json_start = response_text.find('```json') + 7
239
+ json_end = response_text.find('```', json_start)
240
+ response_text = response_text[json_start:json_end].strip()
241
+
242
+ result = json.loads(response_text)
243
+ result['term'] = selected_text
244
+
245
+ return jsonify(result)
246
+
247
+ else:
248
+ return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured"}), 503
249
+
250
+ except Exception as e:
251
+ print(f"AI ANALYSIS ERROR: {e}")
252
+ return jsonify({"error": str(e)}), 500
253
+
254
+ # --- ROUTES DE DADOS (SEM AUTENTICAÇÃO) ---
255
+
256
+ @app.route('/users', methods=['GET'])
257
+ def get_users():
258
+ """Obter lista de usuΓ‘rios"""
259
+ users = get_all_users()
260
+ return jsonify({'users': users, 'total': len(users)})
261
+
262
+ @app.route('/user/<user_id>/flashcards', methods=['GET', 'POST'])
263
+ def user_flashcards(user_id):
264
+ """Gerenciar flashcards do usuΓ‘rio"""
265
+ if request.method == 'POST':
266
+ data = request.get_json()
267
+ if save_user_data(user_id, 'flashcards', data):
268
+ return jsonify({'success': True, 'message': 'Flashcard saved'})
269
+ else:
270
+ return jsonify({'success': False, 'message': 'Failed to save flashcard'}), 500
271
+ else:
272
+ flashcards = load_user_data(user_id, 'flashcards')
273
+ return jsonify({'flashcards': flashcards})
274
+
275
+ @app.route('/user/<user_id>/conversations', methods=['GET', 'POST'])
276
+ def user_conversations(user_id):
277
+ """Gerenciar conversas do usuΓ‘rio"""
278
+ if request.method == 'POST':
279
+ data = request.get_json()
280
+ if save_user_data(user_id, 'conversations', data):
281
+ return jsonify({'success': True, 'message': 'Conversation saved'})
282
+ else:
283
+ return jsonify({'success': False, 'message': 'Failed to save conversation'}), 500
284
+ else:
285
+ conversations = load_user_data(user_id, 'conversations')
286
+ return jsonify({'conversations': conversations})
287
+
288
+ @app.route('/user/<user_id>/analytics', methods=['GET'])
289
+ def user_analytics(user_id):
290
+ """Obter analytics do usuΓ‘rio"""
291
+ analytics = load_user_data(user_id, 'analytics')
292
+ flashcards = load_user_data(user_id, 'flashcards')
293
+ conversations = load_user_data(user_id, 'conversations')
294
+
295
+ return jsonify({
296
+ 'total_flashcards': len(flashcards),
297
+ 'total_conversations': len(conversations),
298
+ 'total_sessions': len(analytics),
299
+ 'recent_activity': analytics[-10:] if analytics else []
300
+ })
301
+
302
+ # --- STATUS E ADMIN ---
303
+
304
+ @app.route('/admin/stats', methods=['GET'])
305
+ def admin_stats():
306
+ """EstatΓ­sticas do sistema"""
307
+ users = get_all_users()
308
+ stats = {
309
+ 'total_users': len(users),
310
+ 'users': []
311
+ }
312
+
313
+ for user_id in users:
314
+ flashcards = len(load_user_data(user_id, 'flashcards'))
315
+ conversations = len(load_user_data(user_id, 'conversations'))
316
+ stats['users'].append({
317
+ 'user_id': user_id,
318
+ 'flashcards': flashcards,
319
+ 'conversations': conversations
320
+ })
321
+
322
+ return jsonify(stats)
323
+
324
+ @app.route('/system/status', methods=['GET'])
325
+ def system_status():
326
+ """Status do sistema"""
327
+ return jsonify({
328
+ 'status': 'running',
329
+ 'version': 'HF-Simplified-1.0',
330
+ 'apis': {
331
+ 'groq': groq_client is not None,
332
+ 'gemini': genai_client is not None
333
+ },
334
+ 'storage': 'file_based',
335
+ 'auth': 'disabled'
336
+ })
337
+
338
+ from study_plan import save_study_plan, load_study_plan, generate_study_plan
339
+
340
+ @app.route('/study-plan', methods=['GET', 'POST'])
341
+ def study_plan():
342
+ """Salvar ou carregar plano de estudos global (sem autenticaΓ§Γ£o)"""
343
+ if request.method == 'POST':
344
+ try:
345
+ data = request.get_json()
346
+ plan = generate_study_plan(data)
347
+ save_study_plan(plan)
348
+ return jsonify({'success': True, 'message': 'Study plan generated and saved', 'plan': plan})
349
+ except Exception as e:
350
+ print(f"Erro ao salvar study plan: {e}")
351
+ return jsonify({'success': False, 'message': 'Failed to save study plan'}), 500
352
+ else:
353
+ try:
354
+ plan = load_study_plan()
355
+ return jsonify({'success': True, 'plan': plan})
356
+ except Exception as e:
357
+ print(f"Erro ao carregar study plan: {e}")
358
+ return jsonify({'success': False, 'message': 'Failed to load study plan'}), 500
359
+
360
+ if __name__ == '__main__':
361
  app.run(host='0.0.0.0', port=7860, debug=True)
requirements.txt CHANGED
@@ -1,16 +1,16 @@
1
- flask>=2.3.0
2
- gtts>=2.3.0
3
- google-generativeai>=0.3.0
4
- groq>=0.4.0
5
- requests>=2.31.0
6
- Pillow>=9.5.0
7
- werkzeug>=2.3.0
8
- email-validator>=2.0.0
9
- beautifulsoup4>=4.12.0
10
- feedparser>=6.0.0
11
- matplotlib>=3.7.0
12
- plotly>=5.15.0
13
- pandas>=2.0.0
14
- numpy>=1.24.0
15
- psutil>=5.9.0
16
  gradio>=4.0.0
 
1
+ flask>=2.3.0
2
+ gtts>=2.3.0
3
+ google-generativeai>=0.3.0
4
+ groq>=0.4.0
5
+ requests>=2.31.0
6
+ Pillow>=9.5.0
7
+ werkzeug>=2.3.0
8
+ email-validator>=2.0.0
9
+ beautifulsoup4>=4.12.0
10
+ feedparser>=6.0.0
11
+ matplotlib>=3.7.0
12
+ plotly>=5.15.0
13
+ pandas>=2.0.0
14
+ numpy>=1.24.0
15
+ psutil>=5.9.0
16
  gradio>=4.0.0
study_plan.py CHANGED
@@ -1,203 +1,203 @@
1
- import logging
2
- try:
3
- from groq import Groq
4
- except ImportError:
5
- Groq = None
6
- try:
7
- import google.generativeai as genai
8
- except ImportError:
9
- genai = None
10
-
11
- logger = logging.getLogger(__name__)
12
- groq_client = None
13
- genai_client = None
14
- try:
15
- GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
16
- if GROQ_API_KEY and Groq:
17
- groq_client = Groq(api_key=GROQ_API_KEY)
18
- except Exception as e:
19
- logger.warning(f"Groq client not available: {e}")
20
- try:
21
- GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
22
- if GEMINI_API_KEY and genai:
23
- genai.configure(api_key=GEMINI_API_KEY)
24
- genai_client = genai
25
- except Exception as e:
26
- logger.warning(f"Gemini client not available: {e}")
27
- def generate_study_plan(user_data):
28
- """Gera um plano de estudos estruturado a partir dos dados do usuΓ‘rio (sem IA)."""
29
- from datetime import timedelta
30
- current_level = user_data.get('english_level', 'B1')
31
- target_level = user_data.get('target_level', 'B2')
32
- weekly_hours = user_data.get('weekly_hours', 5)
33
- interests = user_data.get('interests', {})
34
- context_focus = user_data.get('context_focus', 'General/Social')
35
- study_goals = user_data.get('study_goals', [])
36
-
37
- # ProgressΓ£o de nΓ­veis
38
- level_progression = {
39
- 'A1': {'next': 'A2', 'weeks': 12},
40
- 'A2': {'next': 'B1', 'weeks': 16},
41
- 'B1': {'next': 'B2', 'weeks': 20},
42
- 'B2': {'next': 'C1', 'weeks': 24},
43
- 'C1': {'next': 'C2', 'weeks': 28},
44
- 'C2': {'next': 'C2', 'weeks': 32}
45
- }
46
- # Timeline
47
- base_weeks = level_progression.get(current_level, level_progression['B1'])['weeks']
48
- hour_multiplier = 5 / max(weekly_hours, 1)
49
- adjusted_weeks = int(base_weeks * hour_multiplier)
50
- from datetime import datetime
51
- completion_date = (datetime.now() + timedelta(weeks=adjusted_weeks)).date().isoformat()
52
-
53
- # Estrutura semanal
54
- distributions = {
55
- 'A1': {'reading': 0.25, 'flashcards': 0.30, 'conversation': 0.20, 'writing': 0.15, 'grammar': 0.10},
56
- 'A2': {'reading': 0.30, 'flashcards': 0.25, 'conversation': 0.20, 'writing': 0.15, 'grammar': 0.10},
57
- 'B1': {'reading': 0.30, 'flashcards': 0.20, 'conversation': 0.25, 'writing': 0.20, 'listening': 0.05},
58
- 'B2': {'reading': 0.25, 'flashcards': 0.15, 'conversation': 0.25, 'writing': 0.25, 'listening': 0.10},
59
- 'C1': {'reading': 0.30, 'flashcards': 0.10, 'conversation': 0.25, 'writing': 0.25, 'listening': 0.10},
60
- 'C2': {'reading': 0.35, 'flashcards': 0.05, 'conversation': 0.25, 'writing': 0.25, 'listening': 0.10}
61
- }
62
- base_dist = distributions.get(current_level, distributions['B1'])
63
- total_minutes = weekly_hours * 60
64
- weekly_structure = {}
65
- for activity, percentage in base_dist.items():
66
- minutes = int(total_minutes * percentage)
67
- if minutes >= 10:
68
- weekly_structure[activity] = {
69
- 'minutes_per_week': minutes,
70
- 'sessions_per_week': max(1, minutes // 30),
71
- 'minutes_per_session': minutes // max(1, minutes // 30)
72
- }
73
-
74
- # Dicas de estudo (IA se disponΓ­vel)
75
- def get_default_tips(level):
76
- tips_by_level = {
77
- 'A1': [
78
- "πŸ“š Start with basic vocabulary - 10 new words daily",
79
- "🎯 Focus on present tense in daily conversations",
80
- "πŸ’‘ Use picture dictionaries for visual learning",
81
- "⭐ Practice pronunciation with simple audio materials",
82
- "πŸš€ Don't worry about mistakes - communication is key!"
83
- ],
84
- 'A2': [
85
- "πŸ“š Read simple news articles and stories",
86
- "🎯 Practice past and future tenses regularly",
87
- "πŸ’‘ Join basic English conversation groups",
88
- "⭐ Use language learning apps for daily practice",
89
- "πŸš€ Watch movies with subtitles in your language"
90
- ],
91
- 'B1': [
92
- "πŸ“š Read intermediate articles on topics you enjoy",
93
- "🎯 Practice expressing opinions and preferences",
94
- "πŸ’‘ Start writing short paragraphs daily",
95
- "⭐ Listen to podcasts at normal speed",
96
- "πŸš€ Try to think in English for simple tasks"
97
- ],
98
- 'B2': [
99
- "πŸ“š Read longer articles and opinion pieces",
100
- "🎯 Practice formal and informal writing styles",
101
- "πŸ’‘ Engage in debates and discussions",
102
- "⭐ Watch news programs without subtitles",
103
- "πŸš€ Set specific goals for each study session"
104
- ],
105
- 'C1': [
106
- "πŸ“š Read academic and professional texts",
107
- "🎯 Practice nuanced expressions and idioms",
108
- "πŸ’‘ Write formal reports and presentations",
109
- "⭐ Listen to academic lectures and conferences",
110
- "πŸš€ Focus on specialized vocabulary for your field"
111
- ],
112
- 'C2': [
113
- "πŸ“š Read literature and complex analytical texts",
114
- "🎯 Master subtle language differences",
115
- "πŸ’‘ Write with stylistic sophistication",
116
- "⭐ Engage with native speakers in professional contexts",
117
- "πŸš€ Aim for native-like fluency in all skills"
118
- ]
119
- }
120
- return tips_by_level.get(level, tips_by_level['B1'])
121
-
122
- def generate_ai_tips(user_data):
123
- prompt = f"""
124
- Generate 5 personalized English study tips for a user with these characteristics:
125
- - Current Level: {user_data.get('english_level', 'B1')}
126
- - Target Level: {user_data.get('target_level', 'B2')}
127
- - Weekly Study Time: {user_data.get('weekly_hours', 5)} hours
128
- - Context Focus: {user_data.get('context_focus', 'General/Social')}
129
- - Interests: {', '.join(user_data.get('interests', {}).keys())}
130
- Provide practical, actionable tips that are specific to their level and interests. Format as a simple list of tips, each starting with an emoji.
131
- """
132
- try:
133
- if groq_client:
134
- response = groq_client.chat.completions.create(
135
- model="llama-3.1-8b-instant",
136
- messages=[{"role": "user", "content": prompt}],
137
- temperature=0.7
138
- )
139
- response_text = response.choices[0].message.content
140
- elif genai_client:
141
- model = genai_client.GenerativeModel('gemini-2.5-flash-latest')
142
- response = model.generate_content(prompt)
143
- response_text = response.text
144
- else:
145
- return get_default_tips(user_data.get('english_level', 'B1'))
146
- tips = [line.strip() for line in response_text.split('\n') if line.strip() and any(e in line for e in ['πŸ“š','πŸ’‘','🎯','⭐','πŸš€'])]
147
- return tips[:5] if tips else get_default_tips(user_data.get('english_level', 'B1'))
148
- except Exception as e:
149
- logger.warning(f"AI study tips error: {e}")
150
- return get_default_tips(user_data.get('english_level', 'B1'))
151
-
152
- study_tips = generate_ai_tips(user_data)
153
-
154
- # Milestones
155
- milestones = []
156
- milestone_intervals = max(2, adjusted_weeks // 4)
157
- for i in range(1, 5):
158
- week = milestone_intervals * i
159
- if week <= adjusted_weeks:
160
- milestones.append({
161
- 'week': week,
162
- 'title': f"Milestone {i}",
163
- 'description': f"Progress checkpoint {i}",
164
- 'target_date': (datetime.now() + timedelta(weeks=week)).date().isoformat(),
165
- 'completed': False
166
- })
167
-
168
- plan = {
169
- 'id': f"plan_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
170
- 'created_at': datetime.now().isoformat(),
171
- 'current_level': current_level,
172
- 'target_level': target_level,
173
- 'weekly_hours': weekly_hours,
174
- 'estimated_weeks': adjusted_weeks,
175
- 'completion_date': completion_date,
176
- 'weekly_structure': weekly_structure,
177
- 'study_tips': study_tips,
178
- 'milestones': milestones,
179
- 'interests': interests,
180
- 'context_focus': context_focus,
181
- 'study_goals': study_goals
182
- }
183
- return plan
184
- import os
185
- import json
186
- from datetime import datetime
187
-
188
- STUDY_PLAN_PATH = 'hf_data/study_plan.json'
189
-
190
- def save_study_plan(plan_data):
191
- """Salva o plano de estudos em JSON."""
192
- os.makedirs(os.path.dirname(STUDY_PLAN_PATH), exist_ok=True)
193
- plan_data['saved_at'] = datetime.now().isoformat()
194
- with open(STUDY_PLAN_PATH, 'w', encoding='utf-8') as f:
195
- json.dump(plan_data, f, ensure_ascii=False, indent=2)
196
- return True
197
-
198
- def load_study_plan():
199
- """Carrega o plano de estudos do JSON."""
200
- if os.path.exists(STUDY_PLAN_PATH):
201
- with open(STUDY_PLAN_PATH, 'r', encoding='utf-8') as f:
202
- return json.load(f)
203
- return None
 
1
+ import logging
2
+ try:
3
+ from groq import Groq
4
+ except ImportError:
5
+ Groq = None
6
+ try:
7
+ import google.generativeai as genai
8
+ except ImportError:
9
+ genai = None
10
+
11
+ logger = logging.getLogger(__name__)
12
+ groq_client = None
13
+ genai_client = None
14
+ try:
15
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
16
+ if GROQ_API_KEY and Groq:
17
+ groq_client = Groq(api_key=GROQ_API_KEY)
18
+ except Exception as e:
19
+ logger.warning(f"Groq client not available: {e}")
20
+ try:
21
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
22
+ if GEMINI_API_KEY and genai:
23
+ genai.configure(api_key=GEMINI_API_KEY)
24
+ genai_client = genai
25
+ except Exception as e:
26
+ logger.warning(f"Gemini client not available: {e}")
27
+ def generate_study_plan(user_data):
28
+ """Gera um plano de estudos estruturado a partir dos dados do usuΓ‘rio (sem IA)."""
29
+ from datetime import timedelta
30
+ current_level = user_data.get('english_level', 'B1')
31
+ target_level = user_data.get('target_level', 'B2')
32
+ weekly_hours = user_data.get('weekly_hours', 5)
33
+ interests = user_data.get('interests', {})
34
+ context_focus = user_data.get('context_focus', 'General/Social')
35
+ study_goals = user_data.get('study_goals', [])
36
+
37
+ # ProgressΓ£o de nΓ­veis
38
+ level_progression = {
39
+ 'A1': {'next': 'A2', 'weeks': 12},
40
+ 'A2': {'next': 'B1', 'weeks': 16},
41
+ 'B1': {'next': 'B2', 'weeks': 20},
42
+ 'B2': {'next': 'C1', 'weeks': 24},
43
+ 'C1': {'next': 'C2', 'weeks': 28},
44
+ 'C2': {'next': 'C2', 'weeks': 32}
45
+ }
46
+ # Timeline
47
+ base_weeks = level_progression.get(current_level, level_progression['B1'])['weeks']
48
+ hour_multiplier = 5 / max(weekly_hours, 1)
49
+ adjusted_weeks = int(base_weeks * hour_multiplier)
50
+ from datetime import datetime
51
+ completion_date = (datetime.now() + timedelta(weeks=adjusted_weeks)).date().isoformat()
52
+
53
+ # Estrutura semanal
54
+ distributions = {
55
+ 'A1': {'reading': 0.25, 'flashcards': 0.30, 'conversation': 0.20, 'writing': 0.15, 'grammar': 0.10},
56
+ 'A2': {'reading': 0.30, 'flashcards': 0.25, 'conversation': 0.20, 'writing': 0.15, 'grammar': 0.10},
57
+ 'B1': {'reading': 0.30, 'flashcards': 0.20, 'conversation': 0.25, 'writing': 0.20, 'listening': 0.05},
58
+ 'B2': {'reading': 0.25, 'flashcards': 0.15, 'conversation': 0.25, 'writing': 0.25, 'listening': 0.10},
59
+ 'C1': {'reading': 0.30, 'flashcards': 0.10, 'conversation': 0.25, 'writing': 0.25, 'listening': 0.10},
60
+ 'C2': {'reading': 0.35, 'flashcards': 0.05, 'conversation': 0.25, 'writing': 0.25, 'listening': 0.10}
61
+ }
62
+ base_dist = distributions.get(current_level, distributions['B1'])
63
+ total_minutes = weekly_hours * 60
64
+ weekly_structure = {}
65
+ for activity, percentage in base_dist.items():
66
+ minutes = int(total_minutes * percentage)
67
+ if minutes >= 10:
68
+ weekly_structure[activity] = {
69
+ 'minutes_per_week': minutes,
70
+ 'sessions_per_week': max(1, minutes // 30),
71
+ 'minutes_per_session': minutes // max(1, minutes // 30)
72
+ }
73
+
74
+ # Dicas de estudo (IA se disponΓ­vel)
75
+ def get_default_tips(level):
76
+ tips_by_level = {
77
+ 'A1': [
78
+ "πŸ“š Start with basic vocabulary - 10 new words daily",
79
+ "🎯 Focus on present tense in daily conversations",
80
+ "πŸ’‘ Use picture dictionaries for visual learning",
81
+ "⭐ Practice pronunciation with simple audio materials",
82
+ "πŸš€ Don't worry about mistakes - communication is key!"
83
+ ],
84
+ 'A2': [
85
+ "πŸ“š Read simple news articles and stories",
86
+ "🎯 Practice past and future tenses regularly",
87
+ "πŸ’‘ Join basic English conversation groups",
88
+ "⭐ Use language learning apps for daily practice",
89
+ "πŸš€ Watch movies with subtitles in your language"
90
+ ],
91
+ 'B1': [
92
+ "πŸ“š Read intermediate articles on topics you enjoy",
93
+ "🎯 Practice expressing opinions and preferences",
94
+ "πŸ’‘ Start writing short paragraphs daily",
95
+ "⭐ Listen to podcasts at normal speed",
96
+ "πŸš€ Try to think in English for simple tasks"
97
+ ],
98
+ 'B2': [
99
+ "πŸ“š Read longer articles and opinion pieces",
100
+ "🎯 Practice formal and informal writing styles",
101
+ "πŸ’‘ Engage in debates and discussions",
102
+ "⭐ Watch news programs without subtitles",
103
+ "πŸš€ Set specific goals for each study session"
104
+ ],
105
+ 'C1': [
106
+ "πŸ“š Read academic and professional texts",
107
+ "🎯 Practice nuanced expressions and idioms",
108
+ "πŸ’‘ Write formal reports and presentations",
109
+ "⭐ Listen to academic lectures and conferences",
110
+ "πŸš€ Focus on specialized vocabulary for your field"
111
+ ],
112
+ 'C2': [
113
+ "πŸ“š Read literature and complex analytical texts",
114
+ "🎯 Master subtle language differences",
115
+ "πŸ’‘ Write with stylistic sophistication",
116
+ "⭐ Engage with native speakers in professional contexts",
117
+ "πŸš€ Aim for native-like fluency in all skills"
118
+ ]
119
+ }
120
+ return tips_by_level.get(level, tips_by_level['B1'])
121
+
122
+ def generate_ai_tips(user_data):
123
+ prompt = f"""
124
+ Generate 5 personalized English study tips for a user with these characteristics:
125
+ - Current Level: {user_data.get('english_level', 'B1')}
126
+ - Target Level: {user_data.get('target_level', 'B2')}
127
+ - Weekly Study Time: {user_data.get('weekly_hours', 5)} hours
128
+ - Context Focus: {user_data.get('context_focus', 'General/Social')}
129
+ - Interests: {', '.join(user_data.get('interests', {}).keys())}
130
+ Provide practical, actionable tips that are specific to their level and interests. Format as a simple list of tips, each starting with an emoji.
131
+ """
132
+ try:
133
+ if groq_client:
134
+ response = groq_client.chat.completions.create(
135
+ model="llama-3.1-8b-instant",
136
+ messages=[{"role": "user", "content": prompt}],
137
+ temperature=0.7
138
+ )
139
+ response_text = response.choices[0].message.content
140
+ elif genai_client:
141
+ model = genai_client.GenerativeModel('gemini-2.5-flash-latest')
142
+ response = model.generate_content(prompt)
143
+ response_text = response.text
144
+ else:
145
+ return get_default_tips(user_data.get('english_level', 'B1'))
146
+ tips = [line.strip() for line in response_text.split('\n') if line.strip() and any(e in line for e in ['πŸ“š','πŸ’‘','🎯','⭐','πŸš€'])]
147
+ return tips[:5] if tips else get_default_tips(user_data.get('english_level', 'B1'))
148
+ except Exception as e:
149
+ logger.warning(f"AI study tips error: {e}")
150
+ return get_default_tips(user_data.get('english_level', 'B1'))
151
+
152
+ study_tips = generate_ai_tips(user_data)
153
+
154
+ # Milestones
155
+ milestones = []
156
+ milestone_intervals = max(2, adjusted_weeks // 4)
157
+ for i in range(1, 5):
158
+ week = milestone_intervals * i
159
+ if week <= adjusted_weeks:
160
+ milestones.append({
161
+ 'week': week,
162
+ 'title': f"Milestone {i}",
163
+ 'description': f"Progress checkpoint {i}",
164
+ 'target_date': (datetime.now() + timedelta(weeks=week)).date().isoformat(),
165
+ 'completed': False
166
+ })
167
+
168
+ plan = {
169
+ 'id': f"plan_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
170
+ 'created_at': datetime.now().isoformat(),
171
+ 'current_level': current_level,
172
+ 'target_level': target_level,
173
+ 'weekly_hours': weekly_hours,
174
+ 'estimated_weeks': adjusted_weeks,
175
+ 'completion_date': completion_date,
176
+ 'weekly_structure': weekly_structure,
177
+ 'study_tips': study_tips,
178
+ 'milestones': milestones,
179
+ 'interests': interests,
180
+ 'context_focus': context_focus,
181
+ 'study_goals': study_goals
182
+ }
183
+ return plan
184
+ import os
185
+ import json
186
+ from datetime import datetime
187
+
188
+ STUDY_PLAN_PATH = 'hf_data/study_plan.json'
189
+
190
+ def save_study_plan(plan_data):
191
+ """Salva o plano de estudos em JSON."""
192
+ os.makedirs(os.path.dirname(STUDY_PLAN_PATH), exist_ok=True)
193
+ plan_data['saved_at'] = datetime.now().isoformat()
194
+ with open(STUDY_PLAN_PATH, 'w', encoding='utf-8') as f:
195
+ json.dump(plan_data, f, ensure_ascii=False, indent=2)
196
+ return True
197
+
198
+ def load_study_plan():
199
+ """Carrega o plano de estudos do JSON."""
200
+ if os.path.exists(STUDY_PLAN_PATH):
201
+ with open(STUDY_PLAN_PATH, 'r', encoding='utf-8') as f:
202
+ return json.load(f)
203
+ return None