diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 6cd0dcea6..0196a1d46 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -576,29 +576,43 @@ class AgentServiceImpl: NSObject { return true } + // 关键修复:如果正在结束的是通话模式,则立即将会话重置为 normal 状态 + if self.currentRecognitionMode == "phone_call" { + os_log("通话模式结束,强制重置音频会话至 'normal' 状态", log: logger, type: .info) + self.currentRecognitionMode = "normal" + // 使用 normal 模式的配置来清理和重置音频会话 + self.checkAdnSetAudioSession() + } + os_log("停止语音识别,当前模式: %{public}@", log: logger, type: .info, currentRecognitionMode) let success = azureAsrHelper?.stopContinuousRecognition() ?? false - - if !success && isStartingRecognition { + if success { + // 识别结束后,立即停用 AudioSession,以便其他模块(如TTS)可以接管 + // 这是解决“按住说话”后无法播报问题的关键 + do { + try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation) + os_log("Audio session deactivated successfully after recognition.", log: logger, type: .info) + } catch { + os_log("Failed to deactivate audio session: %{public}@", log: logger, type: .error, error.localizedDescription) + } + } else if isStartingRecognition { // 启动尚未完成,先记录一次待停止请求,onSessionStarted 到来后立即 stop stopRequestedDuringStart = true + // 即使底层 stop 失败,但因为我们已经记录了停止请求,所以对调用方来说,可以认为是成功的 + // 后续的 onSessionStarted 会处理真正的停止逻辑 } stopIdleCheck() // 只有之前在播放时才恢复播放 if wasMusicPlayingBeforeRecognition { - // QPlayAutoManager.shared.play() wasMusicPlayingBeforeRecognition = false MusicService.shared.resume() } - // 重置识别模式 - // currentRecognitionMode = "normal" - // 启动中但已请求停止,也视为已接受停止请求 - return success || isStartingRecognition + return success || stopRequestedDuringStart } func disableBluetoothAudio() { azureAsrHelper?.disableBluetoothAudio() diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift index 89cba21d4..cd66fb76d 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift @@ -848,33 +848,70 @@ public class AzureTtsHelper: NSObject, ITtsService, AVAudioPlayerDelegate { */ private func scheduleAvailablePcmBuffers() { guard ensureStreamingEngineIfNeeded() else { return } - if !hasNotifiedPlaybackStartedForStream, pcmPendingData.count >= streamChunkBytes { - // os_log("调用链: scheduleAvailablePcmBuffers 准备调度 pendingPcm=%{public}d chunk=%{public}d active=%{public}d", log: log, type: .info, pcmPendingData.count, streamChunkBytes, activeStreamSynthesisCount) - } - while pcmPendingData.count >= streamChunkBytes { - let chunk = pcmPendingData.prefix(streamChunkBytes) - pcmPendingData.removeFirst(streamChunkBytes) - guard let buffer = makePcmBuffer(from: Data(chunk)) else { continue } - scheduledBufferCount += 1 - if !hasNotifiedPlaybackStartedForStream { - hasNotifiedPlaybackStartedForStream = true - // os_log("调用链: 流式播放开始 session=%{public}@", log: log, type: .info, sessionid) - notifyEvent(eventType: .playbackStarted) + + if #available(iOS 18.0, *) { + // iOS 18及以上版本,使用原始逻辑:先调度,后播放 + if !hasNotifiedPlaybackStartedForStream, pcmPendingData.count >= streamChunkBytes { + // os_log("调用链: scheduleAvailablePcmBuffers 准备调度 pendingPcm=%{public}d chunk=%{public}d active=%{public}d", log: log, type: .info, pcmPendingData.count, streamChunkBytes, activeStreamSynthesisCount) } - playerNode?.scheduleBuffer(buffer, completionHandler: { [weak self] in - guard let self = self else { return } - self.audioPlaybackQueue.async { - self.scheduledBufferCount = max(0, self.scheduledBufferCount - 1) - self.checkStreamPlaybackCompletedIfNeeded() + while pcmPendingData.count >= streamChunkBytes { + let chunk = pcmPendingData.prefix(streamChunkBytes) + pcmPendingData.removeFirst(streamChunkBytes) + guard let buffer = makePcmBuffer(from: Data(chunk)) else { continue } + scheduledBufferCount += 1 + if !hasNotifiedPlaybackStartedForStream { + hasNotifiedPlaybackStartedForStream = true + // os_log("调用链: 流式播放开始 session=%{public}@", log: log, type: .info, sessionid) + notifyEvent(eventType: .playbackStarted) } - }) - } - if activeStreamSynthesisCount == 0, !pcmPendingData.isEmpty { - if pcmPendingData.count % 2 != 0 { - pcmPendingData.removeLast() + playerNode?.scheduleBuffer(buffer, completionHandler: { [weak self] in + guard let self = self else { return } + self.audioPlaybackQueue.async { + self.scheduledBufferCount = max(0, self.scheduledBufferCount - 1) + self.checkStreamPlaybackCompletedIfNeeded() + } + }) } - if !pcmPendingData.isEmpty, let buffer = makePcmBuffer(from: pcmPendingData) { - pcmPendingData.removeAll(keepingCapacity: true) + if activeStreamSynthesisCount == 0, !pcmPendingData.isEmpty { + if pcmPendingData.count % 2 != 0 { + pcmPendingData.removeLast() + } + if !pcmPendingData.isEmpty, let buffer = makePcmBuffer(from: pcmPendingData) { + pcmPendingData.removeAll(keepingCapacity: true) + scheduledBufferCount += 1 + if !hasNotifiedPlaybackStartedForStream { + hasNotifiedPlaybackStartedForStream = true + // os_log("调用链: 流式播放开始 session=%{public}@", log: log, type: .info, sessionid) + notifyEvent(eventType: .playbackStarted) + } + playerNode?.scheduleBuffer(buffer, completionHandler: { [weak self] in + guard let self = self else { return } + self.audioPlaybackQueue.async { + self.scheduledBufferCount = max(0, self.scheduledBufferCount - 1) + self.checkStreamPlaybackCompletedIfNeeded() + } + }) + } + } + if playerNode?.isPlaying == false, scheduledBufferCount > 0 { + // os_log("调用链: playerNode.play scheduled=%{public}d pendingPcm=%{public}d", log: log, type: .debug, scheduledBufferCount, pcmPendingData.count) + playerNode?.play() + } + } else { + // iOS 17及以下版本,使用修复逻辑:先播放,后调度 + if playerNode?.isPlaying == false { + // os_log("调用链: playerNode.play 提前启动播放器", log: log, type: .debug) + playerNode?.play() + } + + if !hasNotifiedPlaybackStartedForStream, pcmPendingData.count >= streamChunkBytes { + // os_log("调用链: scheduleAvailablePcmBuffers 准备调度 pendingPcm=%{public}d chunk=%{public}d active=%{public}d", log: log, type: .info, pcmPendingData.count, streamChunkBytes, activeStreamSynthesisCount) + } + + while pcmPendingData.count >= streamChunkBytes { + let chunk = pcmPendingData.prefix(streamChunkBytes) + pcmPendingData.removeFirst(streamChunkBytes) + guard let buffer = makePcmBuffer(from: Data(chunk)) else { continue } scheduledBufferCount += 1 if !hasNotifiedPlaybackStartedForStream { hasNotifiedPlaybackStartedForStream = true @@ -889,10 +926,27 @@ public class AzureTtsHelper: NSObject, ITtsService, AVAudioPlayerDelegate { } }) } - } - if playerNode?.isPlaying == false, scheduledBufferCount > 0 { - // os_log("调用链: playerNode.play scheduled=%{public}d pendingPcm=%{public}d", log: log, type: .debug, scheduledBufferCount, pcmPendingData.count) - playerNode?.play() + if activeStreamSynthesisCount == 0, !pcmPendingData.isEmpty { + if pcmPendingData.count % 2 != 0 { + pcmPendingData.removeLast() + } + if !pcmPendingData.isEmpty, let buffer = makePcmBuffer(from: pcmPendingData) { + pcmPendingData.removeAll(keepingCapacity: true) + scheduledBufferCount += 1 + if !hasNotifiedPlaybackStartedForStream { + hasNotifiedPlaybackStartedForStream = true + // os_log("调用链: 流式播放开始 session=%{public}@", log: log, type: .info, sessionid) + notifyEvent(eventType: .playbackStarted) + } + playerNode?.scheduleBuffer(buffer, completionHandler: { [weak self] in + guard let self = self else { return } + self.audioPlaybackQueue.async { + self.scheduledBufferCount = max(0, self.scheduledBufferCount - 1) + self.checkStreamPlaybackCompletedIfNeeded() + } + }) + } + } } } @@ -1367,7 +1421,11 @@ private func applyAudioSessionForTTS() -> Bool { desiredCategory = .playAndRecord if isHeadphonesConnected { desiredMode = .voiceChat - desiredOptions = [.allowBluetooth, .duckOthers] + if #available(iOS 18.0, *) { + desiredOptions = [.allowBluetooth, .duckOthers] + } else { + desiredOptions = [.allowBluetooth, .allowBluetoothA2DP] + } } else { desiredMode = .videoChat desiredOptions = [.allowBluetooth, .defaultToSpeaker] @@ -1404,7 +1462,14 @@ private func applyAudioSessionForTTS() -> Bool { } else { desiredCategory = .playback desiredMode = .default - desiredOptions = [.duckOthers] + if #available(iOS 18.0, *) { + desiredOptions = [] + } else { + desiredOptions = [.defaultToSpeaker] + } + if otherAudioPlaying { + desiredOptions.insert(.duckOthers) + } } do { diff --git a/local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift b/local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift index 9591dfe11..6fdd60df2 100644 --- a/local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift +++ b/local_plugins/chat_storage/ios/chat_storage/Sources/chat_storage/ChatStorageHelper.swift @@ -15,6 +15,8 @@ public class ChatStorageHelper { private let dbPath: String private let logger = OSLog(subsystem: "com.yunqiinnovation.chat_storage", category: "ChatStorageHelper") + private let dbQueue = DispatchQueue(label: "com.yunqiinnovation.chat_storage.dbQueue") + private init() { // 获取文档目录路径(不变) let fileURL = try! FileManager.default @@ -23,39 +25,43 @@ public class ChatStorageHelper { 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, - agent_id TEXT NOT NULL, - session_id TEXT NOT NULL, - message TEXT NOT NULL, - sender TEXT NOT NULL, - metadata TEXT, - created_at INTEGER NOT NULL, - UNIQUE (agent_id, session_id, sender) ON CONFLICT REPLACE - ); - 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); - """ - - if sqlite3_exec(db, createTableString, nil, nil, nil) != SQLITE_OK { - let errmsg = String(cString: sqlite3_errmsg(db)!) - os_log("创建表失败: %{public}@", log: logger, type: .error, errmsg) + dbQueue.sync { + // 打开数据库(不变) + 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, + agent_id TEXT NOT NULL, + session_id TEXT NOT NULL, + message TEXT NOT NULL, + sender TEXT NOT NULL, + metadata TEXT, + created_at INTEGER NOT NULL, + UNIQUE (agent_id, session_id, sender) ON CONFLICT REPLACE + ); + 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); + """ + + if sqlite3_exec(db, createTableString, nil, nil, nil) != SQLITE_OK { + let errmsg = String(cString: sqlite3_errmsg(db)!) + os_log("创建表失败: %{public}@", log: logger, type: .error, errmsg) + } } } deinit { - if db != nil { - sqlite3_close(db) + dbQueue.sync { + if db != nil { + sqlite3_close(db) + } } } @@ -68,48 +74,48 @@ public class ChatStorageHelper { * @return 插入的消息ID,失败则返回-1 */ 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, 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, 5, (metadata as NSString).utf8String, -1, nil) - } else { - sqlite3_bind_null(insertStatement, 5) - } + var result: Int64 = -1 + dbQueue.sync { + // 关键修改:用 INSERT OR REPLACE 替换 INSERT,支持冲突时更新 + let insertStatementString = """ + INSERT OR REPLACE INTO messages + (agent_id, session_id, message, sender, metadata, created_at) + VALUES (?, ?, ?, ?, ?, ?); + """ + var insertStatement: OpaquePointer? - let currentTime = Int(Date().timeIntervalSince1970) - sqlite3_bind_int(insertStatement, 6, Int32(currentTime)) - - // 执行语句(冲突时会自动替换,返回新的 rowid) - if sqlite3_step(insertStatement) == SQLITE_DONE { - let id = sqlite3_last_insert_rowid(db) // 替换后返回新的 id(原 id 会被删除) + 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, 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, 5, (metadata as NSString).utf8String, -1, nil) + } else { + sqlite3_bind_null(insertStatement, 5) + } + + let currentTime = Int(Date().timeIntervalSince1970) + sqlite3_bind_int(insertStatement, 6, Int32(currentTime)) + + // 执行语句(冲突时会自动替换,返回新的 rowid) + if sqlite3_step(insertStatement) == SQLITE_DONE { + let id = sqlite3_last_insert_rowid(db) // 替换后返回新的 id(原 id 会被删除) + os_log("消息保存成功(新增/更新),id: %{public}lld", log: logger, type: .info, id) + result = id + } else { + let errmsg = String(cString: sqlite3_errmsg(db)!) + os_log("插入/更新消息失败: %{public}@", log: logger, type: .error, errmsg) + } 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) } - - return -1 + return result } /** @@ -120,122 +126,106 @@ public class ChatStorageHelper { * @return 消息列表的JSON字符串 */ public func getMessages(agentId: String, page: Int, pageSize: Int) -> String { - let offset = (page - 1) * pageSize - var messagesArray: [[String: Any]] = [] - - // 首先获取总记录数 - let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?" - var countStatement: OpaquePointer? - var totalCount = 0 - - if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK { - sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil) - if sqlite3_step(countStatement) == SQLITE_ROW { - totalCount = Int(sqlite3_column_int(countStatement, 0)) + var result = "{\"messages\":[],\"page\":\(page),\"pageSize\":\(pageSize),\"totalCount\":0,\"totalPages\":0}" + dbQueue.sync { + let offset = (page - 1) * pageSize + var messagesArray: [[String: Any]] = [] + + // 首先获取总记录数 + let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?" + var countStatement: OpaquePointer? + var totalCount = 0 + + if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK { + sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil) + if sqlite3_step(countStatement) == SQLITE_ROW { + totalCount = Int(sqlite3_column_int(countStatement, 0)) + } + sqlite3_finalize(countStatement) } - sqlite3_finalize(countStatement) - } - - // 计算总页数 - let totalPages = totalCount == 0 ? 0 : (totalCount + pageSize - 1) / pageSize - - let queryString = """ - SELECT id, session_id, message, sender, metadata, created_at - FROM messages - WHERE agent_id = ? - ORDER BY created_at DESC - LIMIT ? OFFSET ? - """ - - var queryStatement: OpaquePointer? - - if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { - sqlite3_bind_text(queryStatement, 1, (agentId as NSString).utf8String, -1, nil) - sqlite3_bind_int(queryStatement, 2, Int32(pageSize)) - sqlite3_bind_int(queryStatement, 3, Int32(offset)) - while sqlite3_step(queryStatement) == SQLITE_ROW { - let id = sqlite3_column_int(queryStatement, 0) - - 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, 3) - let sender = senderPtr != nil ? String(cString: senderPtr!) : "" - - let metadataPtr = sqlite3_column_text(queryStatement, 4) - let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil + // 计算总页数 + let totalPages = totalCount == 0 ? 0 : (totalCount + pageSize - 1) / pageSize + + let queryString = """ + SELECT id, session_id, message, sender, metadata, created_at + FROM messages + WHERE agent_id = ? + ORDER BY created_at DESC + LIMIT ? OFFSET ? + """ + + var queryStatement: OpaquePointer? + + if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { + sqlite3_bind_text(queryStatement, 1, (agentId as NSString).utf8String, -1, nil) + sqlite3_bind_int(queryStatement, 2, Int32(pageSize)) + sqlite3_bind_int(queryStatement, 3, Int32(offset)) - let createdAt = sqlite3_column_int(queryStatement, 5) + while sqlite3_step(queryStatement) == SQLITE_ROW { + let id = sqlite3_column_int(queryStatement, 0) + + 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, 3) + let sender = senderPtr != nil ? String(cString: senderPtr!) : "" + + let metadataPtr = sqlite3_column_text(queryStatement, 4) + let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil + + let createdAt = sqlite3_column_int(queryStatement, 5) + + // 将时间戳转换为ISO 8601格式的字符串 + let date = Date(timeIntervalSince1970: TimeInterval(createdAt)) + let formatter = ISO8601DateFormatter() + let timestamp = formatter.string(from: date) + os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .info, timestamp) + + var messageDict: [String: Any] = [ + "id": id, + "agentId": agentId, // 添加agentId字段 + "sessionId": sessionId, // 添加sessionId字段 + "message": message, + "sender": sender, + "timestamp": timestamp // 使用timestamp而不是created_at + ] + + if let metadata = metadata { + messageDict["metadata"] = metadata + } + + messagesArray.append(messageDict) + } - // 将时间戳转换为ISO 8601格式的字符串 - let date = Date(timeIntervalSince1970: TimeInterval(createdAt)) - let formatter = ISO8601DateFormatter() - let timestamp = formatter.string(from: date) - os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .info, timestamp) + sqlite3_finalize(queryStatement) - var messageDict: [String: Any] = [ - "id": id, - "agentId": agentId, // 添加agentId字段 - "sessionId": sessionId, // 添加sessionId字段 - "message": message, - "sender": sender, - "timestamp": timestamp // 使用timestamp而不是created_at + // 构建与Android版本一致的返回格式 + let resultDict: [String: Any] = [ + "messages": messagesArray, + "page": page, + "pageSize": pageSize, + "totalCount": totalCount, + "totalPages": totalPages ] - - if let metadata = metadata { - messageDict["metadata"] = metadata - } - - messagesArray.append(messageDict) - } - - sqlite3_finalize(queryStatement) - - // 构建与Android版本一致的返回格式 - let result: [String: Any] = [ - "messages": messagesArray, - "page": page, - "pageSize": pageSize, - "totalCount": totalCount, - "totalPages": totalPages - ] - os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .error, messagesArray) - do { - let jsonData = try JSONSerialization.data(withJSONObject: result, options: []) - if let jsonString = String(data: jsonData, encoding: .utf8) { - return jsonString + os_log("getMessages: 读取历史记录: %{public}@", log: logger, type: .error, messagesArray) + do { + let jsonData = try JSONSerialization.data(withJSONObject: resultDict, options: []) + if let jsonString = String(data: jsonData, encoding: .utf8) { + result = jsonString + } + } catch { + os_log("getMessages: JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription) } - } catch { - os_log("getMessages: JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription) - } - } else { - let errmsg = String(cString: sqlite3_errmsg(db)!) - os_log("getMessages: SQL准备失败: %{public}@", log: logger, type: .error, errmsg) - } - - // 返回空结果,但保持格式一致 - let emptyResult: [String: Any] = [ - "messages": [], - "page": page, - "pageSize": pageSize, - "totalCount": 0, - "totalPages": 0 - ] - - do { - let jsonData = try JSONSerialization.data(withJSONObject: emptyResult, options: []) - if let jsonString = String(data: jsonData, encoding: .utf8) { - return jsonString + } else { + let errmsg = String(cString: sqlite3_errmsg(db)!) + os_log("getMessages: SQL准备失败: %{public}@", log: logger, type: .error, errmsg) } - } catch { - os_log("getMessages: 空结果JSON转换失败: %{public}@", log: logger, type: .error, error.localizedDescription) } - - return "{\"messages\":[],\"page\":\(page),\"pageSize\":\(pageSize),\"totalCount\":0,\"totalPages\":0}" + return result } /** @@ -245,56 +235,55 @@ public class ChatStorageHelper { * @return 是否删除成功 */ public func deleteMessages(agentId: String?, messageIds: [Int]?) -> Bool { - if let agentId = agentId { - let deleteString = "DELETE FROM messages WHERE agent_id = ?;" - var deleteStatement: OpaquePointer? - - if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK { - sqlite3_bind_text(deleteStatement, 1, (agentId as NSString).utf8String, -1, nil) + var result = false + dbQueue.sync { + if let agentId = agentId { + let deleteString = "DELETE FROM messages WHERE agent_id = ?;" + var deleteStatement: OpaquePointer? - if sqlite3_step(deleteStatement) == SQLITE_DONE { + if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK { + sqlite3_bind_text(deleteStatement, 1, (agentId as NSString).utf8String, -1, nil) + + if sqlite3_step(deleteStatement) == SQLITE_DONE { + sqlite3_finalize(deleteStatement) + result = true + } else { + let errmsg = String(cString: sqlite3_errmsg(db)!) + os_log("删除会话消息失败: %{public}@", log: logger, type: .error, errmsg) + } + sqlite3_finalize(deleteStatement) - return true } else { let errmsg = String(cString: sqlite3_errmsg(db)!) - os_log("删除会话消息失败: %{public}@", log: logger, type: .error, errmsg) - } - - sqlite3_finalize(deleteStatement) - } else { - let errmsg = String(cString: sqlite3_errmsg(db)!) - os_log("删除会话消息语句准备失败: %{public}@", log: logger, type: .error, errmsg) - } - } else if let messageIds = messageIds, !messageIds.isEmpty { - // 构建参数占位符 - let placeholders = String(repeating: "?,", count: messageIds.count).dropLast() - let deleteString = "DELETE FROM messages WHERE id IN (\(placeholders));" - var deleteStatement: OpaquePointer? - - if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK { - for (index, id) in messageIds.enumerated() { - sqlite3_bind_int(deleteStatement, Int32(index + 1), Int32(id)) + os_log("删除会话消息语句准备失败: %{public}@", log: logger, type: .error, errmsg) } + } else if let messageIds = messageIds, !messageIds.isEmpty { + // 构建参数占位符 + let placeholders = String(repeating: "?,", count: messageIds.count).dropLast() + let deleteString = "DELETE FROM messages WHERE id IN (\(placeholders));" + var deleteStatement: OpaquePointer? - if sqlite3_step(deleteStatement) == SQLITE_DONE { + if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK { + for (index, id) in messageIds.enumerated() { + sqlite3_bind_int(deleteStatement, Int32(index + 1), Int32(id)) + } + + if sqlite3_step(deleteStatement) == SQLITE_DONE { + result = true + } else { + let errmsg = String(cString: sqlite3_errmsg(db)!) + os_log("删除特定消息失败: %{public}@", log: logger, type: .error, errmsg) + } sqlite3_finalize(deleteStatement) - return true } 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(deleteStatement) } else { - let errmsg = String(cString: sqlite3_errmsg(db)!) - os_log("删除特定消息语句准备失败: %{public}@", log: logger, type: .error, errmsg) + os_log("删除消息参数无效 - agentId和messageIds都为空", log: logger, type: .error) } - } else { - os_log("删除消息参数无效 - agentId和messageIds都为空", log: logger, type: .error) - return false } - - return false + return result } /** @@ -302,15 +291,18 @@ public class ChatStorageHelper { * @return 是否清空成功 */ public func clearDatabase() -> Bool { - let deleteString = "DELETE FROM messages;" - - if sqlite3_exec(db, deleteString, nil, nil, nil) == SQLITE_OK { - return true - } else { - let errmsg = String(cString: sqlite3_errmsg(db)!) - os_log("清空数据库失败: %{public}@", log: logger, type: .error, errmsg) - return false + var result = false + dbQueue.sync { + let deleteString = "DELETE FROM messages;" + + if sqlite3_exec(db, deleteString, nil, nil, nil) == SQLITE_OK { + result = true + } else { + let errmsg = String(cString: sqlite3_errmsg(db)!) + os_log("清空数据库失败: %{public}@", log: logger, type: .error, errmsg) + } } + return result } /** @@ -321,75 +313,75 @@ public class ChatStorageHelper { */ public func getRecentMessages(agentId: String, limit: Int) -> [[String: Any]] { var messages: [[String: Any]] = [] - - // 首先检查数据库中是否有该会话的消息 - let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?" - var countStatement: OpaquePointer? - - if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK { - sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil) - if sqlite3_step(countStatement) == SQLITE_ROW { - _ = Int(sqlite3_column_int(countStatement, 0)) + dbQueue.sync { + // 首先检查数据库中是否有该会话的消息 + let countQuery = "SELECT COUNT(*) FROM messages WHERE agent_id = ?" + var countStatement: OpaquePointer? + + if sqlite3_prepare_v2(db, countQuery, -1, &countStatement, nil) == SQLITE_OK { + sqlite3_bind_text(countStatement, 1, (agentId as NSString).utf8String, -1, nil) + if sqlite3_step(countStatement) == SQLITE_ROW { + _ = Int(sqlite3_column_int(countStatement, 0)) + } + sqlite3_finalize(countStatement) } - sqlite3_finalize(countStatement) - } - - // 构建查询语句 - 按时间倒序获取最近的N条,然后在结果中再按时间正序 - let queryString = """ - SELECT * FROM ( - SELECT id, session_id, message, sender, metadata, created_at - FROM messages - WHERE agent_id = ? - ORDER BY created_at DESC - LIMIT ? - ) tmp ORDER BY created_at ASC - """ - - var queryStatement: OpaquePointer? - - if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { - sqlite3_bind_text(queryStatement, 1, (agentId as NSString).utf8String, -1, nil) - sqlite3_bind_int(queryStatement, 2, Int32(limit)) - while sqlite3_step(queryStatement) == SQLITE_ROW { - let id = sqlite3_column_int(queryStatement, 0) - - 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, 3) - let sender = senderPtr != nil ? String(cString: senderPtr!) : "" - - let metadataPtr = sqlite3_column_text(queryStatement, 4) - let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil - - let createdAt = sqlite3_column_int(queryStatement, 5) - - var messageDict: [String: Any] = [ - "id": id, - "sessionId":sessionId, - "message": message, - "sender": sender, - "created_at": createdAt - ] + // 构建查询语句 - 按时间倒序获取最近的N条,然后在结果中再按时间正序 + let queryString = """ + SELECT * FROM ( + SELECT id, session_id, message, sender, metadata, created_at + FROM messages + WHERE agent_id = ? + ORDER BY created_at DESC + LIMIT ? + ) tmp ORDER BY created_at ASC + """ + + var queryStatement: OpaquePointer? + + if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { + sqlite3_bind_text(queryStatement, 1, (agentId as NSString).utf8String, -1, nil) + sqlite3_bind_int(queryStatement, 2, Int32(limit)) - if let metadata = metadata { - messageDict["metadata"] = metadata + while sqlite3_step(queryStatement) == SQLITE_ROW { + let id = sqlite3_column_int(queryStatement, 0) + + 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, 3) + let sender = senderPtr != nil ? String(cString: senderPtr!) : "" + + let metadataPtr = sqlite3_column_text(queryStatement, 4) + let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil + + let createdAt = sqlite3_column_int(queryStatement, 5) + + var messageDict: [String: Any] = [ + "id": id, + "sessionId":sessionId, + "message": message, + "sender": sender, + "created_at": createdAt + ] + + if let metadata = metadata { + messageDict["metadata"] = metadata + } + + messages.append(messageDict) } - messages.append(messageDict) + sqlite3_finalize(queryStatement) + } else { + let errmsg = String(cString: sqlite3_errmsg(db)!) + os_log("查询最近消息失败: %{public}@", log: logger, type: .error, errmsg) } - - sqlite3_finalize(queryStatement) - } else { - let errmsg = String(cString: sqlite3_errmsg(db)!) - os_log("查询最近消息失败: %{public}@", log: logger, type: .error, errmsg) } - return messages } }