ABAO77 commited on
Commit
f784bd2
·
1 Parent(s): 54a64d4

Enhance ASR and feedback mechanisms with Vietnamese pronunciation tips

Browse files

- Added VietnameseTipsProvider for detailed pronunciation guidance.
- Implemented audio quality assessment and error handling in EnhancedWav2Vec2CharacterASR.
- Improved transcription process with retry mechanism and audio validation.
- Enhanced feedback generation with specific tips for common phoneme errors.
- Updated error handling in ProductionPronunciationAssessor to provide more helpful suggestions.
- Refactored existing methods to utilize new Vietnamese tips for better user experience.

src/agents/role_play/__pycache__/func.cpython-311.pyc CHANGED
Binary files a/src/agents/role_play/__pycache__/func.cpython-311.pyc and b/src/agents/role_play/__pycache__/func.cpython-311.pyc differ
 
src/agents/role_play/__pycache__/prompt.cpython-311.pyc CHANGED
Binary files a/src/agents/role_play/__pycache__/prompt.cpython-311.pyc and b/src/agents/role_play/__pycache__/prompt.cpython-311.pyc differ
 
src/agents/role_play/__pycache__/scenarios.cpython-311.pyc CHANGED
Binary files a/src/agents/role_play/__pycache__/scenarios.cpython-311.pyc and b/src/agents/role_play/__pycache__/scenarios.cpython-311.pyc differ
 
src/apis/__pycache__/create_app.cpython-311.pyc CHANGED
Binary files a/src/apis/__pycache__/create_app.cpython-311.pyc and b/src/apis/__pycache__/create_app.cpython-311.pyc differ
 
src/apis/controllers/speaking_controller.py CHANGED
@@ -13,10 +13,12 @@ from loguru import logger
13
  import Levenshtein
14
  from dataclasses import dataclass
15
  from enum import Enum
 
16
  from src.AI_Models.wave2vec_inference import (
17
  create_inference,
18
  export_to_onnx,
19
  )
 
20
 
21
  # Download required NLTK data
22
  try:
@@ -70,9 +72,7 @@ class EnhancedWav2Vec2CharacterASR:
70
  if onnx:
71
  import os
72
 
73
- model_path = (
74
- f"wav2vec2-large-960h-lv60-self{'.quant' if quantized else ''}.onnx"
75
- )
76
  if not os.path.exists(model_path):
77
  export_to_onnx(model_name, quantize=quantized)
78
 
@@ -81,17 +81,71 @@ class EnhancedWav2Vec2CharacterASR:
81
  model_name=model_name, use_onnx=onnx, use_onnx_quantize=quantized
82
  )
83
 
84
- def transcribe_with_features(self, audio_path: str) -> Dict:
85
- """Enhanced transcription with audio features for prosody analysis - Optimized"""
 
 
86
  try:
87
  start_time = time.time()
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  # Basic transcription (already fast - 0.3s)
90
  character_transcript = self.model.file_to_text(audio_path)
91
  character_transcript = self._clean_character_transcript(
92
  character_transcript
93
  )
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  # Fast phoneme conversion
96
  phoneme_representation = self._characters_to_phoneme_representation(
97
  character_transcript
@@ -109,11 +163,19 @@ class EnhancedWav2Vec2CharacterASR:
109
  "phoneme_representation": phoneme_representation,
110
  "audio_features": audio_features,
111
  "confidence": self._estimate_confidence(character_transcript),
 
112
  }
113
 
114
  except Exception as e:
115
  logger.error(f"Enhanced ASR error: {e}")
116
- return self._empty_result()
 
 
 
 
 
 
 
117
 
118
  def _extract_basic_audio_features(self, audio_path: str) -> Dict:
119
  """Extract basic audio features for prosody analysis - Optimized"""
@@ -231,6 +293,119 @@ class EnhancedWav2Vec2CharacterASR:
231
  if letter in letter_to_phoneme
232
  ]
233
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  def _estimate_confidence(self, transcript: str) -> float:
235
  """Estimate transcription confidence"""
236
  if not transcript or len(transcript.strip()) < 2:
@@ -240,13 +415,8 @@ class EnhancedWav2Vec2CharacterASR:
240
  return max(0.0, 1.0 - (repeated_chars * 0.2))
241
 
242
  def _empty_result(self) -> Dict:
243
- """Empty result for error cases"""
244
- return {
245
- "character_transcript": "",
246
- "phoneme_representation": "",
247
- "audio_features": {"duration": 0},
248
- "confidence": 0.0,
249
- }
250
 
251
 
252
  class EnhancedG2P:
@@ -1164,7 +1334,7 @@ class EnhancedProsodyAnalyzer:
1164
  return max(1, syllable_count)
1165
 
1166
  def _empty_prosody_result(self) -> Dict:
1167
- """Return empty prosody result for error cases"""
1168
  return {
1169
  "pace_score": 0.5,
1170
  "intonation_score": 0.5,
@@ -1172,7 +1342,12 @@ class EnhancedProsodyAnalyzer:
1172
  "stress_score": 0.5,
1173
  "overall_prosody": 0.5,
1174
  "details": {},
1175
- "feedback": ["Không thể phân tích ngữ điệu"],
 
 
 
 
 
1176
  }
1177
 
1178
 
@@ -1187,43 +1362,68 @@ class EnhancedFeedbackGenerator:
1187
  mode: AssessmentMode,
1188
  prosody_analysis: Dict = None,
1189
  ) -> List[str]:
1190
- """Generate comprehensive feedback based on assessment mode"""
1191
 
1192
  feedback = []
1193
 
1194
- # Overall score feedback
1195
- if overall_score >= 0.9:
1196
- feedback.append("Phát âm xuất sắc! Bạn đã làm rất tốt.")
1197
- elif overall_score >= 0.8:
1198
- feedback.append("Phát âm rất tốt! Chỉ còn một vài điểm nhỏ cần cải thiện.")
1199
- elif overall_score >= 0.6:
1200
- feedback.append("Phát âm khá tốt, còn một số điểm cần luyện tập thêm.")
1201
- elif overall_score >= 0.4:
1202
- feedback.append("Cần luyện tập thêm. Tập trung vào những từ được đánh dấu.")
1203
- else:
1204
- feedback.append("Hãy luyện tập chậm rãi và rõ ràng hơn.")
1205
 
1206
  # Mode-specific feedback
1207
  if mode == AssessmentMode.WORD:
1208
- feedback.extend(
1209
- self._generate_word_mode_feedback(wrong_words, phoneme_comparisons)
1210
  )
 
1211
  elif mode == AssessmentMode.SENTENCE:
1212
- feedback.extend(
1213
- self._generate_sentence_mode_feedback(wrong_words, prosody_analysis)
1214
  )
 
1215
 
1216
- # Common error patterns
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1217
  error_patterns = self._analyze_error_patterns(phoneme_comparisons)
1218
  if error_patterns:
1219
- feedback.extend(error_patterns)
1220
 
1221
  return feedback
1222
 
1223
  def _generate_word_mode_feedback(
1224
  self, wrong_words: List[Dict], phoneme_comparisons: List[Dict]
1225
  ) -> List[str]:
1226
- """Generate feedback specific to word mode"""
1227
  feedback = []
1228
 
1229
  if wrong_words:
@@ -1231,11 +1431,18 @@ class EnhancedFeedbackGenerator:
1231
  word = wrong_words[0]["word"]
1232
  feedback.append(f"Từ '{word}' cần luyện tập thêm")
1233
 
1234
- # Character-level feedback
1235
  char_errors = wrong_words[0].get("character_errors", [])
