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.

434 lines
12 KiB

import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart';
import 'dart:convert';
import '../../../data/services/volcano_ai_service.dart';
import '../../../data/services/volcano_tts_service.dart';
import '../models/message_model.dart';
class ChatController extends GetxController {
late final String agentId;
late final String agentName;
late final String agentAvatar;
late final String? agentSubtitle;
late final String systemPrompt;
late final String welcomeMessage;
late final bool playVoiceOnEnter;
final VolcanoAIService _aiService = VolcanoAIService();
late final VolcanoTtsService _ttsService;
static const String _storagePrefix = 'chat_history_';
final messages = <Message>[].obs;
final isLoading = false.obs;
late final TextEditingController textController;
late final ScrollController scrollController;
final _storage = GetStorage();
// 用于存储当前正在流式生成的消息
final RxString currentStreamMessage = ''.obs;
Message? _currentAssistantMessage;
String _pendingTtsText = ''; // 用于累积TTS文本
static const int _minTtsLength = 20; // TTS最小文本长度
bool _isDisposed = false; // 添加销毁标记
ChatController() {
_initControllers();
_initTtsService();
_initFromArguments();
}
void _initFromArguments() {
final arguments = Get.arguments as Map<String, dynamic>;
agentId = arguments['agentId'] as String;
agentName = arguments['agentName'] as String;
agentAvatar = arguments['agentAvatar'] as String;
agentSubtitle = arguments['agentSubtitle'] as String?;
systemPrompt = arguments['systemPrompt'] as String;
welcomeMessage = arguments['welcomeMessage'] as String;
playVoiceOnEnter = arguments['playVoiceOnEnter'] as bool? ?? false;
}
void _initControllers() {
textController = TextEditingController();
scrollController = ScrollController();
}
void _initTtsService() {
try {
_ttsService = Get.find<VolcanoTtsService>();
} catch (_) {
_ttsService = Get.put(VolcanoTtsService(), permanent: true);
}
_ttsService.isEnabled.value = true;
}
@override
void onInit() async {
super.onInit();
await _initializeController();
// 如果需要在进入时播放语音,延迟一下以确保页面已加载
if (playVoiceOnEnter) {
Future.delayed(const Duration(milliseconds: 500), () {
generateSimpleGreeting();
});
}
}
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 (messages.isEmpty && agentName.isNotEmpty) {
// 生成动态问候语
await _generatePersonalizedGreeting();
}
// 保存聊天记录并滚动到底部
await _saveChatHistory();
Future.delayed(const Duration(milliseconds: 100), _scrollToBottom);
} catch (e) {
print('显示欢迎消息失败: $e');
// 如果生成问候语失败,确保至少显示静态欢迎消息
if (_ttsService.isEnabled.value) {
_ttsService.speak(welcomeMessage);
}
}
}
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 &&
_ttsService.isEnabled.value &&
fullGreeting.isNotEmpty) {
await _ttsService.speak(fullGreeting);
}
} catch (e) {
print('生成问候语失败: $e');
// 移除未完成的消息
if (_currentAssistantMessage != null) {
messages.remove(_currentAssistantMessage);
}
} finally {
if (!_isDisposed) {
_currentAssistantMessage = null;
currentStreamMessage.value = '';
}
}
}
@override
void onClose() {
_cleanupResources();
super.onClose();
}
@override
void dispose() {
_cleanupResources();
super.dispose();
}
void _cleanupResources() {
if (_isDisposed) return; // 防止重复清理
_isDisposed = true;
try {
if (textController.hasListeners) {
textController.dispose();
}
} catch (e) {
print('Disposing textController failed: $e');
}
try {
if (scrollController.hasClients) {
scrollController.dispose();
}
} catch (e) {
print('Disposing scrollController failed: $e');
}
try {
_ttsService.stop();
} catch (e) {
print('Stopping TTS service failed: $e');
}
// 清理其他资源
currentStreamMessage.value = '';
_currentAssistantMessage = null;
_pendingTtsText = '';
}
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();
}
} 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() {
if (scrollController.hasClients) {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
}
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,
);
_scrollToBottom();
}
}
}
void sendMessage(String text) async {
if (text.trim().isEmpty) return;
final userMessage = Message(
role: 'user',
content: text,
timestamp: DateTime.now(),
);
messages.add(userMessage);
textController.clear();
await _saveChatHistory();
_scrollToBottom();
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 && _ttsService.isEnabled.value) {
// 检查是否达到最小长度
if (_pendingTtsText.length >= _minTtsLength) {
// 找到最后一个句子结束的位置
int lastSentenceEnd = _findLastSentenceEnd(_pendingTtsText);
if (lastSentenceEnd > 0) {
// 播放到最后一个句子结束的位置
String textToSpeak =
_pendingTtsText.substring(0, lastSentenceEnd + 1);
_ttsService.speak(textToSpeak);
// 保留剩余的文本
_pendingTtsText = _pendingTtsText.substring(lastSentenceEnd + 1);
}
}
}
}
// 处理剩余的文本
if (!_isDisposed &&
_ttsService.isEnabled.value &&
_pendingTtsText.isNotEmpty) {
_ttsService.speak(_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;
}
}
}
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() {
_ttsService.toggleEnabled();
}
/// 生成简单的问候语
Future<void> generateSimpleGreeting() async {
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 && _ttsService.isEnabled.value && fullGreeting.isNotEmpty) {
await _ttsService.speak(fullGreeting);
}
} catch (e) {
print('生成问候语失败: $e');
// 移除未完成的消息
if (_currentAssistantMessage != null) {
messages.remove(_currentAssistantMessage);
}
} finally {
if (!_isDisposed) {
_currentAssistantMessage = null;
currentStreamMessage.value = '';
}
}
}
}