amauricunha commited on
Commit
ab3c972
·
verified ·
1 Parent(s): 3a1532f

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -1247
app.py DELETED
@@ -1,1247 +0,0 @@
1
- #app.py
2
- import os
3
- import io
4
- import json
5
- import base64
6
- from datetime import datetime
7
- from PIL import Image
8
- from email_validator import validate_email, EmailNotValidError
9
-
10
- from flask import Flask, request, jsonify, send_file, session, render_template_string, redirect, url_for, Response
11
- from flask_session import Session
12
- from gtts import gTTS
13
- from groq import Groq
14
- import google.generativeai as genai
15
- from google.generativeai.types import GenerationConfig
16
-
17
- # Import database functions
18
- from database import (
19
- init_db, close_db, create_user, authenticate_user, confirm_email,
20
- get_user_settings, update_user_settings, save_user_flashcard,
21
- get_user_flashcards, record_study_session, login_required,
22
- get_current_user, send_confirmation_email, save_user_article,
23
- get_user_articles, update_user_interests, get_user_interests,
24
- create_study_plan, get_user_study_plans, add_study_activity,
25
- get_study_activities, record_analytics_metric, get_user_analytics,
26
- get_db_connection
27
- )
28
-
29
- # Import content curation and study planner
30
- from content_curator import content_curator
31
- from study_planner import study_planner
32
- from admin_module import admin_manager, admin_required
33
-
34
- # --- CONFIGURAÇÃO INICIAL ---
35
- app = Flask(__name__)
36
-
37
- # Configuration for sessions
38
- app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-key-change-in-production')
39
- app.config['SESSION_TYPE'] = 'filesystem'
40
- app.config['SESSION_PERMANENT'] = False
41
- app.config['SESSION_USE_SIGNER'] = True
42
- app.config['SESSION_KEY_PREFIX'] = 'englishhelper:'
43
-
44
- Session(app)
45
-
46
- # Initialize database
47
- def initialize_database():
48
- init_db()
49
-
50
- # Initialize on startup
51
- initialize_database()
52
-
53
- # Token tracking helper function
54
- def track_token_usage(user_id, provider, input_tokens, output_tokens, operation):
55
- """Helper function to track token usage"""
56
- try:
57
- admin_manager.record_token_usage(user_id, provider, input_tokens, output_tokens, operation)
58
- except Exception as e:
59
- print(f"Token tracking error: {e}")
60
-
61
- @app.teardown_appcontext
62
- def close_database(error):
63
- close_db(error)
64
-
65
- # --- CONFIGURAÇÃO DAS APIS LLM ---
66
- genai_client = None
67
- groq_client = None
68
-
69
- # 1. Configuração Gemini
70
- try:
71
- GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
72
- if GEMINI_API_KEY:
73
- genai.configure(api_key=GEMINI_API_KEY)
74
- genai_client = genai
75
- else:
76
- print("AVISO: GEMINI_API_KEY não configurada.")
77
- except Exception as e:
78
- genai_client = None
79
- print(f"ERRO ao inicializar o cliente Gemini: {e}.")
80
-
81
- # 2. Configuração Groq
82
- try:
83
- GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
84
- if GROQ_API_KEY:
85
- groq_client = Groq(api_key=GROQ_API_KEY)
86
- else:
87
- print("AVISO: GROQ_API_KEY não configurada.")
88
- except Exception as e:
89
- groq_client = None
90
- print(f"ERRO ao inicializar o cliente Groq: {e}.")
91
-
92
-
93
- # --- ROTA PARA LISTAR MODELOS DINAMICAMENTE ---
94
- @app.route('/list-models')
95
- def list_models():
96
- available_models = []
97
- groq_text_models = [
98
- "llama-3.1-8b-instant",
99
- "llama-3.3-70b-versatile",
100
- "openai/gpt-oss-120b",
101
- "openai/gpt-oss-20b"
102
- ]
103
- try:
104
- if genai_client:
105
- for m in genai_client.list_models():
106
- if 'generateContent' in m.supported_generation_methods:
107
- model_name = m.name.replace("models/", "")
108
- if "flash" in model_name or "pro" in model_name:
109
- available_models.append({
110
- "value": f"gemini:{model_name}",
111
- "name": m.display_name
112
- })
113
- if groq_client:
114
- for model_id in groq_text_models:
115
- display_name = model_id.split('/')[-1].replace('-instant', '').replace('-versatile', '')
116
- available_models.append({
117
- "value": f"groq:{model_id}",
118
- "name": f"Groq: {display_name}"
119
- })
120
- except Exception as e:
121
- print(f"Erro ao listar modelos: {e}")
122
- return jsonify([
123
- {"value": "gemini:gemini-2.5-flash-latest", "name": "Gemini 2.5 Flash (Fallback)"},
124
- {"value": "groq:llama-3.1-8b-instant", "name": "Llama 3.1 8B (Fallback)"}
125
- ])
126
- return jsonify(available_models)
127
-
128
-
129
- # --- ROTAS PRINCIPAIS ---
130
-
131
- @app.route('/tts-proxy', methods=['POST'])
132
- def tts_proxy():
133
- data = request.get_json()
134
- text = data.get('text', '')
135
- tld = data.get('tld', 'co.uk')
136
- if not text: return jsonify({"error": "No text provided"}), 400
137
- try:
138
- tts = gTTS(text=text, lang='en', tld=tld)
139
- mp3_fp = io.BytesIO()
140
- tts.write_to_fp(mp3_fp)
141
- mp3_fp.seek(0)
142
- return send_file(mp3_fp, mimetype='audio/mpeg')
143
- except Exception as e:
144
- return jsonify({"error": f"Failed to generate audio via gTTS: {e}"}), 500
145
-
146
- @app.route('/explain-proxy', methods=['POST'])
147
- def explain_proxy():
148
- data = request.get_json()
149
- model_provider, model_name = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1)
150
- context_focus = data.get('context_focus', 'General/Social')
151
- custom_prompt = data.get('custom_prompt', None)
152
- word = data.get('word', '').strip()
153
- context = data.get('context', '')
154
- for_flashcard = data.get('for_flashcard', False)
155
-
156
- if (model_provider == 'gemini' and not genai_client) or (model_provider == 'groq' and not groq_client):
157
- return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured."}), 503
158
-
159
- system_instruction_base = f"You are a professional English tutor. The user's study focus is '{context_focus}'. All your responses must be in ENGLISH."
160
- try:
161
- if custom_prompt:
162
- activity_text = get_ai_text_response(model_provider, model_name, system_instruction_base, custom_prompt)
163
- return jsonify({"explanation": activity_text})
164
-
165
- if not word: return jsonify({"error": "No word selected."}), 400
166
-
167
- if for_flashcard:
168
- schema = {"type": "object", "properties": {"term": {"type": "string"}, "translation": {"type": "string"}, "context_sentence": {"type": "string"}, "gapped_sentence": {"type": "string"}, "definition": {"type": "string"}}, "required": ["term", "translation", "context_sentence", "gapped_sentence", "definition"]}
169
- prompt = f"Analyze '{word}' in context: '{context}'. Generate a JSON for a flashcard. The 'gapped_sentence' must replace '{word}' with '______________'."
170
- return jsonify(get_ai_text_response(model_provider, model_name, system_instruction_base, prompt, json_schema=schema))
171
- else:
172
- prompt = f"Analyze '{word}' in context: '{context}'. Provide a one-sentence English explanation, then '---', then the Portuguese translation."
173
- parts = get_ai_text_response(model_provider, model_name, system_instruction_base, prompt).split('---', 1)
174
- return jsonify({"explanation": parts[0].strip(), "translation": parts[1].strip() if len(parts) > 1 else 'N/A'})
175
- except Exception as e:
176
- print(f"AI ANALYSIS ERROR in /explain-proxy: {e}")
177
- return jsonify({"error": f"AI analysis failed: {e}"}), 500
178
-
179
- @app.route('/activity-feedback', methods=['POST'])
180
- def activity_feedback():
181
- data = request.get_json()
182
- model_provider, model_name = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1)
183
- context_focus = data.get('context_focus', 'General/Social')
184
- original_prompt = data.get('original_prompt', '')
185
- user_response = data.get('user_response', '')
186
-
187
- if not original_prompt or not user_response: return jsonify({"error": "Original prompt and user response are required."}), 400
188
- if (model_provider == 'gemini' and not genai_client) or (model_provider == 'groq' and not groq_client):
189
- return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured."}), 503
190
-
191
- system_instruction = (
192
- "You are an expert English teacher providing feedback. "
193
- f"The user's study focus is '{context_focus}'. "
194
- "Your entire response MUST be in English. "
195
- "Provide clear, constructive feedback on the user's writing. "
196
- "Point out grammar, spelling, or style errors. "
197
- "Offer a corrected or improved version of their text. "
198
- "Structure your feedback with markdown for clarity (e.g., using ### Corrected Version)."
199
- )
200
- user_prompt = f"The original task was: \"{original_prompt}\"\n\nHere is the user's response:\n---\n{user_response}\n---\nPlease provide your feedback."
201
- try:
202
- feedback_text = get_ai_text_response(model_provider, model_name, system_instruction, user_prompt)
203
- return jsonify({"feedback": feedback_text})
204
- except Exception as e:
205
- return jsonify({"error": f"AI feedback failed: {e}"}), 500
206
-
207
- @app.route('/analyze-image', methods=['POST'])
208
- def analyze_image():
209
- if not genai_client: return jsonify({"error": "GEMINI_API_KEY not configured."}), 503
210
- data = request.get_json()
211
- base64_image = data.get('image')
212
- model_value = data.get('model', 'gemini:gemini-2.5-flash-latest')
213
-
214
- model_name = 'gemini-2.5-flash-latest' # Default
215
- if model_value.startswith('gemini:'):
216
- model_name = model_value.split(':', 1)[1]
217
-
218
- if not base64_image: return jsonify({"error": "No image data."}), 400
219
- try:
220
- image = Image.open(io.BytesIO(base64.b64decode(base64_image.split(',')[1])))
221
- model = genai_client.GenerativeModel(model_name)
222
- schema = { "type": "object", "properties": { "vocabulary": { "type": "array", "items": { "type": "object", "properties": { "term": {"type": "string"}, "definition": {"type": "string"} }, "required": ["term", "definition"] } } }, "required": ["vocabulary"] }
223
- prompt = [ "Act as an English teacher. Identify 5-7 key objects/concepts in this image. For each, provide its English name and a simple definition. Return a single JSON object conforming to the schema.", image ]
224
-
225
- config = GenerationConfig(response_mime_type="application/json", response_schema=schema)
226
- response = model.generate_content(prompt, generation_config=config)
227
-
228
- return jsonify(json.loads(response.text)['vocabulary'])
229
- except Exception as e:
230
- return jsonify({"error": f"Image analysis failed: {e}"}), 500
231
-
232
- @app.route('/chat-with-ai', methods=['POST'])
233
- def chat_with_ai():
234
- if not groq_client: return jsonify({"error": "GROQ_API_KEY not configured."}), 503
235
- data = request.get_json()
236
- history, user_message = data.get('history', []), data.get('message', '')
237
- if not user_message: return jsonify({"error": "No message."}), 400
238
- try:
239
- system = "You are 'Groq Chat', a friendly English tutor. Keep responses concise (1-2 sentences). If the user makes a grammar mistake, gently correct it. Ask questions to keep the conversation flowing. Always respond in English."
240
- messages = [{"role": "system", "content": system}] + history + [{"role": "user", "content": user_message}]
241
- response = groq_client.chat.completions.create(model="llama-3.1-8b-instant", messages=messages, temperature=0.7)
242
-
243
- # Track token usage
244
- user = get_current_user()
245
- if user and hasattr(response, 'usage'):
246
- track_token_usage(
247
- user['id'],
248
- 'groq',
249
- response.usage.prompt_tokens,
250
- response.usage.completion_tokens,
251
- 'conversation'
252
- )
253
-
254
- return jsonify({"response": response.choices[0].message.content.strip()})
255
- except Exception as e:
256
- return jsonify({"error": f"AI chat failed: {e}"}), 500
257
-
258
- @app.route('/pronunciation-feedback', methods=['POST'])
259
- def pronunciation_feedback():
260
- if not groq_client: return jsonify({"error": "GROQ_API_KEY not configured."}), 503
261
- data = request.get_json()
262
- target_text, user_text = data.get('target_text'), data.get('user_text')
263
- if not target_text or not user_text: return jsonify({"error": "Required data missing."}), 400
264
- try:
265
- system_instruction = "You are an expert American English pronunciation coach. The user tried to say a target sentence, and their speech was transcribed. Based on the likely pronunciation differences, provide brief, friendly, and actionable feedback in Portuguese. Focus on 1-2 key points. If it's very close, praise the user."
266
- user_prompt = f"Target: \"{target_text}\"\nTranscription: \"{user_text}\"\n\nProvide pronunciation feedback."
267
- messages = [{"role": "system", "content": system_instruction}, {"role": "user", "content": user_prompt}]
268
- response = groq_client.chat.completions.create(model="llama-3.1-8b-instant", messages=messages, temperature=0.5)
269
-
270
- # Track token usage
271
- user = get_current_user()
272
- if user and hasattr(response, 'usage'):
273
- track_token_usage(
274
- user['id'],
275
- 'groq',
276
- response.usage.prompt_tokens,
277
- response.usage.completion_tokens,
278
- 'pronunciation_feedback'
279
- )
280
-
281
- return jsonify({"feedback": response.choices[0].message.content.strip()})
282
- except Exception as e:
283
- return jsonify({"error": f"Pronunciation analysis failed: {e}"}), 500
284
-
285
- @app.route('/generate-image', methods=['POST'])
286
- def generate_image():
287
- if not genai_client: return jsonify({"error": "GEMINI_API_KEY not configured."}), 503
288
- data = request.get_json()
289
- prompt = data.get('prompt')
290
- if not prompt: return jsonify({"error": "Image prompt is required."}), 400
291
- try:
292
- model = genai_client.GenerativeModel(model_name='gemini-2.5-flash-image-preview')
293
- response = model.generate_content(prompt)
294
- base64_image_data = response.parts[0].inline_data.data
295
- return jsonify({"image_base64": base64_image_data})
296
- except Exception as e:
297
- return jsonify({"error": f"Image generation failed: {e}"}), 500
298
-
299
- # --- FUNÇÃO AUXILIAR E ROTA RAIZ ---
300
- def get_ai_text_response(provider, model_name, system_instruction, user_prompt, json_schema=None):
301
- if provider == 'gemini':
302
- model = genai_client.GenerativeModel(model_name, system_instruction=system_instruction)
303
-
304
- config = None
305
- if json_schema:
306
- config = GenerationConfig(response_mime_type="application/json", response_schema=json_schema)
307
-
308
- response = model.generate_content(user_prompt, generation_config=config)
309
-
310
- if json_schema:
311
- parsed_json = json.loads(response.text)
312
- required_keys = json_schema.get("required", [])
313
- if not all(key in parsed_json and parsed_json[key] for key in required_keys):
314
- raise ValueError(f"AI response missing required keys or has empty values.")
315
- return parsed_json
316
- else:
317
- return response.text.strip()
318
-
319
- elif provider == 'groq':
320
- final_user_prompt = user_prompt
321
- if json_schema:
322
- final_user_prompt += f"\n\nYou MUST respond with a single JSON object that strictly follows this schema. Do not add any other text before or after the JSON object:\n{json.dumps(json_schema)}"
323
-
324
- messages = [{"role": "system", "content": system_instruction}, {"role": "user", "content": final_user_prompt}]
325
- config = {'response_format': {"type": "json_object"}} if json_schema else {}
326
- response = groq_client.chat.completions.create(model=model_name, messages=messages, **config)
327
-
328
- if json_schema:
329
- parsed_json = json.loads(response.choices[0].message.content)
330
- required_keys = json_schema.get("required", [])
331
- if not all(key in parsed_json and parsed_json[key] for key in required_keys):
332
- raise ValueError(f"AI response missing required keys or has empty values.")
333
- return parsed_json
334
- else:
335
- return response.choices[0].message.content.strip()
336
-
337
- raise Exception(f"Unsupported provider: {provider}")
338
-
339
- # --- AUTHENTICATION ROUTES ---
340
-
341
- @app.route('/register', methods=['POST'])
342
- def register():
343
- """User registration endpoint"""
344
- try:
345
- data = request.get_json()
346
- email = data.get('email', '').strip().lower()
347
- password = data.get('password', '')
348
-
349
- # Validate input
350
- if not email or not password:
351
- return jsonify({'error': 'Email and password are required'}), 400
352
-
353
- if len(password) < 8:
354
- return jsonify({'error': 'Password must be at least 8 characters long'}), 400
355
-
356
- # Validate email format
357
- try:
358
- validate_email(email)
359
- except EmailNotValidError:
360
- return jsonify({'error': 'Invalid email format'}), 400
361
-
362
- # Create user
363
- result = create_user(email, password)
364
-
365
- if result['success']:
366
- # Send confirmation email
367
- if send_confirmation_email(email, result['confirmation_token']):
368
- return jsonify({
369
- 'message': 'Registration successful! Please check your email to confirm your account.',
370
- 'email_sent': True
371
- }), 201
372
- else:
373
- return jsonify({
374
- 'message': 'Registration successful! However, we could not send the confirmation email. Please contact support.',
375
- 'email_sent': False
376
- }), 201
377
- else:
378
- return jsonify({'error': result['message']}), 400
379
-
380
- except Exception as e:
381
- print(f"Registration error: {e}")
382
- return jsonify({'error': 'Internal server error'}), 500
383
-
384
- @app.route('/login', methods=['POST'])
385
- def login():
386
- """User login endpoint"""
387
- try:
388
- data = request.get_json()
389
- email = data.get('email', '').strip().lower()
390
- password = data.get('password', '')
391
-
392
- if not email or not password:
393
- return jsonify({'error': 'Email and password are required'}), 400
394
-
395
- result = authenticate_user(email, password)
396
-
397
- if result['success']:
398
- session['user_id'] = result['user_id']
399
- session['user_email'] = result['email']
400
- session.permanent = True
401
-
402
- # Get user settings
403
- settings = get_user_settings(result['user_id'])
404
-
405
- return jsonify({
406
- 'message': 'Login successful',
407
- 'user': {
408
- 'id': result['user_id'],
409
- 'email': result['email'],
410
- 'settings': settings
411
- }
412
- }), 200
413
- else:
414
- return jsonify({'error': result['message']}), 401
415
-
416
- except Exception as e:
417
- print(f"Login error: {e}")
418
- return jsonify({'error': 'Internal server error'}), 500
419
-
420
- @app.route('/logout', methods=['POST'])
421
- def logout():
422
- """User logout endpoint"""
423
- session.clear()
424
- return jsonify({'message': 'Logout successful'}), 200
425
-
426
- @app.route('/confirm-email')
427
- def confirm_email_route():
428
- """Email confirmation endpoint"""
429
- token = request.args.get('token')
430
-
431
- if not token:
432
- return render_template_string('''
433
- <!DOCTYPE html>
434
- <html><head><title>Invalid Link</title></head>
435
- <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
436
- <h2>Invalid Confirmation Link</h2>
437
- <p>This confirmation link is invalid or malformed.</p>
438
- <a href="/" style="color: #4f46e5;">Return to English Helper</a>
439
- </body></html>
440
- '''), 400
441
-
442
- result = confirm_email(token)
443
-
444
- if result['success']:
445
- return render_template_string('''
446
- <!DOCTYPE html>
447
- <html><head><title>Email Confirmed</title></head>
448
- <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
449
- <h2>✅ Email Confirmed!</h2>
450
- <p>Your email has been successfully confirmed. You can now log in to your account.</p>
451
- <a href="/" style="background: #4f46e5; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">Continue to English Helper</a>
452
- </body></html>
453
- ''')
454
- else:
455
- return render_template_string('''
456
- <!DOCTYPE html>
457
- <html><head><title>Confirmation Failed</title></head>
458
- <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
459
- <h2>❌ Confirmation Failed</h2>
460
- <p>This confirmation link is invalid or has expired.</p>
461
- <a href="/" style="color: #4f46e5;">Return to English Helper</a>
462
- </body></html>
463
- '''), 400
464
-
465
- @app.route('/user/profile', methods=['GET'])
466
- @login_required
467
- def get_user_profile():
468
- """Get current user profile"""
469
- user = get_current_user()
470
- if not user:
471
- return jsonify({'error': 'User not found'}), 404
472
-
473
- settings = get_user_settings(user['id'])
474
- flashcards_count = len(get_user_flashcards(user['id'], 1000))
475
-
476
- return jsonify({
477
- 'user': {
478
- 'id': user['id'],
479
- 'email': user['email'],
480
- 'settings': settings,
481
- 'stats': {
482
- 'flashcards_created': flashcards_count
483
- }
484
- }
485
- })
486
-
487
- @app.route('/user/settings', methods=['GET', 'POST'])
488
- @login_required
489
- def user_settings():
490
- """Get or update user settings"""
491
- user = get_current_user()
492
- if not user:
493
- return jsonify({'error': 'User not found'}), 404
494
-
495
- if request.method == 'GET':
496
- settings = get_user_settings(user['id'])
497
- return jsonify({'settings': settings})
498
-
499
- elif request.method == 'POST':
500
- data = request.get_json()
501
- settings = {
502
- 'preferred_model': data.get('preferred_model'),
503
- 'context_focus': data.get('context_focus'),
504
- 'voice_accent': data.get('voice_accent'),
505
- 'daily_goal': data.get('daily_goal', 10),
506
- 'notification_enabled': data.get('notification_enabled', True)
507
- }
508
-
509
- if update_user_settings(user['id'], settings):
510
- return jsonify({'message': 'Settings updated successfully'})
511
- else:
512
- return jsonify({'error': 'Failed to update settings'}), 500
513
-
514
- @app.route('/user/flashcards', methods=['GET', 'POST'])
515
- @login_required
516
- def user_flashcards():
517
- """Get user flashcards or save new flashcard"""
518
- user = get_current_user()
519
- if not user:
520
- return jsonify({'error': 'User not found'}), 404
521
-
522
- if request.method == 'GET':
523
- flashcards = get_user_flashcards(user['id'])
524
- return jsonify({'flashcards': flashcards})
525
-
526
- elif request.method == 'POST':
527
- data = request.get_json()
528
- if save_user_flashcard(user['id'], data):
529
- return jsonify({'message': 'Flashcard saved successfully'})
530
- else:
531
- return jsonify({'error': 'Failed to save flashcard'}), 500
532
-
533
- @app.route('/auth/check', methods=['GET'])
534
- def check_auth():
535
- """Check if user is authenticated"""
536
- user = get_current_user()
537
- if user:
538
- settings = get_user_settings(user['id'])
539
- return jsonify({
540
- 'authenticated': True,
541
- 'user': {
542
- 'id': user['id'],
543
- 'email': user['email'],
544
- 'settings': settings
545
- }
546
- })
547
- else:
548
- return jsonify({'authenticated': False})
549
-
550
- # --- MODIFIED EXISTING ROUTES TO SUPPORT USER DATA ---
551
-
552
- # Override the original explain-proxy to save flashcards for logged-in users
553
- @app.route('/explain-proxy', methods=['POST'])
554
- def explain_proxy():
555
- data = request.get_json()
556
- model_provider, model_name = data.get('model', 'gemini:gemini-2.5-flash-latest').split(':', 1)
557
- context_focus = data.get('context_focus', 'General/Social')
558
- custom_prompt = data.get('custom_prompt', None)
559
- word = data.get('word', '').strip()
560
- context = data.get('context', '')
561
- for_flashcard = data.get('for_flashcard', False)
562
-
563
- if (model_provider == 'gemini' and not genai_client) or (model_provider == 'groq' and not groq_client):
564
- return jsonify({"error": f"{model_provider.upper()}_API_KEY not configured."}), 503
565
-
566
- system_instruction_base = f"You are a professional English tutor. The user's study focus is '{context_focus}'. All your responses must be in ENGLISH."
567
- try:
568
- if custom_prompt:
569
- activity_text = get_ai_text_response(model_provider, model_name, system_instruction_base, custom_prompt)
570
- return jsonify({"explanation": activity_text})
571
-
572
- if not word: return jsonify({"error": "No word selected."}), 400
573
-
574
- if for_flashcard:
575
- schema = {"type": "object", "properties": {"term": {"type": "string"}, "translation": {"type": "string"}, "context_sentence": {"type": "string"}, "gapped_sentence": {"type": "string"}, "definition": {"type": "string"}}, "required": ["term", "translation", "context_sentence", "gapped_sentence", "definition"]}
576
- prompt = f"Analyze '{word}' in context: '{context}'. Generate a JSON for a flashcard. The 'gapped_sentence' must replace '{word}' with '______________'."
577
- flashcard_data = get_ai_text_response(model_provider, model_name, system_instruction_base, prompt, json_schema=schema)
578
-
579
- # Save flashcard for logged-in users
580
- user = get_current_user()
581
- if user:
582
- save_user_flashcard(user['id'], flashcard_data)
583
-
584
- return jsonify(flashcard_data)
585
- else:
586
- prompt = f"Analyze '{word}' in context: '{context}'. Provide a one-sentence English explanation, then '---', then the Portuguese translation."
587
- parts = get_ai_text_response(model_provider, model_name, system_instruction_base, prompt).split('---', 1)
588
- return jsonify({"explanation": parts[0].strip(), "translation": parts[1].strip() if len(parts) > 1 else 'N/A'})
589
- except Exception as e:
590
- print(f"AI ANALYSIS ERROR in /explain-proxy: {e}")
591
- return jsonify({"error": f"AI analysis failed: {e}"}), 500
592
-
593
- # --- CONTENT CURATION ROUTES ---
594
-
595
- @app.route('/content/search', methods=['POST'])
596
- @login_required
597
- def search_content():
598
- """Search for content based on user interests"""
599
- try:
600
- user = get_current_user()
601
- if not user:
602
- return jsonify({'error': 'User not found'}), 404
603
-
604
- data = request.get_json()
605
- query = data.get('query', '')
606
- category = data.get('category', '')
607
-
608
- # Get user settings and interests
609
- settings = get_user_settings(user['id'])
610
- interests = get_user_interests(user['id'])
611
-
612
- if not interests and query:
613
- # Use query as interest if no interests set
614
- interests = {query: 1.0}
615
-
616
- english_level = settings.get('english_level', 'B1') if settings else 'B1'
617
- context_focus = settings.get('context_focus', 'General/Social') if settings else 'General/Social'
618
-
619
- # Search for content
620
- results = content_curator.search_content(
621
- interests=list(interests.keys()) if interests else [query],
622
- english_level=english_level,
623
- context_focus=context_focus,
624
- limit=10
625
- )
626
-
627
- return jsonify({'results': results})
628
-
629
- except Exception as e:
630
- print(f"Content search error: {e}")
631
- return jsonify({'error': 'Content search failed'}), 500
632
-
633
- @app.route('/content/extract', methods=['POST'])
634
- @login_required
635
- def extract_content():
636
- """Extract content from URL"""
637
- try:
638
- data = request.get_json()
639
- url = data.get('url', '')
640
-
641
- if not url:
642
- return jsonify({'error': 'URL required'}), 400
643
-
644
- result = content_curator.extract_content_from_url(url)
645
- return jsonify(result)
646
-
647
- except Exception as e:
648
- print(f"Content extraction error: {e}")
649
- return jsonify({'error': 'Content extraction failed'}), 500
650
-
651
- @app.route('/content/save', methods=['POST'])
652
- @login_required
653
- def save_content():
654
- """Save content/article for user"""
655
- try:
656
- user = get_current_user()
657
- if not user:
658
- return jsonify({'error': 'User not found'}), 404
659
-
660
- data = request.get_json()
661
- title = data.get('title', '')
662
- content = data.get('content', '')
663
- source_url = data.get('source_url')
664
- source_type = data.get('source_type', 'manual')
665
- category = data.get('category')
666
-
667
- if not title or not content:
668
- return jsonify({'error': 'Title and content required'}), 400
669
-
670
- result = save_user_article(user['id'], title, content, source_url, source_type, category)
671
-
672
- if result['success']:
673
- # Record analytics
674
- record_analytics_metric(user['id'], 'content_saved', 1)
675
- return jsonify({'message': 'Content saved successfully', 'article_id': result['article_id']})
676
- else:
677
- return jsonify({'error': result['message']}), 500
678
-
679
- except Exception as e:
680
- print(f"Save content error: {e}")
681
- return jsonify({'error': 'Failed to save content'}), 500
682
-
683
- @app.route('/content/articles', methods=['GET'])
684
- @login_required
685
- def get_articles():
686
- """Get user's saved articles"""
687
- try:
688
- user = get_current_user()
689
- if not user:
690
- return jsonify({'error': 'User not found'}), 404
691
-
692
- category = request.args.get('category')
693
- limit = int(request.args.get('limit', 50))
694
-
695
- articles = get_user_articles(user['id'], category, limit)
696
- return jsonify({'articles': articles})
697
-
698
- except Exception as e:
699
- print(f"Get articles error: {e}")
700
- return jsonify({'error': 'Failed to get articles'}), 500
701
-
702
- @app.route('/content/interests', methods=['GET', 'POST'])
703
- @login_required
704
- def manage_interests():
705
- """Get or update user interests"""
706
- try:
707
- user = get_current_user()
708
- if not user:
709
- return jsonify({'error': 'User not found'}), 404
710
-
711
- if request.method == 'GET':
712
- interests = get_user_interests(user['id'])
713
- return jsonify({'interests': interests})
714
-
715
- elif request.method == 'POST':
716
- data = request.get_json()
717
- interests = data.get('interests', {})
718
-
719
- if update_user_interests(user['id'], interests):
720
- return jsonify({'message': 'Interests updated successfully'})
721
- else:
722
- return jsonify({'error': 'Failed to update interests'}), 500
723
-
724
- except Exception as e:
725
- print(f"Manage interests error: {e}")
726
- return jsonify({'error': 'Failed to manage interests'}), 500
727
-
728
- @app.route('/content/recommendations', methods=['GET'])
729
- @login_required
730
- def get_recommendations():
731
- """Get AI-powered content recommendations"""
732
- try:
733
- user = get_current_user()
734
- if not user:
735
- return jsonify({'error': 'User not found'}), 404
736
-
737
- # Get user data
738
- interests = get_user_interests(user['id'])
739
- recent_articles = get_user_articles(user['id'], limit=10)
740
- settings = get_user_settings(user['id'])
741
-
742
- english_level = settings.get('english_level', 'B1') if settings else 'B1'
743
- context_focus = settings.get('context_focus', 'General/Social') if settings else 'General/Social'
744
-
745
- # Generate recommendations
746
- recommendations = content_curator.generate_personalized_recommendations(
747
- interests, recent_articles, english_level, context_focus, user['id']
748
- )
749
-
750
- return jsonify({'recommendations': recommendations})
751
-
752
- except Exception as e:
753
- print(f"Recommendations error: {e}")
754
- return jsonify({'error': 'Failed to get recommendations'}), 500
755
-
756
- @app.route('/content/analyze', methods=['POST'])
757
- @login_required
758
- def analyze_content():
759
- """Analyze content for learning insights"""
760
- try:
761
- user = get_current_user()
762
- if not user:
763
- return jsonify({'error': 'User not found'}), 404
764
-
765
- data = request.get_json()
766
- content = data.get('content', '')
767
-
768
- if not content:
769
- return jsonify({'error': 'Content required'}), 400
770
-
771
- settings = get_user_settings(user['id'])
772
- english_level = settings.get('english_level', 'B1') if settings else 'B1'
773
-
774
- analysis = content_curator.analyze_content_for_learning(content, english_level)
775
-
776
- return jsonify({'analysis': analysis})
777
-
778
- except Exception as e:
779
- print(f"Content analysis error: {e}")
780
- return jsonify({'error': 'Content analysis failed'}), 500
781
-
782
- # --- STUDY PLANNING ROUTES ---
783
-
784
- @app.route('/study/plans', methods=['GET', 'POST'])
785
- @login_required
786
- def manage_study_plans():
787
- """Get or create study plans"""
788
- try:
789
- user = get_current_user()
790
- if not user:
791
- return jsonify({'error': 'User not found'}), 404
792
-
793
- if request.method == 'GET':
794
- plans = get_user_study_plans(user['id'])
795
- return jsonify({'plans': plans})
796
-
797
- elif request.method == 'POST':
798
- data = request.get_json()
799
- plan_name = data.get('plan_name', '')
800
- target_level = data.get('target_level', 'B2')
801
- current_level = data.get('current_level', 'B1')
802
- objectives = json.dumps(data.get('objectives', []))
803
- weekly_hours = data.get('weekly_hours', 5)
804
-
805
- if not plan_name:
806
- return jsonify({'error': 'Plan name required'}), 400
807
-
808
- result = create_study_plan(user['id'], plan_name, target_level, current_level, objectives, weekly_hours)
809
-
810
- if result['success']:
811
- return jsonify({'message': 'Study plan created', 'plan_id': result['plan_id']})
812
- else:
813
- return jsonify({'error': result['message']}), 500
814
-
815
- except Exception as e:
816
- print(f"Study plans error: {e}")
817
- return jsonify({'error': 'Failed to manage study plans'}), 500
818
-
819
- @app.route('/analytics/dashboard', methods=['GET'])
820
- @login_required
821
- def analytics_dashboard():
822
- """Get analytics dashboard data"""
823
- try:
824
- user = get_current_user()
825
- if not user:
826
- return jsonify({'error': 'User not found'}), 404
827
-
828
- days = int(request.args.get('days', 30))
829
-
830
- # Get various analytics
831
- analytics_data = {
832
- 'flashcards_created': get_user_analytics(user['id'], 'flashcards_created', days),
833
- 'content_saved': get_user_analytics(user['id'], 'content_saved', days),
834
- 'study_sessions': get_user_analytics(user['id'], 'study_session', days),
835
- 'total_flashcards': len(get_user_flashcards(user['id'], 1000)),
836
- 'total_articles': len(get_user_articles(user['id'], limit=1000)),
837
- 'user_level': get_user_settings(user['id']).get('english_level', 'B1')
838
- }
839
-
840
- return jsonify({'analytics': analytics_data})
841
-
842
- except Exception as e:
843
- print(f"Analytics error: {e}")
844
- return jsonify({'error': 'Failed to get analytics'}), 500
845
-
846
- # --- STUDY PLANNER ROUTES ---
847
-
848
- @app.route('/study-plan/create', methods=['POST'])
849
- @login_required
850
- def create_study_plan_route():
851
- """Create a personalized study plan"""
852
- try:
853
- user = get_current_user()
854
- if not user:
855
- return jsonify({'error': 'User not found'}), 404
856
-
857
- data = request.get_json()
858
-
859
- # Get user settings and interests
860
- user_settings = get_user_settings(user['id'])
861
- user_interests = get_user_interests(user['id'])
862
-
863
- # Prepare data for study planner
864
- planner_data = {
865
- 'english_level': data.get('current_level') or user_settings.get('english_level', 'B1'),
866
- 'target_level': data.get('target_level', 'B2'),
867
- 'weekly_hours': int(data.get('weekly_hours', 5)),
868
- 'context_focus': data.get('context_focus') or user_settings.get('context_focus', 'General/Social'),
869
- 'interests': user_interests,
870
- 'study_goals': data.get('study_goals', [])
871
- }
872
-
873
- # Generate the plan
874
- result = study_planner.generate_personalized_plan(planner_data)
875
-
876
- if result['success']:
877
- plan = result['plan']
878
-
879
- # Save to database
880
- plan_id = create_study_plan(
881
- user['id'],
882
- plan['target_level'],
883
- plan['weekly_hours'],
884
- plan['estimated_weeks'],
885
- json.dumps(plan)
886
- )
887
-
888
- plan['id'] = plan_id
889
-
890
- # Record analytics
891
- record_analytics_metric(user['id'], 'study_plan_created', 1)
892
-
893
- return jsonify({'success': True, 'plan': plan})
894
- else:
895
- return jsonify({'success': False, 'error': result['error']}), 500
896
-
897
- except Exception as e:
898
- print(f"Study plan creation error: {e}")
899
- return jsonify({'error': 'Failed to create study plan'}), 500
900
-
901
- @app.route('/study-plan/current', methods=['GET'])
902
- @login_required
903
- def get_current_study_plan():
904
- """Get user's current study plan"""
905
- try:
906
- user = get_current_user()
907
- if not user:
908
- return jsonify({'error': 'User not found'}), 404
909
-
910
- plans = get_user_study_plans(user['id'])
911
-
912
- if plans:
913
- # Get the most recent active plan
914
- current_plan = plans[0] # Assuming most recent first
915
-
916
- # Parse the plan data
917
- plan_data = json.loads(current_plan['plan_data'])
918
-
919
- # Add database ID
920
- plan_data['db_id'] = current_plan['id']
921
-
922
- # Get activities for this plan
923
- activities = get_study_activities(current_plan['id'])
924
- plan_data['completed_activities'] = activities
925
-
926
- return jsonify({'success': True, 'plan': plan_data})
927
- else:
928
- return jsonify({'success': True, 'plan': None})
929
-
930
- except Exception as e:
931
- print(f"Get study plan error: {e}")
932
- return jsonify({'error': 'Failed to get study plan'}), 500
933
-
934
- @app.route('/study-plan/activity/complete', methods=['POST'])
935
- @login_required
936
- def complete_study_activity():
937
- """Mark a study activity as completed"""
938
- try:
939
- user = get_current_user()
940
- if not user:
941
- return jsonify({'error': 'User not found'}), 404
942
-
943
- data = request.get_json()
944
- plan_id = data.get('plan_id')
945
- activity_id = data.get('activity_id')
946
- duration_minutes = data.get('duration_minutes', 0)
947
- notes = data.get('notes', '')
948
-
949
- if not plan_id or not activity_id:
950
- return jsonify({'error': 'Missing plan_id or activity_id'}), 400
951
-
952
- # Add activity completion
953
- add_study_activity(plan_id, activity_id, duration_minutes, notes)
954
-
955
- # Record analytics
956
- record_analytics_metric(user['id'], 'study_activity_completed', 1)
957
- record_analytics_metric(user['id'], 'study_time_minutes', duration_minutes)
958
-
959
- return jsonify({'success': True})
960
-
961
- except Exception as e:
962
- print(f"Complete activity error: {e}")
963
- return jsonify({'error': 'Failed to complete activity'}), 500
964
-
965
- @app.route('/study-plan/progress', methods=['GET'])
966
- @login_required
967
- def get_study_progress():
968
- """Get study plan progress analytics"""
969
- try:
970
- user = get_current_user()
971
- if not user:
972
- return jsonify({'error': 'User not found'}), 404
973
-
974
- plans = get_user_study_plans(user['id'])
975
-
976
- if not plans:
977
- return jsonify({'success': True, 'progress': None})
978
-
979
- current_plan = plans[0]
980
- plan_data = json.loads(current_plan['plan_data'])
981
- activities = get_study_activities(current_plan['id'])
982
-
983
- # Calculate progress
984
- total_activities = len(plan_data.get('activities', []))
985
- completed_activities = len(activities)
986
-
987
- progress_data = {
988
- 'total_activities': total_activities,
989
- 'completed_activities': completed_activities,
990
- 'completion_percentage': (completed_activities / max(total_activities, 1)) * 100,
991
- 'estimated_weeks': plan_data.get('estimated_weeks', 0),
992
- 'weeks_elapsed': max(1, (datetime.now() - datetime.fromisoformat(current_plan['created_at'])).days // 7),
993
- 'target_level': plan_data.get('target_level', 'B2'),
994
- 'weekly_hours': plan_data.get('weekly_hours', 5),
995
- 'recent_activities': activities[-10:] if activities else [] # Last 10 activities
996
- }
997
-
998
- return jsonify({'success': True, 'progress': progress_data})
999
-
1000
- except Exception as e:
1001
- print(f"Study progress error: {e}")
1002
- return jsonify({'error': 'Failed to get study progress'}), 500
1003
-
1004
- # --- ADMIN ROUTES ---
1005
-
1006
- @app.route('/admin/login', methods=['POST'])
1007
- def admin_login():
1008
- """Admin login endpoint"""
1009
- try:
1010
- data = request.get_json()
1011
- username = data.get('username')
1012
- password = data.get('password')
1013
-
1014
- if admin_manager.login_admin(username, password):
1015
- return jsonify({'success': True, 'message': 'Admin logged in successfully'})
1016
- else:
1017
- return jsonify({'success': False, 'error': 'Invalid credentials'}), 401
1018
-
1019
- except Exception as e:
1020
- print(f"Admin login error: {e}")
1021
- return jsonify({'error': 'Admin login failed'}), 500
1022
-
1023
- @app.route('/admin/logout', methods=['POST'])
1024
- @admin_required
1025
- def admin_logout():
1026
- """Admin logout endpoint"""
1027
- try:
1028
- admin_manager.logout_admin()
1029
- return jsonify({'success': True, 'message': 'Admin logged out successfully'})
1030
- except Exception as e:
1031
- print(f"Admin logout error: {e}")
1032
- return jsonify({'error': 'Admin logout failed'}), 500
1033
-
1034
- @app.route('/admin/check', methods=['GET'])
1035
- def admin_check():
1036
- """Check admin authentication status"""
1037
- try:
1038
- is_authenticated = admin_manager.is_admin_logged_in()
1039
- return jsonify({
1040
- 'authenticated': is_authenticated,
1041
- 'username': session.get('admin_username') if is_authenticated else None
1042
- })
1043
- except Exception as e:
1044
- print(f"Admin check error: {e}")
1045
- return jsonify({'authenticated': False})
1046
-
1047
- @app.route('/admin/dashboard', methods=['GET'])
1048
- @admin_required
1049
- def admin_dashboard():
1050
- """Get admin dashboard data"""
1051
- try:
1052
- stats = admin_manager.get_system_stats()
1053
- return jsonify({'success': True, 'stats': stats})
1054
- except Exception as e:
1055
- print(f"Admin dashboard error: {e}")
1056
- return jsonify({'error': 'Failed to load dashboard'}), 500
1057
-
1058
- @app.route('/admin/users', methods=['GET'])
1059
- @admin_required
1060
- def admin_get_users():
1061
- """Get paginated list of users"""
1062
- try:
1063
- page = int(request.args.get('page', 1))
1064
- per_page = int(request.args.get('per_page', 20))
1065
-
1066
- users_data = admin_manager.get_all_users(page, per_page)
1067
- return jsonify({'success': True, 'data': users_data})
1068
- except Exception as e:
1069
- print(f"Admin get users error: {e}")
1070
- return jsonify({'error': 'Failed to get users'}), 500
1071
-
1072
- @app.route('/admin/users/<int:user_id>', methods=['GET'])
1073
- @admin_required
1074
- def admin_get_user_details(user_id):
1075
- """Get detailed information about a user"""
1076
- try:
1077
- user_details = admin_manager.get_user_details(user_id)
1078
- if user_details:
1079
- return jsonify({'success': True, 'user': user_details})
1080
- else:
1081
- return jsonify({'error': 'User not found'}), 404
1082
- except Exception as e:
1083
- print(f"Admin get user details error: {e}")
1084
- return jsonify({'error': 'Failed to get user details'}), 500
1085
-
1086
- @app.route('/admin/users/<int:user_id>', methods=['DELETE'])
1087
- @admin_required
1088
- def admin_delete_user(user_id):
1089
- """Delete a user and all associated data"""
1090
- try:
1091
- if admin_manager.delete_user(user_id):
1092
- return jsonify({'success': True, 'message': 'User deleted successfully'})
1093
- else:
1094
- return jsonify({'error': 'Failed to delete user'}), 500
1095
- except Exception as e:
1096
- print(f"Admin delete user error: {e}")
1097
- return jsonify({'error': 'Failed to delete user'}), 500
1098
-
1099
- @app.route('/admin/database/schema', methods=['GET'])
1100
- @admin_required
1101
- def admin_get_database_schema():
1102
- """Get database schema information"""
1103
- try:
1104
- schema = admin_manager.get_database_schema()
1105
- return jsonify({'success': True, 'schema': schema})
1106
- except Exception as e:
1107
- print(f"Admin get schema error: {e}")
1108
- return jsonify({'error': 'Failed to get database schema'}), 500
1109
-
1110
- @app.route('/admin/token-usage', methods=['POST'])
1111
- def record_token_usage():
1112
- """Record token usage (called by AI functions)"""
1113
- try:
1114
- data = request.get_json()
1115
- user_id = data.get('user_id')
1116
- api_provider = data.get('api_provider')
1117
- input_tokens = data.get('input_tokens', 0)
1118
- output_tokens = data.get('output_tokens', 0)
1119
- operation_type = data.get('operation_type', 'unknown')
1120
-
1121
- admin_manager.record_token_usage(
1122
- user_id, api_provider, input_tokens, output_tokens, operation_type
1123
- )
1124
-
1125
- return jsonify({'success': True})
1126
- except Exception as e:
1127
- print(f"Token usage recording error: {e}")
1128
- return jsonify({'error': 'Failed to record token usage'}), 500
1129
-
1130
- @app.route('/admin/export/users', methods=['GET'])
1131
- @admin_required
1132
- def export_users():
1133
- """Export users data as CSV"""
1134
- try:
1135
- import csv
1136
- from io import StringIO
1137
-
1138
- users_data = admin_manager.get_all_users(page=1, per_page=10000) # Get all users
1139
-
1140
- output = StringIO()
1141
- writer = csv.writer(output)
1142
-
1143
- # Write header
1144
- writer.writerow(['ID', 'Email', 'Created At', 'Email Confirmed', 'Last Login', 'Sessions', 'Flashcards', 'Articles'])
1145
-
1146
- # Write data
1147
- for user in users_data['users']:
1148
- writer.writerow([
1149
- user['id'],
1150
- user['email'],
1151
- user['created_at'],
1152
- user['email_confirmed'],
1153
- user['last_login'] or 'Never',
1154
- user['session_count'],
1155
- user['flashcard_count'],
1156
- user['article_count']
1157
- ])
1158
-
1159
- output.seek(0)
1160
-
1161
- return Response(
1162
- output.getvalue(),
1163
- mimetype='text/csv',
1164
- headers={'Content-Disposition': f'attachment; filename=users_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv'}
1165
- )
1166
-
1167
- except Exception as e:
1168
- print(f"Export users error: {e}")
1169
- return jsonify({'error': 'Failed to export users'}), 500
1170
-
1171
- @app.route('/admin/export/tokens', methods=['GET'])
1172
- @admin_required
1173
- def export_token_usage():
1174
- """Export token usage data as CSV"""
1175
- try:
1176
- import csv
1177
- from io import StringIO
1178
-
1179
- conn = get_db_connection()
1180
- cursor = conn.cursor()
1181
-
1182
- cursor.execute("""
1183
- SELECT t.created_at, u.email, t.api_provider, t.input_tokens,
1184
- t.output_tokens, t.tokens_used, t.operation_type
1185
- FROM token_usage t
1186
- LEFT JOIN users u ON t.user_id = u.id
1187
- ORDER BY t.created_at DESC
1188
- """)
1189
-
1190
- token_data = cursor.fetchall()
1191
- conn.close()
1192
-
1193
- output = StringIO()
1194
- writer = csv.writer(output)
1195
-
1196
- # Write header
1197
- writer.writerow(['Date', 'User Email', 'Provider', 'Input Tokens', 'Output Tokens', 'Total Tokens', 'Operation'])
1198
-
1199
- # Write data
1200
- for row in token_data:
1201
- writer.writerow(row)
1202
-
1203
- output.seek(0)
1204
-
1205
- return Response(
1206
- output.getvalue(),
1207
- mimetype='text/csv',
1208
- headers={'Content-Disposition': f'attachment; filename=token_usage_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv'}
1209
- )
1210
-
1211
- except Exception as e:
1212
- print(f"Export tokens error: {e}")
1213
- return jsonify({'error': 'Failed to export token usage'}), 500
1214
-
1215
- @app.route('/admin/system/health', methods=['GET'])
1216
- @admin_required
1217
- def get_system_health():
1218
- """Get system health metrics"""
1219
- try:
1220
- health = admin_manager.get_system_health()
1221
- return jsonify({'success': True, 'health': health})
1222
- except Exception as e:
1223
- print(f"System health error: {e}")
1224
- return jsonify({'error': 'Failed to get system health'}), 500
1225
-
1226
- @app.route('/admin/system/alerts', methods=['GET'])
1227
- @admin_required
1228
- def get_system_alerts():
1229
- """Get system alerts"""
1230
- try:
1231
- alerts = admin_manager.check_system_alerts()
1232
- return jsonify({'success': True, 'alerts': alerts})
1233
- except Exception as e:
1234
- print(f"System alerts error: {e}")
1235
- return jsonify({'error': 'Failed to get system alerts'}), 500
1236
-
1237
- @app.route('/admin')
1238
- def admin_interface():
1239
- """Serve admin interface"""
1240
- return send_file('admin.html')
1241
-
1242
- @app.route('/')
1243
- def root():
1244
- return send_file('index.html')
1245
-
1246
- if __name__ == '__main__':
1247
- app.run(host='0.0.0.0', port=7860)