1236
  if char_errors:
1237
  error_chars = [err.character for err in char_errors[:3]]
1238
  feedback.append(f"Chú ý các âm: {', '.join(error_chars)}")
 
 
 
 
 
 
 
1239
  else:
1240
  word_list = [w["word"] for w in wrong_words[:3]]
1241
  feedback.append(f"Các từ cần luyện: {', '.join(word_list)}")
@@ -1263,7 +1470,7 @@ class EnhancedFeedbackGenerator:
1263
  return feedback
1264
 
1265
  def _analyze_error_patterns(self, phoneme_comparisons: List[Dict]) -> List[str]:
1266
- """Analyze common error patterns across phonemes"""
1267
  feedback = []
1268
 
1269
  # Count error types
@@ -1276,21 +1483,18 @@ class EnhancedFeedbackGenerator:
1276
  difficult_phonemes[phoneme] += 1
1277
  error_counts[comparison["status"]] += 1
1278
 
1279
- # Most problematic phoneme
1280
  if difficult_phonemes:
1281
  most_difficult = max(difficult_phonemes.items(), key=lambda x: x[1])
1282
  if most_difficult[1] >= 2:
1283
  phoneme = most_difficult[0]
1284
- phoneme_tips = {
1285
- "θ": "Lưỡi giữa răng, thổi nhẹ",
1286
- "ð": "Lưỡi giữa răng, rung dây thanh",
1287
- "v": "Môi dưới chạm răng trên",
1288
- "r": "Cuộn lưỡi nhẹ",
1289
- "z": "Như 's' nhưng rung dây thanh",
1290
- }
1291
 
1292
- if phoneme in phoneme_tips:
1293
- feedback.append(f"Âm khó nhất /{phoneme}/: {phoneme_tips[phoneme]}")
 
 
 
1294
 
1295
  return feedback
1296
 
@@ -1353,8 +1557,36 @@ class ProductionPronunciationAssessor:
1353
  # Step 1: Enhanced ASR transcription with features (0.3s)
1354
  asr_result = self.asr.transcribe_with_features(audio_path)
1355
 
 
 
 
 
 
 
 
 
 
 
 
1356
  if not asr_result["character_transcript"]:
1357
- return self._create_error_result("No speech detected in audio")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1358
 
1359
  # Step 2: Parallel analysis processing
