moshabann commited on
Commit
46395c6
·
verified ·
1 Parent(s): e3a5e15

Update ai_server.py

Browse files
Files changed (1) hide show
  1. ai_server.py +44 -1356
ai_server.py CHANGED
@@ -1,1356 +1,44 @@
1
- // Application Data
2
- const appData = {
3
- modes: [
4
- {
5
- id: "conversation",
6
- name: "المحادثة الحرة",
7
- icon: "💬",
8
- description: "دردشة مفتوحة مع Echo Light لممارسة المهارات اللغوية"
9
- },
10
- {
11
- id: "vocabulary",
12
- name: "بناء المفردات",
13
- icon: "📚",
14
- description: "نظام بطاقات تعليمية ذكي مع تكرار مباعد"
15
- },
16
- {
17
- id: "pronunciation",
18
- name: "تدريب النطق",
19
- icon: "🎤",
20
- description: "تقييم النطق الفوري مع تعليقات مفصلة"
21
- },
22
- {
23
- id: "grammar",
24
- name: "ألعاب القواعد",
25
- icon: "🎯",
26
- description: "تمارين تفاعلية لتعلم القواعد بطريقة ممتعة"
27
- },
28
- {
29
- id: "scenarios",
30
- name: "سيناريوهات واقعية",
31
- icon: "🌍",
32
- description: "محاكاة مواقف حياتية مثل المطاعم والمقابلات"
33
- },
34
- {
35
- id: "daily",
36
- name: "التحدي اليومي",
37
- icon: "⭐",
38
- description: "تمارين يومية قصيرة للحفاظ على استمرارية التعلم"
39
- }
40
- ],
41
- achievements: [
42
- {
43
- id: "first_conversation",
44
- name: "المحادثة الأولى",
45
- description: "أكمل أول محادثة مع Echo Light",
46
- icon: "🎉"
47
- },
48
- {
49
- id: "vocabulary_master",
50
- name: "خبير المفردات",
51
- description: "تعلم 50 كلمة جديدة",
52
- icon: "🏆"
53
- },
54
- {
55
- id: "pronunciation_expert",
56
- name: "خبير النطق",
57
- description: "احصل على تقييم ممتاز في النطق 10 مرات",
58
- icon: "🎯"
59
- },
60
- {
61
- id: "grammar_guru",
62
- name: "معلم القواعد",
63
- description: "أكمل جميع تمارين القواعد بنجاح",
64
- icon: "📖"
65
- },
66
- {
67
- id: "daily_streak",
68
- name: "المواظبة",
69
- description: "أكمل التحدي اليومي لمدة 7 أيام متتالية",
70
- icon: "🔥"
71
- }
72
- ],
73
- vocabulary_cards: [
74
- {
75
- word: "Hello",
76
- translation: "مرحبا",
77
- pronunciation: "/həˈloʊ/",
78
- example: "Hello, how are you?"
79
- },
80
- {
81
- word: "Thank you",
82
- translation: "شكراً لك",
83
- pronunciation: "/θæŋk juː/",
84
- example: "Thank you for your help."
85
- },
86
- {
87
- word: "Beautiful",
88
- translation: "جميل",
89
- pronunciation: "/ˈbjuːtɪfəl/",
90
- example: "The sunset is beautiful."
91
- },
92
- {
93
- word: "Important",
94
- translation: "مهم",
95
- pronunciation: "/ɪmˈpɔːrtənt/",
96
- example: "This is very important."
97
- },
98
- {
99
- word: "Learning",
100
- translation: "تعلم",
101
- pronunciation: "/ˈlɜːrnɪŋ/",
102
- example: "Learning English is fun."
103
- }
104
- ],
105
- grammar_exercises: [
106
- {
107
- question: "Choose the correct form: I ___ to school every day.",
108
- options: ["go", "goes", "going", "went"],
109
- correct: 0,
110
- explanation: "نستخدم 'go' مع الضمير 'I' في المضارع البسيط"
111
- },
112
- {
113
- question: "Complete: She ___ reading a book now.",
114
- options: ["is", "are", "was", "were"],
115
- correct: 0,
116
- explanation: "نستخدم 'is' مع الضمير 'She' في المضارع المستمر"
117
- }
118
- ],
119
- scenarios: [
120
- {
121
- title: "في المطعم",
122
- description: "تعلم كيفية طلب الطعام في المطعم",
123
- dialogue: [
124
- {
125
- speaker: "waiter",
126
- text: "Good evening! Welcome to our restaurant. How can I help you?"
127
- },
128
- {
129
- speaker: "customer",
130
- text: "Good evening! I'd like to see the menu, please."
131
- }
132
- ]
133
- },
134
- {
135
- title: "مقابلة عمل",
136
- description: "تدرب على أسئلة مقابلة العمل الشائعة",
137
- dialogue: [
138
- {
139
- speaker: "interviewer",
140
- text: "Tell me about yourself."
141
- },
142
- {
143
- speaker: "candidate",
144
- text: "I'm a motivated professional with experience in..."
145
- }
146
- ]
147
- }
148
- ],
149
- echoResponses: [
150
- "That's wonderful! Your English is getting better every day.",
151
- "Great job! Can you tell me more about that topic?",
152
- "Excellent! I love how you expressed that idea.",
153
- "Perfect! Let's try using some advanced vocabulary now.",
154
- "Amazing progress! What would you like to talk about next?",
155
- "Fantastic! Your grammar is really improving.",
156
- "Well done! That was a complete and clear sentence.",
157
- "Impressive! You're becoming more confident in English.",
158
- "Brilliant! Let's explore this topic further.",
159
- "Outstanding! Keep up the great work!"
160
- ]
161
- };
162
-
163
- // Application State
164
- const appState = {
165
- currentScreen: 'homeScreen',
166
- userProgress: {
167
- streak: 5,
168
- totalPoints: 125,
169
- wordsLearned: 12,
170
- conversationsCompleted: 3,
171
- pronunciationAccuracy: 78,
172
- grammarScore: 85,
173
- unlockedAchievements: ['first_conversation']
174
- },
175
- currentVocabIndex: 0,
176
- currentGrammarIndex: 0,
177
- currentScenario: null,
178
- isRecording: false,
179
- soundEnabled: true,
180
- dailyProgress: 1,
181
- selectedChallengeWords: [],
182
- recordingTimer: null
183
- };
184
-
185
- // Character Animation Controller
186
- class CharacterController {
187
- constructor() {
188
- this.character = document.getElementById('echoCharacter');
189
- this.mouth = document.getElementById('characterMouth');
190
- this.soundWaves = document.getElementById('soundWaves');
191
- this.isAnimating = false;
192
- this.mouthStates = ['happy', 'speaking', 'surprised', 'neutral'];
193
- this.currentMoodIndex = 0;
194
-
195
- this.startIdleAnimation();
196
- }
197
-
198
- startIdleAnimation() {
199
- // Eye blinking animation
200
- setInterval(() => {
201
- this.blink();
202
- }, 3000 + Math.random() * 2000);
203
-
204
- // Mood changes
205
- setInterval(() => {
206
- this.changeMood();
207
- }, 10000);
208
- }
209
-
210
- blink() {
211
- const eyes = document.querySelectorAll('.eye');
212
- eyes.forEach(eye => {
213
- eye.style.transform = 'scaleY(0.1)';
214
- setTimeout(() => {
215
- eye.style.transform = 'scaleY(1)';
216
- }, 150);
217
- });
218
- }
219
-
220
- changeMood() {
221
- if (this.isAnimating) return;
222
-
223
- const moods = ['happy', 'excited', 'thinking', 'encouraging'];
224
- const randomMood = moods[Math.floor(Math.random() * moods.length)];
225
- this.setMood(randomMood);
226
- }
227
-
228
- setMood(mood) {
229
- const character = this.character;
230
- const mouth = this.mouth;
231
-
232
- // Remove existing mood classes
233
- character.classList.remove('happy', 'excited', 'thinking', 'encouraging');
234
- character.classList.add(mood);
235
-
236
- switch(mood) {
237
- case 'happy':
238
- mouth.style.borderRadius = '0 0 40px 40px';
239
- mouth.style.width = '40px';
240
- mouth.style.background = '#ff6b9d';
241
- break;
242
- case 'excited':
243
- mouth.style.borderRadius = '50%';
244
- mouth.style.width = '30px';
245
- mouth.style.background = '#32b8c6';
246
- break;
247
- case 'thinking':
248
- mouth.style.borderRadius = '40px 40px 0 0';
249
- mouth.style.width = '20px';
250
- mouth.style.background = '#8a2be2';
251
- break;
252
- case 'encouraging':
253
- mouth.style.borderRadius = '0 0 50px 50px';
254
- mouth.style.width = '50px';
255
- mouth.style.background = '#ff6b9d';
256
- break;
257
- }
258
- }
259
-
260
- speak(text) {
261
- this.isAnimating = true;
262
- this.setMood('happy');
263
-
264
- // Animate sound waves
265
- if (this.soundWaves) {
266
- this.soundWaves.style.display = 'block';
267
- }
268
-
269
- // Simulate mouth movement
270
- let speakingInterval = setInterval(() => {
271
- const randomWidth = 20 + Math.random() * 30;
272
- if (this.mouth) {
273
- this.mouth.style.width = randomWidth + 'px';
274
- }
275
- }, 150);
276
-
277
- // Use Web Speech API if available and enabled
278
- if ('speechSynthesis' in window && appState.soundEnabled) {
279
- const utterance = new SpeechSynthesisUtterance(text);
280
- utterance.lang = 'en-US';
281
- utterance.rate = 0.9;
282
- utterance.pitch = 1.1;
283
-
284
- utterance.onend = () => {
285
- clearInterval(speakingInterval);
286
- this.stopSpeaking();
287
- };
288
-
289
- utterance.onerror = () => {
290
- clearInterval(speakingInterval);
291
- this.stopSpeaking();
292
- };
293
-
294
- speechSynthesis.speak(utterance);
295
- } else {
296
- // Fallback animation
297
- setTimeout(() => {
298
- clearInterval(speakingInterval);
299
- this.stopSpeaking();
300
- }, Math.min(text.length * 50, 3000));
301
- }
302
- }
303
-
304
- stopSpeaking() {
305
- this.isAnimating = false;
306
- if (this.soundWaves) {
307
- this.soundWaves.style.display = 'none';
308
- }
309
- if (this.mouth) {
310
- this.mouth.style.width = '40px';
311
- }
312
- this.setMood('happy');
313
- }
314
-
315
- celebrate() {
316
- this.setMood('excited');
317
- if (this.character) {
318
- this.character.style.animation = 'float 0.5s ease-in-out 3';
319
-
320
- setTimeout(() => {
321
- this.character.style.animation = 'float 3s ease-in-out infinite';
322
- this.setMood('happy');
323
- }, 1500);
324
- }
325
- }
326
-
327
- encourage() {
328
- this.setMood('encouraging');
329
- setTimeout(() => {
330
- this.setMood('happy');
331
- }, 2000);
332
- }
333
- }
334
-
335
- // Initialize character controller
336
- let characterController;
337
-
338
- // Screen Management
339
- function showScreen(screenId) {
340
- // Hide all screens
341
- document.querySelectorAll('.screen').forEach(screen => {
342
- screen.classList.remove('active');
343
- });
344
-
345
- // Show target screen
346
- const targetScreen = document.getElementById(screenId);
347
- if (targetScreen) {
348
- targetScreen.classList.add('active');
349
- appState.currentScreen = screenId;
350
-
351
- // Update character mood based on screen
352
- if (characterController) {
353
- switch(screenId) {
354
- case 'homeScreen':
355
- characterController.setMood('happy');
356
- break;
357
- case 'conversationScreen':
358
- characterController.setMood('encouraging');
359
- break;
360
- case 'vocabularyScreen':
361
- case 'grammarScreen':
362
- characterController.setMood('thinking');
363
- break;
364
- case 'pronunciationScreen':
365
- characterController.setMood('excited');
366
- break;
367
- case 'progressScreen':
368
- initializeAchievements();
369
- break;
370
- }
371
- }
372
- }
373
- }
374
-
375
- // Mode Management
376
- function initializeModes() {
377
- const modesGrid = document.getElementById('modesGrid');
378
- if (!modesGrid) return;
379
-
380
- modesGrid.innerHTML = '';
381
-
382
- appData.modes.forEach(mode => {
383
- const modeCard = document.createElement('div');
384
- modeCard.className = 'mode-card';
385
- modeCard.onclick = () => openMode(mode.id);
386
-
387
- modeCard.innerHTML = `
388
- <span class="mode-icon">${mode.icon}</span>
389
- <h3 class="mode-title">${mode.name}</h3>
390
- <p class="mode-description">${mode.description}</p>
391
- `;
392
-
393
- modesGrid.appendChild(modeCard);
394
- });
395
- }
396
-
397
- function openMode(modeId) {
398
- const screenMap = {
399
- 'conversation': 'conversationScreen',
400
- 'vocabulary': 'vocabularyScreen',
401
- 'pronunciation': 'pronunciationScreen',
402
- 'grammar': 'grammarScreen',
403
- 'scenarios': 'scenariosScreen',
404
- 'daily': 'dailyScreen'
405
- };
406
-
407
- const screenId = screenMap[modeId];
408
- if (screenId) {
409
- showScreen(screenId);
410
-
411
- // Initialize mode-specific content
412
- switch(modeId) {
413
- case 'vocabulary':
414
- initializeVocabulary();
415
- break;
416
- case 'grammar':
417
- initializeGrammar();
418
- break;
419
- case 'scenarios':
420
- initializeScenarios();
421
- break;
422
- case 'daily':
423
- initializeDailyChallenge();
424
- break;
425
- case 'pronunciation':
426
- initializePronunciation();
427
- break;
428
- }
429
- }
430
- }
431
-
432
- // Conversation Mode
433
- function sendMessage() {
434
- const input = document.getElementById('messageInput');
435
- if (!input) return;
436
- const message = input.value.trim();
437
- if (!message) return;
438
- addMessage(message, 'user');
439
- input.value = '';
440
-
441
- // استدعاء الذكاء الاصطناعي عبر الخادم
442
- fetch('/chat', {
443
- method: 'POST',
444
- headers: {'Content-Type': 'application/json'},
445
- body: JSON.stringify({message})
446
- })
447
- .then(res => res.json())
448
- .then(data => {
449
- addMessage(data.reply, 'echo');
450
- if (characterController) characterController.speak(data.reply);
451
- })
452
- .catch(() => {
453
- addMessage("Sorry, I couldn't connect to the AI server.", 'echo');
454
- });
455
-
456
- // تحديث النقاط كما هو
457
- appState.userProgress.conversationsCompleted++;
458
- appState.userProgress.totalPoints += 25;
459
- updateProgressDisplay();
460
- }
461
-
462
- function addMessage(text, sender) {
463
- const messagesContainer = document.getElementById('chatMessages');
464
- if (!messagesContainer) return;
465
-
466
- const messageDiv = document.createElement('div');
467
- messageDiv.className = `message ${sender}`;
468
- messageDiv.textContent = text;
469
-
470
- messagesContainer.appendChild(messageDiv);
471
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
472
- }
473
-
474
- function startVoiceInput() {
475
- if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
476
- const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
477
- const recognition = new SpeechRecognition();
478
-
479
- recognition.lang = 'en-US';
480
- recognition.continuous = false;
481
- recognition.interimResults = false;
482
-
483
- recognition.onresult = function(event) {
484
- const transcript = event.results[0][0].transcript;
485
- const input = document.getElementById('messageInput');
486
- if (input) {
487
- input.value = transcript;
488
- }
489
- };
490
-
491
- recognition.onerror = function(event) {
492
- console.log('Speech recognition error:', event.error);
493
- alert('حدث خطأ في التعرف على الصوت. تأكد من السماح بالوصول للميكروفون.');
494
- };
495
-
496
- recognition.start();
497
- } else {
498
- alert('متصفحك لا يدعم تقنية التعرف على الصوت.');
499
- }
500
- }
501
-
502
- // Vocabulary Mode
503
- function initializeVocabulary() {
504
- appState.currentVocabIndex = 0;
505
- updateVocabularyCard();
506
- updateVocabularyProgress();
507
- }
508
-
509
- function updateVocabularyCard() {
510
- const card = appData.vocabulary_cards[appState.currentVocabIndex];
511
-
512
- const elements = {
513
- 'cardWord': card.word,
514
- 'cardPronunciation': card.pronunciation,
515
- 'cardTranslation': card.translation,
516
- 'cardExample': card.example
517
- };
518
-
519
- Object.entries(elements).forEach(([id, value]) => {
520
- const element = document.getElementById(id);
521
- if (element) {
522
- element.textContent = value;
523
- }
524
- });
525
-
526
- // Reset card flip
527
- const flashcard = document.getElementById('flashcard');
528
- if (flashcard) {
529
- flashcard.classList.remove('flipped');
530
- }
531
- }
532
-
533
- function updateVocabularyProgress() {
534
- const progress = ((appState.currentVocabIndex + 1) / appData.vocabulary_cards.length) * 100;
535
- const progressFill = document.getElementById('vocabProgress');
536
- const progressText = document.getElementById('vocabProgressText');
537
-
538
- if (progressFill) {
539
- progressFill.style.width = progress + '%';
540
- }
541
- if (progressText) {
542
- progressText.textContent = `${appState.currentVocabIndex + 1}/${appData.vocabulary_cards.length}`;
543
- }
544
- }
545
-
546
- function flipCard() {
547
- const flashcard = document.getElementById('flashcard');
548
- if (flashcard) {
549
- flashcard.classList.toggle('flipped');
550
- }
551
- }
552
-
553
- function nextCard() {
554
- appState.currentVocabIndex = (appState.currentVocabIndex + 1) % appData.vocabulary_cards.length;
555
- updateVocabularyCard();
556
- updateVocabularyProgress();
557
-
558
- // Update progress
559
- appState.userProgress.wordsLearned++;
560
- appState.userProgress.totalPoints += 10;
561
- updateProgressDisplay();
562
-
563
- if (characterController) {
564
- characterController.encourage();
565
- }
566
- }
567
-
568
- function playPronunciation() {
569
- const card = appData.vocabulary_cards[appState.currentVocabIndex];
570
- if (characterController && appState.soundEnabled) {
571
- characterController.speak(card.word);
572
- }
573
- }
574
-
575
- // Pronunciation Mode
576
- function initializePronunciation() {
577
- const words = appData.vocabulary_cards;
578
- const randomWord = words[Math.floor(Math.random() * words.length)];
579
-
580
- const practiceWord = document.getElementById('practiceWord');
581
- const practicePronunciation = document.getElementById('practicePronunciation');
582
-
583
- if (practiceWord) practiceWord.textContent = randomWord.word;
584
- if (practicePronunciation) practicePronunciation.textContent = randomWord.pronunciation;
585
-
586
- // Clear previous feedback
587
- const feedback = document.getElementById('pronunciationFeedback');
588
- if (feedback) {
589
- feedback.innerHTML = '';
590
- }
591
- }
592
-
593
- function playTargetPronunciation() {
594
- const word = document.getElementById('practiceWord');
595
- if (word && characterController && appState.soundEnabled) {
596
- characterController.speak(word.textContent);
597
- }
598
- }
599
-
600
- function toggleRecording() {
601
- const recordBtn = document.getElementById('recordBtn');
602
- const visualizer = document.getElementById('recordVisualizer');
603
-
604
- if (!recordBtn || !visualizer) return;
605
-
606
- if (!appState.isRecording) {
607
- // Start recording
608
- appState.isRecording = true;
609
- recordBtn.textContent = '⏹️ إيقاف التسجيل';
610
- recordBtn.classList.add('recording');
611
-
612
- // Clear previous content
613
- visualizer.innerHTML = '';
614
-
615
- // Create recording visualization
616
- const bars = [];
617
- for (let i = 0; i < 10; i++) {
618
- const bar = document.createElement('div');
619
- bar.style.cssText = `
620
- width: 15px;
621
- background: #32b8c6;
622
- margin: 2px;
623
- border-radius: 2px;
624
- display: inline-block;
625
- height: 20px;
626
- animation: record-bar 0.8s infinite alternate;
627
- animation-delay: ${i * 0.1}s;
628
- `;
629
- bars.push(bar);
630
- visualizer.appendChild(bar);
631
- }
632
-
633
- // Add CSS for bars animation if not exists
634
- if (!document.getElementById('recordBarStyles')) {
635
- const style = document.createElement('style');
636
- style.id = 'recordBarStyles';
637
- style.textContent = `
638
- @keyframes record-bar {
639
- 0% { height: 10px; opacity: 0.5; }
640
- 100% { height: 50px; opacity: 1; }
641
- }
642
- `;
643
- document.head.appendChild(style);
644
- }
645
-
646
- // Auto-stop after 5 seconds
647
- appState.recordingTimer = setTimeout(() => {
648
- if (appState.isRecording) {
649
- toggleRecording();
650
- }
651
- }, 5000);
652
-
653
- } else {
654
- // Stop recording
655
- appState.isRecording = false;
656
- recordBtn.textContent = '🎤 ابدأ التسجيل';
657
- recordBtn.classList.remove('recording');
658
-
659
- // Clear timer
660
- if (appState.recordingTimer) {
661
- clearTimeout(appState.recordingTimer);
662
- appState.recordingTimer = null;
663
- }
664
-
665
- // Clear visualizer
666
- visualizer.innerHTML = '';
667
-
668
- // Show processing message then feedback
669
- const feedback = document.getElementById('pronunciationFeedback');
670
- if (feedback) {
671
- feedback.innerHTML = '<p>جاري تحليل النطق...</p>';
672
-
673
- setTimeout(() => {
674
- showPronunciationFeedback();
675
- }, 1500);
676
- }
677
- }
678
- }
679
-
680
- function showPronunciationFeedback() {
681
- const feedback = document.getElementById('pronunciationFeedback');
682
- if (!feedback) return;
683
-
684
- const accuracy = 65 + Math.random() * 30; // Simulate accuracy between 65-95%
685
- const roundedAccuracy = Math.round(accuracy);
686
-
687
- let message, color;
688
- if (roundedAccuracy >= 85) {
689
- message = 'ممتاز! نطقك رائع 🎉';
690
- color = '#32b8c6';
691
- } else if (roundedAccuracy >= 70) {
692
- message = 'جيد جداً! يمكنك تحسينه أكثر 👍';
693
- color = '#8a2be2';
694
- } else {
695
- message = 'يحتاج تحسين، استمع للنطق الصحيح 🎯';
696
- color = '#ff6b9d';
697
- }
698
-
699
- feedback.innerHTML = `
700
- <h4>نتيجة النطق</h4>
701
- <div style="font-size: 3em; color: ${color}; margin: 16px 0; text-shadow: 0 0 10px ${color}40;">
702
- ${roundedAccuracy}%
703
- </div>
704
- <p style="color: ${color}; font-weight: bold;">${message}</p>
705
- <button class="btn btn--primary" onclick="initializePronunciation()">كلمة جديدة</button>
706
- `;
707
-
708
- // Update progress
709
- appState.userProgress.pronunciationAccuracy = Math.round(
710
- (appState.userProgress.pronunciationAccuracy + accuracy) / 2
711
- );
712
- appState.userProgress.totalPoints += Math.round(accuracy / 10);
713
- updateProgressDisplay();
714
-
715
- if (characterController) {
716
- if (roundedAccuracy >= 85) {
717
- characterController.celebrate();
718
- } else {
719
- characterController.encourage();
720
- }
721
- }
722
- }
723
-
724
- // Grammar Mode
725
- function initializeGrammar() {
726
- appState.currentGrammarIndex = 0;
727
- showGrammarQuestion();
728
- }
729
-
730
- function showGrammarQuestion() {
731
- const exercise = appData.grammar_exercises[appState.currentGrammarIndex];
732
-
733
- const questionElement = document.getElementById('grammarQuestion');
734
- if (questionElement) {
735
- questionElement.textContent = exercise.question;
736
- }
737
-
738
- const optionsContainer = document.getElementById('grammarOptions');
739
- if (optionsContainer) {
740
- optionsContainer.innerHTML = '';
741
-
742
- exercise.options.forEach((option, index) => {
743
- const optionBtn = document.createElement('button');
744
- optionBtn.className = 'option-btn';
745
- optionBtn.textContent = option;
746
- optionBtn.onclick = () => selectGrammarOption(index);
747
-
748
- optionsContainer.appendChild(optionBtn);
749
- });
750
- }
751
-
752
- // Clear previous feedback
753
- const feedback = document.getElementById('grammarFeedback');
754
- if (feedback) {
755
- feedback.innerHTML = '';
756
- }
757
- }
758
-
759
- function selectGrammarOption(selectedIndex) {
760
- const exercise = appData.grammar_exercises[appState.currentGrammarIndex];
761
- const options = document.querySelectorAll('.option-btn');
762
- const feedback = document.getElementById('grammarFeedback');
763
-
764
- if (!feedback) return;
765
-
766
- // Disable all options
767
- options.forEach((btn, index) => {
768
- btn.disabled = true;
769
- if (index === exercise.correct) {
770
- btn.classList.add('correct');
771
- } else if (index === selectedIndex && index !== exercise.correct) {
772
- btn.classList.add('incorrect');
773
- }
774
- });
775
-
776
- // Show feedback
777
- const isCorrect = selectedIndex === exercise.correct;
778
- feedback.innerHTML = `
779
- <h4>${isCorrect ? '✅ إجابة صحيحة!' : '❌ إجابة خاطئة'}</h4>
780
- <p>${exercise.explanation}</p>
781
- <button class="btn btn--primary" onclick="nextGrammarQuestion()">السؤال التالي</button>
782
- `;
783
-
784
- // Update progress
785
- if (isCorrect) {
786
- appState.userProgress.grammarScore += 10;
787
- appState.userProgress.totalPoints += 15;
788
- updateProgressDisplay();
789
-
790
- if (characterController) {
791
- characterController.celebrate();
792
- }
793
- } else {
794
- if (characterController) {
795
- characterController.encourage();
796
- }
797
- }
798
- }
799
-
800
- function nextGrammarQuestion() {
801
- appState.currentGrammarIndex = (appState.currentGrammarIndex + 1) % appData.grammar_exercises.length;
802
- showGrammarQuestion();
803
- }
804
-
805
- // Scenarios Mode
806
- function initializeScenarios() {
807
- const buttonsContainer = document.getElementById('scenarioButtons');
808
- if (!buttonsContainer) return;
809
-
810
- buttonsContainer.innerHTML = '';
811
-
812
- appData.scenarios.forEach((scenario, index) => {
813
- const btn = document.createElement('button');
814
- btn.className = 'scenario-btn';
815
- btn.textContent = scenario.title;
816
- btn.onclick = () => showScenario(index);
817
-
818
- buttonsContainer.appendChild(btn);
819
- });
820
-
821
- // Show first scenario by default
822
- if (appData.scenarios.length > 0) {
823
- showScenario(0);
824
- }
825
- }
826
-
827
- function showScenario(scenarioIndex) {
828
- const scenario = appData.scenarios[scenarioIndex];
829
- appState.currentScenario = scenarioIndex;
830
-
831
- // Update button states
832
- document.querySelectorAll('.scenario-btn').forEach((btn, index) => {
833
- btn.classList.toggle('active', index === scenarioIndex);
834
- });
835
-
836
- // Show dialogue
837
- const dialogueContainer = document.getElementById('scenarioDialogue');
838
- if (dialogueContainer) {
839
- dialogueContainer.innerHTML = `
840
- <h4>${scenario.title}</h4>
841
- <p>${scenario.description}</p>
842
- <div class="dialogue-content">
843
- ${scenario.dialogue.map(line => `
844
- <div class="dialogue-line ${line.speaker}">
845
- <strong>${line.speaker === 'waiter' ? 'النادل:' :
846
- line.speaker === 'customer' ? 'الزبون:' :
847
- line.speaker === 'interviewer' ? 'المحاور:' : 'المرشح:'}</strong>
848
- <p>${line.text}</p>
849
- </div>
850
- `).join('')}
851
- </div>
852
- <button class="btn btn--primary" onclick="practiceScenario()">🎤 تدرب على هذا السيناريو</button>
853
- `;
854
- }
855
- }
856
-
857
- function practiceScenario() {
858
- if (characterController && appState.currentScenario !== null) {
859
- const scenario = appData.scenarios[appState.currentScenario];
860
- const firstLine = scenario.dialogue[0].text;
861
- characterController.speak(firstLine);
862
- }
863
-
864
- // Show practice message
865
- const dialogueContainer = document.getElementById('scenarioDialogue');
866
- if (dialogueContainer) {
867
- const practiceDiv = document.createElement('div');
868
- practiceDiv.style.cssText = `
869
- background: rgba(50, 184, 198, 0.1);
870
- border: 1px solid #32b8c6;
871
- border-radius: 12px;
872
- padding: 16px;
873
- margin-top: 16px;
874
- text-align: center;
875
- `;
876
- practiceDiv.innerHTML = '<p>🎤 الآن دورك! تدرب على الرد باللغة الإنجليزية</p>';
877
- dialogueContainer.appendChild(practiceDiv);
878
- }
879
- }
880
-
881
- // Daily Challenge Mode
882
- function initializeDailyChallenge() {
883
- appState.dailyProgress = 1;
884
- appState.selectedChallengeWords = [];
885
- updateDailyProgress();
886
- startChallengeTimer();
887
- }
888
-
889
- function updateDailyProgress() {
890
- const progress = (appState.dailyProgress / 3) * 100;
891
- const progressFill = document.getElementById('dailyProgress');
892
- const progressText = document.getElementById('dailyProgressText');
893
-
894
- if (progressFill) {
895
- progressFill.style.width = progress + '%';
896
- }
897
- if (progressText) {
898
- progressText.textContent = `${appState.dailyProgress}/3`;
899
- }
900
- }
901
-
902
- function selectWord(wordElement) {
903
- const word = wordElement.textContent;
904
-
905
- // Toggle selection
906
- if (wordElement.classList.contains('selected')) {
907
- wordElement.classList.remove('selected');
908
- appState.selectedChallengeWords = appState.selectedChallengeWords.filter(w => w !== word);
909
- } else {
910
- // Limit to 2 selections
911
- if (appState.selectedChallengeWords.length < 2) {
912
- wordElement.classList.add('selected');
913
- appState.selectedChallengeWords.push(word);
914
- }
915
- }
916
-
917
- // Check if challenge is complete
918
- if (appState.selectedChallengeWords.length === 2) {
919
- setTimeout(() => {
920
- checkChallengeAnswer();
921
- }, 500);
922
- }
923
- }
924
-
925
- function checkChallengeAnswer() {
926
- const correctWords = ['study', 'important'];
927
- const isCorrect = correctWords.every(word => appState.selectedChallengeWords.includes(word));
928
-
929
- if (isCorrect) {
930
- appState.dailyProgress++;
931
- updateDailyProgress();
932
-
933
- if (characterController) {
934
- characterController.celebrate();
935
- }
936
-
937
- // Update streak and points
938
- appState.userProgress.streak++;
939
- appState.userProgress.totalPoints += 20;
940
- updateProgressDisplay();
941
-
942
- if (appState.dailyProgress >= 3) {
943
- setTimeout(() => {
944
- alert('🎉 أكملت التحدي اليومي! أحسنت!');
945
- checkAchievements();
946
- }, 1000);
947
- } else {
948
- setTimeout(() => {
949
- alert('✅ إجابة صحيحة! استمر...');
950
- }, 500);
951
- }
952
- } else {
953
- if (characterController) {
954
- characterController.encourage();
955
- }
956
- setTimeout(() => {
957
- alert('❌ حاول مرة أخرى!');
958
- }, 500);
959
- }
960
-
961
- // Reset selections
962
- document.querySelectorAll('.word-option').forEach(option => {
963
- option.classList.remove('selected');
964
- });
965
- appState.selectedChallengeWords = [];
966
- }
967
-
968
- function startChallengeTimer() {
969
- let timeLeft = 300; // 5 minutes in seconds
970
- const timerElement = document.getElementById('challengeTimer');
971
- if (!timerElement) return;
972
-
973
- const timer = setInterval(() => {
974
- const minutes = Math.floor(timeLeft / 60);
975
- const seconds = timeLeft % 60;
976
- timerElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
977
-
978
- timeLeft--;
979
-
980
- if (timeLeft < 0) {
981
- clearInterval(timer);
982
- alert('⏰ انتهى الوقت! حاول مرة أخرى غداً.');
983
- }
984
- }, 1000);
985
- }
986
-
987
- // Progress Tracking
988
- function updateProgressDisplay() {
989
- const elements = {
990
- 'streakCount': appState.userProgress.streak,
991
- 'totalPoints': appState.userProgress.totalPoints,
992
- 'wordsLearned': appState.userProgress.wordsLearned,
993
- 'conversationsCompleted': appState.userProgress.conversationsCompleted,
994
- 'pronunciationAccuracy': appState.userProgress.pronunciationAccuracy + '%',
995
- 'grammarScore': appState.userProgress.grammarScore
996
- };
997
-
998
- Object.entries(elements).forEach(([id, value]) => {
999
- const element = document.getElementById(id);
1000
- if (element) {
1001
- element.textContent = value;
1002
- }
1003
- });
1004
- }
1005
-
1006
- // Achievements System
1007
- function checkAchievements() {
1008
- const progress = appState.userProgress;
1009
- const achievements = [
1010
- {
1011
- id: 'first_conversation',
1012
- condition: progress.conversationsCompleted >= 1
1013
- },
1014
- {
1015
- id: 'vocabulary_master',
1016
- condition: progress.wordsLearned >= 50
1017
- },
1018
- {
1019
- id: 'pronunciation_expert',
1020
- condition: progress.pronunciationAccuracy >= 85
1021
- },
1022
- {
1023
- id: 'grammar_guru',
1024
- condition: progress.grammarScore >= 100
1025
- },
1026
- {
1027
- id: 'daily_streak',
1028
- condition: progress.streak >= 7
1029
- }
1030
- ];
1031
-
1032
- achievements.forEach(achievement => {
1033
- if (achievement.condition && !progress.unlockedAchievements.includes(achievement.id)) {
1034
- unlockAchievement(achievement.id);
1035
- }
1036
- });
1037
- }
1038
-
1039
- function unlockAchievement(achievementId) {
1040
- const achievement = appData.achievements.find(a => a.id === achievementId);
1041
- if (!achievement) return;
1042
-
1043
- appState.userProgress.unlockedAchievements.push(achievementId);
1044
- appState.userProgress.totalPoints += 50;
1045
- updateProgressDisplay();
1046
-
1047
- // Show achievement notification
1048
- showAchievementNotification(achievement);
1049
-
1050
- if (characterController) {
1051
- characterController.celebrate();
1052
- }
1053
- }
1054
-
1055
- function showAchievementNotification(achievement) {
1056
- const notification = document.createElement('div');
1057
- notification.style.cssText = `
1058
- position: fixed;
1059
- top: 20px;
1060
- right: 20px;
1061
- background: linear-gradient(135deg, #32b8c6, #8a2be2);
1062
- color: white;
1063
- padding: 20px;
1064
- border-radius: 12px;
1065
- box-shadow: 0 10px 30px rgba(0,0,0,0.3);
1066
- z-index: 1000;
1067
- animation: slideIn 0.5s ease-out;
1068
- max-width: 300px;
1069
- `;
1070
- notification.innerHTML = `
1071
- <div style="font-size: 2em; margin-bottom: 8px; text-align: center;">${achievement.icon}</div>
1072
- <h4 style="margin: 0 0 8px 0; text-align: center;">إنجاز جديد!</h4>
1073
- <p style="margin: 0; font-size: 14px; text-align: center;">${achievement.name}</p>
1074
- `;
1075
-
1076
- document.body.appendChild(notification);
1077
-
1078
- setTimeout(() => {
1079
- notification.remove();
1080
- }, 5000);
1081
- }
1082
-
1083
- function initializeAchievements() {
1084
- const achievementsGrid = document.getElementById('achievementsGrid');
1085
- if (!achievementsGrid) return;
1086
-
1087
- achievementsGrid.innerHTML = '';
1088
-
1089
- appData.achievements.forEach(achievement => {
1090
- const isUnlocked = appState.userProgress.unlockedAchievements.includes(achievement.id);
1091
-
1092
- const achievementCard = document.createElement('div');
1093
- achievementCard.className = `achievement-card ${isUnlocked ? 'unlocked' : ''}`;
1094
- achievementCard.innerHTML = `
1095
- <span class="achievement-icon">${achievement.icon}</span>
1096
- <div class="achievement-name">${achievement.name}</div>
1097
- <div class="achievement-description">${achievement.description}</div>
1098
- `;
1099
-
1100
- achievementsGrid.appendChild(achievementCard);
1101
- });
1102
- }
1103
-
1104
- // Sound Management
1105
- function toggleSound() {
1106
- appState.soundEnabled = !appState.soundEnabled;
1107
- const soundToggle = document.getElementById('soundToggle');
1108
-
1109
- if (soundToggle) {
1110
- soundToggle.textContent = appState.soundEnabled ? '🔊' : '🔇';
1111
- soundToggle.title = appState.soundEnabled ? 'إيقاف الصوت' : 'تشغيل الصوت';
1112
-
1113
- // Visual feedback
1114
- soundToggle.style.color = appState.soundEnabled ? '#32b8c6' : '#ff6b9d';
1115
- }
1116
-
1117
- if (!appState.soundEnabled && 'speechSynthesis' in window) {
1118
- speechSynthesis.cancel();
1119
- }
1120
-
1121
- // Show feedback message
1122
- const message = appState.soundEnabled ? 'تم تشغيل الصوت' : 'تم إيقاف الصوت';
1123
- showTemporaryMessage(message);
1124
- }
1125
-
1126
- function showTemporaryMessage(message) {
1127
- const messageDiv = document.createElement('div');
1128
- messageDiv.style.cssText = `
1129
- position: fixed;
1130
- bottom: 100px;
1131
- right: 20px;
1132
- background: rgba(0, 0, 0, 0.8);
1133
- color: white;
1134
- padding: 12px 16px;
1135
- border-radius: 8px;
1136
- font-size: 14px;
1137
- z-index: 1000;
1138
- animation: slideIn 0.5s ease-out;
1139
- `;
1140
- messageDiv.textContent = message;
1141
-
1142
- document.body.appendChild(messageDiv);
1143
-
1144
- setTimeout(() => {
1145
- messageDiv.remove();
1146
- }, 2000);
1147
- }
1148
-
1149
- // Add slide-in animation CSS
1150
- const style = document.createElement('style');
1151
- style.textContent = `
1152
- @keyframes slideIn {
1153
- from { transform: translateX(100%); opacity: 0; }
1154
- to { transform: translateX(0); opacity: 1; }
1155
- }
1156
- `;
1157
- document.head.appendChild(style);
1158
-
1159
- // === Conversational Voice Loop & Avatar State ===
1160
- let isMuted = false;
1161
- let isConversationActive = false;
1162
- let isAutoLoop = false;
1163
- let recognition = null;
1164
- let isSpeakingVoiceLoop = false; // حماية من التكرار
1165
-
1166
- function setVoiceControlButtonsState() {
1167
- const btnStart = document.querySelector('.voice-controls .btn--primary');
1168
- const btnMute = document.querySelector('.voice-controls .btn--secondary');
1169
- const btnEnd = document.querySelector('.voice-controls .btn--danger');
1170
- if (!btnStart || !btnMute || !btnEnd) return;
1171
- btnStart.disabled = isConversationActive || isSpeakingVoiceLoop;
1172
- btnEnd.disabled = !isConversationActive && !isSpeakingVoiceLoop;
1173
- btnMute.disabled = !isConversationActive && !isSpeakingVoiceLoop;
1174
- btnMute.textContent = isMuted ? '🔇 كتم' : '🔊 كتم';
1175
- btnMute.classList.toggle('muted', isMuted);
1176
- }
1177
-
1178
- function setAvatarState(state) {
1179
- if (!characterController) return;
1180
- switch(state) {
1181
- case 'idle':
1182
- characterController.setMood('happy');
1183
- break;
1184
- case 'listening':
1185
- characterController.setMood('encouraging');
1186
- break;
1187
- case 'speaking':
1188
- characterController.setMood('excited');
1189
- break;
1190
- }
1191
- }
1192
-
1193
- function startVoiceLoop() {
1194
- if (isMuted || isConversationActive || isSpeakingVoiceLoop) return;
1195
- isConversationActive = true;
1196
- isAutoLoop = true;
1197
- setAvatarState('listening');
1198
- setVoiceControlButtonsState();
1199
- startListening();
1200
- }
1201
-
1202
- function stopVoiceLoop() {
1203
- isConversationActive = false;
1204
- isAutoLoop = false;
1205
- if (recognition) recognition.stop();
1206
- if ('speechSynthesis' in window) speechSynthesis.cancel();
1207
- isSpeakingVoiceLoop = false;
1208
- setAvatarState('idle');
1209
- setVoiceControlButtonsState();
1210
- }
1211
-
1212
- function toggleMute() {
1213
- isMuted = !isMuted;
1214
- if (isMuted) {
1215
- if (recognition) recognition.stop();
1216
- if ('speechSynthesis' in window) speechSynthesis.cancel();
1217
- setAvatarState('idle');
1218
- isConversationActive = false;
1219
- isAutoLoop = false;
1220
- isSpeakingVoiceLoop = false;
1221
- } else if (!isConversationActive && !isSpeakingVoiceLoop) {
1222
- isConversationActive = true;
1223
- isAutoLoop = true;
1224
- setAvatarState('listening');
1225
- startListening();
1226
- }
1227
- setVoiceControlButtonsState();
1228
- }
1229
-
1230
- function startListening() {
1231
- if (!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)) {
1232
- alert('Speech recognition not supported.');
1233
- return;
1234
- }
1235
- if (isSpeakingVoiceLoop) return; // لا تستمع أثناء النطق
1236
- setAvatarState('listening');
1237
- const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1238
- recognition = new SpeechRecognition();
1239
- recognition.lang = 'en-US';
1240
- recognition.continuous = false;
1241
- recognition.interimResults = false;
1242
- recognition.onresult = function(event) {
1243
- const transcript = event.results[0][0].transcript;
1244
- addMessage(transcript, 'user');
1245
- handleAIResponse(transcript);
1246
- };
1247
- recognition.onerror = function(event) {
1248
- setAvatarState('idle');
1249
- if (isAutoLoop && !isMuted && isConversationActive) {
1250
- setTimeout(startListening, 1000);
1251
- }
1252
- };
1253
- recognition.onend = function() {
1254
- // Do nothing, will be restarted after AI response
1255
- };
1256
- recognition.start();
1257
- }
1258
-
1259
- function handleAIResponse(userText) {
1260
- setAvatarState('thinking');
1261
- fetch('/chat', {
1262
- method: 'POST',
1263
- headers: {'Content-Type': 'application/json'},
1264
- body: JSON.stringify({message: userText})
1265
- })
1266
- .then(res => res.json())
1267
- .then(data => {
1268
- addMessage(data.reply, 'echo');
1269
- setAvatarState('speaking');
1270
- if (characterController && appState.soundEnabled) {
1271
- const utterance = new SpeechSynthesisUtterance(data.reply);
1272
- utterance.lang = 'en-US';
1273
- utterance.onend = () => {
1274
- setAvatarState('idle');
1275
- if (isAutoLoop && !isMuted && isConversationActive) {
1276
- setTimeout(startListening, 500);
1277
- }
1278
- };
1279
- speechSynthesis.speak(utterance);
1280
- characterController.speak(data.reply);
1281
- } else {
1282
- setAvatarState('idle');
1283
- if (isAutoLoop && !isMuted && isConversationActive) {
1284
- setTimeout(startListening, 500);
1285
- }
1286
- }
1287
- })
1288
- .catch(() => {
1289
- addMessage("Sorry, I couldn't connect to the AI server.", 'echo');
1290
- setAvatarState('idle');
1291
- if (isAutoLoop && !isMuted && isConversationActive) {
1292
- setTimeout(startListening, 1500);
1293
- }
1294
- });
1295
- }
1296
-
1297
- document.addEventListener('DOMContentLoaded', () => {
1298
- // Initialize character controller
1299
- characterController = new CharacterController();
1300
-
1301
- // Initialize modes
1302
- initializeModes();
1303
-
1304
- // Initialize achievements
1305
- initializeAchievements();
1306
-
1307
- // Update progress display
1308
- updateProgressDisplay();
1309
-
1310
- // Welcome message
1311
- setTimeout(() => {
1312
- if (characterController) {
1313
- characterController.speak("Welcome to Echo Light! I'm here to help you learn English in a fun and interactive way.");
1314
- }
1315
- }, 2000);
1316
-
1317
- // Set up Enter key for chat
1318
- const messageInput = document.getElementById('messageInput');
1319
- if (messageInput) {
1320
- messageInput.addEventListener('keypress', (e) => {
1321
- if (e.key === 'Enter') {
1322
- sendMessage();
1323
- }
1324
- });
1325
- }
1326
-
1327
- // Check for achievements periodically
1328
- setInterval(() => {
1329
- checkAchievements();
1330
- }, 10000);
1331
-
1332
- // ربط الأزرار يدويًا بعد تحميل الصفحة
1333
- const btnStart = document.querySelector('.voice-controls .btn--primary');
1334
- const btnMute = document.querySelector('.voice-controls .btn--secondary');
1335
- const btnEnd = document.querySelector('.voice-controls .btn--danger');
1336
- if (btnStart) btnStart.onclick = startVoiceLoop;
1337
- if (btnMute) btnMute.onclick = toggleMute;
1338
- if (btnEnd) btnEnd.onclick = stopVoiceLoop;
1339
- setVoiceControlButtonsState();
1340
- });
1341
-
1342
- // Export functions for global access
1343
- window.showScreen = showScreen;
1344
- window.sendMessage = sendMessage;
1345
- window.startVoiceInput = startVoiceInput;
1346
- window.flipCard = flipCard;
1347
- window.nextCard = nextCard;
1348
- window.playPronunciation = playPronunciation;
1349
- window.playTargetPronunciation = playTargetPronunciation;
1350
- window.toggleRecording = toggleRecording;
1351
- window.selectGrammarOption = selectGrammarOption;
1352
- window.nextGrammarQuestion = nextGrammarQuestion;
1353
- window.showScenario = showScenario;
1354
- window.practiceScenario = practiceScenario;
1355
- window.selectWord = selectWord;
1356
- window.toggleSound = toggleSound;
 
