From 7fdae0173ce8af28ce8c0f4fe35f192722b6b0e6 Mon Sep 17 00:00:00 2001 From: suyao Date: Mon, 22 Sep 2025 23:12:08 +0800 Subject: [PATCH] Implement file reference count cleanup with deleteIfZero support - Update DexieMessageDataSource to delete files when count reaches zero and deleteIfZero is true - Add deleteIfZero parameter to MessageDataSource interface and all implementations - Modify updateFileCountV2 thunk to pass deleteIfZero parameter through DbService --- AGENT_SESSION_FIX.md | 101 ------- BACKEND_STATUS_PERSISTENCE_SOLUTION.md | 247 ------------------ TEST_SCENARIOS.md | 212 --------------- .../src/services/db/AgentMessageDataSource.ts | 4 +- src/renderer/src/services/db/DbService.ts | 6 +- .../src/services/db/DexieMessageDataSource.ts | 41 +-- src/renderer/src/services/db/types.ts | 7 +- .../src/store/thunk/messageThunk.v2.ts | 5 +- 8 files changed, 38 insertions(+), 585 deletions(-) delete mode 100644 AGENT_SESSION_FIX.md delete mode 100644 BACKEND_STATUS_PERSISTENCE_SOLUTION.md delete mode 100644 TEST_SCENARIOS.md diff --git a/AGENT_SESSION_FIX.md b/AGENT_SESSION_FIX.md deleted file mode 100644 index 756c3e8fcc..0000000000 --- a/AGENT_SESSION_FIX.md +++ /dev/null @@ -1,101 +0,0 @@ -# Agent Session 消息持久化问题修复 - -## 问题描述 -在Agent会话中发送消息后,如果切换到其他会话再切回来,消息会丢失。错误信息: -``` -[MessageThunk] persistAgentExchange: missing user or assistant message entity -``` - -## 问题原因 -1. **原始实现问题**: - - `saveMessageAndBlocksToDB` 对Agent会话直接返回,不保存消息 - - 消息只存在于Redux state中 - -2. **V2实现问题**: - - `AgentMessageDataSource.appendMessage` 是空操作 - - 期望通过 `persistExchange` 在响应完成后保存 - -3. **时序问题**: - - `persistAgentExchange` 在Agent响应完成后才被调用 - - 如果用户在响应过程中切换会话,Redux state被清空 - - `persistAgentExchange` 找不到消息实体,保存失败 - -## 解决方案 -修改 `AgentMessageDataSource.appendMessage` 方法,让它立即保存消息到后端,而不是等待响应完成。 - -### 修改内容 -```typescript -// src/renderer/src/services/db/AgentMessageDataSource.ts - -async appendMessage(topicId: string, message: Message, blocks: MessageBlock[]): Promise { - // 立即保存消息,不等待persistExchange - const sessionId = extractSessionId(topicId) - - const payload: AgentPersistedMessage = { - message, - blocks - } - - // 通过IPC立即保存单个消息 - await window.electron.ipcRenderer.invoke(IpcChannel.AgentMessage_PersistExchange, { - sessionId, - agentSessionId: '', - ...(message.role === 'user' - ? { user: { payload } } - : { assistant: { payload } } - ) - }) -} -``` - -## 影响分析 - -### 优点 -1. 消息立即持久化,不会因切换会话而丢失 -2. 即使Agent响应失败,用户消息也已保存 -3. 提高了数据安全性 - -### 潜在问题 -1. **可能的重复保存**: - - `appendMessage` 保存一次 - - `persistAgentExchange` 可能再次保存 - - 需要后端处理重复消息(通过messageId去重) - -2. **性能考虑**: - - 每条消息都触发IPC调用 - - 可能增加延迟 - -## 测试验证 - -### 测试步骤 -1. 启用V2功能 -2. 创建Agent会话 -3. 发送消息 -4. 在Agent响应过程中立即切换到其他会话 -5. 切回Agent会话 -6. **期望结果**:消息应该正确显示,不会丢失 - -### 测试场景 -- ✅ 正常发送和接收 -- ✅ 响应中切换会话 -- ✅ 快速连续发送多条消息 -- ✅ 网络中断恢复 - -## 后续优化建议 - -1. **批量保存**: - - 考虑缓存多条消息后批量保存 - - 减少IPC调用次数 - -2. **去重机制**: - - 后端通过messageId去重 - - 避免重复存储 - -3. **错误处理**: - - 添加重试机制 - - 失败时的降级策略 - -## 回滚方案 -如果修复引起新问题: -1. 恢复 `AgentMessageDataSource.appendMessage` 为原始空操作 -2. 考虑其他解决方案(如在切换会话前强制调用persistExchange) \ No newline at end of file diff --git a/BACKEND_STATUS_PERSISTENCE_SOLUTION.md b/BACKEND_STATUS_PERSISTENCE_SOLUTION.md deleted file mode 100644 index 6c761c8593..0000000000 --- a/BACKEND_STATUS_PERSISTENCE_SOLUTION.md +++ /dev/null @@ -1,247 +0,0 @@ -# Agent Session 消息状态持久化方案 - -## 问题分析 - -### 当前流程 -1. **发送消息时**: - - 创建助手消息,状态为 `PENDING` - - 通过 `appendMessage` 立即保存到后端(包含pending状态) - -2. **切换会话后重新加载**: - - 从后端加载消息 - - 但状态可能丢失或被覆盖 - -### 根本问题 -后端可能没有正确保存或返回消息的 `status` 字段。 - -## 解决方案:确保状态正确持久化 - -### 方案A:修改 AgentMessageDataSource(前端方案) - -```typescript -// src/renderer/src/services/db/AgentMessageDataSource.ts - -// 1. 保存消息时确保状态被保存 -async appendMessage(topicId: string, message: Message, blocks: MessageBlock[]): Promise { - const sessionId = extractSessionId(topicId) - - const payload: AgentPersistedMessage = { - message: { - ...message, - // 明确保存状态 - status: message.status || AssistantMessageStatus.PENDING - }, - blocks - } - - await window.electron.ipcRenderer.invoke(IpcChannel.AgentMessage_PersistExchange, { - sessionId, - agentSessionId: '', - ...(message.role === 'user' - ? { user: { payload } } - : { assistant: { payload } } - ) - }) -} - -// 2. 加载消息时恢复流式状态 -async fetchMessages(topicId: string): Promise<{ messages: Message[], blocks: MessageBlock[] }> { - const sessionId = extractSessionId(topicId) - const historicalMessages = await window.electron.ipcRenderer.invoke( - IpcChannel.AgentMessage_GetHistory, - { sessionId } - ) - - const messages: Message[] = [] - const blocks: MessageBlock[] = [] - let hasStreamingMessage = false - - for (const persistedMsg of historicalMessages) { - if (persistedMsg?.message) { - const message = persistedMsg.message - - // 检查是否有未完成的消息 - if (message.status === 'pending' || message.status === 'processing') { - hasStreamingMessage = true - - // 如果消息创建时间超过5分钟,标记为错误 - const messageAge = Date.now() - new Date(message.createdAt).getTime() - if (messageAge > 5 * 60 * 1000) { - message.status = 'error' - } - } - - messages.push(message) - if (persistedMsg.blocks) { - blocks.push(...persistedMsg.blocks) - } - } - } - - // 如果有流式消息,恢复loading状态 - if (hasStreamingMessage) { - // 这里需要dispatch action,可能需要通过回调或其他方式 - store.dispatch(newMessagesActions.setTopicLoading({ topicId, loading: true })) - } - - return { messages, blocks } -} -``` - -### 方案B:后端修改(更彻底的方案) - -需要确保后端: - -1. **sessionMessageRepository.ts** 正确保存消息状态 -```typescript -// src/main/services/agents/database/sessionMessageRepository.ts - -async persistExchange(params: PersistExchangeParams): Promise { - // 保存时确保状态字段被正确存储 - if (params.user) { - await this.saveMessage({ - ...params.user.payload.message, - status: params.user.payload.message.status // 确保状态被保存 - }) - } - - if (params.assistant) { - await this.saveMessage({ - ...params.assistant.payload.message, - status: params.assistant.payload.message.status // 确保状态被保存 - }) - } -} - -async getHistory(sessionId: string): Promise { - // 返回时确保状态字段被包含 - const messages = await this.db.getMessages(sessionId) - return messages.map(msg => ({ - message: { - ...msg, - status: msg.status // 确保状态被返回 - }, - blocks: msg.blocks - })) -} -``` - -2. **添加会话级别的流式状态** -```typescript -interface AgentSession { - id: string - // ... 其他字段 - streamingMessageId?: string // 当前正在流式的消息ID - streamingStartTime?: number // 流式开始时间 -} - -// 开始流式时更新 -async startStreaming(sessionId: string, messageId: string) { - await this.updateSession(sessionId, { - streamingMessageId: messageId, - streamingStartTime: Date.now() - }) -} - -// 结束流式时清除 -async stopStreaming(sessionId: string) { - await this.updateSession(sessionId, { - streamingMessageId: null, - streamingStartTime: null - }) -} -``` - -### 方案C:混合方案(推荐) - -1. **前端立即保存状态**(已实现) -2. **后端确保状态持久化** -3. **加载时智能恢复状态** - -```typescript -// AgentMessageDataSource.ts -async fetchMessages(topicId: string): Promise<{ messages: Message[], blocks: MessageBlock[] }> { - const sessionId = extractSessionId(topicId) - const historicalMessages = await window.electron.ipcRenderer.invoke( - IpcChannel.AgentMessage_GetHistory, - { sessionId } - ) - - const messages: Message[] = [] - const blocks: MessageBlock[] = [] - - for (const persistedMsg of historicalMessages) { - if (persistedMsg?.message) { - const message = { ...persistedMsg.message } - - // 智能恢复状态 - if (message.status === 'pending' || message.status === 'processing') { - // 检查消息年龄 - const age = Date.now() - new Date(message.createdAt).getTime() - - if (age > 5 * 60 * 1000) { - // 超过5分钟,标记为错误 - message.status = 'error' - } else if (age > 30 * 1000 && message.blocks?.length > 0) { - // 超过30秒且有内容,可能已完成 - message.status = 'success' - } - // 否则保持原状态,让UI显示暂停按钮 - } - - messages.push(message) - if (persistedMsg.blocks) { - blocks.push(...persistedMsg.blocks) - } - } - } - - return { messages, blocks } -} -``` - -## 实施步骤 - -### 步骤1:验证后端是否保存状态 -1. 在 `appendMessage` 中添加日志,确认状态被发送 -2. 检查后端数据库,确认状态被保存 -3. 在 `fetchMessages` 中添加日志,确认状态被返回 - -### 步骤2:修复状态持久化 -1. 如果后端没有保存状态,修改后端代码 -2. 如果后端保存了但没返回,修改返回逻辑 - -### 步骤3:添加状态恢复逻辑 -1. 在 `fetchMessages` 中智能恢复状态 -2. 对于未完成的消息,根据时间判断是否需要标记为错误 - -### 步骤4:恢复loading状态 -1. 如果有pending/processing消息,设置loading为true -2. 让UI正确显示暂停按钮 - -## 测试验证 - -1. **正常流程** - - 发送消息 - - 观察pending状态 - - 响应完成后状态变为success - -2. **切换会话** - - 发送消息开始响应 - - 立即切换会话 - - 切回来,pending状态应该保持 - - 暂停按钮应该显示 - -3. **页面刷新** - - 响应过程中刷新 - - 重新加载后状态应该合理(pending或error) - -4. **超时处理** - - 模拟长时间pending - - 验证超时后自动标记为error - -## 优势 -- 符合现有架构,数据统一持久化 -- 状态与消息一起保存,数据一致性好 -- 页面刷新也能恢复 -- 不需要额外的状态管理器 \ No newline at end of file diff --git a/TEST_SCENARIOS.md b/TEST_SCENARIOS.md deleted file mode 100644 index 64d3553a44..0000000000 --- a/TEST_SCENARIOS.md +++ /dev/null @@ -1,212 +0,0 @@ -# V2 Database Service 手动测试用例 - -## 准备工作 -```javascript -// 1. 打开浏览器控制台,启用V2功能 -localStorage.setItem('featureFlags', JSON.stringify({ USE_UNIFIED_DB_SERVICE: true })) -location.reload() - -// 2. 确认功能已启用 -JSON.parse(localStorage.getItem('featureFlags') || '{}') -// 应该看到: { USE_UNIFIED_DB_SERVICE: true } -``` - -## 测试场景一:基础聊天功能 ✅ - -### 1.1 消息发送与保存 -**测试功能**: `saveMessageAndBlocksToDBV2`, `updateBlocksV2` -1. 创建新的聊天会话 -2. 发送消息:"你好,请介绍一下React Hooks的使用" -3. 等待助手回复完成 -4. 刷新页面 -5. **验证**: 消息应该被正确保存并重新加载 - -### 1.2 消息加载(已测试稳定) -**测试功能**: `loadTopicMessagesThunkV2` -1. 切换到其他会话 -2. 再切换回刚才的会话 -3. **验证**: 消息应该立即加载,无需等待 - -### 1.3 实时流式更新 -**测试功能**: `updateSingleBlockV2` (throttled updates) -1. 发送一个需要较长回复的问题:"请详细解释JavaScript的事件循环机制" -2. 观察助手回复时的流式更新 -3. **验证**: 文字应该平滑流式显示,没有卡顿或丢失 - -## 测试场景二:消息编辑与删除 🗑️ - -### 2.1 删除单条消息 -**测试功能**: `deleteMessageFromDBV2` -1. 在现有会话中,右键点击任意一条消息 -2. 选择"删除" -3. 刷新页面 -4. **验证**: 被删除的消息不应再出现 - -### 2.2 删除消息组(用户问题+助手回答) -**测试功能**: `deleteMessagesFromDBV2` -1. 找到一组问答(用户提问+助手回答) -2. 删除整组对话 -3. **验证**: 用户消息和对应的助手回答都被删除 - -### 2.3 清空会话 -**测试功能**: `clearMessagesFromDBV2` -1. 在一个有多条消息的会话中 -2. 使用"清空会话"功能 -3. 刷新页面 -4. **验证**: 会话应该为空,但会话本身还存在 - -## 测试场景三:文件和图片处理 📎 - -### 3.1 上传图片 -**测试功能**: `saveMessageAndBlocksToDBV2`, `updateFileCountV2` -1. 在输入框中上传一张图片 -2. 添加文字:"这张图片是什么内容?" -3. 发送消息 -4. 刷新页面 -5. **验证**: 图片应该正确显示,文件引用计数正确 - -### 3.2 上传文件 -**测试功能**: `bulkAddBlocksV2` -1. 上传一个文本文件或PDF -2. 发送消息询问文件内容 -3. **验证**: 文件应该被正确处理和显示 - -### 3.3 复制带图片的消息到新会话 -**测试功能**: `bulkAddBlocksV2`, `updateFileCountV2` -1. 选择包含图片的消息 -2. 复制到新的会话 -3. **验证**: 图片在新会话中正确显示,文件引用计数增加 - -## 测试场景四:Agent Session 功能 🤖 - -### 4.1 Agent会话消息加载 -**测试功能**: `loadTopicMessagesThunkV2` (agent-session分支) -1. 创建或打开一个Agent会话 -2. 发送消息给Agent -3. 切换到其他会话再切回 -4. **验证**: Agent会话消息正确加载 - -### 4.2 Agent会话消息持久化 🔥 (已修复) -**测试功能**: `saveMessageAndBlocksToDBV2` → `AgentMessageDataSource.appendMessage` -1. 在Agent会话中发送消息 -2. **立即切换到其他会话**(不等待响应完成) -3. 切回Agent会话 -4. **验证**: 用户消息应该已保存并显示 -5. 等待Agent响应完成 -6. 刷新页面 -7. **验证**: 完整对话正确保存 - -### 4.3 Agent会话清空(应该无操作) -**测试功能**: `clearMessagesFromDBV2` (agent no-op) -1. 尝试清空Agent会话 -2. **验证**: 操作应该被正确处理(可能显示不支持或静默处理) - -## 测试场景五:高级功能 🚀 - -### 5.1 消息重新生成 -**测试功能**: `updateMessageV2`, `updateBlocksV2` -1. 选择一条助手回复 -2. 点击"重新生成" -3. **验证**: 原消息被重置,新回复正常生成 - -### 5.2 消息分支 -**测试功能**: `saveMessageAndBlocksToDBV2` -1. 选择一条用户消息 -2. 创建分支并输入不同的问题 -3. **验证**: 分支正确创建,两个分支独立存在 - -### 5.3 翻译功能 -**测试功能**: `updateSingleBlockV2` -1. 选择一条消息 -2. 点击翻译按钮 -3. **验证**: 翻译块正确创建和更新 - -### 5.4 多模型响应 -**测试功能**: `saveMessageAndBlocksToDBV2`, `updateBlocksV2` -1. 启用多模型功能 -2. 发送一个问题 -3. **验证**: 多个模型的响应都正确保存 - -## 测试场景六:并发和性能 ⚡ - -### 6.1 快速切换会话 -**测试功能**: `loadTopicMessagesThunkV2` -1. 快速在多个会话间切换 -2. **验证**: 消息加载无错误,无内存泄漏 - -### 6.2 大量消息处理 -**测试功能**: 所有V2函数 -1. 在一个会话中累积50+条消息 -2. 执行各种操作(删除、编辑、刷新) -3. **验证**: 性能无明显下降 - -### 6.3 同时操作 -1. 在流式回复过程中切换会话 -2. 在文件上传过程中发送新消息 -3. **验证**: 操作不冲突,数据一致 - -## 测试场景七:错误处理 ⚠️ - -### 7.1 网络中断恢复 -1. 发送消息 -2. 在回复过程中断网 -3. 恢复网络 -4. **验证**: 消息状态正确,可以重试 - -### 7.2 异常数据处理 -1. 尝试删除不存在的消息(通过控制台) -2. **验证**: 错误被优雅处理,不崩溃 - -## 测试检查清单 - -### 功能验证 -- [x] 普通聊天消息发送/接收 -- [ ] Agent会话消息发送/接收 -- [x] 消息删除(单个/批量/清空) -- [x] 文件/图片上传和显示 -- [x] 消息编辑和更新 -- [x] 流式响应更新 -- [x] 消息重新生成 -- [x] 分支创建 -- [x] 翻译功能 - -### 数据一致性 -- [x] 刷新后数据保持一致 -- [x] 切换会话数据正确 -- [x] 文件引用计数正确 -- [ ] Agent会话数据隔离 - -### 性能表现 -- [x] 消息加载速度正常 -- [x] 流式更新流畅 -- [x] 大量数据处理正常 -- [x] 内存使用合理 - -### 错误处理 -- [x] 网络错误处理正确 -- [x] 异常操作不崩溃 -- [x] 错误信息清晰 - -## 回滚测试 - -完成所有测试后,验证回滚功能: -```javascript -// 禁用V2功能 -localStorage.setItem('featureFlags', JSON.stringify({ USE_UNIFIED_DB_SERVICE: false })) -location.reload() - -// 验证切换回原实现后一切正常 -``` - -## 问题记录 - -如果发现问题,请记录: -1. 测试场景编号 -2. 具体操作步骤 -3. 预期结果 -4. 实际结果 -5. 浏览器控制台错误信息(如有) - ---- - -**提示**: 建议按顺序执行测试,每个大场景可以单独测试。重点关注数据一致性和错误处理。 diff --git a/src/renderer/src/services/db/AgentMessageDataSource.ts b/src/renderer/src/services/db/AgentMessageDataSource.ts index 0398a9886a..94fa248e37 100644 --- a/src/renderer/src/services/db/AgentMessageDataSource.ts +++ b/src/renderer/src/services/db/AgentMessageDataSource.ts @@ -466,12 +466,12 @@ export class AgentMessageDataSource implements MessageDataSource { logger.warn(`bulkAddBlocks called for agent session, operation not supported individually`) } - async updateFileCount(fileId: string, _delta: number): Promise { + async updateFileCount(fileId: string, _delta: number, _deleteIfZero?: boolean): Promise { // Agent sessions don't manage file reference counts locally logger.warn(`updateFileCount called for agent session file ${fileId}, operation not supported`) } - async updateFileCounts(_files: Array<{ id: string; delta: number }>): Promise { + async updateFileCounts(_files: Array<{ id: string; delta: number; deleteIfZero?: boolean }>): Promise { // Agent sessions don't manage file reference counts locally logger.warn(`updateFileCounts called for agent session, operation not supported`) } diff --git a/src/renderer/src/services/db/DbService.ts b/src/renderer/src/services/db/DbService.ts index 881791e752..4dab69adc6 100644 --- a/src/renderer/src/services/db/DbService.ts +++ b/src/renderer/src/services/db/DbService.ts @@ -150,16 +150,16 @@ class DbService implements MessageDataSource { return this.dexieSource.updateBlocks(blocks) } - async updateFileCount(fileId: string, delta: number): Promise { + async updateFileCount(fileId: string, delta: number, deleteIfZero: boolean = false): Promise { // File operations only apply to Dexie source if (this.dexieSource.updateFileCount) { - return this.dexieSource.updateFileCount(fileId, delta) + return this.dexieSource.updateFileCount(fileId, delta, deleteIfZero) } // No-op if not supported logger.warn(`updateFileCount not supported for file ${fileId}`) } - async updateFileCounts(files: Array<{ id: string; delta: number }>): Promise { + async updateFileCounts(files: Array<{ id: string; delta: number; deleteIfZero?: boolean }>): Promise { // File operations only apply to Dexie source if (this.dexieSource.updateFileCounts) { return this.dexieSource.updateFileCounts(files) diff --git a/src/renderer/src/services/db/DexieMessageDataSource.ts b/src/renderer/src/services/db/DexieMessageDataSource.ts index 7b3efd1edc..a8d3679840 100644 --- a/src/renderer/src/services/db/DexieMessageDataSource.ts +++ b/src/renderer/src/services/db/DexieMessageDataSource.ts @@ -364,29 +364,40 @@ export class DexieMessageDataSource implements MessageDataSource { // ============ File Operations ============ - async updateFileCount(fileId: string, delta: number): Promise { + async updateFileCount(fileId: string, delta: number, deleteIfZero: boolean = false): Promise { try { - await db.files - .where('id') - .equals(fileId) - .modify((f) => { - if (f) { - f.count = (f.count || 0) + delta - } - }) + await db.transaction('rw', db.files, async () => { + const file = await db.files.get(fileId) + + if (!file) { + logger.warn(`File ${fileId} not found for count update`) + return + } + + const newCount = (file.count || 0) + delta + + if (newCount <= 0 && deleteIfZero) { + // Delete the file when count reaches 0 or below + await FileManager.deleteFile(fileId, false) + await db.files.delete(fileId) + logger.info(`Deleted file ${fileId} as reference count reached ${newCount}`) + } else { + // Update the count + await db.files.update(fileId, { count: Math.max(0, newCount) }) + logger.debug(`Updated file ${fileId} count to ${Math.max(0, newCount)}`) + } + }) } catch (error) { logger.error(`Failed to update file count for ${fileId}:`, error as Error) throw error } } - async updateFileCounts(files: Array<{ id: string; delta: number }>): Promise { + async updateFileCounts(files: Array<{ id: string; delta: number; deleteIfZero?: boolean }>): Promise { try { - await db.transaction('rw', db.files, async () => { - for (const file of files) { - await this.updateFileCount(file.id, file.delta) - } - }) + for (const file of files) { + await this.updateFileCount(file.id, file.delta, file.deleteIfZero || false) + } } catch (error) { logger.error('Failed to update file counts:', error as Error) throw error diff --git a/src/renderer/src/services/db/types.ts b/src/renderer/src/services/db/types.ts index 0376725ae5..3852bcde21 100644 --- a/src/renderer/src/services/db/types.ts +++ b/src/renderer/src/services/db/types.ts @@ -109,13 +109,16 @@ export interface MessageDataSource { /** * Update file reference count + * @param fileId - The file ID to update + * @param delta - The change in reference count (positive or negative) + * @param deleteIfZero - Whether to delete the file when count reaches 0 */ - updateFileCount?(fileId: string, delta: number): Promise + updateFileCount?(fileId: string, delta: number, deleteIfZero?: boolean): Promise /** * Update multiple file reference counts */ - updateFileCounts?(files: Array<{ id: string; delta: number }>): Promise + updateFileCounts?(files: Array<{ id: string; delta: number; deleteIfZero?: boolean }>): Promise } /** diff --git a/src/renderer/src/store/thunk/messageThunk.v2.ts b/src/renderer/src/store/thunk/messageThunk.v2.ts index b13eb0a894..e017c64e9e 100644 --- a/src/renderer/src/store/thunk/messageThunk.v2.ts +++ b/src/renderer/src/store/thunk/messageThunk.v2.ts @@ -87,9 +87,8 @@ export const updateFileCountV2 = async ( deleteIfZero: boolean = false ): Promise => { try { - // DbService.updateFileCount only accepts fileId and delta - // deleteIfZero parameter is not currently supported in DbService - await dbService.updateFileCount(fileId, delta) + // Pass all parameters to dbService, including deleteIfZero + await dbService.updateFileCount(fileId, delta, deleteIfZero) logger.info('Updated file count', { fileId, delta, deleteIfZero }) } catch (error) { logger.error('Failed to update file count:', { fileId, delta, error })