/// 對話場景模型 class DialogueScene { final String id; final String name; final String description; final String backgroundImageUrl; final String characterId; final String difficultyLevel; final List tags; final Map metadata; DialogueScene({ required this.id, required this.name, required this.description, required this.backgroundImageUrl, required this.characterId, required this.difficultyLevel, this.tags = const [], this.metadata = const {}, }); factory DialogueScene.fromJson(Map json) { return DialogueScene( id: json['id'] as String, name: json['name'] as String, description: json['description'] as String, backgroundImageUrl: json['backgroundImageUrl'] as String, characterId: json['characterId'] as String, difficultyLevel: json['difficultyLevel'] as String, tags: List.from(json['tags'] ?? []), metadata: json['metadata'] as Map? ?? {}, ); } Map toJson() { return { 'id': id, 'name': name, 'description': description, 'backgroundImageUrl': backgroundImageUrl, 'characterId': characterId, 'difficultyLevel': difficultyLevel, 'tags': tags, 'metadata': metadata, }; } } /// 對話角色模型 class DialogueCharacter { final String id; final String name; final String description; final String avatarUrl; final String personality; final String role; final String background; final List specialities; final Map localizedNames; DialogueCharacter({ required this.id, required this.name, required this.description, required this.avatarUrl, required this.personality, required this.role, required this.background, this.specialities = const [], this.localizedNames = const {}, }); factory DialogueCharacter.fromJson(Map json) { return DialogueCharacter( id: json['id'] as String, name: json['name'] as String, description: json['description'] as String, avatarUrl: json['avatarUrl'] as String, personality: json['personality'] as String, role: json['role'] as String, background: json['background'] as String, specialities: List.from(json['specialities'] ?? []), localizedNames: Map.from(json['localizedNames'] ?? {}), ); } Map toJson() { return { 'id': id, 'name': name, 'description': description, 'avatarUrl': avatarUrl, 'personality': personality, 'role': role, 'background': background, 'specialities': specialities, 'localizedNames': localizedNames, }; } } /// 對話消息模型 class DialogueMessage { final String id; final String content; final bool isUser; final DateTime timestamp; final DialogueMessageType type; final Map? metadata; final String? audioUrl; final double? confidence; DialogueMessage({ required this.id, required this.content, required this.isUser, required this.timestamp, this.type = DialogueMessageType.text, this.metadata, this.audioUrl, this.confidence, }); factory DialogueMessage.fromJson(Map json) { return DialogueMessage( id: json['id'] as String, content: json['content'] as String, isUser: json['isUser'] as bool, timestamp: DateTime.parse(json['timestamp'] as String), type: DialogueMessageType.values.firstWhere( (e) => e.toString() == 'DialogueMessageType.${json['type']}', orElse: () => DialogueMessageType.text, ), metadata: json['metadata'] as Map?, audioUrl: json['audioUrl'] as String?, confidence: json['confidence'] as double?, ); } Map toJson() { return { 'id': id, 'content': content, 'isUser': isUser, 'timestamp': timestamp.toIso8601String(), 'type': type.toString().split('.').last, 'metadata': metadata, 'audioUrl': audioUrl, 'confidence': confidence, }; } } /// 對話消息類型 enum DialogueMessageType { text, audio, system, hint, } /// 對話任務模型 class DialogueTask { final String id; final String title; final String description; final DialogueTaskType type; final Map requirements; final double progress; final bool isCompleted; final int maxAttempts; final int currentAttempts; final String? completionMessage; DialogueTask({ required this.id, required this.title, required this.description, required this.type, required this.requirements, this.progress = 0.0, this.isCompleted = false, this.maxAttempts = 3, this.currentAttempts = 0, this.completionMessage, }); factory DialogueTask.fromJson(Map json) { return DialogueTask( id: json['id'] as String, title: json['title'] as String, description: json['description'] as String, type: DialogueTaskType.values.firstWhere( (e) => e.toString() == 'DialogueTaskType.${json['type']}', orElse: () => DialogueTaskType.conversation, ), requirements: json['requirements'] as Map, progress: json['progress'] as double? ?? 0.0, isCompleted: json['isCompleted'] as bool? ?? false, maxAttempts: json['maxAttempts'] as int? ?? 3, currentAttempts: json['currentAttempts'] as int? ?? 0, completionMessage: json['completionMessage'] as String?, ); } Map toJson() { return { 'id': id, 'title': title, 'description': description, 'type': type.toString().split('.').last, 'requirements': requirements, 'progress': progress, 'isCompleted': isCompleted, 'maxAttempts': maxAttempts, 'currentAttempts': currentAttempts, 'completionMessage': completionMessage, }; } DialogueTask copyWith({ String? id, String? title, String? description, DialogueTaskType? type, Map? requirements, double? progress, bool? isCompleted, int? maxAttempts, int? currentAttempts, String? completionMessage, }) { return DialogueTask( id: id ?? this.id, title: title ?? this.title, description: description ?? this.description, type: type ?? this.type, requirements: requirements ?? this.requirements, progress: progress ?? this.progress, isCompleted: isCompleted ?? this.isCompleted, maxAttempts: maxAttempts ?? this.maxAttempts, currentAttempts: currentAttempts ?? this.currentAttempts, completionMessage: completionMessage ?? this.completionMessage, ); } } /// 對話任務類型 enum DialogueTaskType { conversation, // 完成對話 vocabulary, // 使用指定詞彙 grammar, // 語法練習 pronunciation, // 發音練習 comprehension, // 理解測試 } /// 對話分析結果模型 class DialogueAnalysis { final String id; final String userReply; final DateTime timestamp; // 三維度評分 final double grammarScore; final double semanticsScore; final double fluencyScore; // 詳細分析 final List grammarIssues; final List usedVocabulary; final List missedVocabulary; final List suggestions; // 任務相關 final double? taskProgress; final bool isDialogueComplete; // 其他 final Map metadata; DialogueAnalysis({ required this.id, required this.userReply, required this.timestamp, required this.grammarScore, required this.semanticsScore, required this.fluencyScore, this.grammarIssues = const [], this.usedVocabulary = const [], this.missedVocabulary = const [], this.suggestions = const [], this.taskProgress, this.isDialogueComplete = false, this.metadata = const {}, }); factory DialogueAnalysis.fromJson(Map json) { return DialogueAnalysis( id: json['id'] as String, userReply: json['userReply'] as String, timestamp: DateTime.parse(json['timestamp'] as String), grammarScore: json['grammarScore'] as double, semanticsScore: json['semanticsScore'] as double, fluencyScore: json['fluencyScore'] as double, grammarIssues: (json['grammarIssues'] as List?) ?.map((e) => GrammarIssue.fromJson(e as Map)) .toList() ?? [], usedVocabulary: List.from(json['usedVocabulary'] ?? []), missedVocabulary: List.from(json['missedVocabulary'] ?? []), suggestions: List.from(json['suggestions'] ?? []), taskProgress: json['taskProgress'] as double?, isDialogueComplete: json['isDialogueComplete'] as bool? ?? false, metadata: json['metadata'] as Map? ?? {}, ); } Map toJson() { return { 'id': id, 'userReply': userReply, 'timestamp': timestamp.toIso8601String(), 'grammarScore': grammarScore, 'semanticsScore': semanticsScore, 'fluencyScore': fluencyScore, 'grammarIssues': grammarIssues.map((e) => e.toJson()).toList(), 'usedVocabulary': usedVocabulary, 'missedVocabulary': missedVocabulary, 'suggestions': suggestions, 'taskProgress': taskProgress, 'isDialogueComplete': isDialogueComplete, 'metadata': metadata, }; } } /// 語法問題模型 class GrammarIssue { final String type; final String description; final String originalText; final String suggestedText; final int position; final int length; final GrammarIssueSeverity severity; GrammarIssue({ required this.type, required this.description, required this.originalText, required this.suggestedText, required this.position, required this.length, required this.severity, }); factory GrammarIssue.fromJson(Map json) { return GrammarIssue( type: json['type'] as String, description: json['description'] as String, originalText: json['originalText'] as String, suggestedText: json['suggestedText'] as String, position: json['position'] as int, length: json['length'] as int, severity: GrammarIssueSeverity.values.firstWhere( (e) => e.toString() == 'GrammarIssueSeverity.${json['severity']}', orElse: () => GrammarIssueSeverity.minor, ), ); } Map toJson() { return { 'type': type, 'description': description, 'originalText': originalText, 'suggestedText': suggestedText, 'position': position, 'length': length, 'severity': severity.toString().split('.').last, }; } } /// 語法問題嚴重程度 enum GrammarIssueSeverity { minor, moderate, major, critical, } /// 對話最終得分模型 class DialogueScore { final double grammarScore; final double semanticsScore; final double fluencyScore; final double taskBonus; final double vocabularyBonus; final double timeBonus; final double totalScore; final int starRating; final DateTime timestamp; final Map breakdown; DialogueScore({ required this.grammarScore, required this.semanticsScore, required this.fluencyScore, required this.taskBonus, required this.vocabularyBonus, required this.timeBonus, required this.totalScore, required this.starRating, DateTime? timestamp, this.breakdown = const {}, }) : timestamp = timestamp ?? DateTime.now(); factory DialogueScore.fromJson(Map json) { return DialogueScore( grammarScore: json['grammarScore'] as double, semanticsScore: json['semanticsScore'] as double, fluencyScore: json['fluencyScore'] as double, taskBonus: json['taskBonus'] as double, vocabularyBonus: json['vocabularyBonus'] as double, timeBonus: json['timeBonus'] as double, totalScore: json['totalScore'] as double, starRating: json['starRating'] as int, timestamp: DateTime.parse(json['timestamp'] as String), breakdown: json['breakdown'] as Map? ?? {}, ); } Map toJson() { return { 'grammarScore': grammarScore, 'semanticsScore': semanticsScore, 'fluencyScore': fluencyScore, 'taskBonus': taskBonus, 'vocabularyBonus': vocabularyBonus, 'timeBonus': timeBonus, 'totalScore': totalScore, 'starRating': starRating, 'timestamp': timestamp.toIso8601String(), 'breakdown': breakdown, }; } String get grade { if (totalScore >= 90) return 'A+'; if (totalScore >= 80) return 'A'; if (totalScore >= 70) return 'B'; if (totalScore >= 60) return 'C'; if (totalScore >= 50) return 'D'; return 'F'; } String get comment { switch (starRating) { case 3: return '優秀!你的表現非常出色!'; case 2: return '很好!繼續努力就能更進一步!'; case 1: return '不錯!還有改進的空間。'; default: return '需要更多練習,加油!'; } } } /// 詞彙項目模型 class VocabularyItem { final String id; final String word; final String definition; final String pronunciation; final List examples; final String category; final int difficulty; final bool isRequired; final bool isUsed; VocabularyItem({ required this.id, required this.word, required this.definition, required this.pronunciation, this.examples = const [], this.category = '', this.difficulty = 1, this.isRequired = false, this.isUsed = false, }); factory VocabularyItem.fromJson(Map json) { return VocabularyItem( id: json['id'] as String, word: json['word'] as String, definition: json['definition'] as String, pronunciation: json['pronunciation'] as String, examples: List.from(json['examples'] ?? []), category: json['category'] as String? ?? '', difficulty: json['difficulty'] as int? ?? 1, isRequired: json['isRequired'] as bool? ?? false, isUsed: json['isUsed'] as bool? ?? false, ); } Map toJson() { return { 'id': id, 'word': word, 'definition': definition, 'pronunciation': pronunciation, 'examples': examples, 'category': category, 'difficulty': difficulty, 'isRequired': isRequired, 'isUsed': isUsed, }; } VocabularyItem copyWith({ String? id, String? word, String? definition, String? pronunciation, List? examples, String? category, int? difficulty, bool? isRequired, bool? isUsed, }) { return VocabularyItem( id: id ?? this.id, word: word ?? this.word, definition: definition ?? this.definition, pronunciation: pronunciation ?? this.pronunciation, examples: examples ?? this.examples, category: category ?? this.category, difficulty: difficulty ?? this.difficulty, isRequired: isRequired ?? this.isRequired, isUsed: isUsed ?? this.isUsed, ); } }