Spaces:
Sleeping
Sleeping
| # flask_app_hf.py - English Helper HF Spaces (Sem Autenticação) | |
| import os | |
| import io | |
| import json | |
| import base64 | |
| from datetime import datetime | |
| from flask import Flask, request, jsonify, send_file, render_template, render_template_string, redirect, url_for, Response | |
| try: | |
| from gtts import gTTS | |
| except Exception: | |
| gTTS = None | |
| try: | |
| from groq import Groq | |
| except Exception: | |
| Groq = None | |
| try: | |
| import google.generativeai as genai | |
| from google.generativeai.types import GenerationConfig | |
| except Exception: | |
| genai = None | |
| # --- CONFIGURAÇÃO INICIAL --- | |
| app = Flask(__name__) | |
| # Configuração simplificada para HF Spaces | |
| app.config['SECRET_KEY'] = 'hf-simple-key-no-auth' | |
| app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 | |
| print(f"✅ Flask HF app inicializado") | |
| print(f"✅ Working directory: {os.getcwd()}") | |
| # In-memory storage (suitable for HF Spaces testing only) | |
| IN_MEMORY = { | |
| 'users': {}, # user_id -> record dict | |
| 'flashcards': {}, # user_id -> [items] | |
| 'conversations': {}, # user_id -> [items] | |
| 'analytics': {}, # user_id -> [items] | |
| 'study_plans': {}, # user_id or 'global' -> plan dict/list | |
| } | |
| # Keep legacy DATA_ROOT variable for compatibility checks (unused in-memory) | |
| _module_dir = os.path.dirname(os.path.abspath(__file__)) | |
| DATA_ROOT = os.environ.get('EH_DATA_ROOT', os.path.join(_module_dir, 'hf_data')) | |
| # --- UTILITÁRIOS DE ARMAZENAMENTO --- | |
| def save_user_data(user_id, data_type, data): | |
| """Salvar dados do usuário em memória.""" | |
| try: | |
| store = IN_MEMORY.setdefault(data_type, {}) | |
| lst = store.setdefault(user_id, []) | |
| # attach timestamp | |
| if isinstance(data, dict): | |
| data = dict(data) | |
| entry = data | |
| if isinstance(entry, dict): | |
| entry.setdefault('timestamp', datetime.now().isoformat()) | |
| lst.append(entry) | |
| # keep last 100 | |
| if len(lst) > 100: | |
| store[user_id] = lst[-100:] | |
| return True | |
| except Exception as e: | |
| print(f"Erro ao salvar dados (in-memory): {e}") | |
| return False | |
| def save_user_record(user_id, metadata=None): | |
| """Create or update a simple user record in memory.""" | |
| try: | |
| record = IN_MEMORY['users'].get(user_id, {}) | |
| record['id'] = user_id | |
| record.setdefault('created_at', datetime.now().isoformat()) | |
| if metadata and isinstance(metadata, dict): | |
| record.update(metadata) | |
| IN_MEMORY['users'][user_id] = record | |
| return True | |
| except Exception as e: | |
| print(f"save_user_record error (in-memory): {e}") | |
| return False | |
| def load_user_data(user_id, data_type): | |
| """Load user data from memory.""" | |
| try: | |
| store = IN_MEMORY.get(data_type, {}) | |
| return list(store.get(user_id, [])) | |
| except Exception as e: | |
| print(f"Erro ao carregar dados (in-memory): {e}") | |
| return [] | |
| def get_all_users(): | |
| """Return sorted list of all user ids known in memory.""" | |
| users = set() | |
| users.update(IN_MEMORY.get('users', {}).keys()) | |
| for data_type in ['flashcards', 'conversations', 'analytics', 'study_plans']: | |
| users.update(IN_MEMORY.get(data_type, {}).keys()) | |
| return sorted([u for u in users if u]) | |
| # --- CONFIGURAÇÃO DE APIs --- | |
| # Configurar APIs | |
| groq_client = None | |
| genai_client = None | |
| try: | |
| groq_api_key = os.environ.get('GROQ_API_KEY') | |
| if groq_api_key: | |
| groq_client = Groq(api_key=groq_api_key) | |
| print("✅ Groq API configurada") | |
| except Exception as e: | |
| print(f"⚠️ Groq API não configurada: {e}") | |
| try: | |
| gemini_api_key = os.environ.get('GEMINI_API_KEY') | |
| if gemini_api_key: | |
| genai.configure(api_key=gemini_api_key) | |
| genai_client = genai | |
| print("✅ Gemini API configurada") | |
| except Exception as e: | |
| print(f"⚠️ Gemini API não configurada: {e}") | |
| # --- ROTAS PRINCIPAIS --- | |
| def index(): | |
| return render_template('index.html') | |
| def admin(): | |
| return render_template('admin.html') | |
| def dashboard_redirect(): | |
| """Legacy route: redirect /dashboard to /admin.""" | |
| return redirect(url_for('admin')) | |
| def status(): | |
| return render_template('status.html') | |
| # --- API ENDPOINTS --- | |
| def list_models(): | |
| """Listar modelos disponíveis""" | |
| available_models = [] | |
| if groq_client: | |
| available_models.extend([ | |
| {"name": "Llama 3.2 90B (Ultra Fast)", "value": "groq:llama-3.2-90b-text-preview"}, | |
| {"name": "Llama 3.2 11B Vision (Fast)", "value": "groq:llama-3.2-11b-vision-preview"}, | |
| {"name": "Llama 3.1 70B (Fast)", "value": "groq:llama-3.1-70b-versatile"}, | |
| {"name": "Mixtral 8x7B (Fast)", "value": "groq:mixtral-8x7b-32768"} | |
| ]) | |
| if genai_client: | |
| available_models.extend([ | |
| {"name": "Gemini 2.5 Flash (Recommended)", "value": "gemini:gemini-2.5-flash-latest"}, | |
| {"name": "Gemini 2.5 Pro Experimental", "value": "gemini:gemini-2.5-pro-exp"}, | |
| {"name": "Gemini 1.5 Flash", "value": "gemini:gemini-1.5-flash-latest"}, | |
| {"name": "Gemini 1.5 Pro", "value": "gemini:gemini-1.5-pro-latest"} | |
| ]) | |
| return jsonify(available_models) | |
| # Cache de áudio TTS em memória | |
| tts_cache = {} | |
| def tts_proxy(): | |
| data = request.get_json() | |
| text = data.get('text', '') | |
| tld = data.get('tld', 'co.uk') | |
| if not text: return jsonify({"error": "No text provided"}), 400 | |
| if len(text) > 10000: | |
| return jsonify({"error": "Text is too long. Maximum 10,000 characters allowed."}), 400 | |
| import hashlib | |
| cache_key = hashlib.md5(f"{text}_{tld}".encode()).hexdigest() | |
| try: | |
| if cache_key in tts_cache: | |
| print(f"🎵 TTS Cache HIT: {len(text)} chars") | |
| cached_audio = tts_cache[cache_key] | |
| audio_fp = io.BytesIO(cached_audio) | |
| return send_file(audio_fp, mimetype='audio/mpeg', as_attachment=False) | |
| print(f"🎵 TTS Cache MISS: Gerando áudio para {len(text)} chars, tld: {tld}") | |
| tts = gTTS(text=text, lang='en', tld=tld) | |
| mp3_fp = io.BytesIO() | |
| tts.write_to_fp(mp3_fp) | |
| mp3_fp.seek(0) | |
| audio_data = mp3_fp.read() | |
| tts_cache[cache_key] = audio_data | |
| if len(tts_cache) > 50: | |
| oldest_key = next(iter(tts_cache)) | |
| del tts_cache[oldest_key] | |
| audio_fp = io.BytesIO(audio_data) | |
| return send_file(audio_fp, mimetype='audio/mpeg', as_attachment=False) | |
| except Exception as e: | |
| print(f"❌ TTS Error: {e}") | |
| return jsonify({"error": f"Failed to generate audio via gTTS: {e}"}), 500 | |
| def explain_proxy(): | |
| """Gerar explicação/flashcard""" | |
| data = request.get_json() | |
| selected_text = data.get('selectedText', '') | |
| model_info = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1) | |
| if len(model_info) != 2: | |
| return jsonify({"error": "Invalid model format"}), 400 | |
| model_provider, model_name = model_info | |
| if not selected_text: | |
| return jsonify({"error": "No text selected"}), 400 | |
| try: | |
| prompt = f""" | |
| Create a comprehensive flashcard for the English term/phrase: "{selected_text}" | |
| Provide: | |
| 1. Clear definition in English | |
| 2. Translation to Portuguese | |
| 3. Example sentence using the term | |
| 4. Same sentence with the term replaced by "____" for practice | |
| Return as JSON with keys: definition, translation, context_sentence, gapped_sentence | |
| """ | |
| if model_provider == 'gemini' and genai_client: | |
| model = genai_client.GenerativeModel(model_name) | |
| response = model.generate_content(prompt) | |
| # Extrair JSON da resposta | |
| response_text = response.text | |
| if '```json' in response_text: | |
| json_start = response_text.find('```json') + 7 | |
| json_end = response_text.find('```', json_start) | |
| response_text = response_text[json_start:json_end].strip() | |
| result = json.loads(response_text) | |
| result['term'] = selected_text | |
| return jsonify(result) | |
| elif model_provider == 'groq' and groq_client: | |
| response = groq_client.chat.completions.create( | |
| messages=[{"role": "user", "content": prompt}], | |
| model=model_name, | |
| temperature=0.3 | |
| ) | |
| response_text = response.choices[0].message.content | |
| if '```json' in response_text: | |
| json_start = response_text.find('```json') + 7 | |
| json_end = response_text.find('```', json_start) | |
| response_text = response_text[json_start:json_end].strip() | |
| result = json.loads(response_text) | |
| result['term'] = selected_text | |
| return jsonify(result) | |
| else: | |
| return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured"}), 503 | |
| except Exception as e: | |
| print(f"AI ANALYSIS ERROR: {e}") | |
| return jsonify({"error": str(e)}), 500 | |
| # --- ROUTES DE DADOS (SEM AUTENTICAÇÃO) --- | |
| def get_users(): | |
| """Obter lista de usuários""" | |
| users = get_all_users() | |
| return jsonify({'users': users, 'total': len(users)}) | |
| def create_user(): | |
| """Create a lightweight user record for HF Spaces (no auth).""" | |
| try: | |
| data = request.get_json() or {} | |
| user_id = data.get('user_id') or data.get('email') or data.get('name') | |
| if not user_id: | |
| return jsonify({'success': False, 'error': 'user_id (or email/name) required'}), 400 | |
| # sanitize user_id to a filename-friendly string | |
| safe_id = ''.join(c for c in user_id if c.isalnum() or c in ('-', '_')).lower() | |
| if not safe_id: | |
| return jsonify({'success': False, 'error': 'invalid user_id'}), 400 | |
| ok = save_user_record(safe_id, {'raw': user_id}) | |
| if not ok: | |
| return jsonify({'success': False, 'error': 'failed to save user record'}), 500 | |
| users = get_all_users() | |
| return jsonify({'success': True, 'user_id': safe_id, 'users': users}) | |
| except Exception as e: | |
| print(f"create_user error: {e}") | |
| return jsonify({'success': False, 'error': str(e)}), 500 | |
| def user_flashcards(user_id): | |
| """Gerenciar flashcards do usuário""" | |
| if request.method == 'POST': | |
| data = request.get_json() | |
| if save_user_data(user_id, 'flashcards', data): | |
| return jsonify({'success': True, 'message': 'Flashcard saved'}) | |
| else: | |
| return jsonify({'success': False, 'message': 'Failed to save flashcard'}), 500 | |
| else: | |
| flashcards = load_user_data(user_id, 'flashcards') | |
| return jsonify({'flashcards': flashcards}) | |
| def user_conversations(user_id): | |
| """Gerenciar conversas do usuário""" | |
| if request.method == 'POST': | |
| data = request.get_json() | |
| if save_user_data(user_id, 'conversations', data): | |
| return jsonify({'success': True, 'message': 'Conversation saved'}) | |
| else: | |
| return jsonify({'success': False, 'message': 'Failed to save conversation'}), 500 | |
| else: | |
| conversations = load_user_data(user_id, 'conversations') | |
| return jsonify({'conversations': conversations}) | |
| def user_analytics(user_id): | |
| """Obter analytics do usuário""" | |
| analytics = load_user_data(user_id, 'analytics') | |
| flashcards = load_user_data(user_id, 'flashcards') | |
| conversations = load_user_data(user_id, 'conversations') | |
| return jsonify({ | |
| 'total_flashcards': len(flashcards), | |
| 'total_conversations': len(conversations), | |
| 'total_sessions': len(analytics), | |
| 'recent_activity': analytics[-10:] if analytics else [] | |
| }) | |
| # --- STATUS E ADMIN --- | |
| def admin_stats(): | |
| """Estatísticas do sistema""" | |
| users = get_all_users() | |
| stats = { | |
| 'total_users': len(users), | |
| 'users': [] | |
| } | |
| for user_id in users: | |
| flashcards = len(load_user_data(user_id, 'flashcards')) | |
| conversations = len(load_user_data(user_id, 'conversations')) | |
| stats['users'].append({ | |
| 'user_id': user_id, | |
| 'flashcards': flashcards, | |
| 'conversations': conversations | |
| }) | |
| return jsonify(stats) | |
| def system_status(): | |
| """Status do sistema""" | |
| return jsonify({ | |
| 'status': 'running', | |
| 'version': 'HF-Simplified-1.0', | |
| 'apis': { | |
| 'groq': groq_client is not None, | |
| 'gemini': genai_client is not None | |
| }, | |
| 'storage': 'in_memory', | |
| 'demo_mode': True, | |
| 'auth': 'disabled' | |
| }) | |
| # --- ADMIN / HEALTH / EXPORT (file-based implementations for HF Spaces) --- | |
| def admin_system_health(): | |
| """Return simple system health info using file-based storage (no external deps).""" | |
| try: | |
| import shutil | |
| # compute simple stats from IN_MEMORY | |
| schema = {} | |
| total_rows = 0 | |
| db_size = 0 | |
| for table, table_data in IN_MEMORY.items(): | |
| if isinstance(table_data, dict): | |
| row_count = sum(1 for _ in table_data.keys()) | |
| # approximate size by serializing entries | |
| size = 0 | |
| for k, v in table_data.items(): | |
| try: | |
| size += len(json.dumps(v, ensure_ascii=False).encode('utf-8')) | |
| except Exception: | |
| pass | |
| schema[table] = {'row_count': row_count, 'size_bytes': size} | |
| total_rows += row_count | |
| db_size += size | |
| # disk usage for current filesystem (informational) | |
| try: | |
| du = shutil.disk_usage('.') | |
| disk = { | |
| 'total': du.total, | |
| 'used': du.used, | |
| 'free': du.free, | |
| 'percent': round(du.used / du.total * 100, 2) if du.total else 0 | |
| } | |
| except Exception: | |
| disk = {} | |
| health = { | |
| 'memory': True, | |
| 'disk': disk, | |
| 'database': { | |
| 'storage': 'in_memory', | |
| 'total_rows': total_rows, | |
| 'estimated_size_bytes': db_size, | |
| 'tables': schema | |
| }, | |
| 'uptime': datetime.now().isoformat() | |
| } | |
| return jsonify({'success': True, 'health': health}) | |
| except Exception as e: | |
| print(f"Health endpoint error: {e}") | |
| return jsonify({'success': False, 'error': str(e)}), 500 | |
| def admin_database_schema(): | |
| """Return a simple schema overview derived from hf_data folders.""" | |
| try: | |
| # derive schema from IN_MEMORY | |
| schema = {} | |
| for table, table_data in IN_MEMORY.items(): | |
| cols = [] | |
| row_count = 0 | |
| if isinstance(table_data, dict): | |
| row_count = sum(1 for _ in table_data.keys()) | |
| # infer columns/types from first value | |
| try: | |
| first_val = None | |
| for v in table_data.values(): | |
| first_val = v | |
| break | |
| sample = None | |
| if isinstance(first_val, list) and first_val: | |
| sample = first_val[0] | |
| elif isinstance(first_val, dict): | |
| sample = first_val | |
| if isinstance(sample, dict): | |
| cols = [{'name': k, 'type': type(v).__name__} for k, v in sample.items()] | |
| except Exception: | |
| cols = [] | |
| schema[table] = { | |
| 'row_count': row_count, | |
| 'columns': cols | |
| } | |
| return jsonify({'success': True, 'schema': schema}) | |
| except Exception as e: | |
| print(f"Schema error: {e}") | |
| return jsonify({'success': False, 'error': str(e)}), 500 | |
| def admin_list_users(): | |
| """Return paginated list of users (derived from hf_data files).""" | |
| try: | |
| page = int(request.args.get('page', 1)) | |
| per_page = int(request.args.get('per_page', 20)) | |
| users = get_all_users() | |
| total = len(users) | |
| total_pages = max(1, (total + per_page - 1) // per_page) | |
| start = (page - 1) * per_page | |
| end = start + per_page | |
| users_page = [] | |
| for uid in users[start:end]: | |
| # gather basic stats | |
| flashcards = len(load_user_data(uid, 'flashcards')) | |
| conversations = len(load_user_data(uid, 'conversations')) | |
| analytics = len(load_user_data(uid, 'analytics')) | |
| # created_at from user record if available | |
| created_at = None | |
| try: | |
| urec = IN_MEMORY.get('users', {}).get(uid) | |
| if urec and isinstance(urec, dict): | |
| created_at = urec.get('created_at') | |
| except Exception: | |
| created_at = None | |
| users_page.append({ | |
| 'id': uid, | |
| 'email': uid, | |
| 'created_at': created_at, | |
| 'flashcard_count': flashcards, | |
| 'conversation_count': conversations, | |
| 'session_count': analytics | |
| }) | |
| return jsonify({'success': True, 'data': {'users': users_page, 'page': page, 'per_page': per_page, 'total': total, 'total_pages': total_pages}}) | |
| except Exception as e: | |
| print(f"admin_list_users error: {e}") | |
| return jsonify({'success': False, 'error': str(e)}), 500 | |
| def admin_user_detail(user_id): | |
| """Get detailed info for a user or delete their data (file-based).""" | |
| try: | |
| if request.method == 'DELETE': | |
| # remove in-memory records across tables | |
| removed = [] | |
| for folder in ['flashcards', 'conversations', 'analytics', 'study_plans']: | |
| try: | |
| tbl = IN_MEMORY.get(folder, {}) | |
| if user_id in tbl: | |
| del tbl[user_id] | |
| removed.append(f"{folder}/{user_id}") | |
| except Exception as ex: | |
| print(f"Failed deleting in-memory {folder}/{user_id}: {ex}") | |
| # remove user record | |
| try: | |
| if user_id in IN_MEMORY.get('users', {}): | |
| del IN_MEMORY['users'][user_id] | |
| except Exception: | |
| pass | |
| return jsonify({'success': True, 'deleted': removed}) | |
| # GET -> return analytics, flashcards, conversations | |
| flashcards = load_user_data(user_id, 'flashcards') | |
| conversations = load_user_data(user_id, 'conversations') | |
| analytics = load_user_data(user_id, 'analytics') | |
| # Build token_usage overview if present in analytics entries | |
| token_usage = {} | |
| for entry in analytics: | |
| if isinstance(entry, dict) and 'token_usage' in entry: | |
| for prov, usage in entry['token_usage'].items(): | |
| s = token_usage.setdefault(prov, {'input': 0, 'output': 0, 'calls': 0}) | |
| s['input'] += usage.get('input_tokens', 0) | |
| s['output'] += usage.get('output_tokens', 0) | |
| s['calls'] += 1 | |
| user_info = { | |
| 'user': {'id': user_id, 'email': user_id, 'created_at': None}, | |
| 'flashcards': flashcards, | |
| 'conversations': conversations, | |
| 'recent_sessions': analytics[-10:] if analytics else [], | |
| 'settings': {}, | |
| 'token_usage': [{'provider': k, 'input_tokens': v['input'], 'output_tokens': v['output'], 'calls': v['calls']} for k, v in token_usage.items()] | |
| } | |
| return jsonify({'success': True, 'user': user_info}) | |
| except Exception as e: | |
| print(f"admin_user_detail error: {e}") | |
| return jsonify({'success': False, 'error': str(e)}), 500 | |
| def admin_system_alerts(): | |
| """Return current system alerts (file-based: empty by default).""" | |
| # In HF file-based mode we have no centralized alerting - return empty list | |
| return jsonify({'success': True, 'alerts': []}) | |
| def admin_export_users(): | |
| """Export users list as CSV (file-based).""" | |
| try: | |
| import csv | |
| users = get_all_users() | |
| output = io.StringIO() | |
| writer = csv.writer(output) | |
| writer.writerow(['id', 'email', 'flashcards', 'conversations', 'analytics']) | |
| for uid in users: | |
| fc = len(load_user_data(uid, 'flashcards')) | |
| conv = len(load_user_data(uid, 'conversations')) | |
| an = len(load_user_data(uid, 'analytics')) | |
| writer.writerow([uid, uid, fc, conv, an]) | |
| mem = io.BytesIO(output.getvalue().encode('utf-8')) | |
| mem.seek(0) | |
| return send_file(mem, mimetype='text/csv', as_attachment=True, download_name='users_export.csv') | |
| except Exception as e: | |
| print(f"export users error: {e}") | |
| return jsonify({'success': False, 'error': str(e)}), 500 | |
| def admin_export_tokens(): | |
| """Export token usage summary as CSV (aggregated from analytics).""" | |
| try: | |
| import csv | |
| users = get_all_users() | |
| output = io.StringIO() | |
| writer = csv.writer(output) | |
| writer.writerow(['user_id', 'provider', 'input_tokens', 'output_tokens', 'calls']) | |
| for uid in users: | |
| analytics = load_user_data(uid, 'analytics') | |
| agg = {} | |
| for entry in analytics: | |
| if isinstance(entry, dict) and 'token_usage' in entry: | |
| for prov, usage in entry['token_usage'].items(): | |
| a = agg.setdefault(prov, {'input': 0, 'output': 0, 'calls': 0}) | |
| a['input'] += usage.get('input_tokens', 0) | |
| a['output'] += usage.get('output_tokens', 0) | |
| a['calls'] += 1 | |
| for prov, vals in agg.items(): | |
| writer.writerow([uid, prov, vals['input'], vals['output'], vals['calls']]) | |
| mem = io.BytesIO(output.getvalue().encode('utf-8')) | |
| mem.seek(0) | |
| return send_file(mem, mimetype='text/csv', as_attachment=True, download_name='tokens_export.csv') | |
| except Exception as e: | |
| print(f"export tokens error: {e}") | |
| return jsonify({'success': False, 'error': str(e)}), 500 | |
| def admin_export_all(): | |
| """Package the entire DATA_ROOT into a zip and send for download.""" | |
| try: | |
| import zipfile | |
| mem_zip = io.BytesIO() | |
| with zipfile.ZipFile(mem_zip, 'w', compression=zipfile.ZIP_DEFLATED) as zf: | |
| # dump each table as files | |
| for table, table_data in IN_MEMORY.items(): | |
| if isinstance(table_data, dict): | |
| for uid, val in table_data.items(): | |
| try: | |
| payload = json.dumps(val, ensure_ascii=False, indent=2) | |
| except Exception: | |
| payload = str(val) | |
| arcname = os.path.join(table, f"{uid}.json") | |
| zf.writestr(arcname, payload) | |
| else: | |
| # serialize whole object | |
| try: | |
| payload = json.dumps(table_data, ensure_ascii=False, indent=2) | |
| except Exception: | |
| payload = str(table_data) | |
| arcname = f"{table}.json" | |
| zf.writestr(arcname, payload) | |
| mem_zip.seek(0) | |
| return send_file(mem_zip, mimetype='application/zip', as_attachment=True, download_name='hf_data_export.zip') | |
| except Exception as e: | |
| print(f"export all error: {e}") | |
| return jsonify({'success': False, 'error': str(e)}), 500 | |
| def admin_dashboard_compat(): | |
| """Compatibility endpoint for older admin UI that expects /admin/dashboard.""" | |
| try: | |
| stats = admin_stats() # reuse existing | |
| return jsonify({'success': True, 'stats': stats.get_json() if isinstance(stats, Response) else stats}) | |
| except Exception as e: | |
| print(f"admin_dashboard error: {e}") | |
| return jsonify({'success': False, 'error': str(e)}), 500 | |
| from study_plan import save_study_plan, load_study_plan, generate_study_plan | |
| def study_plan(): | |
| """Salvar ou carregar plano de estudos global (sem autenticação)""" | |
| if request.method == 'POST': | |
| try: | |
| data = request.get_json() | |
| user_id = data.get('user_id') | |
| plan = generate_study_plan(data) | |
| # save globally | |
| save_study_plan(plan) | |
| # also save per-user if provided | |
| if user_id: | |
| save_user_data(user_id, 'study_plans', plan) | |
| return jsonify({'success': True, 'message': 'Study plan generated and saved', 'plan': plan}) | |
| except Exception as e: | |
| print(f"Erro ao salvar study plan: {e}") | |
| return jsonify({'success': False, 'message': 'Failed to save study plan'}), 500 | |
| else: | |
| try: | |
| plan = load_study_plan() | |
| return jsonify({'success': True, 'plan': plan}) | |
| except Exception as e: | |
| print(f"Erro ao carregar study plan: {e}") | |
| return jsonify({'success': False, 'message': 'Failed to load study plan'}), 500 | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=7860, debug=True) |