|
|
@ -11,6 +11,7 @@ import 'package:get_storage/get_storage.dart'; |
|
|
import 'package:permission_handler/permission_handler.dart'; |
|
|
import 'package:permission_handler/permission_handler.dart'; |
|
|
import 'package:app_settings/app_settings.dart'; |
|
|
import 'package:app_settings/app_settings.dart'; |
|
|
import 'package:deep_voice/core/utils/logger.dart'; |
|
|
import 'package:deep_voice/core/utils/logger.dart'; |
|
|
|
|
|
import 'package:path_provider/path_provider.dart'; |
|
|
|
|
|
|
|
|
class Message { |
|
|
class Message { |
|
|
final bool isUser; //标识信息是否来自用户还是AI |
|
|
final bool isUser; //标识信息是否来自用户还是AI |
|
|
@ -136,10 +137,11 @@ class AgentController extends GetxController { |
|
|
Future<void> _loadChatHistory() async { |
|
|
Future<void> _loadChatHistory() async { |
|
|
try { |
|
|
try { |
|
|
Logger.i(TAG, '正在加载聊天历史...'); |
|
|
Logger.i(TAG, '正在加载聊天历史...'); |
|
|
|
|
|
//通过本地插件ChatStorage获取聊天记录 |
|
|
final result = await ChatStorage.getMessages( |
|
|
final result = await ChatStorage.getMessages( |
|
|
sessionId: sessionId, |
|
|
sessionId: sessionId, //会话ID |
|
|
page: 1, |
|
|
page: 1, //从第一页开始 |
|
|
pageSize: 20, |
|
|
pageSize: 20, //每页20条记录 |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
if (result.messages.isNotEmpty) { |
|
|
if (result.messages.isNotEmpty) { |
|
|
@ -148,11 +150,22 @@ class AgentController extends GetxController { |
|
|
final meta = msg.metadata != null && msg.metadata != "" |
|
|
final meta = msg.metadata != null && msg.metadata != "" |
|
|
? jsonDecode(msg.metadata!) |
|
|
? jsonDecode(msg.metadata!) |
|
|
: null; // 如果为空,使用空对象 |
|
|
: null; // 如果为空,使用空对象 |
|
|
|
|
|
|
|
|
|
|
|
// 从元数据中提取图片信息 |
|
|
|
|
|
final hasImage = meta != null && meta.containsKey('hasImage') |
|
|
|
|
|
? meta['hasImage'] as bool |
|
|
|
|
|
: false; |
|
|
|
|
|
final imagePath = meta != null && meta.containsKey('imagePath') |
|
|
|
|
|
? meta['imagePath'] as String? |
|
|
|
|
|
: null; |
|
|
|
|
|
|
|
|
return Message( |
|
|
return Message( |
|
|
isUser: msg.sender == 'user', |
|
|
isUser: msg.sender == 'user', |
|
|
text: msg.message, |
|
|
text: msg.message, |
|
|
meta: meta, |
|
|
meta: meta, |
|
|
timestamp: DateTime.parse(msg.timestamp), |
|
|
timestamp: DateTime.parse(msg.timestamp), |
|
|
|
|
|
hasImage: hasImage, |
|
|
|
|
|
imagePath: imagePath, |
|
|
); |
|
|
); |
|
|
}).toList(); |
|
|
}).toList(); |
|
|
|
|
|
|
|
|
@ -161,6 +174,22 @@ class AgentController extends GetxController { |
|
|
messages.assignAll(historyMessages); |
|
|
messages.assignAll(historyMessages); |
|
|
|
|
|
|
|
|
Logger.i(TAG, '成功加载 ${historyMessages.length} 条聊天记录'); |
|
|
Logger.i(TAG, '成功加载 ${historyMessages.length} 条聊天记录'); |
|
|
|
|
|
// 详细打印每条消息的内容和属性,便于调试 |
|
|
|
|
|
Logger.d(TAG, '===== 历史消息详情开始 ====='); |
|
|
|
|
|
for (int i = 0; i < historyMessages.length; i++) { |
|
|
|
|
|
final msg = historyMessages[i]; |
|
|
|
|
|
Logger.d( |
|
|
|
|
|
TAG, |
|
|
|
|
|
'消息[$i]: {' |
|
|
|
|
|
'isUser: ${msg.isUser}, ' |
|
|
|
|
|
'text: "${msg.text.length > 50 ? msg.text.substring(0, 50) + "..." : msg.text}", ' |
|
|
|
|
|
'time: ${msg.timestamp}, ' |
|
|
|
|
|
'hasImage: ${msg.hasImage}, ' |
|
|
|
|
|
'imagePath: ${msg.imagePath ?? "无"}, ' |
|
|
|
|
|
'meta: ${msg.meta != null ? "有" : "无"}' |
|
|
|
|
|
'}'); |
|
|
|
|
|
} |
|
|
|
|
|
Logger.d(TAG, '===== 历史消息详情结束 ====='); |
|
|
} else { |
|
|
} else { |
|
|
// 没有历史记录时添加欢迎消息 |
|
|
// 没有历史记录时添加欢迎消息 |
|
|
_addWelcomeMessage(); |
|
|
_addWelcomeMessage(); |
|
|
@ -180,7 +209,7 @@ class AgentController extends GetxController { |
|
|
void _addWelcomeMessage() { |
|
|
void _addWelcomeMessage() { |
|
|
messages.add(Message( |
|
|
messages.add(Message( |
|
|
isUser: false, |
|
|
isUser: false, |
|
|
text: '你好!我是**Emma**,我能帮你:\n\n' |
|
|
text: '你好!我是**小言**,我能帮你:\n\n' |
|
|
'• 答问题和提供信息\n\n' |
|
|
'• 答问题和提供信息\n\n' |
|
|
'• 实时语音翻译\n\n' |
|
|
'• 实时语音翻译\n\n' |
|
|
'• 推荐音乐和播客\n\n' |
|
|
'• 推荐音乐和播客\n\n' |
|
|
@ -424,7 +453,7 @@ class AgentController extends GetxController { |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 发送文本消息 |
|
|
// 发送消息(包括所有支持的消息类型) |
|
|
Future<void> sendTextMessage() async { |
|
|
Future<void> sendTextMessage() async { |
|
|
// 如果图片输入是活跃的,使用选中的图片和文本框的内容 |
|
|
// 如果图片输入是活跃的,使用选中的图片和文本框的内容 |
|
|
if (isImageInputActive.value && selectedImagePath.value != null) { |
|
|
if (isImageInputActive.value && selectedImagePath.value != null) { |
|
|
@ -466,6 +495,9 @@ class AgentController extends GetxController { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 复制图片到应用永久存储目录 |
|
|
|
|
|
final permanentImagePath = await _copyImageToPermanentStorage(imagePath); |
|
|
|
|
|
|
|
|
// 显示的文本,如果没有提供则使用默认值 |
|
|
// 显示的文本,如果没有提供则使用默认值 |
|
|
final displayText = text?.isNotEmpty == true ? text! : '[图片]'; |
|
|
final displayText = text?.isNotEmpty == true ? text! : '[图片]'; |
|
|
|
|
|
|
|
|
@ -474,7 +506,7 @@ class AgentController extends GetxController { |
|
|
isUser: true, |
|
|
isUser: true, |
|
|
text: displayText, |
|
|
text: displayText, |
|
|
hasImage: true, |
|
|
hasImage: true, |
|
|
imagePath: imagePath, |
|
|
imagePath: permanentImagePath, |
|
|
); |
|
|
); |
|
|
messages.add(message); |
|
|
messages.add(message); |
|
|
|
|
|
|
|
|
@ -489,9 +521,12 @@ class AgentController extends GetxController { |
|
|
// 标记为新的AI回复 |
|
|
// 标记为新的AI回复 |
|
|
_isNewAssistantResponse = true; |
|
|
_isNewAssistantResponse = true; |
|
|
|
|
|
|
|
|
|
|
|
// 保存消息到持久化存储 |
|
|
|
|
|
//await _saveMessageToStorage(message); |
|
|
|
|
|
|
|
|
// 调用Agent Service处理图片,考虑TTS状态 |
|
|
// 调用Agent Service处理图片,考虑TTS状态 |
|
|
await AgentService.processImageInput( |
|
|
await AgentService.processImageInput( |
|
|
imagePath, |
|
|
permanentImagePath, |
|
|
text: text ?? '', |
|
|
text: text ?? '', |
|
|
speakResponse: isTtsEnabled.value, |
|
|
speakResponse: isTtsEnabled.value, |
|
|
); |
|
|
); |
|
|
@ -502,6 +537,68 @@ class AgentController extends GetxController { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 将图片复制到应用的永久存储目录 |
|
|
|
|
|
Future<String> _copyImageToPermanentStorage(String sourcePath) async { |
|
|
|
|
|
try { |
|
|
|
|
|
final File sourceFile = File(sourcePath); |
|
|
|
|
|
if (!await sourceFile.exists()) { |
|
|
|
|
|
Logger.e(TAG, '源图片文件不存在: $sourcePath'); |
|
|
|
|
|
return sourcePath; // 如果源文件不存在,返回原路径 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建应用文档目录下的chatImages文件夹 |
|
|
|
|
|
final appDir = await getApplicationDocumentsDirectory(); |
|
|
|
|
|
final imagesDir = Directory('${appDir.path}/chatImages'); |
|
|
|
|
|
if (!await imagesDir.exists()) { |
|
|
|
|
|
await imagesDir.create(recursive: true); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 生成唯一文件名 |
|
|
|
|
|
final fileName = |
|
|
|
|
|
'${DateTime.now().millisecondsSinceEpoch}_${sourcePath.split('/').last}'; |
|
|
|
|
|
final targetPath = '${imagesDir.path}/$fileName'; |
|
|
|
|
|
|
|
|
|
|
|
// 复制文件 |
|
|
|
|
|
await sourceFile.copy(targetPath); |
|
|
|
|
|
Logger.i(TAG, '图片已复制到永久存储: $targetPath'); |
|
|
|
|
|
|
|
|
|
|
|
return targetPath; |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
Logger.e(TAG, '复制图片到永久存储失败: $e'); |
|
|
|
|
|
return sourcePath; // 出错时返回原路径 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 保存消息到持久化存储 |
|
|
|
|
|
// Future<void> _saveMessageToStorage(Message message) async { |
|
|
|
|
|
// try { |
|
|
|
|
|
// // 准备元数据,包含图片信息 |
|
|
|
|
|
// Map<String, dynamic> metadata = {}; |
|
|
|
|
|
// if (message.meta != null) { |
|
|
|
|
|
// metadata = Map<String, dynamic>.from(message.meta!); |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
// // 添加图片相关信息到元数据 |
|
|
|
|
|
// if (message.hasImage) { |
|
|
|
|
|
// metadata['hasImage'] = true; |
|
|
|
|
|
// metadata['imagePath'] = message.imagePath; |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
// // 保存消息 |
|
|
|
|
|
// await ChatStorage.saveMessage( |
|
|
|
|
|
// sessionId: sessionId, |
|
|
|
|
|
// sender: message.isUser ? 'user' : 'assistant', |
|
|
|
|
|
// message: message.text, |
|
|
|
|
|
// metadata: metadata.isNotEmpty ? jsonEncode(metadata) : null, |
|
|
|
|
|
// //timestamp: message.timestamp.toIso8601String(), |
|
|
|
|
|
// ); |
|
|
|
|
|
|
|
|
|
|
|
// Logger.i(TAG, '消息已保存到持久化存储'); |
|
|
|
|
|
// } catch (e) { |
|
|
|
|
|
// Logger.e(TAG, '保存消息到持久化存储失败: $e'); |
|
|
|
|
|
// } |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
// 切换输入模式 |
|
|
// 切换输入模式 |
|
|
void toggleInputMode() async { |
|
|
void toggleInputMode() async { |
|
|
// 如果当前在图片输入模式,先清除 |
|
|
// 如果当前在图片输入模式,先清除 |
|
|
|