1
+ import os
2
+ from flask import Flask, request, jsonify, send_from_directory
3
+ from flask_cors import CORS
4
+ import openai
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv()
8
+
9
+ app = Flask(__name__, static_folder='.', static_url_path='')
10
+ CORS(app)
11
+
12
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or os.getenv("NVIDIA_API_KEY")
13
+ openai.api_key = OPENAI_API_KEY
14
+
15
+ @app.route("/chat", methods=["POST"])
16
+ def chat():
17
+ data = request.get_json(force=True)
18
+ user_message = data.get("message", "")
19
+ if not user_message:
20
+ return jsonify({"reply": "Please say something!"})
21
+ try:
22
+ # Compatible with openai>=1.0.0
23
+ response = openai.chat.completions.create(
24
+ model="gpt-3.5-turbo", # or your NVIDIA LLM if available
25
+ messages=[{"role": "user", "content": user_message}]
26
+ )
27
+ reply = response.choices[0].message.content
28
+ return jsonify({"reply": reply})
29
+ except Exception as e:
30
+ return jsonify({"reply": f"AI error: {str(e)}"})
31
+
32
+ @app.route('/')
33
+ def serve_index():
34
+ return send_from_directory('.', 'index.html')
35
+
36
+ @app.route('/<path:path>')
37
+ def serve_static(path):
38
+ if os.path.exists(path):
39
+ return send_from_directory('.', path)
40
+ return jsonify({'error': 'File not found'}), 404
41
+
42
+ if __name__ == "__main__":
43
+ port = int(os.environ.get("PORT", 7860))
44
+ app.run(host="0.0.0.0", port=port)