Browse Source

上传优化优化代码

weicu
liwei1dao 1 year ago
parent
commit
585357acd8
  1. 16
      lib/modules/agent/controllers/agent_controller.dart
  2. 34
      lib/modules/agent/views/agent_view.dart
  3. 18
      lib/modules/agent/views/message_bubble.dart
  4. 51
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  5. 70
      local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift
  6. 2
      pubspec.yaml

16
lib/modules/agent/controllers/agent_controller.dart

@ -79,7 +79,8 @@ class AgentController extends GetxController with WidgetsBindingObserver {
final isTtsEnabled = true.obs;
// TTS播报打断启用状态
final isTtsInterruptEnabled = true.obs;
// 是否显示工具调用
final isShowToolInterruptEnabled = false.obs;
// 图片处理状态
final isImageProcessing = false.obs;
@ -272,6 +273,19 @@ class AgentController extends GetxController with WidgetsBindingObserver {
}
}
/// 切换TTS播报打断状态
/// 当启用播报打断时,用户说话会自动停止当前的TTS播放
Future<void> toggleShowAIToolInterrupt() async {
isShowToolInterruptEnabled.toggle();
// 保存偏好设置
try {
await AgentService.isInterrupt();
} catch (e) {
Logger.e(TAG, '保存TTS播报打断偏好设置失败: $e');
}
}
// 设置文本输入监听器
void _setupTextInputListener() {
textController.addListener(() {

34
lib/modules/agent/views/agent_view.dart

@ -172,6 +172,39 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
),
color: isDarkMode ? Colors.grey[800] : Colors.white,
itemBuilder: (context) => [
// 播报打断开关选项
PopupMenuItem(
child: Obx(() => Row(
children: [
Icon(
controller.isShowToolInterruptEnabled.value
? Icons.build_circle
: Icons.build_circle_outlined,
color: controller.isShowToolInterruptEnabled.value
? primaryColor
: (isDarkMode ? Colors.grey[400] : Colors.grey),
size: 20.sp,
),
SizedBox(width: 8.w),
Text(
//'ttsInterruptToggle'.tr,
"显示工具",
style: TextStyle(fontSize: 14.sp),
),
const Spacer(),
Switch(
value: controller.isShowToolInterruptEnabled.value,
onChanged: (value) =>
controller.toggleShowAIToolInterrupt(),
activeColor: primaryColor,
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
),
],
)),
onTap: () => controller.toggleTtsInterrupt(),
),
// 播报打断开关选项
PopupMenuItem(
child: Obx(() => Row(
@ -204,6 +237,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
)),
onTap: () => controller.toggleTtsInterrupt(),
),
// 清除聊天记录选项
PopupMenuItem(
child: Row(

18
lib/modules/agent/views/message_bubble.dart

@ -84,7 +84,8 @@ class MessageBubble extends GetView<AgentController> {
],
),
child: Padding(
padding: (!isUser &&
padding: (controller.isShowToolInterruptEnabled.value &&
!isUser &&
functionCalls != null &&
functionCalls!.isNotEmpty)
? const EdgeInsets.only(top: 30)
@ -93,7 +94,8 @@ class MessageBubble extends GetView<AgentController> {
),
),
//工具调用流程显示(仅在AI消息且有functionCalls时显示)
if (!isUser &&
if (controller.isShowToolInterruptEnabled.value &&
!isUser &&
functionCalls != null &&
functionCalls!.isNotEmpty)
_buildFunctionCallsWidget(context),
@ -183,12 +185,20 @@ class MessageBubble extends GetView<AgentController> {
);
}
if (!isUser && message.isEmpty && functionCalls == null && meta == null) {
if (!isUser &&
message.isEmpty &&
(functionCalls == null ||
!controller.isShowToolInterruptEnabled.value) &&
meta == null) {
return _buildTypingIndicator();
}
// 如果只有工具调用而没有文本内容,显示一个占位符确保气泡有足够宽度
if (!isUser && message.isEmpty && functionCalls != null && meta == null) {
if (!isUser &&
message.isEmpty &&
functionCalls != null &&
controller.isShowToolInterruptEnabled.value &&
meta == null) {
return Container(
constraints: const BoxConstraints(
minWidth: 200, // 确保最小宽度

51
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -657,6 +657,15 @@ audioStream.saveAudioDataTo(data: audioData)
messages.append(userMessage)
os_log("添加用户消息", log: logger, type: .info)
// 保存用户消息
self.chatStorageHelper.saveMessage(
agentId: self.agentId,
sessionId: sessionid,
message: displayText,
sender: "user",
metadata:""
)
addToHistoryMessages(chatApiService.createUserMessage(content: displayText))
currsessionId = sessionid
let callback = ChatApiStreamCallback(
@ -781,16 +790,9 @@ audioStream.saveAudioDataTo(data: audioData)
*/
internal func saveChatMessage(sessionId:String,userMessage: String, assistantMessage: String, metadata: String = "",userMetadata: String) {
DispatchQueue.global(qos: .utility).async {
// 保存用户消息
let userMessageId = self.chatStorageHelper.saveMessage(
agentId: self.agentId,
sessionId: sessionId,
message: userMessage,
sender: "user",
metadata: userMetadata
)
if userMessageId != -1 {
// if userMessageId != -1 {
if !assistantMessage.isEmpty || !metadata.isEmpty {
// 保存AI回复
let assistantMessageId = self.chatStorageHelper.saveMessage(
@ -804,9 +806,9 @@ audioStream.saveAudioDataTo(data: audioData)
os_log("保存助手消息失败", log: self.logger, type: .error)
}
}
} else {
os_log("保存用户消息失败", log: self.logger, type: .error)
}
// } else {
// os_log("保存用户消息失败", log: self.logger, type: .error)
// }
}
}
@ -817,6 +819,7 @@ internal func autoHandleFunctionCallResult(toolName: String, functionCallResult:
guard let contextValue = functionCallResult["context"] else {
return [
"meta": "",
"iscard":false,
"broadcast": true,
"notreply": false,
]
@ -884,9 +887,9 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -
metadata["isinternal"] = isinternal
} else if let cardQQMusiclist = jsonDict["data"] as? [[String: Any]], toolName == "qqmusic_searchandplay_songs" {
processQQMusicPlayList(cardQQMusiclist)
} else if toolName == "maps_weather" {
} else if toolName == "map_weather" {
iscard = true
}else if toolName == "map_weather" {
}else if toolName == "maps_weather" {
iscard = true
}
metadata["iscard"] = iscard
@ -900,6 +903,7 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -
return [
"meta": metaStr,
"iscard":iscard,
"broadcast": broadcast,
"notreply": notreply
]
@ -1788,11 +1792,15 @@ class ChatApiStreamCallback: StreamCallback {
// agentService.audioPlayer?.stopCallingSound()
let resultmap = agentService.autoHandleFunctionCallResult(toolName:name,functionCallResult:functionCallResult)
metadata = resultmap["meta"] as? String ?? ""
broadcast = resultmap["broadcast"] as? Bool ?? true
reply = !(resultmap["notreply"] as? Bool ?? true)
iscard = !(resultmap["iscard"] as? Bool ?? false)
iscard = resultmap["iscard"] as? Bool ?? false
os_log("收到函数调用结果! broadcast: %{public}@ reply: %{public}@ %{iscard}@",
log: agentService.logger,
type: .info,
"\(broadcast)", "\(reply)", "\(iscard)")
if (iscard){
metadata = resultmap["meta"] as? String ?? ""
agentService.sendEvent(name: "function_call_result", data: [
"sessionid":sessionid,
"function_name": name,
@ -1800,10 +1808,13 @@ class ChatApiStreamCallback: StreamCallback {
"result": functionCallResult,
"meta":metadata,
])
os_log("收到函数调用结果! broadcast: %{public}@ reply: %{public}@ %{metadata}@",
log: agentService.logger,
type: .info,
"\(broadcast)", "\(reply)", "\(metadata)")
}else{
agentService.sendEvent(name: "function_call_result", data: [
"sessionid":sessionid,
"function_name": name,
"function_call": functionCall,
"result": functionCallResult,
])
}
}catch{

70
local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift

@ -16,21 +16,21 @@ public class ChatStorageHelper {
private let logger = OSLog(subsystem: "com.yunqiinnovation.chat_storage", category: "ChatStorageHelper")
private init() {
// 获取文档目录路径
// 获取文档目录路径(不变)
let fileURL = try! FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("chat_storage.db")
dbPath = fileURL.path
// 打开数据库
// 打开数据库(不变)
if sqlite3_open(dbPath, &db) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("无法打开数据库: %{public}@", log: logger, type: .error, errmsg)
return
}
// 创建消息表
// 关键修改:新增 UNIQUE (agent_id, session_id, sender) 联合唯一索引
let createTableString = """
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -39,9 +39,10 @@ public class ChatStorageHelper {
message TEXT NOT NULL,
sender TEXT NOT NULL,
metadata TEXT,
created_at INTEGER NOT NULL
created_at INTEGER NOT NULL,
UNIQUE (agent_id, session_id, sender) ON CONFLICT REPLACE
);
CREATE INDEX IF NOT EXISTS idx_agent_id ON messages (session_id);
CREATE INDEX IF NOT EXISTS idx_agent_id ON messages (agent_id);
CREATE INDEX IF NOT EXISTS idx_session_id ON messages (session_id);
CREATE INDEX IF NOT EXISTS idx_created_at ON messages (created_at);
"""
@ -66,38 +67,46 @@ public class ChatStorageHelper {
* @param metadata 可选的元数据
* @return 插入的消息ID,失败则返回-1
*/
public func saveMessage(agentId:String,sessionId: String, message: String, sender: String, metadata: String?) -> Int64 {
let insertStatementString = "INSERT INTO messages (agent_id,session_id, message, sender, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?);"
public func saveMessage(agentId: String, sessionId: String, message: String, sender: String, metadata: String?) -> Int64 {
// 关键修改:用 INSERT OR REPLACE 替换 INSERT,支持冲突时更新
let insertStatementString = """
INSERT OR REPLACE INTO messages
(agent_id, session_id, message, sender, metadata, created_at)
VALUES (?, ?, ?, ?, ?, ?);
"""
var insertStatement: OpaquePointer?
if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
// 绑定参数(逻辑不变,确保三个唯一字段正确传入)
sqlite3_bind_text(insertStatement, 1, (agentId as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 1, (sessionId as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 2, (message as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 3, (sender as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 2, (sessionId as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 3, (message as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 4, (sender as NSString).utf8String, -1, nil)
if let metadata = metadata {
sqlite3_bind_text(insertStatement, 4, (metadata as NSString).utf8String, -1, nil)
sqlite3_bind_text(insertStatement, 5, (metadata as NSString).utf8String, -1, nil)
} else {
sqlite3_bind_null(insertStatement, 4)
sqlite3_bind_null(insertStatement, 5)
}
let currentTime = Int(Date().timeIntervalSince1970)
sqlite3_bind_int(insertStatement, 5, Int32(currentTime))
sqlite3_bind_int(insertStatement, 6, Int32(currentTime))
// 执行语句(冲突时会自动替换,返回新的 rowid)
if sqlite3_step(insertStatement) == SQLITE_DONE {
let id = sqlite3_last_insert_rowid(db)
let id = sqlite3_last_insert_rowid(db) // 替换后返回新的 id(原 id 会被删除)
sqlite3_finalize(insertStatement)
os_log("消息保存成功(新增/更新),id: %{public}lld", log: logger, type: .info, id)
return id
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("插入消息失败: %{public}@", log: logger, type: .error, errmsg)
os_log("插入/更新消息失败: %{public}@", log: logger, type: .error, errmsg)
}
sqlite3_finalize(insertStatement)
} else {
let errmsg = String(cString: sqlite3_errmsg(db)!)
os_log("插入消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
os_log("插入/更新消息语句准备失败: %{public}@", log: logger, type: .error, errmsg)
}
return -1
@ -131,7 +140,7 @@ public class ChatStorageHelper {
let totalPages = totalCount == 0 ? 0 : (totalCount + pageSize - 1) / pageSize
let queryString = """
SELECT id, message, sender, metadata, created_at
SELECT id, session_id, message, sender, metadata, created_at
FROM messages
WHERE agent_id = ?
ORDER BY created_at DESC
@ -148,16 +157,19 @@ public class ChatStorageHelper {
while sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0)
let messagePtr = sqlite3_column_text(queryStatement, 1)
let sessionIdPtr = sqlite3_column_text(queryStatement, 1)
let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : ""
let messagePtr = sqlite3_column_text(queryStatement, 2)
let message = messagePtr != nil ? String(cString: messagePtr!) : ""
let senderPtr = sqlite3_column_text(queryStatement, 2)
let senderPtr = sqlite3_column_text(queryStatement, 3)
let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
let metadataPtr = sqlite3_column_text(queryStatement, 3)
let metadataPtr = sqlite3_column_text(queryStatement, 4)
let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
let createdAt = sqlite3_column_int(queryStatement, 4)
let createdAt = sqlite3_column_int(queryStatement, 5)
// 将时间戳转换为ISO 8601格式的字符串
let date = Date(timeIntervalSince1970: TimeInterval(createdAt))
@ -167,6 +179,7 @@ public class ChatStorageHelper {
var messageDict: [String: Any] = [
"id": id,
"agentId": agentId, // 添加agentId字段
"sessionId": sessionId, // 添加sessionId字段
"message": message,
"sender": sender,
"timestamp": timestamp // 使用timestamp而不是created_at
@ -323,7 +336,7 @@ public class ChatStorageHelper {
// 构建查询语句 - 按时间倒序获取最近的N条,然后在结果中再按时间正序
let queryString = """
SELECT * FROM (
SELECT id, message, sender, metadata, created_at
SELECT id, session_id, message, sender, metadata, created_at
FROM messages
WHERE agent_id = ?
ORDER BY created_at DESC
@ -340,19 +353,24 @@ public class ChatStorageHelper {
while sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0)
let messagePtr = sqlite3_column_text(queryStatement, 1)
let sessionIdPtr = sqlite3_column_text(queryStatement, 1)
let sessionId = sessionIdPtr != nil ? String(cString: sessionIdPtr!) : ""
let messagePtr = sqlite3_column_text(queryStatement, 2)
let message = messagePtr != nil ? String(cString: messagePtr!) : ""
let senderPtr = sqlite3_column_text(queryStatement, 2)
let senderPtr = sqlite3_column_text(queryStatement, 3)
let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
let metadataPtr = sqlite3_column_text(queryStatement, 3)
let metadataPtr = sqlite3_column_text(queryStatement, 4)
let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
let createdAt = sqlite3_column_int(queryStatement, 4)
let createdAt = sqlite3_column_int(queryStatement, 5)
var messageDict: [String: Any] = [
"id": id,
"sessionId":sessionId,
"message": message,
"sender": sender,
"created_at": createdAt

2
pubspec.yaml

@ -1,7 +1,7 @@
name: voitrans
description: "Voitrans - AI Voice Assistant."
publish_to: "none"
version: 1.0.16+41
version: 1.0.17+46
environment:
sdk: ">=3.3.0 <4.0.0"

Loading…
Cancel
Save