1360
  future_word_analysis = self.executor.submit(
@@ -1436,7 +1668,13 @@ class ProductionPronunciationAssessor:
1436
 
1437
  except Exception as e:
1438
  logger.error(f"Production assessment error: {e}")
1439
- return self._create_error_result(f"Assessment failed: {str(e)}")
 
 
 
 
 
 
1440
 
1441
  def _normalize_mode(self, mode: str, reference_text: str) -> AssessmentMode:
1442
  """Normalize mode parameter with backward compatibility"""
@@ -1581,7 +1819,30 @@ class ProductionPronunciationAssessor:
1581
  return result
1582
 
1583
  def _create_error_result(self, error_message: str) -> Dict:
1584
- """Create error result structure"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1585
  return {
1586
  "transcript": "",
1587
  "transcript_phonemes": "",
@@ -1591,8 +1852,17 @@ class ProductionPronunciationAssessor:
1591
  "word_highlights": [],
1592
  "phoneme_differences": [],
1593
  "wrong_words": [],
1594
- "feedback": [f"Lỗi: {error_message}"],
 
 
 
 
 
 
1595
  "error": error_message,
 
 
 
1596
  "assessment_mode": "error",
1597
  "processing_info": {
1598
  "processing_time": 0,
@@ -1601,6 +1871,7 @@ class ProductionPronunciationAssessor:
1601
  "confidence": 0.0,
1602
  "enhanced_features": False,
1603
  "optimized": True,
 
1604
  },
1605
  }
1606
 
 
13
  import Levenshtein
14
  from dataclasses import dataclass
15
  from enum import Enum
16
+ import os
17
  from src.AI_Models.wave2vec_inference import (
18
  create_inference,
19
  export_to_onnx,
20
  )
21
+ from src.utils.vietnamese_tips import vietnamese_tips
22
 
23
  # Download required NLTK data
24
  try:
 
72
  if onnx:
73
  import os
74
 
75
+ model_path = f"{model_name}{'.quant' if quantized else ''}.onnx"
 
 
76
  if not os.path.exists(model_path):
77
  export_to_onnx(model_name, quantize=quantized)
78
 
 
81
  model_name=model_name, use_onnx=onnx, use_onnx_quantize=quantized
82
  )
83
 
84
+ def transcribe_with_features(self, audio_path: str, retry_count: int = 0) -> Dict:
85
+ """Enhanced transcription with audio features for prosody analysis - Optimized with retry mechanism"""
86
+ max_retries = 2
87
+
88
  try:
89
  start_time = time.time()
90
 
91
+ # Validate audio file
92
+ if not os.path.exists(audio_path):
93
+ logger.error(f"Audio file not found: {audio_path}")
94
+ return self._create_detailed_error_result(
95
+ "Không tìm thấy file âm thanh"
96
+ )
97
+
98
+ # Check audio file size and duration
99
+ try:
100
+ y, sr = librosa.load(audio_path, sr=self.sample_rate)
101
+ duration = len(y) / sr
102
+
103
+ if duration < 0.5:
104
+ logger.warning(f"Audio too short: {duration}s")
105
+ return self._create_detailed_error_result(
106
+ "Âm thanh quá ngắn. Hãy nói dài hơn và rõ ràng hơn."
107
+ )
108
+
109
+ if duration > 30:
110
+ logger.warning(f"Audio too long: {duration}s")
111
+ return self._create_detailed_error_result(
112
+ "Âm thanh quá dài. Hãy nói ngắn gọn hơn."
113
+ )
114
+
115
+ # Check audio volume/energy
116
+ rms_energy = np.sqrt(np.mean(y**2))
117
+ if rms_energy < 0.01: # Very quiet audio
118
+ logger.warning(f"Audio too quiet: RMS={rms_energy}")
119
+ return self._create_detailed_error_result(
120
+ "Âm thanh quá nhỏ. Hãy nói to hơn hoặc di chuyển gần microphone hơn."
121
+ )
122
+
123
+ except Exception as e:
124
+ logger.error(f"Audio preprocessing error: {e}")
125
+ return self._create_detailed_error_result(
126
+ "Lỗi xử lý âm thanh. Hãy kiểm tra định dạng file âm thanh."
127
+ )
128
+
129
  # Basic transcription (already fast - 0.3s)
130
  character_transcript = self.model.file_to_text(audio_path)
131
  character_transcript = self._clean_character_transcript(
132
  character_transcript
133
  )
134
 
135
+ # Check if transcription is empty or too short
136
+ if not character_transcript or len(character_transcript.strip()) < 2:
137
+ if retry_count < max_retries:
138
+ logger.warning(
139
+ f"Empty transcription, retry {retry_count + 1}/{max_retries}"
140
+ )
141
+ time.sleep(0.1) # Brief pause before retry
142
+ return self.transcribe_with_features(audio_path, retry_count + 1)
143
+ else:
144
+ logger.error("No speech detected after retries")
145
+ return self._create_detailed_error_result(
146
+ "Không phát hiện được giọng nói. Hãy nói to và rõ ràng hơn, hoặc kiểm tra microphone."
147
+ )
148
+
149
  # Fast phoneme conversion
150
  phoneme_representation = self._characters_to_phoneme_representation(
151
  character_transcript
 
163
  "phoneme_representation": phoneme_representation,
164
  "audio_features": audio_features,
165
  "confidence": self._estimate_confidence(character_transcript),
166
+ "audio_quality": self._assess_audio_quality(y, sr),
167
  }
168
 
169
  except Exception as e:
170
  logger.error(f"Enhanced ASR error: {e}")
171
+ if retry_count < max_retries:
172
+ logger.warning(f"ASR failed, retry {retry_count + 1}/{max_retries}")
173
+ time.sleep(0.2)
174
+ return self.transcribe_with_features(audio_path, retry_count + 1)
175
+ else:
176
+ return self._create_detailed_error_result(
177
+ f"Lỗi xử lý âm thanh sau {max_retries} lần thử. Hãy kiểm tra file âm thanh và thử lại."
178
+ )
179
 
180
  def _extract_basic_audio_features(self, audio_path: str) -> Dict:
181
  """Extract basic audio features for prosody analysis - Optimized"""
 
293
  if letter in letter_to_phoneme
294
  ]
295
 
296
+ def _assess_audio_quality(self, y: np.ndarray, sr: int) -> Dict:
297
+ """Assess audio quality and provide feedback"""
298
+ try:
299
+ # RMS energy
300
+ rms_energy = np.sqrt(np.mean(y**2))
301
+
302
+ # Signal-to-noise ratio estimation
303
+ # Calculate energy in different frequency bands
304
+ stft = librosa.stft(y)
305
+ mag_spectrum = np.abs(stft)
306
+
307
+ # Estimate noise floor (lowest 10% of energy)
308
+ noise_floor = np.percentile(mag_spectrum, 10)
309
+ signal_energy = np.mean(mag_spectrum)
310
+ snr_estimate = 20 * np.log10(signal_energy / (noise_floor + 1e-10))
311
+
312
+ # Assess clipping
313
+ clipping_ratio = np.sum(np.abs(y) > 0.95) / len(y)
314
+
315
+ quality_score = 1.0
316
+ issues = []
317
+
318
+ if rms_energy < 0.01:
319
+ quality_score -= 0.3
320
+ issues.append("volume_too_low")
321
+ elif rms_energy > 0.8:
322
+ quality_score -= 0.2
323
+ issues.append("volume_too_high")
324
+
325
+ if snr_estimate < 10:
326
+ quality_score -= 0.3
327
+ issues.append("noisy_background")
328
+
329
+ if clipping_ratio > 0.01:
330
+ quality_score -= 0.4
331
+ issues.append("audio_clipping")
332
+
333
+ return {
334
+ "quality_score": max(0.0, quality_score),
335
+ "rms_energy": float(rms_energy),
336
+ "snr_estimate": float(snr_estimate),
337
+ "clipping_ratio": float(clipping_ratio),
338
+ "issues": issues,
339
+ "recommendations": self._get_audio_quality_recommendations(issues),
340
+ }
341
+
342
+ except Exception as e:
343
+ logger.error(f"Audio quality assessment error: {e}")
344
+ return {
345
+ "quality_score": 0.5,
346
+ "issues": ["assessment_failed"],
347
+ "recommendations": ["Thử ghi âm lại với chất lượng tốt hơn"],
348
+ }
349
+
350
+ def _get_audio_quality_recommendations(self, issues: List[str]) -> List[str]:
351
+ """Get specific recommendations based on audio quality issues"""
352
+ recommendations = []
353
+
354
+ if "volume_too_low" in issues:
355
+ recommendations.extend(
356
+ [
357
+ "Nói to hơn hoặc di chuyển gần microphone hơn",
358
+ "Kiểm tra cài đặt âm lượng microphone",
359
+ ]
360
+ )
361
+
362
+ if "volume_too_high" in issues:
363
+ recommendations.extend(
364
+ [
365
+ "Nói nhỏ hơn hoặc di chuyển xa microphone",
366
+ "Giảm âm lượng microphone trong cài đặt",
367
+ ]
368
+ )
369
+
370
+ if "noisy_background" in issues:
371
+ recommendations.extend(
372
+ [
373
+ "Tìm nơi yên tĩnh hơn để ghi âm",
374
+ "Tắt các thiết bị gây tiếng ồn xung quanh",
375
+ ]
376
+ )
377
+
378
+ if "audio_clipping" in issues:
379
+ recommendations.extend(
380
+ ["Giảm âm lượng microphone", "Nói nhỏ hơn hoặc di chuyển xa microphone"]
381
+ )
382
+
383
+ return recommendations
384
+
385
+ def _create_detailed_error_result(self, error_message: str) -> Dict:
386
+ """Create detailed error result with helpful tips"""
387
+ return {
388
+ "character_transcript": "",
389
+ "phoneme_representation": "",
390
+ "audio_features": {"duration": 0},
391
+ "confidence": 0.0,
392
+ "error": True,
393
+ "error_message": error_message,
394
+ "audio_quality": {
395
+ "quality_score": 0.0,
396
+ "issues": ["detection_failed"],
397
+ "recommendations": vietnamese_tips.get_audio_quality_tips(),
398
+ },
399
+ "retry_suggestions": [
400
+ vietnamese_tips.get_retry_encouragement(),
401
+ "Thử các bước sau:",
402
+ "1. Kiểm tra microphone hoạt động tốt",
403
+ "2. Nói to và rõ ràng hơn",
404
+ "3. Tìm nơi yên tĩnh để ghi âm",
405
+ "4. Thử ghi âm lại",
406
+ ],
407
+ }
408
+
409
  def _estimate_confidence(self, transcript: str) -> float:
410
  """Estimate transcription confidence"""
411
  if not transcript or len(transcript.strip()) < 2:
 
415
  return max(0.0, 1.0 - (repeated_chars * 0.2))
416
 
417
  def _empty_result(self) -> Dict:
418
+ """Empty result for error cases - deprecated, use _create_detailed_error_result instead"""
419
+ return self._create_detailed_error_result("Không thể xử lý âm thanh")
 
 
 
 
 
420
 
421
 
422
  class EnhancedG2P:
 
1334
  return max(1, syllable_count)
1335
 
1336
  def _empty_prosody_result(self) -> Dict:
1337
+ """Return enhanced prosody result for error cases"""
1338
  return {
1339
  "pace_score": 0.5,
1340
  "intonation_score": 0.5,
 
1342
  "stress_score": 0.5,
1343
  "overall_prosody": 0.5,
1344
  "details": {},
1345
+ "feedback": [
1346
+ "Không thể phân tích ngữ điệu chi tiết.",
1347
+ "Hãy thử nói rõ ràng và tự nhiên hơn.",
1348
+ vietnamese_tips.get_retry_encouragement(),
1349
+ ],
1350
+ "suggestions": vietnamese_tips.get_audio_quality_tips()[:3],
1351
  }
1352
 
1353
 
 
1362
  mode: AssessmentMode,
1363
  prosody_analysis: Dict = None,
1364
  ) -> List[str]:
1365
+ """Generate comprehensive feedback based on assessment mode with Vietnamese tips"""
1366
 
1367
  feedback = []
1368
 
1369
+ # Overall score feedback with encouragement
1370
+ encouragement = vietnamese_tips.get_encouragement_message(overall_score)
1371
+ feedback.append(encouragement)
1372
+
1373
+ # Collect problematic phonemes for detailed tips
1374
+ problematic_phonemes = []
1375
+ for comparison in phoneme_comparisons:
1376
+ if comparison.get("status") in ["wrong", "substitution"]:
1377
+ ref_phoneme = comparison.get("reference_phoneme")
1378
+ if ref_phoneme and ref_phoneme not in problematic_phonemes:
1379
+ problematic_phonemes.append(ref_phoneme)
1380
 
1381
  # Mode-specific feedback
1382
  if mode == AssessmentMode.WORD:
1383
+ mode_feedback = self._generate_word_mode_feedback(
1384
+ wrong_words, phoneme_comparisons
1385
  )
1386
+ feedback.extend(mode_feedback)
1387
  elif mode == AssessmentMode.SENTENCE:
1388
+ mode_feedback = self._generate_sentence_mode_feedback(
1389
+ wrong_words, prosody_analysis
1390
  )
1391
+ feedback.extend(mode_feedback)
1392
 
1393
+ # Add detailed Vietnamese tips for problematic phonemes
1394
+ if problematic_phonemes and overall_score < 0.8:
1395
+ vietnamese_feedback = vietnamese_tips.get_comprehensive_feedback(
1396
+ problematic_phonemes[:3], overall_score # Limit to top 3
1397
+ )
1398
+
1399
+ if vietnamese_feedback["detailed_tips"]:
1400
+ feedback.append("🎯 Hướng dẫn cải thiện:")
1401
+ for tip in vietnamese_feedback["detailed_tips"][
1402
+ :2
1403
+ ]: # Limit to 2 most important
1404
+ tip_lines = tip["instruction"].strip().split("\n")
1405
+ # Add first few lines of instruction
1406
+ for line in tip_lines[
1407
+ 1:4
1408
+ ]: # Skip title, take first 3 instruction lines
1409
+ if line.strip():
1410
+ feedback.append(f" • {line.strip()}")
1411
+
1412
+ # Add pattern-based tips
1413
+ if vietnamese_feedback["pattern_tips"]:
1414
+ feedback.extend(vietnamese_feedback["pattern_tips"][:2])
1415
+
1416
+ # Common error patterns analysis
1417
  error_patterns = self._analyze_error_patterns(phoneme_comparisons)
1418
  if error_patterns:
1419
+ feedback.extend(error_patterns[:2]) # Limit to avoid overwhelming
1420
 
1421
  return feedback
1422
 
1423
  def _generate_word_mode_feedback(
1424
  self, wrong_words: List[Dict], phoneme_comparisons: List[Dict]
1425
  ) -> List[str]:
1426
+ """Generate feedback specific to word mode with detailed phoneme guidance"""
1427
  feedback = []
1428
 
1429
  if wrong_words:
 
1431
  word = wrong_words[0]["word"]
1432
  feedback.append(f"Từ '{word}' cần luyện tập thêm")
1433
 
1434
+ # Character-level feedback with phoneme guidance
1435
  char_errors = wrong_words[0].get("character_errors", [])
1436
  if char_errors:
1437
  error_chars = [err.character for err in char_errors[:3]]
1438
  feedback.append(f"Chú ý các âm: {', '.join(error_chars)}")
1439
+
1440
+ # Add specific tips for each problematic character/phoneme
1441
+ for char_error in char_errors[:2]: # Limit to 2 most important
1442
+ phoneme = char_error.expected_sound
1443
+ tip = vietnamese_tips.get_detailed_tip(phoneme)
1444
+ if tip:
1445
+ feedback.append(f"Âm /{phoneme}/: {tip.mouth_position}")
1446
  else:
1447
  word_list = [w["word"] for w in wrong_words[:3]]
1448
  feedback.append(f"Các từ cần luyện: {', '.join(word_list)}")
 
1470
  return feedback
1471
 
1472
  def _analyze_error_patterns(self, phoneme_comparisons: List[Dict]) -> List[str]:
1473
+ """Analyze common error patterns across phonemes with detailed Vietnamese guidance"""
1474
  feedback = []
1475
 
1476
  # Count error types
 
1483
  difficult_phonemes[phoneme] += 1
1484
  error_counts[comparison["status"]] += 1
1485
 
1486
+ # Most problematic phoneme with detailed Vietnamese tips
1487
  if difficult_phonemes:
1488
  most_difficult = max(difficult_phonemes.items(), key=lambda x: x[1])
1489
  if most_difficult[1] >= 2:
1490
  phoneme = most_difficult[0]
1491
+ tip = vietnamese_tips.get_detailed_tip(phoneme)
 
 
 
 
 
 
1492
 
1493
+ if tip:
1494
+ feedback.append(f"Âm khó nhất /{phoneme}/ ({tip.vietnamese_name})")
1495
+ feedback.append(f"💡 Mẹo: {tip.mouth_position}")
1496
+ if tip.common_mistakes:
1497
+ feedback.append(f"Tránh lỗi: {tip.common_mistakes[0]}")
1498
 
1499
  return feedback
1500
 
 
1557
  # Step 1: Enhanced ASR transcription with features (0.3s)
1558
  asr_result = self.asr.transcribe_with_features(audio_path)
1559
 
1560
+ # Enhanced error handling for ASR results
1561
+ if asr_result.get("error", False):
1562
+ error_message = asr_result.get(
1563
+ "error_message", "Không thể xử lý âm thanh"
1564
+ )
1565
+ return self._create_enhanced_error_result(
1566
+ error_message,
1567
+ asr_result.get("retry_suggestions", []),
1568
+ asr_result.get("audio_quality", {}),
1569
+ )
1570
+
1571
  if not asr_result["character_transcript"]:
1572
+ # Provide more helpful error message based on audio quality
1573
+ audio_quality = asr_result.get("audio_quality", {})
1574
+ quality_score = audio_quality.get("quality_score", 0.0)
1575
+
1576
+ if quality_score < 0.3:
1577
+ error_msg = "Chất lượng âm thanh không đủ tốt để nhận diện."
1578
+ suggestions = audio_quality.get("recommendations", [])
1579
+ else:
1580
+ error_msg = "Không phát hiện được giọng nói rõ ràng."
1581
+ suggestions = [
1582
+ "Hãy nói to và rõ ràng hơn",
1583
+ "Kiểm tra microphone có hoạt động tốt không",
1584
+ "Thử ghi âm trong môi trường yên tĩnh hơn",
1585
+ ]
1586
+
1587
+ return self._create_enhanced_error_result(
1588
+ error_msg, suggestions, audio_quality
1589
+ )
1590
 
1591
  # Step 2: Parallel analysis processing
1592
  future_word_analysis = self.executor.submit(
 
1668
 
1669
  except Exception as e:
1670
  logger.error(f"Production assessment error: {e}")
1671
+ error_msg = f"Lỗi xử lý: {str(e)}"
1672
+ suggestions = [
1673
+ "Thử ghi âm lại với chất lượng tốt hơn",
1674
+ "Kiểm tra định dạng file âm thanh",
1675
+ "Đảm bảo file âm thanh không bị hỏng",
1676
+ ]
1677
+ return self._create_enhanced_error_result(error_msg, suggestions)
1678
 
1679
  def _normalize_mode(self, mode: str, reference_text: str) -> AssessmentMode:
1680
  """Normalize mode parameter with backward compatibility"""
 
1819
  return result
1820
 
1821
  def _create_error_result(self, error_message: str) -> Dict:
1822
+ """Create error result structure - deprecated, use _create_enhanced_error_result"""
1823
+ return self._create_enhanced_error_result(error_message, [], {})
1824
+
1825
+ def _create_enhanced_error_result(
1826
+ self,
1827
+ error_message: str,
1828
+ suggestions: List[str] = None,
1829
+ audio_quality: Dict = None,
1830
+ ) -> Dict:
1831
+ """Create enhanced error result with helpful tips and suggestions"""
1832
+
1833
+ if suggestions is None:
1834
+ suggestions = vietnamese_tips.get_audio_quality_tips()
1835
+
1836
+ if audio_quality is None:
1837
+ audio_quality = {
1838
+ "quality_score": 0.0,
1839
+ "issues": ["unknown"],
1840
+ "recommendations": suggestions,
1841
+ }
1842
+
1843
+ # Add encouraging retry message
1844
+ retry_encouragement = vietnamese_tips.get_retry_encouragement()
1845
+
1846
  return {
1847
  "transcript": "",
1848
  "transcript_phonemes": "",
 
1852
  "word_highlights": [],
1853
  "phoneme_differences": [],
1854
  "wrong_words": [],
1855
+ "feedback": [
1856
+ f"❌ {error_message}",
1857
+ f"💪 {retry_encouragement}",
1858
+ "",
1859
+ "🔧 Gợi ý cải thiện:",
1860
+ *[f" • {suggestion}" for suggestion in suggestions[:4]],
1861
+ ],
1862
  "error": error_message,
1863
+ "error_type": "audio_processing",
1864
+ "audio_quality": audio_quality,
1865
+ "retry_suggestions": suggestions,
1866
  "assessment_mode": "error",
1867
  "processing_info": {
1868
  "processing_time": 0,
 
1871
  "confidence": 0.0,
1872
  "enhanced_features": False,
1873
  "optimized": True,
1874
+ "error_handled": True,
1875
  },
1876
  }
1877
 
src/apis/routes/__pycache__/chat_route.cpython-311.pyc CHANGED
Binary files a/src/apis/routes/__pycache__/chat_route.cpython-311.pyc and b/src/apis/routes/__pycache__/chat_route.cpython-311.pyc differ
 
src/apis/routes/__pycache__/user_route.cpython-311.pyc CHANGED
Binary files a/src/apis/routes/__pycache__/user_route.cpython-311.pyc and b/src/apis/routes/__pycache__/user_route.cpython-311.pyc differ
 
src/config/__pycache__/llm.cpython-311.pyc CHANGED
Binary files a/src/config/__pycache__/llm.cpython-311.pyc and b/src/config/__pycache__/llm.cpython-311.pyc differ
 
src/utils/__pycache__/logger.cpython-311.pyc CHANGED
Binary files a/src/utils/__pycache__/logger.cpython-311.pyc and b/src/utils/__pycache__/logger.cpython-311.pyc differ
 
src/utils/vietnamese_tips.py ADDED
@@ -0,0 +1,528 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Vietnamese Pronunciation Tips for English Learners
3
+ Mẹo phát âm tiếng Anh dành cho người Việt Nam
4
+
5
+ This module provides detailed pronunciation guidance in Vietnamese
6
+ for common English phonemes that are challenging for Vietnamese speakers.
7
+ """
8
+
9
+ from typing import Dict, List, Optional, Tuple
10
+ from dataclasses import dataclass
11
+ import random
12
+
13
+
14
+ @dataclass
15
+ class PronunciationTip:
16
+ """Structured pronunciation tip with detailed guidance"""
17
+
18
+ phoneme: str
19
+ vietnamese_name: str
20
+ mouth_position: str
21
+ tongue_position: str
22
+ breathing: str
23
+ common_mistakes: List[str]
24
+ practice_words: List[str]
25
+ detailed_instruction: str
26
+ difficulty_level: int # 1-5, where 5 is most difficult
27
+
28
+
29
+ class VietnameseTipsProvider:
30
+ """Provides detailed pronunciation tips in Vietnamese for English learners"""
31
+
32
+ def __init__(self):
33
+ self.tips_database = self._initialize_tips_database()
34
+ self.error_patterns = self._initialize_error_patterns()
35
+
36
+ def _initialize_tips_database(self) -> Dict[str, PronunciationTip]:
37
+ """Initialize comprehensive tips database"""
38
+
39
+ tips = {
40
+ # Consonants that are difficult for Vietnamese speakers
41
+ "θ": PronunciationTip(
42
+ phoneme="θ",
43
+ vietnamese_name="âm th không thanh (như trong 'think')",
44
+ mouth_position="Miệng mở vừa phải, môi tự nhiên",
45
+ tongue_position="Đưa lưỡi ra ngoài, đặt giữa răng trên và răng dưới",
46
+ breathing="Thổi khí nhẹ qua khe giữa lưỡi và răng",
47
+ common_mistakes=[
48
+ "Phát âm thành /f/ (như 'think' thành 'fink')",
49
+ "Phát âm thành /s/ (như 'think' thành 'sink')",
50
+ "Phát âm thành /t/ (như 'think' thành 'tink')",
51
+ ],
52
+ practice_words=["think", "three", "thank", "thing", "throw"],
53
+ detailed_instruction="""
54
+ Cách luyện tập chi tiết:
55
+ 1. Đưa lưỡi ra ngoài sao cho đầu lưỡi chạm nhẹ vào răng trên
56
+ 2. Thổi khí nhẹ qua khe giữa lưỡi và răng - KHÔNG rung dây thanh
57
+ 3. Luyện tập từ 'think' - bắt đầu chậm, sau đó tăng tốc
58
+ 4. Đặt gương trước mặt để kiểm tra vị trí lưỡi
59
+ 5. Luyện tập 10-15 phút mỗi ngày
60
+ """,
61
+ difficulty_level=5,
62
+ ),
63
+ "ð": PronunciationTip(
64
+ phoneme="ð",
65
+ vietnamese_name="âm th có thanh (như trong 'this')",
66
+ mouth_position="Miệng mở vừa phải, môi tự nhiên",
67
+ tongue_position="Đưa lưỡi ra ngoài, đặt giữa răng trên và răng dưới",
68
+ breathing="Rung dây thanh và thổi khí nhẹ",
69
+ common_mistakes=[
70
+ "Phát âm thành /d/ (như 'this' thành 'dis')",
71
+ "Phát âm thành /z/ (như 'this' thành 'zis')",
72
+ "Phát âm thành /v/ (như 'this' thành 'vis')",
73
+ ],
74
+ practice_words=["this", "that", "they", "there", "mother"],
75
+ detailed_instruction="""
76
+ Cách luyện tập chi tiết:
77
+ 1. Giống như âm /θ/ nhưng phải rung dây thanh
78
+ 2. Đặt tay lên cổ họng để cảm nhận rung động
79
+ 3. Bắt đầu với âm /z/, sau đó đưa lưỡi ra giữa răng
80
+ 4. Luyện cặp từ: 'sink/think' và 'zis/this'
81
+ 5. Tập đọc câu: 'This is the thing they threw'
82
+ """,
83
+ difficulty_level=5,
84
+ ),
85
+ "v": PronunciationTip(
86
+ phoneme="v",
87
+ vietnamese_name="âm v (như trong 'very')",
88
+ mouth_position="Môi dưới chạm nhẹ vào răng trên",
89
+ tongue_position="Lưỡi ở vị trí tự nhiên, không chạm răng",
90
+ breathing="Rung dây thanh, khí thổi qua khe môi-răng",
91
+ common_mistakes=[
92
+ "Phát âm thành /w/ (như 'very' thành 'wery')",
93
+ "Phát âm thành /f/ (như 'very' thành 'fery')",
94
+ "Dùng cả hai môi như âm /w/ trong tiếng Việt",
95
+ ],
96
+ practice_words=["very", "voice", "video", "love", "have"],
97
+ detailed_instruction="""
98
+ Cách luyện tập chi tiết:
99
+ 1. CHỈ dùng môi dưới chạm răng trên - đây là điểm khác biệt chính
100
+ 2. Đừng dùng cả hai môi như âm 'u' trong tiếng Việt
101
+ 3. Luyện phân biệt: 'west/vest', 'wine/vine'
102
+ 4. Đặt ngón tay lên môi trên để tránh cử động
103
+ 5. Tập đọc: 'Very valuable video'
104
+ """,
105
+ difficulty_level=4,
106
+ ),
107
+ "w": PronunciationTip(
108
+ phoneme="w",
109
+ vietnamese_name="âm w (như trong 'water')",
110
+ mouth_position="Cả hai môi chu tròn như âm 'u'",
111
+ tongue_position="Lưỡi co lại ở phía sau",
112
+ breathing="Rung dây thanh, thổi qua môi tròn",
113
+ common_mistakes=[
114
+ "Phát âm thành /v/ (như 'water' thành 'vater')",
115
+ "Không chu môi đủ tròn",
116
+ "Lưỡi đặt sai vị trí",
117
+ ],
118
+ practice_words=["water", "wind", "wall", "woman", "sweet"],
119
+ detailed_instruction="""
120
+ Cách luyện tập chi tiết:
121
+ 1. Bắt đầu với âm 'u' trong tiếng Việt, sau đó chuyển sang âm tiếp theo
122
+ 2. Cả hai môi phải chu tròn - không chỉ môi dưới
123
+ 3. Luyện phân biệt: 'vest/west', 'vine/wine'
124
+ 4. Tập nói chậm: 'W-A-T-E-R' rồi tăng tốc
125
+ 5. Luyện câu: 'We went to the west wall'
126
+ """,
127
+ difficulty_level=3,
128
+ ),
129
+ "r": PronunciationTip(
130
+ phoneme="r",
131
+ vietnamese_name="âm r tiếng Anh (như trong 'red')",
132
+ mouth_position="Miệng mở vừa, không mỉm cười",
133
+ tongue_position="Cuộn lưỡi lên không chạm vòm miệng",
134
+ breathing="Rung dây thanh, khí đi qua lưỡi cuộn",
135
+ common_mistakes=[
136
+ "Cuộn lưỡi quá mạnh như tiếng Việt",
137
+ "Lưỡi chạm vào vòm miệng",
138
+ "Phát âm thành /l/",
139
+ "Rung lưỡi như âm 'r' tiếng Việt",
140
+ ],
141
+ practice_words=["red", "run", "right", "car", "very"],
142
+ detailed_instruction="""
143
+ Cách luyện tập chi tiết:
144
+ 1. Cuộn lưỡi lên NHƯNG KHÔNG chạm vào vòm miệng
145
+ 2. Âm /r/ tiếng Anh mềm hơn nhiều so với tiếng Việt
146
+ 3. Luyện phân biệt: 'light/right', 'long/wrong'
147
+ 4. Bắt đầu với âm 'ơ' rồi cuộn lưỡi nhẹ
148
+ 5. Tập từ cuối: 'car', 'far', 'star' - đây dễ hơn
149
+ """,
150
+ difficulty_level=4,
151
+ ),
152
+ "l": PronunciationTip(
153
+ phoneme="l",
154
+ vietnamese_name="âm l tiếng Anh (như trong 'love')",
155
+ mouth_position="Miệng mở tự nhiên",
156
+ tongue_position="Đầu lưỡi chạm vào nướu răng trên",
157
+ breathing="Rung dây thanh, khí đi qua hai bên lưỡi",
158
+ common_mistakes=[
159
+ "Phát âm thành /r/",
160
+ "Đầu lưỡi không chạm đúng vị trí",
161
+ "Phát âm quá nặng như tiếng Việt",
162
+ ],
163
+ practice_words=["love", "light", "blue", "apple", "wall"],
164
+ detailed_instruction="""
165
+ Cách luyện tập chi tiết:
166
+ 1. Đầu lưỡi phải chạm vào nướu răng trên, không phải răng
167
+ 2. Âm /l/ tiếng Anh nhẹ hơn tiếng Việt
168
+ 3. Luyện phân biệt: 'right/light', 'wrong/long'
169
+ 4. Tập âm /l/ cuối từ: 'wall', 'ball', 'call'
170
+ 5. Luyện câu: 'Lucy loves blue apples'
171
+ """,
172
+ difficulty_level=3,
173
+ ),
174
+ "z": PronunciationTip(
175
+ phoneme="z",
176
+ vietnamese_name="âm z (như trong 'zero')",
177
+ mouth_position="Miệng như âm /s/ nhưng rung dây thanh",
178
+ tongue_position="Đầu lưỡi gần răng trên nhưng không chạm",
179
+ breathing="Rung dây thanh, khí đi qua khe nhỏ",
180
+ common_mistakes=[
181
+ "Phát âm thành /s/ (không rung dây thanh)",
182
+ "Phát âm thành /dʒ/ (như 'gi' trong tiếng Việt)",
183
+ "Quá to, quá rõ",
184
+ ],
185
+ practice_words=["zero", "zoo", "easy", "buzz", "maze"],
186
+ detailed_instruction="""
187
+ Cách luyện tập chi tiết:
188
+ 1. Giống hệt âm /s/ nhưng phải rung dây thanh
189
+ 2. Đặt tay lên cổ họng để kiểm tra rung động
190
+ 3. Luyện cặp: 'sip/zip', 'seal/zeal'
191
+ 4. Bắt đầu với âm 's' rồi thêm rung dây thanh
192
+ 5. Tập câu: 'Zero zebras in the zoo'
193
+ """,
194
+ difficulty_level=3,
195
+ ),
196
+ "ʒ": PronunciationTip(
197
+ phoneme="ʒ",
198
+ vietnamese_name="âm zh (như trong 'measure')",
199
+ mouth_position="Môi chu tròn nhẹ",
200
+ tongue_position="Đầu lưỡi gần vòm miệng nhưng không chạm",
201
+ breathing="Rung dây thanh, khí đi qua khe rộng hơn /z/",
202
+ common_mistakes=[
203
+ "Phát âm thành /ʃ/ (như 'sh' nhưng không thanh)",
204
+ "Phát âm thành /z/",
205
+ "Quá giống âm 'gi' tiếng Việt",
206
+ ],
207
+ practice_words=["measure", "pleasure", "vision", "usual", "garage"],
208
+ detailed_instruction="""
209
+ Cách luyện tập chi tiết:
210
+ 1. Giống âm /ʃ/ (sh) nhưng có rung dây thanh
211
+ 2. Luyện cặp: 'ship/vision', 'cash/casual'
212
+ 3. Bắt đầu với 'sh' rồi thêm thanh âm
213
+ 4. Âm này ít gặp ở đầu từ, nhiều ở giữa và cuối
214
+ 5. Tập câu: 'It's my pleasure to measure'
215
+ """,
216
+ difficulty_level=4,
217
+ ),
218
+ "ʃ": PronunciationTip(
219
+ phoneme="ʃ",
220
+ vietnamese_name="âm sh (như trong 'ship')",
221
+ mouth_position="Môi chu tròn nhẹ, hơi thụt vào",
222
+ tongue_position="Lưỡi cong lên gần vòm miệng",
223
+ breathing="Không rung dây thanh, thổi khí mạnh",
224
+ common_mistakes=[
225
+ "Phát âm thành /s/",
226
+ "Không chu môi",
227
+ "Lưỡi đặt sai vị trí",
228
+ ],
229
+ practice_words=["ship", "shoe", "wash", "fish", "shell"],
230
+ detailed_instruction="""
231
+ Cách luyện tập chi tiết:
232
+ 1. Chu môi như chuẩn bị huýt sáo nhưng nhẹ hơn
233
+ 2. Lưỡi cong lên, tạo khoảng rộng hơn âm /s/
234
+ 3. Luyện phân biệt: 'sip/ship', 'sass/shash'
235
+ 4. Âm /ʃ/ dài hơn và êm hơn âm /s/
236
+ 5. Tập câu: 'She sells seashells'
237
+ """,
238
+ difficulty_level=3,
239
+ ),
240
+ # Vowels challenging for Vietnamese speakers
241
+ "æ": PronunciationTip(
242
+ phoneme="æ",
243
+ vietnamese_name="âm ae (như trong 'cat')",
244
+ mouth_position="Miệng mở rộng, góc miệng kéo ra",
245
+ tongue_position="Lưỡi thấp, phẳng ở đáy miệng",
246
+ breathing="Rung dây thanh",
247
+ common_mistakes=[
248
+ "Phát âm thành /ɛ/ (như 'bet')",
249
+ "Phát âm thành /a/ (như 'car')",
250
+ "Không mở miệng đủ rộng",
251
+ ],
252
+ practice_words=["cat", "hat", "bad", "man", "apple"],
253
+ detailed_instruction="""
254
+ Cách luyện tập chi tiết:
255
+ 1. Mở miệng rộng như đang cười nhưng kéo dài
256
+ 2. Lưỡi phải thấp xuống đáy miệng
257
+ 3. Luyện phân biệt: 'bet/bat', 'pen/pan'
258
+ 4. Âm này không có trong tiếng Việt
259
+ 5. Tập câu: 'The fat cat sat on the mat'
260
+ """,
261
+ difficulty_level=4,
262
+ ),
263
+ "ɪ": PronunciationTip(
264
+ phoneme="ɪ",
265
+ vietnamese_name="âm i ngắn (như trong 'bit')",
266
+ mouth_position="Miệng mở hẹp hơn âm /i/",
267
+ tongue_position="Lưỡi hơi thấp hơn âm /i/ dài",
268
+ breathing="Rung dây thanh, âm ngắn",
269
+ common_mistakes=[
270
+ "Phát âm thành /i/ dài (như 'bee')",
271
+ "Kéo dài âm quá mức",
272
+ "Lưỡi đặt quá cao",
273
+ ],
274
+ practice_words=["bit", "sit", "big", "fish", "list"],
275
+ detailed_instruction="""
276
+ Cách luyện tập chi tiết:
277
+ 1. Ngắn và thư giãn hơn âm /i/ dài
278
+ 2. Miệng mở hơi rộng hơn âm /i/
279
+ 3. Luyện phân biệt: 'bit/beat', 'sit/seat'
280
+ 4. Đừng kéo dài như âm 'i' tiếng Việt
281
+ 5. Tập câu: 'The big fish sits in the dish'
282
+ """,
283
+ difficulty_level=3,
284
+ ),
285
+ "ʊ": PronunciationTip(
286
+ phoneme="ʊ",
287
+ vietnamese_name="âm u ngắn (như trong 'book')",
288
+ mouth_position="Môi chu tròn nhẹ, không căng",
289
+ tongue_position="Lưỡi co lại ở phía sau, thấp hơn /u/",
290
+ breathing="Rung dây thanh, âm ngắn",
291
+ common_mistakes=[
292
+ "Phát âm thành /u/ dài (như 'boot')",
293
+ "Chu môi quá mạnh",
294
+ "Kéo dài âm",
295
+ ],
296
+ practice_words=["book", "look", "good", "put", "could"],
297
+ detailed_instruction="""
298
+ Cách luyện tập chi tiết:
299
+ 1. Thư giãn hơn âm /u/ dài
300
+ 2. Môi chu tròn nhẹ, không căng
301
+ 3. Luyện phân biệt: 'book/boot', 'look/Luke'
302
+ 4. Âm ngắn và êm dịu
303
+ 5. Tập câu: 'Look at the good book'
304
+ """,
305
+ difficulty_level=3,
306
+ ),
307
+ "ŋ": PronunciationTip(
308
+ phoneme="ŋ",
309
+ vietnamese_name="âm ng (như trong 'sing')",
310
+ mouth_position="Miệng mở tự nhiên",
311
+ tongue_position="Phần sau lưỡi chạm vào vòm miệng mềm",
312
+ breathing="Rung dây thanh, khí đi qua mũi",
313
+ common_mistakes=[
314
+ "Thêm âm /g/ ở cuối (sing thành 'singg')",
315
+ "Phát âm thành /n/",
316
+ "Không dùng mũi",
317
+ ],
318
+ practice_words=["sing", "ring", "long", "thing", "walking"],
319
+ detailed_instruction="""
320
+ Cách luyện tập chi tiết:
321
+ 1. Phần sau lưỡi chạm vòm miệng mềm - như chuẩn bị nuốt
322
+ 2. KHÔNG phát âm /g/ ở cuối
323
+ 3. Khí ra qua mũi, không qua miệng
324
+ 4. Luyện: 'sin' vs 'sing' - cảm nhận khác biệt
325
+ 5. Tập câu: 'The king is singing a long song'
326
+ """,
327
+ difficulty_level=2,
328
+ ),
329
+ }
330
+
331
+ return tips
332
+
333
+ def _initialize_error_patterns(self) -> Dict[str, List[str]]:
334
+ """Initialize common error patterns for Vietnamese speakers"""
335
+ return {
336
+ "th_sounds": [
337
+ "Âm /θ/ và /ð/ là khó nhất với người Việt - hãy kiên nhẫn luyện tập",
338
+ "Đặt gương trước mặt để kiểm tra vị trí lưỡi khi phát âm /θ/ và /ð/",
339
+ "Luyện tập /θ/ và /ð/ ít nhất 10 phút mỗi ngày",
340
+ ],
341
+ "v_w_confusion": [
342
+ "Nhớ: âm /v/ chỉ dùng môi dưới, âm /w/ dùng cả hai môi",
343
+ "Luyện cặp từ: 'very/wary', 'vest/west' để phân biệt /v/ và /w/",
344
+ "Đặt ngón tay lên môi trên khi tập âm /v/ để tránh cử động",
345
+ ],
346
+ "r_l_confusion": [
347
+ "Âm /r/ tiếng Anh không rung lưỡi như tiếng Việt",
348
+ "Âm /l/ cần đầu lưỡi chạm nướu răng trên",
349
+ "Luyện từ có cả /r/ và /l/: 'really', 'library', 'world'",
350
+ ],
351
+ "final_consonants": [
352
+ "Phát âm âm cuối rõ ràng nhưng không thêm nguyên âm",
353
+ "Tập nhóm âm cuối: -st, -nd, -th, -ng",
354
+ "Đừng nuốt âm cuối như trong tiếng Việt",
355
+ ],
356
+ "vowel_length": [
357
+ "Phân biệt nguyên âm dài/ngắn: /i/ vs /ɪ/, /u/ vs /ʊ/",
358
+ "Nguyên âm tiếng Anh có nhiều biến thể hơn tiếng Việt",
359
+ "Luyện các cặp tối thiểu: 'bit/beat', 'book/boot'",
360
+ ],
361
+ }
362
+
363
+ def get_detailed_tip(self, phoneme: str) -> Optional[PronunciationTip]:
364
+ """Get detailed pronunciation tip for a specific phoneme"""
365
+ return self.tips_database.get(phoneme)
366
+
367
+ def get_error_pattern_tips(self, error_phonemes: List[str]) -> List[str]:
368
+ """Get tips based on common error patterns"""
369
+ tips = []
370
+
371
+ # Analyze error patterns
372
+ has_th_sounds = any(p in ["θ", "ð"] for p in error_phonemes)
373
+ has_v_w = any(p in ["v", "w"] for p in error_phonemes)
374
+ has_r_l = any(p in ["r", "l"] for p in error_phonemes)
375
+
376
+ if has_th_sounds:
377
+ tips.extend(random.sample(self.error_patterns["th_sounds"], 2))
378
+
379
+ if has_v_w:
380
+ tips.extend(random.sample(self.error_patterns["v_w_confusion"], 1))
381
+
382
+ if has_r_l:
383
+ tips.extend(random.sample(self.error_patterns["r_l_confusion"], 1))
384
+
385
+ return tips
386
+
387
+ def get_comprehensive_feedback(
388
+ self, incorrect_phonemes: List[str], difficulty_threshold: float = 0.7
389
+ ) -> Dict[str, any]:
390
+ """
391
+ Generate comprehensive feedback with detailed tips
392
+
393
+ Args:
394
+ incorrect_phonemes: List of phonemes that were pronounced incorrectly
395
+ difficulty_threshold: Score threshold below which to provide detailed tips
396
+
397
+ Returns:
398
+ Dictionary with comprehensive feedback and tips
399
+ """
400
+
401
+ if not incorrect_phonemes:
402
+ return {
403
+ "status": "excellent",
404
+ "message": "Phát âm xuất sắc! Bạn đã phát âm chính xác.",
405
+ "tips": [],
406
+ "practice_suggestions": [
407
+ "Tiếp tục luyện tập để duy trì độ chính xác",
408
+ "Thử các từ khó hơn để thách thức bản thân",
409
+ ],
410
+ }
411
+
412
+ # Get detailed tips for each problematic phoneme
413
+ detailed_tips = []
414
+ practice_words = set()
415
+
416
+ for phoneme in incorrect_phonemes[:3]: # Limit to top 3 problematic phonemes
417
+ tip = self.get_detailed_tip(phoneme)
418
+ if tip:
419
+ detailed_tips.append(
420
+ {
421
+ "phoneme": phoneme,
422
+ "vietnamese_name": tip.vietnamese_name,
423
+ "instruction": tip.detailed_instruction,
424
+ "common_mistakes": tip.common_mistakes,
425
+ "difficulty": tip.difficulty_level,
426
+ }
427
+ )
428
+ practice_words.update(tip.practice_words[:3])
429
+
430
+ # Get pattern-based tips
431
+ pattern_tips = self.get_error_pattern_tips(incorrect_phonemes)
432
+
433
+ # Generate practice suggestions
434
+ practice_suggestions = [
435
+ f"Luyện tập các từ này: {', '.join(list(practice_words)[:5])}",
436
+ "Tập phát âm trước gương để kiểm tra vị trí môi và lưỡi",
437
+ "Thu âm và nghe lại để so sánh với bản gốc",
438
+ "Luyện tập 10-15 phút mỗi ngày cho mỗi âm kh��",
439
+ ]
440
+
441
+ return {
442
+ "status": "needs_improvement",
443
+ "message": f"Cần cải thiện {len(incorrect_phonemes)} âm. Đừng nản lòng, cứ luyện tập từ từ!",
444
+ "detailed_tips": detailed_tips,
445
+ "pattern_tips": pattern_tips,
446
+ "practice_suggestions": practice_suggestions,
447
+ "priority_phonemes": incorrect_phonemes[
448
+ :2
449
+ ], # Focus on top 2 most problematic
450
+ }
451
+
452
+ def get_encouragement_message(self, score: float) -> str:
453
+ """Get encouraging message based on score"""
454
+
455
+ if score >= 0.9:
456
+ messages = [
457
+ "Tuyệt vời! Phát âm của bạn rất chuẩn!",
458
+ "Xuất sắc! Bạn đã tiến bộ rất nhiều!",
459
+ "Hoàn hảo! Tiếp tục duy trì nhé!",
460
+ ]
461
+ elif score >= 0.8:
462
+ messages = [
463
+ "Rất tốt! Chỉ còn một vài điểm nhỏ cần cải thiện.",
464
+ "Tuyệt! Bạn đang trên đúng con đường.",
465
+ "Giỏi lắm! Tiếp tục cố gắng nhé!",
466
+ ]
467
+ elif score >= 0.6:
468
+ messages = [
469
+ "Khá tốt! Hãy tập trung vào những âm được gạch chân.",
470
+ "Bạn đang tiến bộ! Cứ luyện tập đều đặn.",
471
+ "Tốt đấy! Từ từ sẽ chuẩn thôi.",
472
+ ]
473
+ elif score >= 0.4:
474
+ messages = [
475
+ "Đừng nản lòng! Mọi người đều phải trải qua giai đoạn này.",
476
+ "Tiếp tục cố gắng! Luyện tập là chìa khóa thành công.",
477
+ "Bạn đang học! Mỗi lần luyện là một bước tiến.",
478
+ ]
479
+ else:
480
+ messages = [
481
+ "Bạn đang bắt đầu hành trình! Hãy luyện chậm và rõ ràng.",
482
+ "Đừng vội! Phát âm cần thời gian để hoàn thiện.",
483
+ "Cứ bình tĩnh luyện tập, bạn sẽ tiến bộ nhanh thôi!",
484
+ ]
485
+
486
+ return random.choice(messages)
487
+
488
+ def get_audio_quality_tips(self) -> List[str]:
489
+ """Get tips for better audio recording quality"""
490
+ return [
491
+ "Nói to và rõ ràng hơn - microphone cần âm thanh đủ mạnh",
492
+ "Tìm nơi yên tĩnh để thu âm, tránh tiếng ồn xung quanh",
493
+ "Giữ khoảng cách 15-20cm từ miệng đến microphone",
494
+ "Nói chậm rãi và phát âm từng âm một cách rõ ràng",
495
+ "Kiểm tra microphone có hoạt động tốt không",
496
+ "Thử thu âm lại nếu âm thanh không rõ ràng",
497
+ ]
498
+
499
+ def get_retry_encouragement(self) -> str:
500
+ """Get encouraging message for retry attempts"""
501
+ messages = [
502
+ "Không sao! Hãy thử lại và nói to hơn một chút.",
503
+ "Microphone không nghe rõ. Bạn có thể nói rõ ràng hơn không?",
504
+ "Thử nói chậm và to hơn để hệ thống có thể nhận diện tốt hơn.",
505
+ "Đừng lo! Hãy thở sâu và thử lại một lần nữa.",
506
+ "Có thể do tiếng ồn xung quanh. Hãy tìm nơi yên tĩnh hơn và thử lại.",
507
+ ]
508
+ return random.choice(messages)
509
+
510
+
511
+ # Global instance for easy access
512
+ vietnamese_tips = VietnameseTipsProvider()
513
+
514
+
515
+ def get_phoneme_tip(phoneme: str) -> Optional[str]:
516
+ """Quick access function to get tip for a phoneme"""
517
+ tip = vietnamese_tips.get_detailed_tip(phoneme)
518
+ return tip.detailed_instruction if tip else None
519
+
520
+
521
+ def get_quick_tips(phonemes: List[str]) -> List[str]:
522
+ """Get quick tips for a list of phonemes"""
523
+ tips = []
524
+ for phoneme in phonemes[:3]: # Limit to 3 most important
525
+ tip = vietnamese_tips.get_detailed_tip(phoneme)
526
+ if tip:
527
+ tips.append(f"Âm /{phoneme}/: {tip.vietnamese_name} - {tip.mouth_position}")
528
+ return tips