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.

629 lines
17 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/azure_tts_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';
import '../../../data/services/voice_interaction_service.dart';
class ChatController extends GetxController {
// 服务
final VolcanoAIService _aiService = Get.find<VolcanoAIService>();
final AzureTtsService _ttsService = Get.find<AzureTtsService>();
final AzureAsrService _asrService = Get.find<AzureAsrService>();
final ChatHistoryService _historyService = Get.find<ChatHistoryService>();
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>();
// 添加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;
final RxBool isSpeechDetected = false.obs;
// 临时消息索引,用于更新临时消息
int _tempMessageIndex = -1;
// 流式响应控制
StreamSubscription? _aiResponseSubscription;
StreamSubscription? _recognitionSubscription;
StreamSubscription? _voiceInteractionSubscription;
// 取消标志
bool _shouldCancelAiResponse = false;
@override
Future<void> onInit() async {
super.onInit();
Logger.info('ChatController 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;
// 设置TTS服务
await _ttsService.setVoice(agent.voice);
// 加载聊天历史
_loadChatHistory();
// 如果没有历史记录,添加欢迎消息
if (messages.isEmpty && agent.welcomeMessage.isNotEmpty) {
messages.add(Message(
role: 'assistant',
content: agent.welcomeMessage,
timestamp: DateTime.now(),
));
// 如果启用了TTS,播放欢迎消息
if (isTtsEnabled.value) {
_ttsService.speak(agent.welcomeMessage);
}
}
// 如果playVoiceOnEnter为true,自动激活语音识别
if (args['playVoiceOnEnter'] == true) {
startVoiceRecognition();
}
// 订阅VoiceInteractionService事件
_subscribeToVoiceInteractionEvents();
// 使用微任务在布局完成后自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
}
/// 订阅VoiceInteractionService事件
void _subscribeToVoiceInteractionEvents() {
if (_voiceInteractionService.eventStream != null) {
_voiceInteractionSubscription = _voiceInteractionService.eventStream!.listen((event) {
if (event is RecognitionStartedEvent) {
// 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS
_handleRecognitionStartedEvent();
} else if (event is ChatHistoryEvent) {
// 收到chatHistory事件时,更新聊天界面
_handleChatHistoryEvent(event);
}
});
}
}
/// 处理recognitionStarted事件
void _handleRecognitionStartedEvent() {
Logger.info('收到后台语音识别开始事件,停止当前AI交互');
// 停止当前AI交互
if (isSending.value || _aiResponseSubscription != null) {
_interruptCurrentResponse("收到后台语音识别开始事件,中断当前响应");
}
// 停止TTS
_ttsService.stop();
// 停止语音识别
if (isVoiceRecognitionActive.value) {
stopVoiceRecognition();
}
}
/// 处理chatHistory事件
void _handleChatHistoryEvent(ChatHistoryEvent event) {
Logger.info('收到后台聊天历史事件: agentId=${event.agentId}');
// 检查是否是当前Agent的消息
if (event.agentId != agent.id) {
Logger.info('收到的消息不属于当前Agent,忽略');
return;
}
// 创建用户消息和助手消息
final userMsg = Message(
role: 'user',
content: event.userMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp),
);
final assistantMsg = Message(
role: 'assistant',
content: event.assistantMessage,
timestamp: DateTime.fromMillisecondsSinceEpoch(event.timestamp + 1), // 确保助手消息时间戳晚于用户消息
);
// 检查是否已存在相同内容的消息(避免重复添加)
final bool userMsgExists = messages.any((m) =>
m.role == userMsg.role &&
m.content == userMsg.content &&
(m.timestamp.millisecondsSinceEpoch - userMsg.timestamp.millisecondsSinceEpoch).abs() < 5000
);
final bool assistantMsgExists = messages.any((m) =>
m.role == assistantMsg.role &&
m.content == assistantMsg.content &&
(m.timestamp.millisecondsSinceEpoch - assistantMsg.timestamp.millisecondsSinceEpoch).abs() < 5000
);
// 如果消息不存在,则添加到聊天记录中
if (!userMsgExists) {
messages.add(userMsg);
}
if (!assistantMsgExists) {
messages.add(assistantMsg);
}
// 如果有新消息添加,刷新列表并滚动到底部
if (!userMsgExists || !assistantMsgExists) {
messages.refresh();
}
_scrollToBottom();
}
@override
void onClose() {
Logger.info('ChatController onClose');
// 清理资源
scrollController.dispose();
_aiResponseSubscription?.cancel();
_recognitionSubscription?.cancel();
_voiceInteractionSubscription?.cancel();
// 停止TTS
_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);
}
}
}
/// 中断当前响应
void _interruptCurrentResponse(String reason) {
Logger.info('${reason}');
// 停止TTS
_ttsService.stop();
// 取消AI响应流订阅
if (_aiResponseSubscription != null) {
_shouldCancelAiResponse = true;
_aiResponseSubscription!.cancel();
_aiResponseSubscription = null;
// 将加载中的消息转换为正常消息,保留已输出的内容
final index = messages.indexWhere((m) => m.isLoading);
if (index != -1) {
messages[index] = messages[index].copyWith(
isLoading: false,
);
messages.refresh();
}
// 重置发送状态
isSending.value = false;
}
}
// 发送消息
Future<void> sendMessage(String text) async {
if (text.trim().isEmpty) return;
_voiceInteractionService.pauseVoiceInteraction();
// 中断当前响应
_interruptCurrentResponse("发送新消息,中断当前响应");
// 添加用户消息
final userMessage = Message(
role: 'user',
content: text,
timestamp: DateTime.now(),
);
messages.add(userMessage);
_scrollToBottom();
// 清空输入框
inputText.value = '';
// 添加加载中的消息
final loadingMessage = Message.loading();
messages.add(loadingMessage);
_scrollToBottom();
isSending.value = true;
try {
// 构建消息历史
final List<Map<String, String>> messageHistory = _buildMessageHistory();
// 重置取消标志
_shouldCancelAiResponse = false;
String fullResponse = '';
// 获取AI响应流
final responseStream = _aiService.sendMessageStream(
messages: messageHistory,
systemPrompt: agent.systemPrompt,
);
// 订阅响应流
_aiResponseSubscription = responseStream.listen(
(chunk) {
if (_shouldCancelAiResponse) return;
fullResponse += chunk;
// 更新加载中的消息内容
_updateLoadingMessage(fullResponse);
// 如果TTS启用,使用流式处理发送到TTS
if (isTtsEnabled.value) {
_ttsService.processStreamText(chunk);
}
},
onDone: () {
if (_shouldCancelAiResponse) return;
// 完成响应,将isLoading设为false
_finalizeResponse(fullResponse);
// 如果TTS启用,处理缓冲区中的剩余文本
if (isTtsEnabled.value) {
_ttsService.flushStreamBuffer();
}
// 保存聊天历史
_saveChatHistory();
},
onError: (error) {
Logger.error('AI响应错误', error);
// 更新加载中的消息为错误消息
_handleResponseError();
},
);
} catch (e) {
Logger.error('发送消息失败', e);
_handleResponseError();
}
}
// 构建消息历史
List<Map<String, String>> _buildMessageHistory() {
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,
});
}
return messageHistory;
}
// 更新加载中的消息
void _updateLoadingMessage(String content) {
final index = messages.indexWhere((m) => m.isLoading);
if (index != -1) {
messages[index] = messages[index].copyWith(
content: content,
isLoading: true,
);
messages.refresh();
_scrollToBottom();
}
}
// 完成响应
void _finalizeResponse(String content) {
final index = messages.indexWhere((m) => m.isLoading);
if (index != -1) {
messages[index] = messages[index].copyWith(
content: content,
isLoading: false,
);
messages.refresh();
_scrollToBottom();
}
isSending.value = false;
_aiResponseSubscription = null;
}
// 处理响应错误
void _handleResponseError() {
final index = messages.indexWhere((m) => m.isLoading);
if (index != -1) {
messages[index] = messages[index].copyWith(
content: '抱歉,我遇到了一些问题,无法回应您的请求。',
isLoading: false,
);
messages.refresh();
}
isSending.value = false;
_aiResponseSubscription = null;
}
// 切换TTS状态
void toggleTts() {
isTtsEnabled.toggle();
// 如果关闭TTS,停止当前播放
if (!isTtsEnabled.value) {
_ttsService.stop();
}
}
// 开始语音识别
Future<void> startVoiceRecognition() async {
if (isVoiceRecognitionActive.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try {
// 初始化语音识别
await _asrService.initialize(language: 'zh-CN');
// 启动连续语音识别
// 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据
final recognitionStream = await _asrService.startContinuousRecognition();
isVoiceRecognitionActive.value = true;
isListening.value = true;
recognizedText.value = '';
// 重置临时消息索引
_tempMessageIndex = -1;
// 监听识别事件
_recognitionSubscription = recognitionStream.listen(
_handleRecognitionEvent,
onError: _handleRecognitionError,
);
} catch (e) {
Logger.error('启动语音识别失败', e);
isVoiceRecognitionActive.value = false;
isListening.value = false;
}
}
// 处理语音识别事件
void _handleRecognitionEvent(RecognitionEvent event) {
switch (event.type) {
case RecognitionEventType.intermediateResult:
_handleRecognizing(event.text);
break;
case RecognitionEventType.finalResult:
_handleFinalResult(event.text);
break;
case RecognitionEventType.error:
_handleRecognitionEventError(event.error);
break;
default:
// 不做任何处理
break;
}
}
// 处理语音识别中的状态
void _handleRecognizing(String text) {
if (text.isEmpty) return;
recognizedText.value = text;
// 设置语音检测状态为true,用于控制波形动画
if (!isSpeechDetected.value) {
isSpeechDetected.value = true;
}
// 用户开始讲话时,立即停止当前TTS和中断AI响应
if (isSending.value || _aiResponseSubscription != null) {
_interruptCurrentResponse("检测到用户开始讲话,中断当前响应");
}
// 更新或创建临时消息
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) {
// 更新已有的临时消息
messages[_tempMessageIndex] = messages[_tempMessageIndex].copyWith(
content: text,
);
messages.refresh();
} else {
// 添加新的临时消息
final tempMessage = Message(
role: 'user',
content: text,
timestamp: DateTime.now(),
);
messages.add(tempMessage);
_tempMessageIndex = messages.length - 1;
messages.refresh();
}
_scrollToBottom();
}
// 处理语音识别最终结果
void _handleFinalResult(String text) {
if (text.isEmpty) return;
Logger.info('处理语音识别最终结果: $text');
recognizedText.value = text;
// 查找并移除临时消息
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) {
messages.removeAt(_tempMessageIndex);
}
// 重置临时消息索引
_tempMessageIndex = -1;
// 发送识别到的文本
sendMessage(text);
// 重置识别文本,但不关闭语音识别框
recognizedText.value = '';
// 重置语音检测状态
isSpeechDetected.value = false;
}
// 处理语音识别事件错误
void _handleRecognitionEventError(String? error) {
Logger.error('语音识别错误: ${error ?? "未知错误"}');
_cleanupRecognition();
}
// 处理语音识别流错误
void _handleRecognitionError(dynamic error) {
Logger.error('语音识别错误', error);
_cleanupRecognition();
}
// 清理语音识别资源
void _cleanupRecognition() {
// 查找并移除临时消息
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) {
if (messages[_tempMessageIndex].content.trim().isEmpty) {
messages.removeAt(_tempMessageIndex);
}
}
// 重置临时消息索引
_tempMessageIndex = -1;
// 停止语音识别
isVoiceRecognitionActive.value = false;
isListening.value = false;
isSpeechDetected.value = false;
_asrService.stopContinuousRecognition();
}
// 停止语音识别
Future<void> stopVoiceRecognition() async {
if (!isVoiceRecognitionActive.value) return;
try {
await _asrService.stopContinuousRecognition();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
isVoiceRecognitionActive.value = false;
isListening.value = false;
recognizedText.value = '';
isSpeechDetected.value = false;
// 查找并移除临时消息
if (_tempMessageIndex >= 0 && _tempMessageIndex < messages.length) {
if (messages[_tempMessageIndex].content.trim().isEmpty) {
messages.removeAt(_tempMessageIndex);
}
}
// 重置临时消息索引
_tempMessageIndex = -1;
} 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();
_scrollToBottom();
}
}