diff --git a/.env.example b/.env.example index 11a73b7d4..0d57ffc03 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,3 @@ CSLOGGER_MAIN_LEVEL=info CSLOGGER_RENDERER_LEVEL=info #CSLOGGER_MAIN_SHOW_MODULES= #CSLOGGER_RENDERER_SHOW_MODULES= - -# Feature Flags (must be prefixed with VITE_ to be accessible in renderer) -# VITE_USE_UNIFIED_DB_SERVICE=true # Enable unified DB service for chat/agent sessions diff --git a/src/renderer/src/config/featureFlags.ts b/src/renderer/src/config/featureFlags.ts deleted file mode 100644 index ed74b3223..000000000 --- a/src/renderer/src/config/featureFlags.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { loggerService } from '@logger' - -const logger = loggerService.withContext('Feature Flag') -/** - * Feature flags for controlling gradual rollout of new features - * These flags can be toggled to enable/disable features without code changes - */ - -interface FeatureFlags { - /** - * Enable unified database service for both regular chats and agent sessions - * When enabled, uses the new DbService facade pattern - * When disabled, uses the original implementation with conditional checks - */ - USE_UNIFIED_DB_SERVICE: boolean -} - -/** - * Default feature flag values - * Set to false initially for safe rollout - */ -export const featureFlags: FeatureFlags = { - USE_UNIFIED_DB_SERVICE: false -} - -/** - * Override feature flags from environment or local storage - * Priority order (highest to lowest): - * 1. localStorage (runtime overrides) - * 2. Environment variables (build-time config) - * 3. Default values - */ -export function initializeFeatureFlags(): void { - // First, check environment variables (build-time configuration) - // In Vite, env vars must be prefixed with VITE_ to be exposed to the client - // Usage: VITE_USE_UNIFIED_DB_SERVICE=true yarn dev - if (import.meta.env?.VITE_USE_UNIFIED_DB_SERVICE === 'true') { - featureFlags.USE_UNIFIED_DB_SERVICE = true - } - - // Then check localStorage for runtime overrides (higher priority) - // This allows toggling features without rebuilding - try { - const localOverrides = localStorage.getItem('featureFlags') - if (localOverrides) { - const overrides = JSON.parse(localOverrides) - Object.keys(overrides).forEach((key) => { - if (key in featureFlags) { - featureFlags[key as keyof FeatureFlags] = overrides[key] - } - }) - } - } catch (e) { - logger.warn('Failed to parse feature flags from localStorage:', e as Error) - } -} - -/** - * Update a feature flag value at runtime - * Useful for A/B testing or gradual rollout - */ -export function setFeatureFlag(flag: keyof FeatureFlags, value: boolean): void { - featureFlags[flag] = value - - // Persist to localStorage for consistency across app restarts - const currentFlags = localStorage.getItem('featureFlags') - const flags = currentFlags ? JSON.parse(currentFlags) : {} - flags[flag] = value - localStorage.setItem('featureFlags', JSON.stringify(flags)) -} - -/** - * Get current value of a feature flag - */ -export function getFeatureFlag(flag: keyof FeatureFlags): boolean { - return featureFlags[flag] -} - -// Initialize on import -initializeFeatureFlags() diff --git a/src/renderer/src/pages/home/Messages/Tools/MessageAgentTools/MultiEditTool.tsx b/src/renderer/src/pages/home/Messages/Tools/MessageAgentTools/MultiEditTool.tsx index 75f7e5180..033fe8467 100644 --- a/src/renderer/src/pages/home/Messages/Tools/MessageAgentTools/MultiEditTool.tsx +++ b/src/renderer/src/pages/home/Messages/Tools/MessageAgentTools/MultiEditTool.tsx @@ -6,7 +6,7 @@ import { ToolTitle } from './GenericTools' import type { MultiEditToolInput, MultiEditToolOutput } from './types' import { AgentToolsType } from './types' -export function MultiEditTool({ input, output }: { input: MultiEditToolInput; output?: MultiEditToolOutput }) { +export function MultiEditTool({ input }: { input: MultiEditToolInput; output?: MultiEditToolOutput }) { return ( { - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - return saveMessageAndBlocksToDBV2(message.topicId, message, blocks, messageIndex) - } - - // Original implementation - try { - if (isAgentSessionTopicId(message.topicId)) { - return - } - if (blocks.length > 0) { - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - await updateBlocksV2(blocks) - } else { - await db.message_blocks.bulkPut(blocks) - } - } - const topic = await db.topics.get(message.topicId) - if (topic) { - const _messageIndex = topic.messages.findIndex((m) => m.id === message.id) - const updatedMessages = [...topic.messages] - - if (_messageIndex !== -1) { - updatedMessages[_messageIndex] = message - } else { - if (messageIndex !== -1) { - updatedMessages.splice(messageIndex, 0, message) - } else { - updatedMessages.push(message) - } - } - await db.topics.update(message.topicId, { messages: updatedMessages }) - store.dispatch(updateTopicUpdatedAt({ topicId: message.topicId })) - } else { - logger.error(`[saveMessageAndBlocksToDB] Topic ${message.topicId} not found.`) - } - } catch (error) { - logger.error(`[saveMessageAndBlocksToDB] Failed to save message ${message.id}:`, error as Error) - } + return saveMessageAndBlocksToDBV2(message.topicId, message, blocks, messageIndex) } const updateExistingMessageAndBlocksInDB = async ( @@ -399,43 +359,19 @@ const updateExistingMessageAndBlocksInDB = async ( try { // Always update blocks if provided if (updatedBlocks.length > 0) { - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - await updateBlocksV2(updatedBlocks) - } else { - await db.transaction('rw', db.topics, db.message_blocks, async () => { - await db.message_blocks.bulkPut(updatedBlocks) - }) - } + await updateBlocksV2(updatedBlocks) } // Check if there are message properties to update beyond id and topicId const messageKeysToUpdate = Object.keys(updatedMessage).filter((key) => key !== 'id' && key !== 'topicId') if (messageKeysToUpdate.length > 0) { - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - const messageUpdatesPayload = messageKeysToUpdate.reduce>((acc, key) => { - acc[key] = updatedMessage[key] - return acc - }, {}) + const messageUpdatesPayload = messageKeysToUpdate.reduce>((acc, key) => { + acc[key] = updatedMessage[key] + return acc + }, {}) - await updateMessageV2(updatedMessage.topicId, updatedMessage.id, messageUpdatesPayload) - } else { - // 使用 where().modify() 进行原子更新 - await db.topics - .where('id') - .equals(updatedMessage.topicId) - .modify((topic) => { - if (!topic) return - - const messageIndex = topic.messages.findIndex((m) => m.id === updatedMessage.id) - if (messageIndex !== -1) { - // 直接在原对象上更新需要修改的属性 - messageKeysToUpdate.forEach((key) => { - topic.messages[messageIndex][key] = updatedMessage[key] - }) - } - }) - } + await updateMessageV2(updatedMessage.topicId, updatedMessage.id, messageUpdatesPayload) store.dispatch(updateTopicUpdatedAt({ topicId: updatedMessage.topicId })) } @@ -481,12 +417,7 @@ const getBlockThrottler = (id: string) => { }) blockUpdateRafs.set(id, rafId) - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - await updateSingleBlockV2(id, blockUpdate) - } else { - await db.message_blocks.update(id, blockUpdate) - } + await updateSingleBlockV2(id, blockUpdate) }, 150) blockUpdateThrottlers.set(id, throttler) @@ -1023,57 +954,7 @@ export const loadAgentSessionMessagesThunk = export const loadTopicMessagesThunk = (topicId: string, forceReload: boolean = false) => async (dispatch: AppDispatch, getState: () => RootState) => { - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - return loadTopicMessagesThunkV2(topicId, forceReload)(dispatch, getState) - } - - // Original implementation - const state = getState() - const topicMessagesExist = !!state.messages.messageIdsByTopic[topicId] - dispatch(newMessagesActions.setCurrentTopicId(topicId)) - - // Check if it's an agent session topic - if (isAgentSessionTopicId(topicId)) { - if (topicMessagesExist && !forceReload) { - return // Keep existing messages in memory - } - // Load from agent backend instead of local DB - const sessionId = topicId.replace('agent-session:', '') - return dispatch(loadAgentSessionMessagesThunk(sessionId)) - } - - if (topicMessagesExist && !forceReload) { - return - } - - try { - const topic = await db.topics.get(topicId) - if (!topic) { - await db.topics.add({ id: topicId, messages: [] }) - } - - const messagesFromDB = topic?.messages || [] - - if (messagesFromDB.length > 0) { - const messageIds = messagesFromDB.map((m) => m.id) - const blocks = await db.message_blocks.where('messageId').anyOf(messageIds).toArray() - - if (blocks && blocks.length > 0) { - dispatch(upsertManyBlocks(blocks)) - } - const messagesWithBlockIds = messagesFromDB.map((m) => ({ - ...m, - blocks: m.blocks?.map(String) || [] - })) - dispatch(newMessagesActions.messagesReceived({ topicId, messages: messagesWithBlockIds })) - } else { - dispatch(newMessagesActions.messagesReceived({ topicId, messages: [] })) - } - } catch (error: any) { - logger.error(`[loadTopicMessagesThunk] Failed to load messages for topic ${topicId}:`, error) - // dispatch(newMessagesActions.setTopicLoading({ topicId, loading: false })) - } + return loadTopicMessagesThunkV2(topicId, forceReload)(dispatch, getState) } /** @@ -1093,20 +974,7 @@ export const deleteSingleMessageThunk = try { dispatch(newMessagesActions.removeMessage({ topicId, messageId })) cleanupMultipleBlocks(dispatch, blockIdsToDelete) - - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - await deleteMessageFromDBV2(topicId, messageId) - } else { - // Original implementation - await db.message_blocks.bulkDelete(blockIdsToDelete) - const topic = await db.topics.get(topicId) - if (topic) { - const finalMessagesToSave = selectMessagesForTopic(getState(), topicId) - await db.topics.update(topicId, { messages: finalMessagesToSave }) - dispatch(updateTopicUpdatedAt({ topicId })) - } - } + await deleteMessageFromDBV2(topicId, messageId) } catch (error) { logger.error(`[deleteSingleMessage] Failed to delete message ${messageId}:`, error as Error) } @@ -1145,20 +1013,7 @@ export const deleteMessageGroupThunk = try { dispatch(newMessagesActions.removeMessagesByAskId({ topicId, askId })) cleanupMultipleBlocks(dispatch, blockIdsToDelete) - - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - await deleteMessagesFromDBV2(topicId, messageIdsToDelete) - } else { - // Original implementation - await db.message_blocks.bulkDelete(blockIdsToDelete) - const topic = await db.topics.get(topicId) - if (topic) { - const finalMessagesToSave = selectMessagesForTopic(getState(), topicId) - await db.topics.update(topicId, { messages: finalMessagesToSave }) - dispatch(updateTopicUpdatedAt({ topicId })) - } - } + await deleteMessagesFromDBV2(topicId, messageIdsToDelete) } catch (error) { logger.error(`[deleteMessageGroup] Failed to delete messages with askId ${askId}:`, error as Error) } @@ -1183,18 +1038,7 @@ export const clearTopicMessagesThunk = dispatch(newMessagesActions.clearTopicMessages(topicId)) cleanupMultipleBlocks(dispatch, blockIdsToDelete) - - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - await clearMessagesFromDBV2(topicId) - } else { - // Original implementation - await db.topics.update(topicId, { messages: [] }) - dispatch(updateTopicUpdatedAt({ topicId })) - if (blockIdsToDelete.length > 0) { - await db.message_blocks.bulkDelete(blockIdsToDelete) - } - } + await clearMessagesFromDBV2(topicId) } catch (error) { logger.error(`[clearTopicMessagesThunk] Failed to clear messages for topic ${topicId}:`, error as Error) } @@ -1515,13 +1359,7 @@ export const updateTranslationBlockThunk = // 更新Redux状态 dispatch(updateOneBlock({ id: blockId, changes })) - // 更新数据库 - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - await updateSingleBlockV2(blockId, changes) - } else { - await db.message_blocks.update(blockId, changes) - } + await updateSingleBlockV2(blockId, changes) // Logger.log(`[updateTranslationBlockThunk] Successfully updated translation block ${blockId}.`) } catch (error) { logger.error(`[updateTranslationBlockThunk] Failed to update translation block ${blockId}:`, error as Error) @@ -1734,33 +1572,12 @@ export const cloneMessagesToNewTopicThunk = // Add the NEW blocks if (clonedBlocks.length > 0) { - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - await bulkAddBlocksV2(clonedBlocks) - } else { - await db.message_blocks.bulkAdd(clonedBlocks) - } + await bulkAddBlocksV2(clonedBlocks) } // Update file counts const uniqueFiles = [...new Map(filesToUpdateCount.map((f) => [f.id, f])).values()] - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - // Use V2 implementation for file count updates - for (const file of uniqueFiles) { - await updateFileCountV2(file.id, 1, false) - } - } else { - // Original implementation - for (const file of uniqueFiles) { - await db.files - .where('id') - .equals(file.id) - .modify((f) => { - if (f) { - // Ensure file exists before modifying - f.count = (f.count || 0) + 1 - } - }) - } + for (const file of uniqueFiles) { + await updateFileCountV2(file.id, 1, false) } }) @@ -1812,47 +1629,13 @@ export const updateMessageAndBlocksThunk = if (blockUpdatesList.length > 0) { dispatch(upsertManyBlocks(blockUpdatesList)) } - - // 2. 更新数据库 (在事务中) - // Use V2 implementation if feature flag is enabled - if (featureFlags.USE_UNIFIED_DB_SERVICE) { - // Update message properties if provided - if (messageUpdates && Object.keys(messageUpdates).length > 0 && messageId) { - await updateMessageV2(topicId, messageId, messageUpdates) - } - // Update blocks if provided - if (blockUpdatesList.length > 0) { - await updateBlocksV2(blockUpdatesList) - } - } else { - // Original implementation with transaction - await db.transaction('rw', db.topics, db.message_blocks, async () => { - // Only update topic.messages if there were actual message changes - if (messageUpdates && Object.keys(messageUpdates).length > 0) { - const topic = await db.topics.get(topicId) - if (topic && topic.messages) { - const messageIndex = topic.messages.findIndex((m) => m.id === messageId) - if (messageIndex !== -1) { - Object.assign(topic.messages[messageIndex], messageUpdates) - await db.topics.update(topicId, { messages: topic.messages }) - } else { - logger.error( - `[updateMessageAndBlocksThunk] Message ${messageId} not found in DB topic ${topicId} for property update.` - ) - throw new Error(`Message ${messageId} not found in DB topic ${topicId} for property update.`) - } - } else { - logger.error( - `[updateMessageAndBlocksThunk] Topic ${topicId} not found or empty for message property update.` - ) - throw new Error(`Topic ${topicId} not found or empty for message property update.`) - } - } - - if (blockUpdatesList.length > 0) { - await db.message_blocks.bulkPut(blockUpdatesList) - } - }) + // Update message properties if provided + if (messageUpdates && Object.keys(messageUpdates).length > 0 && messageId) { + await updateMessageV2(topicId, messageId, messageUpdates) + } + // Update blocks if provided + if (blockUpdatesList.length > 0) { + await updateBlocksV2(blockUpdatesList) } dispatch(updateTopicUpdatedAt({ topicId }))