390 lines
11 KiB
Dart
390 lines
11 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../models/dialogue_models.dart';
|
|
import '../services/dialogue_service.dart';
|
|
|
|
/// 對話狀態提供者
|
|
final dialogueProvider = StateNotifierProvider<DialogueNotifier, DialogueState>((ref) {
|
|
final dialogueService = ref.watch(dialogueServiceProvider);
|
|
return DialogueNotifier(dialogueService);
|
|
});
|
|
|
|
/// 對話服務提供者
|
|
final dialogueServiceProvider = Provider<DialogueService>((ref) {
|
|
return DialogueService();
|
|
});
|
|
|
|
/// 對話狀態管理
|
|
class DialogueNotifier extends StateNotifier<DialogueState> {
|
|
final DialogueService _dialogueService;
|
|
|
|
DialogueNotifier(this._dialogueService) : super(DialogueState.initial());
|
|
|
|
/// 初始化對話
|
|
Future<void> initializeDialogue({
|
|
required String scenarioId,
|
|
required String levelId,
|
|
bool isTimeChallenge = false,
|
|
}) async {
|
|
state = state.copyWith(isLoading: true);
|
|
|
|
try {
|
|
final scene = await _dialogueService.loadScene(scenarioId, levelId);
|
|
final character = await _dialogueService.loadCharacter(scene.characterId);
|
|
final task = await _dialogueService.loadTask(levelId);
|
|
final vocabulary = await _dialogueService.loadRequiredVocabulary(levelId);
|
|
|
|
// 載入開場對話
|
|
final openingDialogue = await _dialogueService.getOpeningDialogue(
|
|
scenarioId,
|
|
levelId,
|
|
);
|
|
|
|
state = state.copyWith(
|
|
isLoading: false,
|
|
currentScene: scene,
|
|
currentCharacter: character,
|
|
currentTask: task,
|
|
requiredVocabulary: vocabulary,
|
|
currentDialogue: openingDialogue,
|
|
scenarioId: scenarioId,
|
|
levelId: levelId,
|
|
isTimeChallenge: isTimeChallenge,
|
|
);
|
|
} catch (e) {
|
|
state = state.copyWith(
|
|
isLoading: false,
|
|
error: e.toString(),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 發送用戶回覆
|
|
Future<void> sendReply(String replyText) async {
|
|
if (replyText.trim().isEmpty) return;
|
|
|
|
state = state.copyWith(isProcessing: true);
|
|
|
|
try {
|
|
// 創建用戶回覆
|
|
final userReply = DialogueMessage(
|
|
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
|
content: replyText,
|
|
isUser: true,
|
|
timestamp: DateTime.now(),
|
|
);
|
|
|
|
// 分析回覆
|
|
final analysis = await _dialogueService.analyzeReply(
|
|
scenarioId: state.scenarioId!,
|
|
levelId: state.levelId!,
|
|
replyText: replyText,
|
|
requiredVocabulary: state.requiredVocabulary,
|
|
currentTask: state.currentTask,
|
|
);
|
|
|
|
// 獲取AI回應
|
|
final aiResponse = await _dialogueService.getAIResponse(
|
|
scenarioId: state.scenarioId!,
|
|
levelId: state.levelId!,
|
|
userReply: replyText,
|
|
analysis: analysis,
|
|
);
|
|
|
|
// 更新使用的詞彙
|
|
final newUsedVocabulary = Set<String>.from(state.usedVocabulary);
|
|
newUsedVocabulary.addAll(analysis.usedVocabulary);
|
|
|
|
// 更新任務進度
|
|
DialogueTask? updatedTask = state.currentTask;
|
|
if (analysis.taskProgress != null && updatedTask != null) {
|
|
updatedTask = updatedTask.copyWith(
|
|
progress: analysis.taskProgress!,
|
|
isCompleted: analysis.taskProgress! >= 1.0,
|
|
);
|
|
}
|
|
|
|
state = state.copyWith(
|
|
isProcessing: false,
|
|
lastUserReply: userReply,
|
|
currentDialogue: aiResponse,
|
|
currentTask: updatedTask,
|
|
usedVocabulary: newUsedVocabulary,
|
|
lastAnalysis: analysis,
|
|
conversationHistory: [...state.conversationHistory, userReply, aiResponse],
|
|
);
|
|
|
|
// 檢查對話是否完成
|
|
if (analysis.isDialogueComplete || (updatedTask?.isCompleted ?? false)) {
|
|
_completeDialogue();
|
|
}
|
|
} catch (e) {
|
|
state = state.copyWith(
|
|
isProcessing: false,
|
|
error: e.toString(),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 顯示回覆輔助
|
|
Future<void> showReplyAssistance() async {
|
|
if (state.diamonds < 30) return;
|
|
|
|
state = state.copyWith(isProcessing: true);
|
|
|
|
try {
|
|
final suggestions = await _dialogueService.getReplyAssistance(
|
|
scenarioId: state.scenarioId!,
|
|
levelId: state.levelId!,
|
|
currentDialogue: state.currentDialogue?.content ?? '',
|
|
currentTask: state.currentTask,
|
|
);
|
|
|
|
state = state.copyWith(
|
|
isProcessing: false,
|
|
showReplyAssistance: true,
|
|
replySuggestions: suggestions,
|
|
diamonds: state.diamonds - 30, // 扣除鑽石
|
|
);
|
|
} catch (e) {
|
|
state = state.copyWith(
|
|
isProcessing: false,
|
|
error: e.toString(),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 隱藏回覆輔助
|
|
void hideReplyAssistance() {
|
|
state = state.copyWith(
|
|
showReplyAssistance: false,
|
|
replySuggestions: [],
|
|
);
|
|
}
|
|
|
|
/// 使用時光卷道具
|
|
Future<void> useTimeWarpCard() async {
|
|
// TODO: 實現時光卷功能
|
|
}
|
|
|
|
/// 完成對話
|
|
void _completeDialogue() {
|
|
final finalScore = _calculateFinalScore();
|
|
|
|
state = state.copyWith(
|
|
isCompleted: true,
|
|
finalScore: finalScore,
|
|
);
|
|
}
|
|
|
|
/// 計算最終得分
|
|
DialogueScore _calculateFinalScore() {
|
|
// 計算三維度得分
|
|
double grammarScore = 0.0;
|
|
double semanticsScore = 0.0;
|
|
double fluencyScore = 0.0;
|
|
int totalReplies = 0;
|
|
|
|
for (final analysis in state.analysisHistory) {
|
|
grammarScore += analysis.grammarScore;
|
|
semanticsScore += analysis.semanticsScore;
|
|
fluencyScore += analysis.fluencyScore;
|
|
totalReplies++;
|
|
}
|
|
|
|
if (totalReplies > 0) {
|
|
grammarScore /= totalReplies;
|
|
semanticsScore /= totalReplies;
|
|
fluencyScore /= totalReplies;
|
|
}
|
|
|
|
// 計算任務完成度獎勵
|
|
double taskBonus = 0.0;
|
|
if (state.currentTask?.isCompleted ?? false) {
|
|
taskBonus = 20.0;
|
|
}
|
|
|
|
// 計算詞彙使用獎勵
|
|
double vocabularyBonus = 0.0;
|
|
if (state.requiredVocabulary.isNotEmpty) {
|
|
vocabularyBonus = (state.usedVocabulary.length / state.requiredVocabulary.length) * 10.0;
|
|
}
|
|
|
|
// 計算時間獎勵(限時挑戰)
|
|
double timeBonus = 0.0;
|
|
if (state.isTimeChallenge) {
|
|
// TODO: 根據剩餘時間計算獎勵
|
|
timeBonus = 5.0;
|
|
}
|
|
|
|
final totalScore = grammarScore + semanticsScore + fluencyScore + taskBonus + vocabularyBonus + timeBonus;
|
|
|
|
return DialogueScore(
|
|
grammarScore: grammarScore,
|
|
semanticsScore: semanticsScore,
|
|
fluencyScore: fluencyScore,
|
|
taskBonus: taskBonus,
|
|
vocabularyBonus: vocabularyBonus,
|
|
timeBonus: timeBonus,
|
|
totalScore: totalScore,
|
|
starRating: _calculateStarRating(totalScore),
|
|
);
|
|
}
|
|
|
|
/// 計算星級評價
|
|
int _calculateStarRating(double totalScore) {
|
|
if (totalScore >= 90) return 3;
|
|
if (totalScore >= 70) return 2;
|
|
if (totalScore >= 50) return 1;
|
|
return 0;
|
|
}
|
|
|
|
/// 重置對話狀態
|
|
void reset() {
|
|
state = DialogueState.initial();
|
|
}
|
|
}
|
|
|
|
/// 對話狀態
|
|
class DialogueState {
|
|
final bool isLoading;
|
|
final bool isProcessing;
|
|
final bool isCompleted;
|
|
final String? error;
|
|
|
|
// 場景信息
|
|
final String? scenarioId;
|
|
final String? levelId;
|
|
final bool isTimeChallenge;
|
|
final DialogueScene? currentScene;
|
|
final DialogueCharacter? currentCharacter;
|
|
|
|
// 對話內容
|
|
final DialogueMessage? currentDialogue;
|
|
final DialogueMessage? lastUserReply;
|
|
final List<DialogueMessage> conversationHistory;
|
|
|
|
// 任務和詞彙
|
|
final DialogueTask? currentTask;
|
|
final List<String> requiredVocabulary;
|
|
final Set<String> usedVocabulary;
|
|
|
|
// AI分析
|
|
final DialogueAnalysis? lastAnalysis;
|
|
final List<DialogueAnalysis> analysisHistory;
|
|
|
|
// 回覆輔助
|
|
final bool showReplyAssistance;
|
|
final List<String> replySuggestions;
|
|
|
|
// 資源
|
|
final int lifePoints;
|
|
final int diamonds;
|
|
|
|
// 設置
|
|
final String currentLanguage;
|
|
|
|
// 最終結果
|
|
final DialogueScore? finalScore;
|
|
|
|
DialogueState({
|
|
required this.isLoading,
|
|
required this.isProcessing,
|
|
required this.isCompleted,
|
|
this.error,
|
|
this.scenarioId,
|
|
this.levelId,
|
|
required this.isTimeChallenge,
|
|
this.currentScene,
|
|
this.currentCharacter,
|
|
this.currentDialogue,
|
|
this.lastUserReply,
|
|
required this.conversationHistory,
|
|
this.currentTask,
|
|
required this.requiredVocabulary,
|
|
required this.usedVocabulary,
|
|
this.lastAnalysis,
|
|
required this.analysisHistory,
|
|
required this.showReplyAssistance,
|
|
required this.replySuggestions,
|
|
required this.lifePoints,
|
|
required this.diamonds,
|
|
required this.currentLanguage,
|
|
this.finalScore,
|
|
});
|
|
|
|
factory DialogueState.initial() {
|
|
return DialogueState(
|
|
isLoading: false,
|
|
isProcessing: false,
|
|
isCompleted: false,
|
|
isTimeChallenge: false,
|
|
conversationHistory: [],
|
|
requiredVocabulary: [],
|
|
usedVocabulary: {},
|
|
analysisHistory: [],
|
|
showReplyAssistance: false,
|
|
replySuggestions: [],
|
|
lifePoints: 5,
|
|
diamonds: 100,
|
|
currentLanguage: 'zh-TW',
|
|
);
|
|
}
|
|
|
|
DialogueState copyWith({
|
|
bool? isLoading,
|
|
bool? isProcessing,
|
|
bool? isCompleted,
|
|
String? error,
|
|
String? scenarioId,
|
|
String? levelId,
|
|
bool? isTimeChallenge,
|
|
DialogueScene? currentScene,
|
|
DialogueCharacter? currentCharacter,
|
|
DialogueMessage? currentDialogue,
|
|
DialogueMessage? lastUserReply,
|
|
List<DialogueMessage>? conversationHistory,
|
|
DialogueTask? currentTask,
|
|
List<String>? requiredVocabulary,
|
|
Set<String>? usedVocabulary,
|
|
DialogueAnalysis? lastAnalysis,
|
|
List<DialogueAnalysis>? analysisHistory,
|
|
bool? showReplyAssistance,
|
|
List<String>? replySuggestions,
|
|
int? lifePoints,
|
|
int? diamonds,
|
|
String? currentLanguage,
|
|
DialogueScore? finalScore,
|
|
}) {
|
|
return DialogueState(
|
|
isLoading: isLoading ?? this.isLoading,
|
|
isProcessing: isProcessing ?? this.isProcessing,
|
|
isCompleted: isCompleted ?? this.isCompleted,
|
|
error: error ?? this.error,
|
|
scenarioId: scenarioId ?? this.scenarioId,
|
|
levelId: levelId ?? this.levelId,
|
|
isTimeChallenge: isTimeChallenge ?? this.isTimeChallenge,
|
|
currentScene: currentScene ?? this.currentScene,
|
|
currentCharacter: currentCharacter ?? this.currentCharacter,
|
|
currentDialogue: currentDialogue ?? this.currentDialogue,
|
|
lastUserReply: lastUserReply ?? this.lastUserReply,
|
|
conversationHistory: conversationHistory ?? this.conversationHistory,
|
|
currentTask: currentTask ?? this.currentTask,
|
|
requiredVocabulary: requiredVocabulary ?? this.requiredVocabulary,
|
|
usedVocabulary: usedVocabulary ?? this.usedVocabulary,
|
|
lastAnalysis: lastAnalysis ?? this.lastAnalysis,
|
|
analysisHistory: analysisHistory ?? List.from(this.analysisHistory),
|
|
showReplyAssistance: showReplyAssistance ?? this.showReplyAssistance,
|
|
replySuggestions: replySuggestions ?? this.replySuggestions,
|
|
lifePoints: lifePoints ?? this.lifePoints,
|
|
diamonds: diamonds ?? this.diamonds,
|
|
currentLanguage: currentLanguage ?? this.currentLanguage,
|
|
finalScore: finalScore ?? this.finalScore,
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return 'DialogueState(loading: $isLoading, processing: $isProcessing, completed: $isCompleted, scenarioId: $scenarioId, levelId: $levelId)';
|
|
}
|
|
} |