547 lines
15 KiB
Dart
547 lines
15 KiB
Dart
/// 對話場景模型
|
|
class DialogueScene {
|
|
final String id;
|
|
final String name;
|
|
final String description;
|
|
final String backgroundImageUrl;
|
|
final String characterId;
|
|
final String difficultyLevel;
|
|
final List<String> tags;
|
|
final Map<String, dynamic> 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<String, dynamic> 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<String>.from(json['tags'] ?? []),
|
|
metadata: json['metadata'] as Map<String, dynamic>? ?? {},
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> 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<String> specialities;
|
|
final Map<String, String> 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<String, dynamic> 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<String>.from(json['specialities'] ?? []),
|
|
localizedNames: Map<String, String>.from(json['localizedNames'] ?? {}),
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> 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<String, dynamic>? 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<String, dynamic> 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<String, dynamic>?,
|
|
audioUrl: json['audioUrl'] as String?,
|
|
confidence: json['confidence'] as double?,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic>,
|
|
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<String, dynamic> 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<String, dynamic>? 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<GrammarIssue> grammarIssues;
|
|
final List<String> usedVocabulary;
|
|
final List<String> missedVocabulary;
|
|
final List<String> suggestions;
|
|
|
|
// 任務相關
|
|
final double? taskProgress;
|
|
final bool isDialogueComplete;
|
|
|
|
// 其他
|
|
final Map<String, dynamic> 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<String, dynamic> 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<String, dynamic>))
|
|
.toList() ?? [],
|
|
usedVocabulary: List<String>.from(json['usedVocabulary'] ?? []),
|
|
missedVocabulary: List<String>.from(json['missedVocabulary'] ?? []),
|
|
suggestions: List<String>.from(json['suggestions'] ?? []),
|
|
taskProgress: json['taskProgress'] as double?,
|
|
isDialogueComplete: json['isDialogueComplete'] as bool? ?? false,
|
|
metadata: json['metadata'] as Map<String, dynamic>? ?? {},
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic>? ?? {},
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> 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<String> 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<String, dynamic> 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<String>.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<String, dynamic> 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<String>? 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,
|
|
);
|
|
}
|
|
} |