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 0000000000..c7cb2adebe
Binary files /dev/null and b/src/renderer/src/assets/images/apps/aistudio.png differ
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'
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
/**
* 判断模型是否支持图片增强(包括编辑、增强、修复等)
diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx
index d76e7c9192..3b5eb80fdf 100644
--- a/src/renderer/src/pages/home/Chat.tsx
+++ b/src/renderer/src/pages/home/Chat.tsx
@@ -237,6 +237,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..7f7900b8c5 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,29 @@ 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 +108,7 @@ const AgentSessionMessages: React.FC = ({ agentId, sessionId }) => {
+ {messageNavigation === 'anchor' && }
)
}
diff --git a/src/renderer/src/pages/home/Messages/Messages.tsx b/src/renderer/src/pages/home/Messages/Messages.tsx
index f37e829a2a..12e3e04988 100644
--- a/src/renderer/src/pages/home/Messages/Messages.tsx
+++ b/src/renderer/src/pages/home/Messages/Messages.tsx
@@ -163,7 +163,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 51061818b3..b210c532ce 100644
--- a/src/renderer/src/store/thunk/messageThunk.ts
+++ b/src/renderer/src/store/thunk/messageThunk.ts
@@ -20,6 +20,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'
@@ -58,18 +59,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')
@@ -364,9 +365,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,
@@ -375,7 +376,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
@@ -387,7 +388,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 }))
}
@@ -433,7 +434,7 @@ const getBlockThrottler = (id: string) => {
})
blockUpdateRafs.set(id, rafId)
- await updateSingleBlockV2(id, blockUpdate)
+ await updateSingleBlock(id, blockUpdate)
}, 150)
blockUpdateThrottlers.set(id, throttler)
@@ -894,7 +895,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))
@@ -912,7 +913,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 () => {
@@ -935,7 +936,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 () => {
@@ -1001,11 +1002,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.
@@ -1024,7 +1025,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)
}
@@ -1063,7 +1064,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)
}
@@ -1088,7 +1089,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)
}
@@ -1409,7 +1410,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)
@@ -1480,7 +1481,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 })
@@ -1632,12 +1633,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)
}
})
@@ -1691,11 +1692,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 }))
@@ -1749,3 +1750,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 7d39c6a5ff..0000000000
--- a/src/renderer/src/store/thunk/messageThunk.v2.ts
+++ /dev/null
@@ -1,248 +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,下面注释可能也提到了v2,但该文件和v2 Data Refactor没有任何关系,该文件仍旧属于v1的旧代码,请人类和AI都要产生误判
- */
-
-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
- */
-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
- }
-}