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.

971 lines
28 KiB

import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart';
import 'dart:convert';
import 'package:flutter/rendering.dart';
import '../../../data/services/volcano_ai_service.dart';
import '../../../data/services/volcano_tts_api_service.dart';
import '../models/message_model.dart';
import '../controllers/voice_input_controller.dart';
import 'dart:async';
class ChatController extends GetxController {
String agentId = '';
String agentName = '';
String agentAvatar = '';
String? agentSubtitle;
String systemPrompt = '';
String welcomeMessage = '';
bool playVoiceOnEnter = false;
final VolcanoAIService _aiService = VolcanoAIService();
late final VolcanoTtsApiService _ttsService;
static const String _storagePrefix = 'chat_history_';
final messages = <Message>[].obs;
final isLoading = false.obs;
late final TextEditingController messageController;
late final ScrollController scrollController;
final _storage = GetStorage();
// TTS启用状态
final isTtsEnabled = true.obs;
// 用于存储当前正在流式生成的消息
final RxString currentStreamMessage = ''.obs;
Message? _currentAssistantMessage;
String _pendingTtsText = '';
static const int _minTtsLength = 20;
bool _isDisposed = false;
// TTS事件订阅
StreamSubscription? _ttsEventSubscription;
bool _isTtsSpeaking = false;
// 语音输入相关状态
final isRecording = false.obs;
final recordingText = ''.obs;
final isVoiceInputVisible = false.obs;
final isVoiceConnecting = true.obs;
final isVoiceMuted = false.obs;
Message? _currentVoiceMessage;
// 添加一个标志,表示当前是否有正在进行的语音识别
final isRecognizing = false.obs;
// 输入框状态
final isInputCollapsed = false.obs;
final isVoiceMode = true.obs; // 默认为语音模式
final isRecordingVoice = false.obs; // 是否正在录音(按住说话状态)
// 添加是否在底部的状态变量
final RxBool isAtBottom = true.obs;
// 切换输入框折叠状态
void toggleInputCollapsed() {
isInputCollapsed.value = !isInputCollapsed.value;
}
// 切换语音/文本输入模式
void toggleInputMode() {
isVoiceMode.value = !isVoiceMode.value;
}
// 辅助方法:管理TTS状态
void _manageTtsState(bool enable) {
if (!enable && isTtsEnabled.value) {
// 需要禁用TTS
_ttsService.endSession(); // 先停止当前播放
isTtsEnabled.value = false;
} else if (enable && !isTtsEnabled.value) {
// 需要启用TTS
isTtsEnabled.value = true;
}
}
// 开始语音输入
void startVoiceInput() {
// 确保先停止任何可能正在进行的语音识别会话
if (Get.isRegistered<VoiceInputController>()) {
final voiceController = Get.find<VoiceInputController>();
voiceController.stopRecording();
Get.delete<VoiceInputController>();
}
// 如果TTS正在播放,先停止它
if (_isTtsSpeaking) {
_ttsService.endSession();
}
isVoiceInputVisible.value = true;
isVoiceConnecting.value = true;
isRecording.value = false;
recordingText.value = '';
isRecognizing.value = false;
// 当语音面板打开时,确保ListView滚动到适当位置,防止最后的消息被遮挡
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!_isDisposed && scrollController.hasClients && messages.isNotEmpty) {
// 计算需要额外滚动的距离(语音面板高度)
final extraScrollDistance = 120.0;
// 获取当前滚动位置
final currentPosition = scrollController.position.pixels;
final maxScrollExtent = scrollController.position.maxScrollExtent;
// 如果已经接近底部,则向上滚动一定距离,确保最后的消息可见
if (maxScrollExtent - currentPosition < extraScrollDistance) {
scrollController.animateTo(
currentPosition + extraScrollDistance,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
}
});
// 创建语音输入控制器
Get.put(VoiceInputController(
onRecordingResult: handleVoiceResult,
onClosePanel: stopVoiceInput,
onRecognizing: handleRecognizing,
));
// 滚动到底部
_scrollToBottom(animate: true);
}
void startRecording() {
if (!isVoiceConnecting.value && isVoiceInputVisible.value) {
// 不再需要禁用TTS,因为已启用回声消除
isRecording.value = true;
}
}
void stopRecording() {
if (isRecording.value) {
isRecording.value = false;
}
}
void toggleMute() {
isVoiceMuted.value = !isVoiceMuted.value;
}
void stopVoiceInput() {
// 确保停止语音识别
if (Get.isRegistered<VoiceInputController>()) {
try {
final voiceController = Get.find<VoiceInputController>();
voiceController.stopRecording();
} catch (e) {
debugPrint('Error stopping voice recording: $e');
}
}
isVoiceInputVisible.value = false;
isVoiceConnecting.value = true;
isRecording.value = false;
recordingText.value = '';
isRecognizing.value = false;
// 不再需要恢复TTS状态,因为已启用回声消除
// 如果存在临时语音消息但没有实际内容,则移除它
if (_currentVoiceMessage != null && (_currentVoiceMessage!.content == '🎤 ...' || _currentVoiceMessage!.content.startsWith('🎤 '))) {
messages.remove(_currentVoiceMessage);
}
_currentVoiceMessage = null;
// 当语音面板关闭时,确保ListView滚动回适当位置
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!_isDisposed && scrollController.hasClients && messages.isNotEmpty) {
// 滚动到底部,确保最新消息可见
_scrollToBottom(animate: true);
}
});
// 删除语音输入控制器
try {
if (Get.isRegistered<VoiceInputController>()) {
Get.delete<VoiceInputController>();
}
} catch (e) {
debugPrint('Error deleting VoiceInputController: $e');
}
// 确保按住说话状态被重置
isRecordingVoice.value = false;
}
void handleRecognizing(String text) {
if (text.isNotEmpty) {
// 更新识别中的文本
recordingText.value = text;
// 如果还没有创建临时语音消息,则创建一个
if (_currentVoiceMessage == null) {
_currentVoiceMessage = Message(
role: 'user',
content: '🎤 ' + text,
timestamp: DateTime.now(),
);
messages.add(_currentVoiceMessage!);
isRecognizing.value = true;
// 滚动到底部
_scrollToBottom(animate: true);
} else {
// 更新现有的临时语音消息
final index = messages.indexWhere((msg) =>
msg.timestamp == _currentVoiceMessage!.timestamp &&
msg.role == _currentVoiceMessage!.role);
if (index != -1) {
messages[index] = Message(
role: 'user',
content: '🎤 ' + text,
timestamp: _currentVoiceMessage!.timestamp,
);
// 滚动到底部
_scrollToBottom(animate: false);
}
}
}
}
void handleVoiceResult(String text) {
if (text.isNotEmpty) {
// 如果有临时语音消息,更新它
if (_currentVoiceMessage != null) {
final index = messages.indexWhere((msg) =>
msg.timestamp == _currentVoiceMessage!.timestamp &&
msg.role == _currentVoiceMessage!.role);
if (index != -1) {
// 更新消息,移除麦克风图标
messages[index] = Message(
role: 'user',
content: text,
timestamp: _currentVoiceMessage!.timestamp,
);
// 保存聊天历史
_saveChatHistory();
// 滚动到底部
_scrollToBottom(animate: true);
// 重新启用TTS,以便AI回复时可以播放
_manageTtsState(true);
// 发送给AI处理
_processAIResponse(messages[index]);
// 重置临时语音消息
_currentVoiceMessage = null;
isRecognizing.value = false;
}
} else {
// 如果没有临时语音消息,创建一个新的用户消息
final userMessage = Message(
role: 'user',
content: text,
timestamp: DateTime.now(),
);
// 添加到消息列表
messages.add(userMessage);
// 保存聊天历史
_saveChatHistory();
// 滚动到底部
_scrollToBottom(animate: true);
// 重新启用TTS,以便AI回复时可以播放
_manageTtsState(true);
// 发送给AI处理
_processAIResponse(userMessage);
}
}
}
// 处理AI响应
Future<void> _processAIResponse(Message userMessage) async {
if (_isDisposed) return;
try {
isLoading.value = true;
currentStreamMessage.value = '';
_currentAssistantMessage = Message(
role: 'assistant',
content: '',
timestamp: DateTime.now(),
);
messages.add(_currentAssistantMessage!);
_pendingTtsText = '';
// 确保在开始新的合成前停止当前播放
if (isTtsEnabled.value) {
await _ttsService.endSession();
// 添加短暂延迟确保停止完成
await Future.delayed(const Duration(milliseconds: 200));
}
await for (final chunk in _aiService.sendMessageStream(
messages: messages
.map((m) => {
'role': m.role,
'content': m.content,
})
.toList(),
systemPrompt: systemPrompt,
)) {
if (_isDisposed) break;
currentStreamMessage.value += chunk;
_updateAssistantMessage(currentStreamMessage.value);
// 累积文本并合成
_pendingTtsText += chunk;
if (!_isDisposed && isTtsEnabled.value) {
// 检查是否达到最小长度
if (_pendingTtsText.length >= _minTtsLength) {
// 找到最后一个句子结束的位置
int lastSentenceEnd = _findLastSentenceEnd(_pendingTtsText);
if (lastSentenceEnd > 0) {
// 播放到最后一个句子结束的位置
String textToSpeak =
_pendingTtsText.substring(0, lastSentenceEnd + 1);
// 使用新方法替代直接调用speak
_speakText(textToSpeak);
// 保留剩余的文本
_pendingTtsText = _pendingTtsText.substring(lastSentenceEnd + 1);
}
}
}
}
// 处理剩余的文本
if (!_isDisposed &&
isTtsEnabled.value &&
_pendingTtsText.isNotEmpty) {
// 使用新方法替代直接调用speak
_speakText(_pendingTtsText);
}
_pendingTtsText = '';
if (!_isDisposed) {
await _saveChatHistory();
}
} catch (e) {
if (!_isDisposed) {
if (_currentAssistantMessage != null) {
messages.remove(_currentAssistantMessage);
}
Get.snackbar(
'Error',
'Failed to get response from AI: $e',
snackPosition: SnackPosition.BOTTOM,
);
}
} finally {
if (!_isDisposed) {
_currentAssistantMessage = null;
currentStreamMessage.value = '';
isLoading.value = false;
}
}
}
// 播放文本并使用WebSocket流式合成
Future<void> _speakText(String text) async {
if (text.isEmpty || !isTtsEnabled.value) return;
try {
// 如果当前有正在进行的TTS会话,先结束它
if (_isTtsSpeaking) {
await _ttsService.endSession();
}
// 确保连接已建立
if (!_ttsService.isConnected.value) {
final connected = await _ttsService.connect();
if (!connected) {
print('无法连接到TTS服务');
return;
}
}
// 开始合成
await _ttsService.synthesize(text, 'zh_female_1'); // 使用默认说话人
} catch (e) {
print('TTS播放出错: $e');
}
}
// 确保滚动到底部的方法,使用多种策略确保成功
void _ensureScrollToBottom() {
// 立即尝试滚动
_scrollToBottom();
// 延迟100ms后再次尝试滚动(等待视图构建)
Future.delayed(const Duration(milliseconds: 100), () {
if (!_isDisposed) _scrollToBottom(animate: true);
});
// 延迟500ms后再次尝试滚动(确保所有元素都已加载)
Future.delayed(const Duration(milliseconds: 500), () {
if (!_isDisposed) _scrollToBottom(animate: true);
});
// 使用帧回调确保在渲染后滚动
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!_isDisposed) _scrollToBottom(animate: true);
});
}
// 滚动监听器
void _scrollListener() {
if (_isDisposed) return;
// 检测是否接近底部
if (scrollController.hasClients) {
final maxScroll = scrollController.position.maxScrollExtent;
final currentScroll = scrollController.offset;
isAtBottom.value = (maxScroll - currentScroll) < 50;
}
}
@override
void onInit() {
super.onInit();
messageController = TextEditingController();
scrollController = ScrollController();
// 确保清理任何可能存在的语音输入控制器
if (Get.isRegistered<VoiceInputController>()) {
try {
final voiceController = Get.find<VoiceInputController>();
voiceController.stopRecording();
Get.delete<VoiceInputController>();
} catch (e) {
debugPrint('Error cleaning up existing VoiceInputController: $e');
}
}
_initTtsService();
_initFromArguments();
_initializeController();
// 添加滚动监听器
scrollController.addListener(_scrollListener);
// 确保在页面加载完成后滚动到底部
_ensureScrollToBottom();
// 添加帧回调,确保在页面完全渲染后滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
// 延迟执行,确保页面已完全加载
Future.delayed(const Duration(milliseconds: 300), () {
if (!_isDisposed) {
_scrollToBottom(animate: true);
}
});
});
}
void _initFromArguments() {
try {
final arguments = Get.arguments;
if (arguments == null) {
throw Exception('No arguments provided to ChatController');
}
final args = arguments as Map<String, dynamic>;
agentId = args['agentId'] as String? ?? '';
agentName = args['agentName'] as String? ?? 'Assistant';
agentAvatar = args['agentAvatar'] as String? ?? 'assets/images/default_avatar.png';
agentSubtitle = args['agentSubtitle'] as String?;
systemPrompt = args['systemPrompt'] as String? ?? '';
welcomeMessage = args['welcomeMessage'] as String? ?? 'Hello! How can I help you today?';
playVoiceOnEnter = args['playVoiceOnEnter'] as bool? ?? false;
if (agentId.isEmpty) {
throw Exception('Invalid agent ID');
}
} catch (e) {
debugPrint('🔴 Error initializing ChatController: $e');
Get.back(); // 返回上一页
Get.snackbar(
'Error',
'Failed to initialize chat: Invalid parameters',
snackPosition: SnackPosition.BOTTOM,
);
}
}
void _initTtsService() {
_ttsService = Get.find<VolcanoTtsApiService>();
isTtsEnabled.value = true;
// 设置TTS事件监听
_setupTtsEventListener();
}
void _setupTtsEventListener() {
_ttsEventSubscription = _ttsService.eventStream.listen(_handleTtsEvent);
}
void _handleTtsEvent(Map<String, dynamic> event) {
if (_isDisposed) return;
final String eventType = event['type'] as String? ?? 'unknown';
switch (eventType) {
case 'ttsSentenceStart':
_isTtsSpeaking = true;
break;
case 'ttsSentenceEnd':
case 'sessionFinished':
case 'connectionFinished':
_isTtsSpeaking = false;
break;
case 'connectionFailed':
case 'sessionFailed':
_isTtsSpeaking = false;
print('TTS错误: ${event['message']}');
break;
}
}
Future<void> _initializeController() async {
try {
await _loadChatHistory();
await _showWelcomeMessages();
} catch (e) {
Get.snackbar(
'Error',
'Failed to initialize chat: $e',
snackPosition: SnackPosition.BOTTOM,
);
}
}
Future<void> _showWelcomeMessages() async {
try {
if (agentName.isNotEmpty && playVoiceOnEnter) {
// 只有 playVoiceOnEnter 为 true 时才生成动态问候语
await _generatePersonalizedGreeting();
// 保存聊天记录并立即滚动到底部
await _saveChatHistory();
_scrollToBottom(animate: true);
// 添加一个系统消息,告知用户已启用连续语音对话
final systemMessage = Message(
role: 'system',
content: '已为您自动启用连续语音对话模式,您可以直接开始说话',
timestamp: DateTime.now(),
);
messages.add(systemMessage);
await _saveChatHistory();
// 延迟一小段时间后自动启动语音输入模式
Future.delayed(const Duration(milliseconds: 500), () {
if (!_isDisposed) {
startVoiceInput();
}
});
}
} catch (e) {
print('显示欢迎消息失败: $e');
// 如果生成问候语失败,确保至少显示静态欢迎消息
if (isTtsEnabled.value) {
// 使用新方法替代直接调用speak
_speakText(welcomeMessage);
}
// 即使问候语生成失败,如果playVoiceOnEnter为true,仍然启动语音输入
if (playVoiceOnEnter) {
Future.delayed(const Duration(milliseconds: 500), () {
if (!_isDisposed) {
startVoiceInput();
}
});
}
}
}
Future<void> _generatePersonalizedGreeting() async {
if (_isDisposed) return;
String fullGreeting = '';
try {
currentStreamMessage.value = '';
_currentAssistantMessage = Message(
role: 'assistant',
content: '',
timestamp: DateTime.now(),
);
messages.add(_currentAssistantMessage!);
// 构建用于生成个性化问候语的消息列表
final greetingMessages = [
{'role': 'system', 'content': systemPrompt},
{
'role': 'user',
'content': '请根据你的身份和性格,用一句简短的话向我打个招呼,要体现你的特点,不要超过18个字。'
},
];
await for (final chunk in _aiService.sendMessageStream(
messages: greetingMessages,
systemPrompt: systemPrompt,
)) {
if (_isDisposed) break;
// 更新消息内容
currentStreamMessage.value += chunk;
_updateAssistantMessage(currentStreamMessage.value);
fullGreeting += chunk;
}
// 完整问候语生成完毕后,一次性播放
if (!_isDisposed && isTtsEnabled.value && fullGreeting.isNotEmpty) {
// 使用新方法替代直接调用speak
await _speakText(fullGreeting);
}
} catch (e) {
print('生成问候语失败: $e');
// 移除未完成的消息
if (_currentAssistantMessage != null) {
messages.remove(_currentAssistantMessage);
}
} finally {
if (!_isDisposed) {
_currentAssistantMessage = null;
currentStreamMessage.value = '';
}
}
}
@override
void onClose() {
_isDisposed = true;
// 确保语音输入被停止
stopVoiceInput();
// 先停止所有正在进行的操作
_ttsService.endSession(); // 使用endSession替代stop
// 取消TTS事件订阅
_ttsEventSubscription?.cancel();
currentStreamMessage.value = '';
_currentAssistantMessage = null;
_pendingTtsText = '';
// 最后再处理控制器的销毁
try {
messageController.dispose();
} catch (e) {
debugPrint('Error disposing messageController: $e');
}
try {
scrollController.dispose();
} catch (e) {
debugPrint('Error disposing scrollController: $e');
}
super.onClose();
}
Future<void> _loadChatHistory() async {
try {
final historyJson = _storage.read('${_storagePrefix}${agentId}');
if (historyJson != null) {
final List<dynamic> historyList = json.decode(historyJson);
messages.value = historyList.map((item) => Message.fromJson(item)).toList();
// 如果有历史消息,等待下一帧再滚动到底部
if (messages.isNotEmpty) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom(animate: true);
});
}
}
} catch (e) {
Get.snackbar(
'Error',
'Failed to load chat history: $e',
snackPosition: SnackPosition.BOTTOM,
);
}
}
Future<void> _saveChatHistory() async {
try {
final historyJson =
json.encode(messages.map((msg) => msg!.toJson()).toList());
await _storage.write('${_storagePrefix}${agentId}', historyJson);
} catch (e) {
Get.snackbar(
'Error',
'Failed to save chat history: $e',
snackPosition: SnackPosition.BOTTOM,
);
}
}
void _scrollToBottom({bool animate = false}) {
if (scrollController.hasClients) {
if (animate) {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} else {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
}
} else {
// 如果 scrollController 还没有附加到 ListView,延迟执行
Future.delayed(const Duration(milliseconds: 100), () {
if (!_isDisposed) {
_scrollToBottom(animate: animate);
}
});
}
}
void _updateAssistantMessage(String content) {
if (_currentAssistantMessage != null) {
final index = messages.indexWhere((msg) =>
msg.timestamp == _currentAssistantMessage?.timestamp &&
msg.role == _currentAssistantMessage?.role);
if (index != -1) {
messages[index] = Message(
role: 'assistant',
content: content,
timestamp: _currentAssistantMessage!.timestamp,
);
// 只在非初始化阶段滚动
if (!playVoiceOnEnter || content.isNotEmpty) {
_scrollToBottom(animate: false);
}
}
}
}
void clearMessage() {
if (!_isDisposed && messageController.text.isNotEmpty) {
messageController.clear();
}
}
Future<void> sendMessage() async {
if (_isDisposed) return;
final text = messageController.text.trim();
if (text.isEmpty) return;
try {
final userMessage = Message(
role: 'user',
content: text,
timestamp: DateTime.now(),
);
messages.add(userMessage);
clearMessage();
await _saveChatHistory();
_scrollToBottom(animate: true);
isLoading.value = true;
try {
currentStreamMessage.value = '';
_currentAssistantMessage = Message(
role: 'assistant',
content: '',
timestamp: DateTime.now(),
);
messages.add(_currentAssistantMessage!);
_pendingTtsText = '';
await for (final chunk in _aiService.sendMessageStream(
messages: messages
.map((m) => {
'role': m.role,
'content': m.content,
})
.toList(),
systemPrompt: systemPrompt,
)) {
if (_isDisposed) break;
currentStreamMessage.value += chunk;
_updateAssistantMessage(currentStreamMessage.value);
// 累积文本并合成
_pendingTtsText += chunk;
if (!_isDisposed && isTtsEnabled.value) {
// 检查是否达到最小长度
if (_pendingTtsText.length >= _minTtsLength) {
// 找到最后一个句子结束的位置
int lastSentenceEnd = _findLastSentenceEnd(_pendingTtsText);
if (lastSentenceEnd > 0) {
// 播放到最后一个句子结束的位置
String textToSpeak =
_pendingTtsText.substring(0, lastSentenceEnd + 1);
// 使用新方法替代直接调用speak
_speakText(textToSpeak);
// 保留剩余的文本
_pendingTtsText = _pendingTtsText.substring(lastSentenceEnd + 1);
}
}
}
}
// 处理剩余的文本
if (!_isDisposed && isTtsEnabled.value && _pendingTtsText.isNotEmpty) {
// 使用新方法替代直接调用speak
_speakText(_pendingTtsText);
}
_pendingTtsText = '';
if (!_isDisposed) {
await _saveChatHistory();
}
} catch (e) {
if (!_isDisposed) {
if (_currentAssistantMessage != null) {
messages.remove(_currentAssistantMessage);
}
Get.snackbar(
'Error',
'Failed to get response from AI: $e',
snackPosition: SnackPosition.BOTTOM,
);
}
} finally {
if (!_isDisposed) {
_currentAssistantMessage = null;
currentStreamMessage.value = '';
isLoading.value = false;
}
}
} catch (e) {
print('发送消息失败: $e');
}
}
int _findLastSentenceEnd(String text) {
final sentenceEnds = [
text.lastIndexOf('。'),
text.lastIndexOf('!'),
text.lastIndexOf('?'),
text.lastIndexOf('.'),
text.lastIndexOf('!'),
text.lastIndexOf('?'),
];
return sentenceEnds.reduce((max, pos) => pos > max ? pos : max);
}
Future<void> clearChat() async {
try {
messages.clear();
await _storage.remove('${_storagePrefix}${agentId}');
await _showWelcomeMessages();
} catch (e) {
Get.snackbar(
'Error',
'Failed to clear chat: $e',
snackPosition: SnackPosition.BOTTOM,
);
}
}
void toggleTTS() {
// 切换TTS启用状态
isTtsEnabled.toggle();
if (!isTtsEnabled.value) {
_ttsService.endSession(); // 使用endSession替代stop
}
}
/// 生成简单的问候语
Future<void> generateSimpleGreeting() async {
if (_isDisposed) return;
try {
currentStreamMessage.value = '';
_currentAssistantMessage = Message(
role: 'assistant',
content: '',
timestamp: DateTime.now(),
);
messages.add(_currentAssistantMessage!);
// 构建用于生成问候语的消息列表
final greetingMessages = [
{'role': 'system', 'content': systemPrompt},
{'role': 'user', 'content': '请用一句简短的话向我打个招呼,要体现你的特点,不要超过12个字。'},
];
String fullGreeting = '';
await for (final chunk in _aiService.sendMessageStream(
messages: greetingMessages,
systemPrompt: systemPrompt,
)) {
if (_isDisposed) break;
// 更新消息内容
currentStreamMessage.value += chunk;
_updateAssistantMessage(currentStreamMessage.value);
fullGreeting += chunk;
}
// 完整问候语生成完毕后,一次性播放
if (!_isDisposed && isTtsEnabled.value && fullGreeting.isNotEmpty) {
// 使用新方法替代直接调用speak
await _speakText(fullGreeting);
}
} catch (e) {
print('生成问候语失败: $e');
// 移除未完成的消息
if (_currentAssistantMessage != null) {
messages.remove(_currentAssistantMessage);
}
} finally {
if (!_isDisposed) {
_currentAssistantMessage = null;
currentStreamMessage.value = '';
}
}
}
// 开始按住说话
void startPressToTalk() {
isRecordingVoice.value = true;
startVoiceInput();
}
// 结束按住说话
void endPressToTalk() {
isRecordingVoice.value = false;
stopVoiceInput();
}
}