From 77e024027c53d3e0d73f1a684c9f97a55474ea54 Mon Sep 17 00:00:00 2001 From: Northword <44738481+northword@users.noreply.github.com> Date: Thu, 1 Jan 2026 19:13:24 +0800 Subject: [PATCH 1/4] fix(miniapps): switch to new google ai studio logo (#12229) --- .../src/assets/images/apps/aistudio.png | Bin 0 -> 7398 bytes .../src/assets/images/apps/aistudio.svg | 27 ------------------ src/renderer/src/config/minapps.ts | 2 +- 3 files changed, 1 insertion(+), 28 deletions(-) create mode 100644 src/renderer/src/assets/images/apps/aistudio.png delete mode 100644 src/renderer/src/assets/images/apps/aistudio.svg diff --git a/src/renderer/src/assets/images/apps/aistudio.png b/src/renderer/src/assets/images/apps/aistudio.png new file mode 100644 index 0000000000000000000000000000000000000000..c7cb2adebe3413fa191a3714b565fcc38409bf73 GIT binary patch literal 7398 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 YFA`m%bPJdSK>z>%07*qoM6N<$f=w*g1^@s6 literal 0 HcmV?d00001 diff --git a/src/renderer/src/assets/images/apps/aistudio.svg b/src/renderer/src/assets/images/apps/aistudio.svg deleted file mode 100644 index 2c08015593..0000000000 --- a/src/renderer/src/assets/images/apps/aistudio.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/renderer/src/config/minapps.ts b/src/renderer/src/config/minapps.ts index 81a4a98723..eeefb218d2 100644 --- a/src/renderer/src/config/minapps.ts +++ b/src/renderer/src/config/minapps.ts @@ -1,7 +1,7 @@ import { loggerService } from '@logger' import ThreeMinTopAppLogo from '@renderer/assets/images/apps/3mintop.png?url' import AbacusLogo from '@renderer/assets/images/apps/abacus.webp?url' -import AIStudioLogo from '@renderer/assets/images/apps/aistudio.svg?url' +import AIStudioLogo from '@renderer/assets/images/apps/aistudio.png?url' import ApplicationLogo from '@renderer/assets/images/apps/application.png?url' import BaiduAiAppLogo from '@renderer/assets/images/apps/baidu-ai.png?url' import BaiduAiSearchLogo from '@renderer/assets/images/apps/baidu-ai-search.webp?url' From 48a582820f179a9bc650adeeff59fd6a64d144a8 Mon Sep 17 00:00:00 2001 From: SuYao Date: Fri, 2 Jan 2026 16:26:28 +0800 Subject: [PATCH 2/4] feat: update-t2i-image (#12236) * chore: comment * chore: comment 2 * fix: comment * chore: var name --- src/renderer/src/config/models/vision.ts | 57 ++++++++++++++++++------ 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/renderer/src/config/models/vision.ts b/src/renderer/src/config/models/vision.ts index fe4bc9912c..d93c677638 100644 --- a/src/renderer/src/config/models/vision.ts +++ b/src/renderer/src/config/models/vision.ts @@ -75,12 +75,37 @@ const VISION_REGEX = new RegExp( 'i' ) -// For middleware to identify models that must use the dedicated Image API +// All dedicated image generation models (only generate images, no text chat capability) +// These models need: +// 1. Route to dedicated image generation API +// 2. Exclude from reasoning/websearch/tooluse selection const DEDICATED_IMAGE_MODELS = [ - 'grok-2-image(?:-[\\w-]+)?', + // OpenAI series 'dall-e(?:-[\\w-]+)?', - 'gpt-image-1(?:-[\\w-]+)?', - 'imagen(?:-[\\w-]+)?' + 'gpt-image(?:-[\\w-]+)?', + // xAI + 'grok-2-image(?:-[\\w-]+)?', + // Google + 'imagen(?:-[\\w-]+)?', + // Stable Diffusion series + 'flux(?:-[\\w-]+)?', + 'stable-?diffusion(?:-[\\w-]+)?', + 'stabilityai(?:-[\\w-]+)?', + 'sd-[\\w-]+', + 'sdxl(?:-[\\w-]+)?', + // zhipu + 'cogview(?:-[\\w-]+)?', + // Alibaba + 'qwen-image(?:-[\\w-]+)?', + // Others + 'janus(?:-[\\w-]+)?', + 'midjourney(?:-[\\w-]+)?', + 'mj-[\\w-]+', + 'z-image(?:-[\\w-]+)?', + 'longcat-image(?:-[\\w-]+)?', + 'hunyuanimage(?:-[\\w-]+)?', + 'seedream(?:-[\\w-]+)?', + 'kandinsky(?:-[\\w-]+)?' ] const IMAGE_ENHANCEMENT_MODELS = [ @@ -133,13 +158,23 @@ const GENERATE_IMAGE_MODELS_REGEX = new RegExp(GENERATE_IMAGE_MODELS.join('|'), const MODERN_GENERATE_IMAGE_MODELS_REGEX = new RegExp(MODERN_IMAGE_MODELS.join('|'), 'i') -export const isDedicatedImageGenerationModel = (model: Model): boolean => { +/** + * Check if the model is a dedicated image generation model + * Dedicated image generation models can only generate images, no text chat capability + * + * These models need: + * 1. Route to dedicated image generation API + * 2. Exclude from reasoning/websearch/tooluse selection + */ +export function isDedicatedImageModel(model: Model): boolean { if (!model) return false - const modelId = getLowerBaseModelName(model.id) return DEDICATED_IMAGE_MODELS_REGEX.test(modelId) } +// Backward compatible aliases +export const isDedicatedImageGenerationModel = isDedicatedImageModel + export const isAutoEnableImageGenerationModel = (model: Model): boolean => { if (!model) return false @@ -195,14 +230,8 @@ export function isPureGenerateImageModel(model: Model): boolean { return !OPENAI_TOOL_USE_IMAGE_GENERATION_MODELS.some((m) => modelId.includes(m)) } -// TODO: refine the regex -// Text to image models -const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview|janus|midjourney|mj-|imagen|gpt-image/i - -export function isTextToImageModel(model: Model): boolean { - const modelId = getLowerBaseModelName(model.id) - return TEXT_TO_IMAGE_REGEX.test(modelId) -} +// Backward compatible alias - now uses unified dedicated image model detection +export const isTextToImageModel = isDedicatedImageModel /** * 判断模型是否支持图片增强(包括编辑、增强、修复等) From 078cf39313a531748a84891322df56438c0872b1 Mon Sep 17 00:00:00 2001 From: Hizome <18071447006@163.com> Date: Sat, 3 Jan 2026 16:14:12 +0800 Subject: [PATCH 3/4] fix: implement navigation in agent mode (#12238) fix: add navigation in agentm mode Co-authored-by: harry --- src/renderer/src/pages/home/Chat.tsx | 1 + .../home/Messages/AgentSessionMessages.tsx | 36 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index 01bd12377c..fb24d55d65 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -233,6 +233,7 @@ const Chat: FC = (props) => { ) : ( )} + {messageNavigation === 'buttons' && } )} diff --git a/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx b/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx index 611216919a..d32e9bdf8b 100644 --- a/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx +++ b/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx @@ -2,13 +2,17 @@ import { loggerService } from '@logger' import ContextMenu from '@renderer/components/ContextMenu' import { useSession } from '@renderer/hooks/agents/useSession' import { useTopicMessages } from '@renderer/hooks/useMessageOperations' +import useScrollPosition from '@renderer/hooks/useScrollPosition' +import { useSettings } from '@renderer/hooks/useSettings' +import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { getGroupedMessages } from '@renderer/services/MessagesService' import { type Topic, TopicType } from '@renderer/types' import { buildAgentSessionTopicId } from '@renderer/utils/agentSession' import { Spin } from 'antd' -import { memo, useMemo } from 'react' +import { memo, useCallback, useEffect, useMemo, useRef } from 'react' import styled from 'styled-components' +import MessageAnchorLine from './MessageAnchorLine' import MessageGroup from './MessageGroup' import NarrowLayout from './NarrowLayout' import PermissionModeDisplay from './PermissionModeDisplay' @@ -26,6 +30,10 @@ const AgentSessionMessages: React.FC = ({ agentId, sessionId }) => { const sessionTopicId = useMemo(() => buildAgentSessionTopicId(sessionId), [sessionId]) // Use the same hook as Messages.tsx for consistent behavior const messages = useTopicMessages(sessionTopicId) + const { messageNavigation } = useSettings() + const scrollContainerRef = useRef(null) + + const { handleScroll: handleScrollPosition } = useScrollPosition(`agent-session-${sessionId}`) const displayMessages = useMemo(() => { if (!messages || messages.length === 0) return [] @@ -60,8 +68,31 @@ const AgentSessionMessages: React.FC = ({ agentId, sessionId }) => { messageCount: messages.length }) + // Scroll to bottom function + const scrollToBottom = useCallback(() => { + if (scrollContainerRef.current) { + requestAnimationFrame(() => { + if (scrollContainerRef.current) { + scrollContainerRef.current.scrollTo({ top: 0 }) + } + }) + } + }, [scrollContainerRef]) + + // Listen for send message events to auto-scroll to bottom + useEffect(() => { + const unsubscribes = [ + EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, scrollToBottom) + ] + return () => unsubscribes.forEach((unsub) => unsub()) + }, [scrollToBottom]) + return ( - + @@ -79,6 +110,7 @@ const AgentSessionMessages: React.FC = ({ agentId, sessionId }) => { + {messageNavigation === 'anchor' && } ) } From ca2b0ac28db26b4859d9138d1346e08920bb1a89 Mon Sep 17 00:00:00 2001 From: fullex <0xfullex@gmail.com> Date: Sat, 3 Jan 2026 16:36:53 +0800 Subject: [PATCH 4/4] refactor: merge messageThunk.v2.ts into messageThunk.ts Remove the confusing V2 naming from message thunk functions to avoid conflicts with the upcoming real V2 data refactoring. Changes: - Inline V2 function implementations directly into messageThunk.ts - Replace V2 function calls with direct dbService calls - Remove messageThunk.v2.ts file - Remove misleading "V2 DATA&UI REFACTORING" header comments The V2 suffix was originally added for agent session support, not for a data layer refactoring. This cleanup clears the naming space for the actual V2 refactoring work. --- .../home/Messages/AgentSessionMessages.tsx | 4 +- .../src/pages/home/Messages/Messages.tsx | 2 +- src/renderer/src/store/thunk/messageThunk.ts | 265 +++++++++++++++--- .../src/store/thunk/messageThunk.v2.ts | 249 ---------------- 4 files changed, 232 insertions(+), 288 deletions(-) delete mode 100644 src/renderer/src/store/thunk/messageThunk.v2.ts diff --git a/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx b/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx index d32e9bdf8b..7f7900b8c5 100644 --- a/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx +++ b/src/renderer/src/pages/home/Messages/AgentSessionMessages.tsx @@ -81,9 +81,7 @@ const AgentSessionMessages: React.FC = ({ agentId, sessionId }) => { // Listen for send message events to auto-scroll to bottom useEffect(() => { - const unsubscribes = [ - EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, scrollToBottom) - ] + const unsubscribes = [EventEmitter.on(EVENT_NAMES.SEND_MESSAGE, scrollToBottom)] return () => unsubscribes.forEach((unsub) => unsub()) }, [scrollToBottom]) diff --git a/src/renderer/src/pages/home/Messages/Messages.tsx b/src/renderer/src/pages/home/Messages/Messages.tsx index 7e0e03a774..05ee5b8fbb 100644 --- a/src/renderer/src/pages/home/Messages/Messages.tsx +++ b/src/renderer/src/pages/home/Messages/Messages.tsx @@ -162,7 +162,7 @@ const Messages: React.FC = ({ assistant, topic, setActiveTopic, o const { message: clearMessage } = getUserMessage({ assistant, topic, type: 'clear' }) dispatch(newMessagesActions.addMessage({ topicId: topic.id, message: clearMessage })) - await saveMessageAndBlocksToDB(clearMessage, []) + await saveMessageAndBlocksToDB(topic.id, clearMessage, []) scrollToBottom() } finally { diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts index 45d7fd760a..aaa2ffc2c4 100644 --- a/src/renderer/src/store/thunk/messageThunk.ts +++ b/src/renderer/src/store/thunk/messageThunk.ts @@ -19,6 +19,7 @@ import { AiSdkToChunkAdapter } from '@renderer/aiCore/chunk/AiSdkToChunkAdapter' import { AgentApiClient } from '@renderer/api/agent' import db from '@renderer/databases' import { fetchMessagesSummary, transformMessagesAndFetch } from '@renderer/services/ApiService' +import { dbService } from '@renderer/services/db' import { DbService } from '@renderer/services/db/DbService' import FileManager from '@renderer/services/FileManager' import { BlockManager } from '@renderer/services/messageStreaming/BlockManager' @@ -57,18 +58,18 @@ import { mutate } from 'swr' import type { AppDispatch, RootState } from '../index' import { removeManyBlocks, updateOneBlock, upsertManyBlocks, upsertOneBlock } from '../messageBlock' import { newMessagesActions, selectMessagesForTopic } from '../newMessage' -import { - bulkAddBlocksV2, - clearMessagesFromDBV2, - deleteMessageFromDBV2, - deleteMessagesFromDBV2, - loadTopicMessagesThunkV2, - saveMessageAndBlocksToDBV2, - updateBlocksV2, - updateFileCountV2, - updateMessageV2, - updateSingleBlockV2 -} from './messageThunk.v2' +// import { +// bulkAddBlocksV2, +// clearMessagesFromDBV2, +// deleteMessageFromDBV2, +// deleteMessagesFromDBV2, +// loadTopicMessagesThunkV2, +// saveMessageAndBlocksToDBV2, +// updateBlocksV2, +// updateFileCountV2, +// updateMessageV2, +// updateSingleBlockV2 +// } from './messageThunk.v2' const logger = loggerService.withContext('MessageThunk') @@ -363,9 +364,9 @@ const createAgentMessageStream = async ( return createSSEReadableStream(response.body, signal) } // TODO: 后续可以将db操作移到Listener Middleware中 -export const saveMessageAndBlocksToDB = async (message: Message, blocks: MessageBlock[], messageIndex: number = -1) => { - return saveMessageAndBlocksToDBV2(message.topicId, message, blocks, messageIndex) -} +// export const saveMessageAndBlocksToDB = async (message: Message, blocks: MessageBlock[], messageIndex: number = -1) => { +// return saveMessageAndBlocksToDBV2(message.topicId, message, blocks, messageIndex) +// } const updateExistingMessageAndBlocksInDB = async ( updatedMessage: Partial & Pick, @@ -374,7 +375,7 @@ const updateExistingMessageAndBlocksInDB = async ( try { // Always update blocks if provided if (updatedBlocks.length > 0) { - await updateBlocksV2(updatedBlocks) + await updateBlocks(updatedBlocks) } // Check if there are message properties to update beyond id and topicId @@ -386,7 +387,7 @@ const updateExistingMessageAndBlocksInDB = async ( return acc }, {}) - await updateMessageV2(updatedMessage.topicId, updatedMessage.id, messageUpdatesPayload) + await updateMessage(updatedMessage.topicId, updatedMessage.id, messageUpdatesPayload) store.dispatch(updateTopicUpdatedAt({ topicId: updatedMessage.topicId })) } @@ -432,7 +433,7 @@ const getBlockThrottler = (id: string) => { }) blockUpdateRafs.set(id, rafId) - await updateSingleBlockV2(id, blockUpdate) + await updateSingleBlock(id, blockUpdate) }, 150) blockUpdateThrottlers.set(id, throttler) @@ -893,7 +894,7 @@ export const sendMessage = userMessage.agentSessionId = activeAgentSession.agentSessionId } - await saveMessageAndBlocksToDB(userMessage, userMessageBlocks) + await saveMessageAndBlocksToDB(topicId, userMessage, userMessageBlocks) dispatch(newMessagesActions.addMessage({ topicId, message: userMessage })) if (userMessageBlocks.length > 0) { dispatch(upsertManyBlocks(userMessageBlocks)) @@ -911,7 +912,7 @@ export const sendMessage = if (activeAgentSession.agentSessionId && !assistantMessage.agentSessionId) { assistantMessage.agentSessionId = activeAgentSession.agentSessionId } - await saveMessageAndBlocksToDB(assistantMessage, []) + await saveMessageAndBlocksToDB(topicId, assistantMessage, []) dispatch(newMessagesActions.addMessage({ topicId, message: assistantMessage })) queue.add(async () => { @@ -934,7 +935,7 @@ export const sendMessage = model: assistant.model, traceId: userMessage.traceId }) - await saveMessageAndBlocksToDB(assistantMessage, []) + await saveMessageAndBlocksToDB(topicId, assistantMessage, []) dispatch(newMessagesActions.addMessage({ topicId, message: assistantMessage })) queue.add(async () => { @@ -1000,11 +1001,11 @@ export const loadAgentSessionMessagesThunk = * Loads messages and their blocks for a specific topic from the database * and updates the Redux store. */ -export const loadTopicMessagesThunk = - (topicId: string, forceReload: boolean = false) => - async (dispatch: AppDispatch, getState: () => RootState) => { - return loadTopicMessagesThunkV2(topicId, forceReload)(dispatch, getState) - } +// export const loadTopicMessagesThunk = +// (topicId: string, forceReload: boolean = false) => +// async (dispatch: AppDispatch, getState: () => RootState) => { +// return loadTopicMessagesThunkV2(topicId, forceReload)(dispatch, getState) +// } /** * Thunk to delete a single message and its associated blocks. @@ -1023,7 +1024,7 @@ export const deleteSingleMessageThunk = try { dispatch(newMessagesActions.removeMessage({ topicId, messageId })) cleanupMultipleBlocks(dispatch, blockIdsToDelete) - await deleteMessageFromDBV2(topicId, messageId) + await deleteMessageFromDB(topicId, messageId) } catch (error) { logger.error(`[deleteSingleMessage] Failed to delete message ${messageId}:`, error as Error) } @@ -1062,7 +1063,7 @@ export const deleteMessageGroupThunk = try { dispatch(newMessagesActions.removeMessagesByAskId({ topicId, askId })) cleanupMultipleBlocks(dispatch, blockIdsToDelete) - await deleteMessagesFromDBV2(topicId, messageIdsToDelete) + await deleteMessagesFromDB(topicId, messageIdsToDelete) } catch (error) { logger.error(`[deleteMessageGroup] Failed to delete messages with askId ${askId}:`, error as Error) } @@ -1087,7 +1088,7 @@ export const clearTopicMessagesThunk = dispatch(newMessagesActions.clearTopicMessages(topicId)) cleanupMultipleBlocks(dispatch, blockIdsToDelete) - await clearMessagesFromDBV2(topicId) + await clearMessagesFromDB(topicId) } catch (error) { logger.error(`[clearTopicMessagesThunk] Failed to clear messages for topic ${topicId}:`, error as Error) } @@ -1408,7 +1409,7 @@ export const updateTranslationBlockThunk = // 更新Redux状态 dispatch(updateOneBlock({ id: blockId, changes })) - await updateSingleBlockV2(blockId, changes) + await updateSingleBlock(blockId, changes) // Logger.log(`[updateTranslationBlockThunk] Successfully updated translation block ${blockId}.`) } catch (error) { logger.error(`[updateTranslationBlockThunk] Failed to update translation block ${blockId}:`, error as Error) @@ -1479,7 +1480,7 @@ export const appendAssistantResponseThunk = const insertAtIndex = existingMessageIndex !== -1 ? existingMessageIndex + 1 : currentTopicMessageIds.length // 4. Update Database (Save the stub to the topic's message list) - await saveMessageAndBlocksToDB(newAssistantMessageStub, [], insertAtIndex) + await saveMessageAndBlocksToDB(topicId, newAssistantMessageStub, [], insertAtIndex) dispatch( newMessagesActions.insertMessageAtIndex({ topicId, message: newAssistantMessageStub, index: insertAtIndex }) @@ -1631,12 +1632,12 @@ export const cloneMessagesToNewTopicThunk = // Add the NEW blocks if (clonedBlocks.length > 0) { - await bulkAddBlocksV2(clonedBlocks) + await bulkAddBlocks(clonedBlocks) } // Update file counts const uniqueFiles = [...new Map(filesToUpdateCount.map((f) => [f.id, f])).values()] for (const file of uniqueFiles) { - await updateFileCountV2(file.id, 1, false) + await updateFileCount(file.id, 1, false) } }) @@ -1690,11 +1691,11 @@ export const updateMessageAndBlocksThunk = } // Update message properties if provided if (messageUpdates && Object.keys(messageUpdates).length > 0 && messageId) { - await updateMessageV2(topicId, messageId, messageUpdates) + await updateMessage(topicId, messageId, messageUpdates) } // Update blocks if provided if (blockUpdatesList.length > 0) { - await updateBlocksV2(blockUpdatesList) + await updateBlocks(blockUpdatesList) } dispatch(updateTopicUpdatedAt({ topicId })) @@ -1748,3 +1749,197 @@ export const removeBlocksThunk = throw error } } + +//以下内容从原 messageThunk.v2.ts 迁移过来,原文件已经删除 +//原因:v2.ts并不是v2数据重构的一部分,而相关命名对v2重构造成重大误解,故两文件合并,以消除误解 + +/** + * Load messages for a topic using unified DbService + */ +export const loadTopicMessagesThunk = + (topicId: string, forceReload: boolean = false) => + async (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState() + + dispatch(newMessagesActions.setCurrentTopicId(topicId)) + + // Skip if already cached and not forcing reload + if (!forceReload && state.messages.messageIdsByTopic[topicId]) { + return + } + + try { + dispatch(newMessagesActions.setTopicLoading({ topicId, loading: true })) + + // Unified call - no need to check isAgentSessionTopicId + const { messages, blocks } = await dbService.fetchMessages(topicId) + + logger.silly('Loaded messages via DbService', { + topicId, + messageCount: messages.length, + blockCount: blocks.length + }) + + // Update Redux state with fetched data + if (blocks.length > 0) { + dispatch(upsertManyBlocks(blocks)) + } + dispatch(newMessagesActions.messagesReceived({ topicId, messages })) + } catch (error) { + logger.error(`Failed to load messages for topic ${topicId}:`, error as Error) + // Could dispatch an error action here if needed + } finally { + dispatch(newMessagesActions.setTopicLoading({ topicId, loading: false })) + } + } + +/** + * Get raw topic data using unified DbService + * Returns topic with messages array + */ +export const getRawTopic = async (topicId: string): Promise<{ id: string; messages: Message[] } | undefined> => { + try { + const rawTopic = await dbService.getRawTopic(topicId) + logger.silly('Retrieved raw topic via DbService', { topicId, found: !!rawTopic }) + return rawTopic + } catch (error) { + logger.error('Failed to get raw topic:', { topicId, error }) + return undefined + } +} + +/** + * Update file reference count + * Only applies to Dexie data source, no-op for agent sessions + */ +export const updateFileCount = async (fileId: string, delta: number, deleteIfZero: boolean = false): Promise => { + try { + // Pass all parameters to dbService, including deleteIfZero + await dbService.updateFileCount(fileId, delta, deleteIfZero) + logger.silly('Updated file count', { fileId, delta, deleteIfZero }) + } catch (error) { + logger.error('Failed to update file count:', { fileId, delta, error }) + throw error + } +} + +/** + * Delete a single message from database + */ +export const deleteMessageFromDB = async (topicId: string, messageId: string): Promise => { + try { + await dbService.deleteMessage(topicId, messageId) + logger.silly('Deleted message via DbService', { topicId, messageId }) + } catch (error) { + logger.error('Failed to delete message:', { topicId, messageId, error }) + throw error + } +} + +/** + * Delete multiple messages from database + */ +export const deleteMessagesFromDB = async (topicId: string, messageIds: string[]): Promise => { + try { + await dbService.deleteMessages(topicId, messageIds) + logger.silly('Deleted messages via DbService', { topicId, count: messageIds.length }) + } catch (error) { + logger.error('Failed to delete messages:', { topicId, messageIds, error }) + throw error + } +} + +/** + * Clear all messages from a topic + */ +export const clearMessagesFromDB = async (topicId: string): Promise => { + try { + await dbService.clearMessages(topicId) + logger.silly('Cleared all messages via DbService', { topicId }) + } catch (error) { + logger.error('Failed to clear messages:', { topicId, error }) + throw error + } +} + +/** + * Save a message and its blocks to database + * Uses unified interface, no need for isAgentSessionTopicId check + */ +export const saveMessageAndBlocksToDB = async ( + topicId: string, + message: Message, + blocks: MessageBlock[], + messageIndex: number = -1 +): Promise => { + try { + const blockIds = blocks.map((block) => block.id) + const shouldSyncBlocks = + blockIds.length > 0 && (!message.blocks || blockIds.some((id, index) => message.blocks?.[index] !== id)) + + const messageWithBlocks = shouldSyncBlocks ? { ...message, blocks: blockIds } : message + // Direct call without conditional logic, now with messageIndex + await dbService.appendMessage(topicId, messageWithBlocks, blocks, messageIndex) + logger.silly('Saved message and blocks via DbService', { + topicId, + messageId: message.id, + blockCount: blocks.length, + messageIndex + }) + } catch (error) { + logger.error('Failed to save message and blocks:', { topicId, messageId: message.id, error }) + throw error + } +} + +/** + * Update a message in the database + */ +export const updateMessage = async (topicId: string, messageId: string, updates: Partial): Promise => { + try { + await dbService.updateMessage(topicId, messageId, updates) + logger.silly('Updated message via DbService', { topicId, messageId }) + } catch (error) { + logger.error('Failed to update message:', { topicId, messageId, error }) + throw error + } +} + +/** + * Update a single message block + */ +export const updateSingleBlock = async (blockId: string, updates: Partial): Promise => { + try { + await dbService.updateSingleBlock(blockId, updates) + logger.silly('Updated single block via DbService', { blockId }) + } catch (error) { + logger.error('Failed to update single block:', { blockId, error }) + throw error + } +} + +/** + * Bulk add message blocks (for new blocks) + */ +export const bulkAddBlocks = async (blocks: MessageBlock[]): Promise => { + try { + await dbService.bulkAddBlocks(blocks) + logger.silly('Bulk added blocks via DbService', { count: blocks.length }) + } catch (error) { + logger.error('Failed to bulk add blocks:', { count: blocks.length, error }) + throw error + } +} + +/** + * Update multiple message blocks (upsert operation) + */ +export const updateBlocks = async (blocks: MessageBlock[]): Promise => { + try { + await dbService.updateBlocks(blocks) + logger.silly('Updated blocks via DbService', { count: blocks.length }) + } catch (error) { + logger.error('Failed to update blocks:', { count: blocks.length, error }) + throw error + } +} diff --git a/src/renderer/src/store/thunk/messageThunk.v2.ts b/src/renderer/src/store/thunk/messageThunk.v2.ts deleted file mode 100644 index 587a9baf68..0000000000 --- a/src/renderer/src/store/thunk/messageThunk.v2.ts +++ /dev/null @@ -1,249 +0,0 @@ -/** - * @deprecated Scheduled for removal in v2.0.0 - * -------------------------------------------------------------------------- - * ⚠️ NOTICE: V2 DATA&UI REFACTORING (by 0xfullex) - * -------------------------------------------------------------------------- - * STOP: Feature PRs affecting this file are currently BLOCKED. - * Only critical bug fixes are accepted during this migration phase. - * - * This file is being refactored to v2 standards. - * Any non-critical changes will conflict with the ongoing work. - * - * 🔗 Context & Status: - * - Contribution Hold: https://github.com/CherryHQ/cherry-studio/issues/10954 - * - v2 Refactor PR : https://github.com/CherryHQ/cherry-studio/pull/10162 - * -------------------------------------------------------------------------- - */ -/** - * V2 implementations of message thunk functions using the unified DbService - * These implementations will be gradually rolled out using feature flags - */ - -import { loggerService } from '@logger' -import { dbService } from '@renderer/services/db' -import type { Message, MessageBlock } from '@renderer/types/newMessage' - -import type { AppDispatch, RootState } from '../index' -import { upsertManyBlocks } from '../messageBlock' -import { newMessagesActions } from '../newMessage' - -const logger = loggerService.withContext('MessageThunkV2') - -// ================================================================= -// Phase 2.1 - Batch 1: Read-only operations (lowest risk) -// ================================================================= - -/** - * Load messages for a topic using unified DbService - * This is the V2 implementation that will replace the original - */ -export const loadTopicMessagesThunkV2 = - (topicId: string, forceReload: boolean = false) => - async (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState() - - dispatch(newMessagesActions.setCurrentTopicId(topicId)) - - // Skip if already cached and not forcing reload - if (!forceReload && state.messages.messageIdsByTopic[topicId]) { - return - } - - try { - dispatch(newMessagesActions.setTopicLoading({ topicId, loading: true })) - - // Unified call - no need to check isAgentSessionTopicId - const { messages, blocks } = await dbService.fetchMessages(topicId) - - logger.silly('Loaded messages via DbService', { - topicId, - messageCount: messages.length, - blockCount: blocks.length - }) - - // Update Redux state with fetched data - if (blocks.length > 0) { - dispatch(upsertManyBlocks(blocks)) - } - dispatch(newMessagesActions.messagesReceived({ topicId, messages })) - } catch (error) { - logger.error(`Failed to load messages for topic ${topicId}:`, error as Error) - // Could dispatch an error action here if needed - } finally { - dispatch(newMessagesActions.setTopicLoading({ topicId, loading: false })) - } - } - -/** - * Get raw topic data using unified DbService - * Returns topic with messages array - */ -export const getRawTopicV2 = async (topicId: string): Promise<{ id: string; messages: Message[] } | undefined> => { - try { - const rawTopic = await dbService.getRawTopic(topicId) - logger.silly('Retrieved raw topic via DbService', { topicId, found: !!rawTopic }) - return rawTopic - } catch (error) { - logger.error('Failed to get raw topic:', { topicId, error }) - return undefined - } -} - -// ================================================================= -// Phase 2.2 - Batch 2: Helper functions -// ================================================================= - -/** - * Update file reference count - * Only applies to Dexie data source, no-op for agent sessions - */ -export const updateFileCountV2 = async ( - fileId: string, - delta: number, - deleteIfZero: boolean = false -): Promise => { - try { - // Pass all parameters to dbService, including deleteIfZero - await dbService.updateFileCount(fileId, delta, deleteIfZero) - logger.silly('Updated file count', { fileId, delta, deleteIfZero }) - } catch (error) { - logger.error('Failed to update file count:', { fileId, delta, error }) - throw error - } -} - -// ================================================================= -// Phase 2.3 - Batch 3: Delete operations -// ================================================================= - -/** - * Delete a single message from database - */ -export const deleteMessageFromDBV2 = async (topicId: string, messageId: string): Promise => { - try { - await dbService.deleteMessage(topicId, messageId) - logger.silly('Deleted message via DbService', { topicId, messageId }) - } catch (error) { - logger.error('Failed to delete message:', { topicId, messageId, error }) - throw error - } -} - -/** - * Delete multiple messages from database - */ -export const deleteMessagesFromDBV2 = async (topicId: string, messageIds: string[]): Promise => { - try { - await dbService.deleteMessages(topicId, messageIds) - logger.silly('Deleted messages via DbService', { topicId, count: messageIds.length }) - } catch (error) { - logger.error('Failed to delete messages:', { topicId, messageIds, error }) - throw error - } -} - -/** - * Clear all messages from a topic - */ -export const clearMessagesFromDBV2 = async (topicId: string): Promise => { - try { - await dbService.clearMessages(topicId) - logger.silly('Cleared all messages via DbService', { topicId }) - } catch (error) { - logger.error('Failed to clear messages:', { topicId, error }) - throw error - } -} - -// ================================================================= -// Phase 2.4 - Batch 4: Complex write operations -// ================================================================= - -/** - * Save a message and its blocks to database - * Uses unified interface, no need for isAgentSessionTopicId check - */ -export const saveMessageAndBlocksToDBV2 = async ( - topicId: string, - message: Message, - blocks: MessageBlock[], - messageIndex: number = -1 -): Promise => { - try { - const blockIds = blocks.map((block) => block.id) - const shouldSyncBlocks = - blockIds.length > 0 && (!message.blocks || blockIds.some((id, index) => message.blocks?.[index] !== id)) - - const messageWithBlocks = shouldSyncBlocks ? { ...message, blocks: blockIds } : message - // Direct call without conditional logic, now with messageIndex - await dbService.appendMessage(topicId, messageWithBlocks, blocks, messageIndex) - logger.silly('Saved message and blocks via DbService', { - topicId, - messageId: message.id, - blockCount: blocks.length, - messageIndex - }) - } catch (error) { - logger.error('Failed to save message and blocks:', { topicId, messageId: message.id, error }) - throw error - } -} - -// Note: sendMessageV2 would be implemented here but it's more complex -// and would require more of the supporting code from messageThunk.ts - -// ================================================================= -// Phase 2.5 - Batch 5: Update operations -// ================================================================= - -/** - * Update a message in the database - */ -export const updateMessageV2 = async (topicId: string, messageId: string, updates: Partial): Promise => { - try { - await dbService.updateMessage(topicId, messageId, updates) - logger.silly('Updated message via DbService', { topicId, messageId }) - } catch (error) { - logger.error('Failed to update message:', { topicId, messageId, error }) - throw error - } -} - -/** - * Update a single message block - */ -export const updateSingleBlockV2 = async (blockId: string, updates: Partial): Promise => { - try { - await dbService.updateSingleBlock(blockId, updates) - logger.silly('Updated single block via DbService', { blockId }) - } catch (error) { - logger.error('Failed to update single block:', { blockId, error }) - throw error - } -} - -/** - * Bulk add message blocks (for new blocks) - */ -export const bulkAddBlocksV2 = async (blocks: MessageBlock[]): Promise => { - try { - await dbService.bulkAddBlocks(blocks) - logger.silly('Bulk added blocks via DbService', { count: blocks.length }) - } catch (error) { - logger.error('Failed to bulk add blocks:', { count: blocks.length, error }) - throw error - } -} - -/** - * Update multiple message blocks (upsert operation) - */ -export const updateBlocksV2 = async (blocks: MessageBlock[]): Promise => { - try { - await dbService.updateBlocks(blocks) - logger.silly('Updated blocks via DbService', { count: blocks.length }) - } catch (error) { - logger.error('Failed to update blocks:', { count: blocks.length, error }) - throw error - } -}