import 'dart:async'; import '../models/dialogue_models.dart'; /// 對話服務 /// /// 提供完整的對話管理功能,包括: /// - 場景和角色加載 /// - AI回應生成 /// - 語言分析和評分 /// - 任務進度跟踪 class DialogueService { /// 加載場景信息 Future loadScene(String scenarioId, String levelId) async { // 模擬API調用延遲 await Future.delayed(const Duration(milliseconds: 500)); // 返回模擬數據 return DialogueScene( id: scenarioId, name: '餐廳用餐', description: '在餐廳與服務員進行日常對話', backgroundImageUrl: 'assets/images/restaurant_bg.jpg', characterId: 'waiter_001', difficultyLevel: 'beginner', tags: ['restaurant', 'ordering', 'daily'], ); } /// 加載角色信息 Future loadCharacter(String characterId) async { await Future.delayed(const Duration(milliseconds: 300)); return DialogueCharacter( id: characterId, name: '小王', description: '友善的餐廳服務員', avatarUrl: 'assets/images/waiter_avatar.jpg', personality: '友善、耐心、專業', role: '服務員', background: '在餐廳工作了3年,非常熟悉菜單和服務流程', specialities: ['點餐服務', '菜品介紹', '客戶服務'], ); } /// 加載任務信息 Future loadTask(String levelId) async { await Future.delayed(const Duration(milliseconds: 200)); return DialogueTask( id: 'task_$levelId', title: '完成點餐', description: '與服務員完成一次完整的點餐對話,包括詢問菜品、下訂單、確認價格', type: DialogueTaskType.conversation, requirements: { 'minTurns': 5, 'mustUseWords': ['menu', 'order', 'price'], 'completionCriteria': ['greeting', 'ordering', 'confirmation'], }, ); } /// 加載必需詞彙 Future> loadRequiredVocabulary(String levelId) async { await Future.delayed(const Duration(milliseconds: 150)); return [ 'menu', 'order', 'price', 'recommendation', 'delicious', 'bill', ]; } /// 獲取開場對話 Future getOpeningDialogue(String scenarioId, String levelId) async { await Future.delayed(const Duration(milliseconds: 400)); return DialogueMessage( id: 'opening_${DateTime.now().millisecondsSinceEpoch}', content: '歡迎光臨!請問您需要什麼嗎?我可以為您介紹今天的特色菜。', isUser: false, timestamp: DateTime.now(), type: DialogueMessageType.text, ); } /// 分析用戶回覆 Future analyzeReply({ required String scenarioId, required String levelId, required String replyText, required List requiredVocabulary, DialogueTask? currentTask, }) async { await Future.delayed(const Duration(milliseconds: 800)); // 模擬AI分析 final usedWords = _findUsedVocabulary(replyText, requiredVocabulary); final grammarIssues = _analyzeGrammar(replyText); // 計算得分 final grammarScore = _calculateGrammarScore(grammarIssues); final semanticsScore = _calculateSemanticsScore(replyText, currentTask); final fluencyScore = _calculateFluencyScore(replyText); // 計算任務進度 double? taskProgress; if (currentTask != null) { taskProgress = _calculateTaskProgress(replyText, currentTask, usedWords); } return DialogueAnalysis( id: 'analysis_${DateTime.now().millisecondsSinceEpoch}', userReply: replyText, timestamp: DateTime.now(), grammarScore: grammarScore, semanticsScore: semanticsScore, fluencyScore: fluencyScore, grammarIssues: grammarIssues, usedVocabulary: usedWords, missedVocabulary: requiredVocabulary.where((word) => !usedWords.contains(word)).toList(), suggestions: _generateSuggestions(replyText, grammarIssues), taskProgress: taskProgress, isDialogueComplete: taskProgress != null && taskProgress >= 1.0, ); } /// 獲取AI回應 Future getAIResponse({ required String scenarioId, required String levelId, required String userReply, required DialogueAnalysis analysis, }) async { await Future.delayed(const Duration(milliseconds: 600)); // 根據用戶回覆生成AI回應 String response = _generateAIResponse(userReply, analysis); return DialogueMessage( id: 'ai_response_${DateTime.now().millisecondsSinceEpoch}', content: response, isUser: false, timestamp: DateTime.now(), type: DialogueMessageType.text, metadata: { 'responseType': 'contextual', 'grammarScore': analysis.grammarScore, 'semanticsScore': analysis.semanticsScore, }, ); } /// 獲取回覆輔助建議 Future> getReplyAssistance({ required String scenarioId, required String levelId, required String currentDialogue, DialogueTask? currentTask, }) async { await Future.delayed(const Duration(milliseconds: 400)); // 根據當前對話內容生成建議回覆 return [ '可以給我看一下菜單嗎?', '請推薦一些招牌菜。', '這個菜的價格是多少?', '我想要點這個。', '謝謝,我考慮一下。', ]; } /// 查找使用的詞彙 List _findUsedVocabulary(String text, List requiredVocabulary) { final usedWords = []; final lowerText = text.toLowerCase(); for (final word in requiredVocabulary) { if (lowerText.contains(word.toLowerCase())) { usedWords.add(word); } } return usedWords; } /// 分析語法 List _analyzeGrammar(String text) { final issues = []; // 簡單的語法檢查模擬 if (text.length < 5) { issues.add(GrammarIssue( type: 'length', description: '回覆太短,請提供更完整的句子', originalText: text, suggestedText: '$text(建議擴展內容)', position: 0, length: text.length, severity: GrammarIssueSeverity.minor, )); } if (!text.endsWith('.') && !text.endsWith('?') && !text.endsWith('!') && !text.endsWith('?')) { issues.add(GrammarIssue( type: 'punctuation', description: '建議在句尾加上標點符號', originalText: text, suggestedText: '$text。', position: text.length, length: 0, severity: GrammarIssueSeverity.minor, )); } return issues; } /// 計算語法得分 double _calculateGrammarScore(List issues) { if (issues.isEmpty) return 95.0; double penalty = 0.0; for (final issue in issues) { switch (issue.severity) { case GrammarIssueSeverity.critical: penalty += 20.0; break; case GrammarIssueSeverity.major: penalty += 15.0; break; case GrammarIssueSeverity.moderate: penalty += 10.0; break; case GrammarIssueSeverity.minor: penalty += 5.0; break; } } return (100.0 - penalty).clamp(0.0, 100.0); } /// 計算語意得分 double _calculateSemanticsScore(String text, DialogueTask? task) { // 基礎語意得分 double score = 75.0; // 根據文字長度和內容豐富度調整 if (text.length > 20) score += 10.0; if (text.length > 50) score += 5.0; // 根據任務相關性調整 if (task != null) { final requirements = task.requirements['mustUseWords'] as List?; if (requirements != null) { final requiredWords = requirements.cast(); final usedCount = requiredWords.where((word) => text.toLowerCase().contains(word.toLowerCase())).length; score += (usedCount / requiredWords.length) * 20.0; } } return score.clamp(0.0, 100.0); } /// 計算流暢度得分 double _calculateFluencyScore(String text) { // 基礎流暢度得分 double score = 80.0; // 根據句子結構調整 if (text.contains(',') || text.contains(',')) score += 5.0; if (text.split(' ').length > 5 || text.length > 15) score += 10.0; // 檢查是否有重複詞語 final words = text.split(RegExp(r'\s+')); final uniqueWords = words.toSet(); if (words.length != uniqueWords.length) score -= 5.0; return score.clamp(0.0, 100.0); } /// 計算任務進度 double _calculateTaskProgress(String text, DialogueTask task, List usedWords) { double progress = 0.0; final requirements = task.requirements; // 檢查必需詞彙 final mustUseWords = requirements['mustUseWords'] as List?; if (mustUseWords != null) { final requiredWords = mustUseWords.cast(); final usedRequiredWords = requiredWords.where((word) => usedWords.contains(word)).length; progress += (usedRequiredWords / requiredWords.length) * 0.5; } // 檢查完成標準 final completionCriteria = requirements['completionCriteria'] as List?; if (completionCriteria != null) { final criteria = completionCriteria.cast(); int metCriteria = 0; for (final criterion in criteria) { if (_checkCriterion(text, criterion)) { metCriteria++; } } progress += (metCriteria / criteria.length) * 0.5; } return progress.clamp(0.0, 1.0); } /// 檢查完成標準 bool _checkCriterion(String text, String criterion) { final lowerText = text.toLowerCase(); switch (criterion) { case 'greeting': return lowerText.contains('hello') || lowerText.contains('hi') || lowerText.contains('你好') || lowerText.contains('哈囉'); case 'ordering': return lowerText.contains('order') || lowerText.contains('want') || lowerText.contains('點') || lowerText.contains('要'); case 'confirmation': return lowerText.contains('confirm') || lowerText.contains('yes') || lowerText.contains('ok') || lowerText.contains('確認') || lowerText.contains('好的'); default: return false; } } /// 生成建議 List _generateSuggestions(String text, List issues) { final suggestions = []; for (final issue in issues) { suggestions.add('${issue.description}: "${issue.suggestedText}"'); } if (text.length < 10) { suggestions.add('試著提供更詳細的回應'); } return suggestions; } /// 生成AI回應 String _generateAIResponse(String userReply, DialogueAnalysis analysis) { final lowerReply = userReply.toLowerCase(); // 根據用戶回覆內容生成相應回應 if (lowerReply.contains('menu') || lowerReply.contains('菜單')) { return '好的,這是我們的菜單。我們今天的特色菜是紅燒肉和宮保雞丁,都很受歡迎呢!'; } else if (lowerReply.contains('recommend') || lowerReply.contains('推薦')) { return '我推薦我們的招牌菜紅燒肉,還有今天新鮮的清蒸魚。您比較喜歡什麼口味的呢?'; } else if (lowerReply.contains('price') || lowerReply.contains('多少錢') || lowerReply.contains('價格')) { return '紅燒肉是28元,清蒸魚是35元。這些都是我們的人氣菜品,分量也很足。'; } else if (lowerReply.contains('order') || lowerReply.contains('點') || lowerReply.contains('要')) { return '好的,已經為您記下了。還需要什麼其他的嗎?飲料或者湯品?'; } else if (lowerReply.contains('thank') || lowerReply.contains('謝謝')) { return '不客氣!如果還有什麼需要,請隨時告訴我。'; } else { // 預設回應 return '我明白了。還有什麼我可以為您服務的嗎?'; } } }