You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
509 lines
15 KiB
509 lines
15 KiB
import 'dart:async';
|
|
import 'package:get/get.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import '../models/message_model.dart';
|
|
import '../../../data/services/volcano_ai_service.dart';
|
|
import '../../../data/services/volcano_tts_api_service.dart';
|
|
import '../../../data/services/azure_asr_service.dart';
|
|
import '../../../data/providers/agent_provider.dart';
|
|
import '../../../data/models/agent.dart';
|
|
import '../../../core/utils/logger.dart';
|
|
import '../../../data/services/chat_history_service.dart';
|
|
|
|
class ChatController extends GetxController {
|
|
// 服务
|
|
final VolcanoAIService _aiService = Get.find<VolcanoAIService>();
|
|
final VolcanoTtsApiService _ttsService = Get.find<VolcanoTtsApiService>();
|
|
final AzureAsrService _asrService = Get.find<AzureAsrService>();
|
|
final ChatHistoryService _historyService = Get.find<ChatHistoryService>();
|
|
|
|
// 添加ScrollController用于控制列表滚动
|
|
final ScrollController scrollController = ScrollController();
|
|
|
|
// Agent 信息
|
|
late final Agent agent;
|
|
|
|
// 消息列表
|
|
final RxList<Message> messages = <Message>[].obs;
|
|
|
|
// 输入控制
|
|
final RxString inputText = ''.obs;
|
|
|
|
// 状态控制
|
|
final RxBool isLoading = false.obs;
|
|
final RxBool isSending = false.obs;
|
|
final RxBool isTtsEnabled = true.obs;
|
|
final RxBool isVoiceRecognitionActive = false.obs;
|
|
|
|
// 语音识别状态
|
|
final RxString recognizedText = ''.obs;
|
|
final RxBool isListening = false.obs;
|
|
|
|
// 临时消息ID,用于语音识别中的消息
|
|
String? _tempRecognitionMessageId;
|
|
|
|
// 流式响应控制
|
|
StreamSubscription? _aiResponseSubscription;
|
|
StreamSubscription? _recognitionSubscription;
|
|
|
|
// 取消标志
|
|
bool _shouldCancelAiResponse = false;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
|
|
// 从路由参数获取Agent信息
|
|
final Map<String, dynamic> args = Get.arguments ?? {};
|
|
final String agentId = args['agentId'] ?? 'personal_assistant';
|
|
|
|
// 获取Agent
|
|
final Agent? foundAgent = AgentProvider.getAgentById(agentId);
|
|
if (foundAgent == null) {
|
|
Logger.error('无法找到Agent: $agentId');
|
|
Get.back();
|
|
return;
|
|
}
|
|
|
|
agent = foundAgent;
|
|
|
|
// 加载聊天历史
|
|
_loadChatHistory();
|
|
|
|
// 如果没有历史记录,添加欢迎消息
|
|
if (messages.isEmpty && agent.welcomeMessage.isNotEmpty) {
|
|
messages.add(Message(
|
|
role: 'assistant',
|
|
content: agent.welcomeMessage,
|
|
timestamp: DateTime.now(),
|
|
));
|
|
// 如果启用了TTS,播放欢迎消息
|
|
if (isTtsEnabled.value) {
|
|
_ttsService.speakSingle(agent.welcomeMessage);
|
|
}
|
|
}
|
|
|
|
|
|
// 如果playVoiceOnEnter为true,自动激活语音识别
|
|
if (args['playVoiceOnEnter'] == true) {
|
|
// 延迟一段时间后激活语音识别,等待TTS播放完成
|
|
startVoiceRecognition();
|
|
}
|
|
|
|
// 使用微任务在布局完成后自动滚动到底部
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_scrollToBottom();
|
|
});
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
// 关闭ScrollController
|
|
scrollController.dispose();
|
|
|
|
// 取消所有订阅
|
|
_aiResponseSubscription?.cancel();
|
|
_recognitionSubscription?.cancel();
|
|
|
|
// 停止TTS
|
|
if (_ttsService.isPlaying) {
|
|
_ttsService.stop();
|
|
}
|
|
|
|
// 停止语音识别
|
|
if (isVoiceRecognitionActive.value) {
|
|
_asrService.stopContinuousRecognition();
|
|
}
|
|
|
|
super.onClose();
|
|
}
|
|
|
|
/// 滚动到列表底部方法
|
|
void _scrollToBottom() {
|
|
if (scrollController.hasClients && messages.isNotEmpty) {
|
|
try {
|
|
scrollController.animateTo(
|
|
scrollController.position.maxScrollExtent,
|
|
duration: Duration(milliseconds: 300),
|
|
curve: Curves.easeOut,
|
|
);
|
|
} catch (e) {
|
|
Logger.error('滚动到底部失败', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 发送消息
|
|
Future<void> sendMessage(String text) async {
|
|
if (text.trim().isEmpty) return;
|
|
|
|
// 停止TTS
|
|
if (_ttsService.isPlaying) {
|
|
_ttsService.stop();
|
|
}
|
|
|
|
// 添加用户消息
|
|
final userMessage = Message(
|
|
role: 'user',
|
|
content: text,
|
|
timestamp: DateTime.now(),
|
|
);
|
|
messages.add(userMessage);
|
|
|
|
// 滚动到底部显示用户消息
|
|
Future.delayed(Duration(milliseconds: 100), _scrollToBottom);
|
|
|
|
// 清空输入框
|
|
inputText.value = '';
|
|
|
|
// 添加加载中的消息
|
|
final loadingMessage = Message.loading();
|
|
messages.add(loadingMessage);
|
|
|
|
// 再次滚动到底部显示加载中的消息
|
|
Future.delayed(Duration(milliseconds: 100), _scrollToBottom);
|
|
|
|
isSending.value = true;
|
|
|
|
try {
|
|
// 构建消息历史
|
|
final List<Map<String, String>> messageHistory = [];
|
|
|
|
// 只取最近的10条消息作为上下文
|
|
final recentMessages = messages.where((m) => !m.isLoading).toList();
|
|
final startIndex =
|
|
recentMessages.length > 10 ? recentMessages.length - 10 : 0;
|
|
|
|
for (int i = startIndex; i < recentMessages.length; i++) {
|
|
final m = recentMessages[i];
|
|
messageHistory.add({
|
|
'role': m.role,
|
|
'content': m.content,
|
|
});
|
|
}
|
|
|
|
// 重置取消标志
|
|
_shouldCancelAiResponse = false;
|
|
|
|
// 准备TTS会话
|
|
if (isTtsEnabled.value) {
|
|
await _ttsService.startSession(agent.voice);
|
|
}
|
|
|
|
String fullResponse = '';
|
|
|
|
// 获取AI响应流
|
|
final responseStream = _aiService.sendMessageStream(
|
|
messages: messageHistory,
|
|
systemPrompt: agent.systemPrompt,
|
|
);
|
|
|
|
// 订阅响应流
|
|
_aiResponseSubscription = responseStream.listen(
|
|
(chunk) {
|
|
if (_shouldCancelAiResponse) return;
|
|
|
|
fullResponse += chunk;
|
|
|
|
// 更新加载中的消息内容,但保持isLoading状态
|
|
final index = messages.indexWhere((m) => m.isLoading);
|
|
if (index != -1) {
|
|
// 直接更新消息内容,然后使用refresh()触发UI更新
|
|
messages[index] = messages[index].copyWith(
|
|
content: fullResponse,
|
|
isLoading: true, // 明确设置为true以确保状态一致
|
|
);
|
|
|
|
// 使用refresh()方法刷新列表,触发UI更新
|
|
messages.refresh();
|
|
|
|
// 每次收到新内容后滚动到底部
|
|
Future.delayed(Duration(milliseconds: 100), _scrollToBottom);
|
|
}
|
|
|
|
// 如果TTS启用,发送到TTS
|
|
if (isTtsEnabled.value && !_shouldCancelAiResponse) {
|
|
// 直接将文本块发送到TTS,服务会自己处理断句
|
|
_ttsService.speak(chunk);
|
|
}
|
|
},
|
|
onDone: () {
|
|
if (_shouldCancelAiResponse) return;
|
|
|
|
// 完成响应,此时才将isLoading设为false
|
|
final index = messages.indexWhere((m) => m.isLoading);
|
|
if (index != -1) {
|
|
messages[index] = messages[index].copyWith(
|
|
content: fullResponse,
|
|
isLoading: false,
|
|
);
|
|
|
|
// 滚动到底部显示完整回复
|
|
Future.delayed(Duration(milliseconds: 100), _scrollToBottom);
|
|
}
|
|
|
|
// 结束TTS会话
|
|
if (isTtsEnabled.value) {
|
|
_ttsService.endSession();
|
|
}
|
|
|
|
isSending.value = false;
|
|
|
|
// 保存聊天历史
|
|
_saveChatHistory();
|
|
},
|
|
onError: (error) {
|
|
Logger.error('AI响应错误', error);
|
|
|
|
// 更新加载中的消息为错误消息
|
|
final index = messages.indexWhere((m) => m.isLoading);
|
|
if (index != -1) {
|
|
messages[index] = messages[index].copyWith(
|
|
content: '抱歉,我遇到了一些问题,无法回应您的请求。',
|
|
isLoading: false,
|
|
);
|
|
}
|
|
|
|
// 结束TTS会话
|
|
if (isTtsEnabled.value) {
|
|
_ttsService.endSession();
|
|
}
|
|
|
|
isSending.value = false;
|
|
},
|
|
);
|
|
} catch (e) {
|
|
Logger.error('发送消息失败', e);
|
|
|
|
// 更新加载中的消息为错误消息
|
|
final index = messages.indexWhere((m) => m.isLoading);
|
|
if (index != -1) {
|
|
messages[index] = messages[index].copyWith(
|
|
content: '抱歉,我遇到了一些问题,无法回应您的请求。',
|
|
isLoading: false,
|
|
);
|
|
}
|
|
|
|
isSending.value = false;
|
|
}
|
|
}
|
|
|
|
// 切换TTS状态
|
|
void toggleTts() {
|
|
isTtsEnabled.toggle();
|
|
|
|
// 如果关闭TTS,停止当前播放
|
|
if (!isTtsEnabled.value && _ttsService.isPlaying) {
|
|
_ttsService.stop();
|
|
}
|
|
}
|
|
|
|
// 开始语音识别
|
|
Future<void> startVoiceRecognition() async {
|
|
|
|
|
|
if (isVoiceRecognitionActive.value) return;
|
|
|
|
// 每次收到新内容后滚动到底部
|
|
Future.delayed(Duration(milliseconds: 100), _scrollToBottom);
|
|
// 停止TTS
|
|
if (_ttsService.isPlaying) {
|
|
_ttsService.stop();
|
|
|
|
// 如果正在接收AI响应,取消它
|
|
if (isSending.value) {
|
|
_shouldCancelAiResponse = true;
|
|
_aiResponseSubscription?.cancel();
|
|
|
|
// 移除加载中的消息
|
|
messages.removeWhere((m) => m.isLoading);
|
|
isSending.value = false;
|
|
}
|
|
}
|
|
|
|
try {
|
|
// 启动语音识别
|
|
final success = await _asrService.startContinuousRecognition();
|
|
|
|
if (success) {
|
|
isVoiceRecognitionActive.value = true;
|
|
isListening.value = true;
|
|
recognizedText.value = '';
|
|
|
|
// 不再添加"正在聆听..."的临时消息
|
|
|
|
// 监听识别事件
|
|
_recognitionSubscription = _asrService.recognitionStream?.listen(
|
|
(event) {
|
|
switch (event.type) {
|
|
case RecognitionEventType.recognizing:
|
|
// 实时更新识别文本
|
|
if (event.text.isNotEmpty) {
|
|
recognizedText.value = event.text;
|
|
|
|
// 检查是否已经有临时消息
|
|
final tempMessageIndex = messages.indexWhere((m) =>
|
|
m.role == 'user' &&
|
|
m.timestamp.millisecondsSinceEpoch >
|
|
DateTime.now().millisecondsSinceEpoch - 5000);
|
|
|
|
if (tempMessageIndex >= 0) {
|
|
// 更新已有的临时消息
|
|
messages[tempMessageIndex] =
|
|
messages[tempMessageIndex].copyWith(
|
|
content: event.text,
|
|
);
|
|
// 每次收到新内容后滚动到底部
|
|
Future.delayed(
|
|
Duration(milliseconds: 100), _scrollToBottom);
|
|
// 使用refresh()方法刷新列表
|
|
messages.refresh();
|
|
} else {
|
|
// 添加新的临时消息,但只有在有实际内容时
|
|
final tempMessage = Message(
|
|
role: 'user',
|
|
content: event.text,
|
|
timestamp: DateTime.now(),
|
|
);
|
|
messages.add(tempMessage);
|
|
}
|
|
}
|
|
break;
|
|
case RecognitionEventType.finalResult:
|
|
// 最终结果
|
|
if (event.text.isNotEmpty) {
|
|
recognizedText.value = event.text;
|
|
|
|
// 查找并移除临时消息
|
|
final tempMessageIndex = messages.indexWhere((m) =>
|
|
m.role == 'user' &&
|
|
m.timestamp.millisecondsSinceEpoch >
|
|
DateTime.now().millisecondsSinceEpoch - 5000);
|
|
|
|
if (tempMessageIndex >= 0) {
|
|
messages.removeAt(tempMessageIndex);
|
|
}
|
|
|
|
// 发送识别到的文本
|
|
sendMessage(event.text);
|
|
|
|
// 重置识别文本
|
|
recognizedText.value = '';
|
|
}
|
|
break;
|
|
case RecognitionEventType.sessionStopped:
|
|
case RecognitionEventType.canceled:
|
|
// 会话结束
|
|
isVoiceRecognitionActive.value = false;
|
|
isListening.value = false;
|
|
|
|
// 查找并移除临时消息
|
|
final tempMessageIndex = messages.indexWhere((m) =>
|
|
m.role == 'user' &&
|
|
m.timestamp.millisecondsSinceEpoch >
|
|
DateTime.now().millisecondsSinceEpoch - 5000);
|
|
|
|
if (tempMessageIndex >= 0 &&
|
|
messages[tempMessageIndex].content.trim().isEmpty) {
|
|
messages.removeAt(tempMessageIndex);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
},
|
|
onError: (error) {
|
|
Logger.error('语音识别错误', error);
|
|
isVoiceRecognitionActive.value = false;
|
|
isListening.value = false;
|
|
|
|
// 查找并移除临时消息
|
|
final tempMessageIndex = messages.indexWhere((m) =>
|
|
m.role == 'user' &&
|
|
m.timestamp.millisecondsSinceEpoch >
|
|
DateTime.now().millisecondsSinceEpoch - 5000);
|
|
|
|
if (tempMessageIndex >= 0 &&
|
|
messages[tempMessageIndex].content.trim().isEmpty) {
|
|
messages.removeAt(tempMessageIndex);
|
|
}
|
|
},
|
|
);
|
|
} else {
|
|
Logger.error('启动语音识别失败');
|
|
}
|
|
} catch (e) {
|
|
Logger.error('启动语音识别失败', e);
|
|
isVoiceRecognitionActive.value = false;
|
|
isListening.value = false;
|
|
}
|
|
}
|
|
|
|
// 停止语音识别
|
|
Future<void> stopVoiceRecognition() async {
|
|
if (!isVoiceRecognitionActive.value) return;
|
|
|
|
try {
|
|
await _asrService.stopContinuousRecognition();
|
|
_recognitionSubscription?.cancel();
|
|
_recognitionSubscription = null;
|
|
|
|
isVoiceRecognitionActive.value = false;
|
|
isListening.value = false;
|
|
recognizedText.value = '';
|
|
|
|
// 查找并移除临时消息
|
|
final tempMessageIndex = messages.indexWhere((m) =>
|
|
m.role == 'user' &&
|
|
m.timestamp.millisecondsSinceEpoch >
|
|
DateTime.now().millisecondsSinceEpoch - 5000);
|
|
|
|
if (tempMessageIndex >= 0 &&
|
|
messages[tempMessageIndex].content.trim().isEmpty) {
|
|
messages.removeAt(tempMessageIndex);
|
|
}
|
|
} catch (e) {
|
|
Logger.error('停止语音识别失败', e);
|
|
}
|
|
}
|
|
|
|
// 清空聊天记录
|
|
void clearChat() {
|
|
messages.clear();
|
|
|
|
// 添加欢迎消息
|
|
if (agent.welcomeMessage.isNotEmpty) {
|
|
messages.add(Message(
|
|
role: 'assistant',
|
|
content: agent.welcomeMessage,
|
|
timestamp: DateTime.now(),
|
|
));
|
|
}
|
|
|
|
// 删除存储的历史记录
|
|
_historyService.clearHistory(agent.id);
|
|
}
|
|
|
|
// 加载聊天历史
|
|
void _loadChatHistory() {
|
|
final history = _historyService.loadHistory(agent.id);
|
|
if (history.isNotEmpty) {
|
|
messages.clear();
|
|
messages.addAll(history);
|
|
}
|
|
}
|
|
|
|
// 保存聊天历史
|
|
void _saveChatHistory() {
|
|
_historyService.saveHistory(agent.id, messages);
|
|
}
|
|
|
|
// 添加消息,供外部调用
|
|
void addMessage(Message message) {
|
|
messages.add(message);
|
|
_saveChatHistory();
|
|
|
|
// 滚动到底部显示新添加的消息
|
|
Future.delayed(Duration(milliseconds: 100), _scrollToBottom);
|
|
}
|
|
}
|
|
|