diff --git a/src/renderer/src/databases/index.ts b/src/renderer/src/databases/index.ts index d85fa7fe..6f027338 100644 --- a/src/renderer/src/databases/index.ts +++ b/src/renderer/src/databases/index.ts @@ -1,7 +1,7 @@ import { FileType, KnowledgeItem, Topic, TranslateHistory } from '@renderer/types' import { Dexie, type EntityTable } from 'dexie' -import { upgradeToV5, upgradeToV6 } from './upgrades' +import { upgradeToV5 } from './upgrades' // Database declaration (move this to its own module also) export const db = new Dexie('CherryStudio') as Dexie & { files: EntityTable @@ -46,27 +46,4 @@ db.version(5) }) .upgrade((tx) => upgradeToV5(tx)) -db.version(6) - .stores({ - files: 'id, name, origin_name, path, size, ext, type, created_at, count', - topics: '&id, messages, createdAt, updatedAt', - settings: '&id, value', - knowledge_notes: '&id, baseId, type, content, created_at, updated_at', - translate_history: '&id, sourceText, targetText, sourceLanguage, targetLanguage, createdAt' - }) - .upgrade((tx) => upgradeToV6(tx)) - -// Add hooks for automatic timestamp handling -db.topics.hook('creating', (_, obj: any) => { - const now = new Date().toISOString() - obj.createdAt = now - obj.updatedAt = now -}) - -db.topics.hook('updating', (modifications: any) => { - if (typeof modifications === 'object') { - modifications.updatedAt = new Date().toISOString() - } -}) - export default db diff --git a/src/renderer/src/databases/upgrades.ts b/src/renderer/src/databases/upgrades.ts index ec63dbbd..f4a07f91 100644 --- a/src/renderer/src/databases/upgrades.ts +++ b/src/renderer/src/databases/upgrades.ts @@ -37,6 +37,7 @@ export async function upgradeToV5(tx: Transaction): Promise { } } +// 为每个 topic 添加时间戳,兼容老数据,默认按照最新的时间戳来,不确定是否要加 export async function upgradeToV6(tx: Transaction): Promise { const topics = await tx.table('topics').toArray() diff --git a/src/renderer/src/hooks/useMessageOperations.ts b/src/renderer/src/hooks/useMessageOperations.ts index 9ee0eb4f..4ffe598e 100644 --- a/src/renderer/src/hooks/useMessageOperations.ts +++ b/src/renderer/src/hooks/useMessageOperations.ts @@ -1,5 +1,6 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { useAppDispatch, useAppSelector } from '@renderer/store' +import store from '@renderer/store' import { clearStreamMessage, clearTopicMessages, @@ -13,6 +14,7 @@ import { updateMessages } from '@renderer/store/messages' import type { Assistant, Message, Topic } from '@renderer/types' +import { abortCompletion } from '@renderer/utils/abortController' import { useCallback } from 'react' /** * 自定义Hook,提供消息操作相关的功能 @@ -149,6 +151,52 @@ export function useMessageOperations(topic: Topic) { // */ // const getMessages = useCallback(() => messages, [messages]) + /** + * 暂停消息生成 + */ + const pauseMessage = useCallback( + async (messageId: string) => { + // 1. 调用 abort + abortCompletion(messageId) + + // 2. 更新消息状态 + await editMessage(messageId, { status: 'paused' }) + + // 3. 清理流式消息 + clearStreamMessageAction(messageId) + }, + [editMessage, clearStreamMessageAction] + ) + + const pauseMessages = useCallback(async () => { + // 从 store 获取当前 topic 的所有流式消息 + const streamMessages = store.getState().messages.streamMessagesByTopic[topic.id] + if (streamMessages) { + // 获取所有流式消息的 askId + const askIds = new Set( + Object.values(streamMessages) + .map((msg) => msg.askId) + .filter(Boolean) + ) + + // 对每个 askId 执行暂停 + for (const askId of askIds) { + await pauseMessage(askId) + } + } + }, [topic.id, pauseMessage]) + + /** + * 恢复/重发消息 + * 暂时不需要 + */ + const resumeMessage = useCallback( + async (message: Message, assistant: Assistant) => { + return resendMessageAction(message, assistant) + }, + [resendMessageAction] + ) + return { messages, loading, @@ -163,6 +211,9 @@ export function useMessageOperations(topic: Topic) { commitStreamMessage: commitStreamMessageAction, clearStreamMessage: clearStreamMessageAction, createNewContext, - clearTopicMessages: clearTopicMessagesAction + clearTopicMessages: clearTopicMessagesAction, + pauseMessage, + pauseMessages, + resumeMessage } } diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 4a11a7cf..8d530704 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -22,15 +22,15 @@ import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon' import { addAssistantMessagesToTopic, getDefaultTopic } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import FileManager from '@renderer/services/FileManager' -import { estimateTextTokens as estimateTxtTokens } from '@renderer/services/TokenService' +import { getUserMessage } from '@renderer/services/MessagesService' +import { estimateMessageUsage, estimateTextTokens as estimateTxtTokens } from '@renderer/services/TokenService' import { translateText } from '@renderer/services/TranslateService' import WebSearchService from '@renderer/services/WebSearchService' -import store, { useAppDispatch } from '@renderer/store' +import { useAppDispatch } from '@renderer/store' import { sendMessage as _sendMessage } from '@renderer/store/messages' -import { setGenerating, setSearching } from '@renderer/store/runtime' +import { setSearching } from '@renderer/store/runtime' import { Assistant, FileType, KnowledgeBase, MCPServer, Message, Model, Topic } from '@renderer/types' import { classNames, delay, getFileExtension } from '@renderer/utils' -import { abortCompletion } from '@renderer/utils/abortController' import { getFilesFromDropEvent } from '@renderer/utils/input' import { documentExts, imageExts, textExts } from '@shared/config/constant' import { Button, Popconfirm, Tooltip } from 'antd' @@ -84,7 +84,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const containerRef = useRef(null) const { searching } = useRuntime() const { isBubbleStyle } = useMessageStyle() - const { loading } = useMessageOperations(topic) + const { loading, pauseMessages } = useMessageOperations(topic) const dispatch = useAppDispatch() const [spaceClickCount, setSpaceClickCount] = useState(0) const spaceClickTimer = useRef() @@ -140,14 +140,27 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = try { // Dispatch the sendMessage action with all options const uploadedFiles = await FileManager.uploadFiles(files) - dispatch( - _sendMessage(text, assistant, topic, { - files: uploadedFiles, - knowledgeBaseIds: selectedKnowledgeBases?.map((base) => base.id), - mentionModels, - enabledMCPs - }) - ) + const userMessage = getUserMessage({ assistant, topic, type: 'text', content: text }) + + if (uploadedFiles) { + userMessage.files = uploadedFiles + } + const knowledgeBaseIds = selectedKnowledgeBases?.map((base) => base.id) + if (knowledgeBaseIds) { + userMessage.knowledgeBaseIds = knowledgeBaseIds + } + + if (mentionModels) { + userMessage.mentions = mentionModels + } + + if (enabledMCPs) { + userMessage.enabledMCPs = enabledMCPs + } + userMessage.usage = await estimateMessageUsage(userMessage) + currentMessageId.current = userMessage.id + + dispatch(_sendMessage(userMessage, assistant, topic)) // Clear input setText('') @@ -158,7 +171,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = } catch (error) { console.error('Failed to send message:', error) } - }, [inputEmpty, files, dispatch, text, assistant, selectedKnowledgeBases, mentionModels, enabledMCPs]) + }, [inputEmpty, files, dispatch, text, assistant, topic, selectedKnowledgeBases, mentionModels, enabledMCPs, loading]) const translate = async () => { if (isTranslating) { @@ -282,24 +295,23 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = clickAssistantToShowTopic && setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0) }, [addTopic, assistant, clickAssistantToShowTopic, setActiveTopic, setModel]) + const onPause = async () => { + await pauseMessages() + } + const clearTopic = async () => { if (loading) { - onPause() + await onPause() await delay(1) } EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES) } - const onPause = () => { - if (currentMessageId.current) { - abortCompletion(currentMessageId.current) - } - window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, true) - store.dispatch(setGenerating(false)) - } - const onNewContext = () => { - if (loading) return onPause() + if (loading) { + onPause() + return + } EventEmitter.emit(EVENT_NAMES.NEW_CONTEXT) } diff --git a/src/renderer/src/pages/home/Messages/MessageStream.tsx b/src/renderer/src/pages/home/Messages/MessageStream.tsx index e05f3984..9a79f1de 100644 --- a/src/renderer/src/pages/home/Messages/MessageStream.tsx +++ b/src/renderer/src/pages/home/Messages/MessageStream.tsx @@ -52,7 +52,6 @@ const MessageStream: React.FC = ({ // 在hooks调用后进行条件判断 const isStreaming = !!(streamMessage && streamMessage.id === _message.id) const message = isStreaming ? streamMessage : regularMessage - console.log('isStreaming', isStreaming) return ( = ({ assistant: _assistant, activeTopic, setActiveTopic }, []) const onClearMessages = useCallback((topic: Topic) => { - window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, true) + // window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, true) store.dispatch(setGenerating(false)) EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES, topic) }, []) diff --git a/src/renderer/src/pages/home/components/Suggestions.tsx b/src/renderer/src/pages/home/components/Suggestions.tsx index 00317082..32df674c 100644 --- a/src/renderer/src/pages/home/components/Suggestions.tsx +++ b/src/renderer/src/pages/home/components/Suggestions.tsx @@ -1,4 +1,5 @@ import { fetchSuggestions } from '@renderer/services/ApiService' +import { getUserMessage } from '@renderer/services/MessagesService' import { useAppDispatch } from '@renderer/store' import { sendMessage } from '@renderer/store/messages' import { Assistant, Message, Suggestion } from '@renderer/types' @@ -6,6 +7,7 @@ import { last } from 'lodash' import { FC, memo, useEffect, useState } from 'react' import BeatLoader from 'react-spinners/BeatLoader' import styled from 'styled-components' + interface Props { assistant: Assistant messages: Message[] @@ -22,7 +24,9 @@ const Suggestions: FC = ({ assistant, messages }) => { const [loadingSuggestions, setLoadingSuggestions] = useState(false) const handleSuggestionClick = async (content: string) => { - await dispatch(sendMessage(content, assistant, assistant.topics[0])) + const userMessage = getUserMessage({ assistant, topic: assistant.topics[0], type: 'text', content }) + + await dispatch(sendMessage(userMessage, assistant, assistant.topics[0])) } const suggestionsHandle = async () => { diff --git a/src/renderer/src/providers/AnthropicProvider.ts b/src/renderer/src/providers/AnthropicProvider.ts index cd66ca5f..dc8ffaad 100644 --- a/src/renderer/src/providers/AnthropicProvider.ts +++ b/src/renderer/src/providers/AnthropicProvider.ts @@ -323,10 +323,10 @@ export default class AnthropicProvider extends BaseProvider { resolve() }) .on('error', (error) => reject(error)) - }).finally(cleanup) + }) } - await processStream(body) + await processStream(body).finally(cleanup) } public async translate(message: Message, assistant: Assistant, onResponse?: (text: string) => void) { diff --git a/src/renderer/src/providers/GeminiProvider.ts b/src/renderer/src/providers/GeminiProvider.ts index dd04fed3..778f78d3 100644 --- a/src/renderer/src/providers/GeminiProvider.ts +++ b/src/renderer/src/providers/GeminiProvider.ts @@ -222,7 +222,7 @@ export default class GeminiProvider extends BaseProvider { const { abortController, cleanup } = this.createAbortController(lastUserMessage?.id) const { signal } = abortController - const userMessagesStream = await chat.sendMessageStream(messageContents.parts, { signal }).finally(cleanup) + const userMessagesStream = await chat.sendMessageStream(messageContents.parts, { signal }) let time_first_token_millsec = 0 const processStream = async (stream: GenerateContentStreamResult) => { @@ -275,8 +275,8 @@ export default class GeminiProvider extends BaseProvider { parts: fcallParts }) const newChat = geminiModel.startChat({ history }) - const newStream = await newChat.sendMessageStream(fcRespParts, { signal }).finally(cleanup) - await processStream(newStream) + const newStream = await newChat.sendMessageStream(fcRespParts, { signal }) + await processStream(newStream).finally(cleanup) } } @@ -298,7 +298,7 @@ export default class GeminiProvider extends BaseProvider { } } - await processStream(userMessagesStream) + await processStream(userMessagesStream).finally(cleanup) } async translate(message: Message, assistant: Assistant, onResponse?: (text: string) => void) { diff --git a/src/renderer/src/providers/OpenAIProvider.ts b/src/renderer/src/providers/OpenAIProvider.ts index 6f957697..dbe39c4d 100644 --- a/src/renderer/src/providers/OpenAIProvider.ts +++ b/src/renderer/src/providers/OpenAIProvider.ts @@ -428,7 +428,6 @@ export default class OpenAIProvider extends BaseProvider { signal } ) - .finally(cleanup) await processStream(newStream) } @@ -469,9 +468,8 @@ export default class OpenAIProvider extends BaseProvider { signal } ) - .finally(cleanup) - await processStream(stream) + await processStream(stream).finally(cleanup) } async translate(message: Message, assistant: Assistant, onResponse?: (text: string) => void) { diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index c2d4b252..2da9dcd7 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -3,7 +3,6 @@ import i18n from '@renderer/i18n' import store from '@renderer/store' import { setGenerating } from '@renderer/store/runtime' import { Assistant, Message, Model, Provider, Suggestion } from '@renderer/types' -import { addAbortController } from '@renderer/utils/abortController' import { formatMessageError } from '@renderer/utils/error' import { cloneDeep, findLast, isEmpty } from 'lodash' @@ -31,24 +30,15 @@ export async function fetchChatCompletion({ assistant: Assistant onResponse: (message: Message) => void }) { - window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, false) - const provider = getAssistantProvider(assistant) const webSearchProvider = WebSearchService.getWebSearchProvider() const AI = new AiProvider(provider) - store.dispatch(setGenerating(true)) + // store.dispatch(setGenerating(true)) - onResponse({ ...message }) + // onResponse({ ...message }) - const pauseFn = (message: Message) => { - message.status = 'paused' - EventEmitter.emit(EVENT_NAMES.RECEIVE_MESSAGE, message) - store.dispatch(setGenerating(false)) - onResponse({ ...message, status: 'paused' }) - } - - addAbortController(message.askId ?? message.id, pauseFn.bind(null, message)) + // addAbortController(message.askId ?? message.id) try { let _messages: Message[] = [] @@ -136,9 +126,6 @@ export async function fetchChatCompletion({ message.error = formatMessageError(error) } - // Update message status - message.status = window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED) ? 'paused' : message.status - // Emit chat completion event EventEmitter.emit(EVENT_NAMES.RECEIVE_MESSAGE, message) onResponse(message) diff --git a/src/renderer/src/store/messages.ts b/src/renderer/src/store/messages.ts index f4a48e00..95dc8337 100644 --- a/src/renderer/src/store/messages.ts +++ b/src/renderer/src/store/messages.ts @@ -3,10 +3,9 @@ import db from '@renderer/databases' import { TopicManager } from '@renderer/hooks/useTopic' import { fetchChatCompletion } from '@renderer/services/ApiService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' -import { getAssistantMessage, getUserMessage, resetAssistantMessage } from '@renderer/services/MessagesService' -import { estimateMessageUsage } from '@renderer/services/TokenService' +import { getAssistantMessage, resetAssistantMessage } from '@renderer/services/MessagesService' import type { AppDispatch, RootState } from '@renderer/store' -import type { Assistant, FileType, MCPServer, Message, Model, Topic } from '@renderer/types' +import type { Assistant, Message, Topic } from '@renderer/types' import { clearTopicQueue, getTopicQueue, waitForTopicQueue } from '@renderer/utils/queue' import { throttle } from 'lodash' @@ -205,24 +204,21 @@ export const { clearStreamMessage } = messagesSlice.actions -const handleResponseMessageUpdate = (message, topicId, dispatch, getState) => { - dispatch(setStreamMessage({ topicId, message })) - +const handleResponseMessageUpdate = (message, topicId, dispatch) => { // When message is complete, commit to messages and sync with DB - if (message.status !== 'pending') { - if (message.status === 'success') { - EventEmitter.emit(EVENT_NAMES.AI_AUTO_RENAME) - } - if (message.status !== 'sending') { - dispatch(commitStreamMessage({ topicId, messageId: message.id })) - const state = getState() - const topicMessages = state.messages.messagesByTopic[topicId] - if (topicMessages) { - syncMessagesWithDB(topicId, topicMessages) - } - dispatch(setTopicLoading({ topicId, loading: false })) - } - } + // if (message.status !== 'pending') { + // if (message.status === 'success') { + // EventEmitter.emit(EVENT_NAMES.AI_AUTO_RENAME) + // } + // if (message.status !== 'sending') { + // dispatch(commitStreamMessage({ topicId, messageId: message.id })) + // const state = getState() + // const topicMessages = state.messages.messagesByTopic[topicId] + // if (topicMessages) { + // syncMessagesWithDB(topicId, topicMessages) + // } + // } + // } } // Helper function to sync messages with database @@ -240,16 +236,12 @@ const syncMessagesWithDB = async (topicId: string, messages: Message[]) => { // Modified sendMessage thunk export const sendMessage = ( - content: string, + userMessage: Message, assistant: Assistant, topic: Topic, options?: { - files?: FileType[] - knowledgeBaseIds?: string[] - mentionModels?: Model[] - resendUserMessage?: Message resendAssistantMessage?: Message - enabledMCPs?: MCPServer[] + isMentionModel?: boolean } ) => async (dispatch: AppDispatch, getState: () => RootState) => { @@ -262,41 +254,11 @@ export const sendMessage = dispatch(clearTopicMessages(topic.id)) } - // 判断是否重发消息 - const isResend = !!options?.resendUserMessage - - // 使用用户消息 - let userMessage: Message - if (isResend) { - userMessage = options.resendUserMessage - } else { - // 创建新的用户消息 - userMessage = getUserMessage({ assistant, topic, type: 'text', content }) - if (options?.files) { - userMessage.files = options.files - } - - if (options?.knowledgeBaseIds) { - userMessage.knowledgeBaseIds = options.knowledgeBaseIds - } - - if (options?.mentionModels) { - userMessage.mentions = options.mentionModels - } - - if (options?.enabledMCPs) { - userMessage.enabledMCPs = options.enabledMCPs - } - userMessage.usage = await estimateMessageUsage(userMessage) - } - EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE) // 处理助手消息 - // let assistantMessage: Message let assistantMessages: Message[] = [] - // 使用助手消息 - if (isResend && options.resendAssistantMessage) { + if (options?.resendAssistantMessage) { // 直接使用传入的助手消息,进行重置 const messageToReset = options.resendAssistantMessage const { model, id } = messageToReset @@ -307,9 +269,9 @@ export const sendMessage = assistantMessages.push(resetMessage) } else { // 不是重发情况 - // 为每个被 mention 的模型创建一个助手消息 - if (options?.mentionModels?.length) { - assistantMessages = options.mentionModels.map((m) => { + if (userMessage.mentions?.length) { + // 为每个被 mention 的模型创建一个助手消息 + assistantMessages = userMessage.mentions.map((m) => { const assistantMessage = getAssistantMessage({ assistant: { ...assistant, model: m }, topic }) assistantMessage.model = m assistantMessage.askId = userMessage.id @@ -323,20 +285,16 @@ export const sendMessage = assistantMessage.status = 'sending' assistantMessages.push(assistantMessage) } - } - - // 如果不是重发 - !options?.resendAssistantMessage && dispatch( addMessage({ topicId: topic.id, - messages: !isResend ? [userMessage, ...assistantMessages] : assistantMessages + messages: !options?.isMentionModel ? [userMessage, ...assistantMessages] : assistantMessages }) ) + } const queue = getTopicQueue(topic.id) for (const assistantMessage of assistantMessages) { - // console.log('assistantMessage', assistantMessage) // Set as stream message instead of adding to messages dispatch(setStreamMessage({ topicId: topic.id, message: assistantMessage })) @@ -348,10 +306,9 @@ export const sendMessage = await syncMessagesWithDB(topic.id, currentTopicMessages) } // 保证请求有序,防止请求静态,限制并发数量 - queue.add(async () => { + await queue.add(async () => { try { - const state = getState() - const messages = state.messages.messagesByTopic[topic.id] + const messages = getState().messages.messagesByTopic[topic.id] if (!messages) { dispatch(clearTopicMessages(topic.id)) return @@ -369,7 +326,13 @@ export const sendMessage = } // 节流 - const throttledDispatch = throttle(handleResponseMessageUpdate, 100, { trailing: true }) // 100ms的节流时间应足够平衡用户体验和性能 + const throttledDispatch = throttle( + (topicId, message) => dispatch(setStreamMessage({ topicId, message })), + 100, + { trailing: true } + ) // 100ms的节流时间应足够平衡用户体验和性能 + + let resultMessage: Message = { ...assistantMessage } await fetchChatCompletion({ message: { ...assistantMessage }, @@ -382,12 +345,27 @@ export const sendMessage = assistant: assistantWithModel, onResponse: async (msg) => { // 允许在回调外维护一个最新的消息状态,每次都更新这个对象,但只通过节流函数分发到Redux - const updatedMsg = { ...msg, status: msg.status || 'pending', content: msg.content || '' } + const updateMessage = { ...msg, status: msg.status || 'pending', content: msg.content || '' } + resultMessage = { + ...assistantMessage, + ...updateMessage + } // 创建节流函数,限制Redux更新频率 // 使用节流函数更新Redux - throttledDispatch({ ...assistantMessage, ...updatedMsg }, topic.id, dispatch, getState) + throttledDispatch(topic.id, resultMessage) } }) + if (resultMessage?.status === 'success') { + EventEmitter.emit(EVENT_NAMES.AI_AUTO_RENAME) + } + if (resultMessage?.status !== 'sending') { + dispatch(commitStreamMessage({ topicId: topic.id, messageId: assistantMessage.id })) + const state = getState() + const topicMessages = state.messages.messagesByTopic[topic.id] + if (topicMessages) { + syncMessagesWithDB(topic.id, topicMessages) + } + } } catch (error: any) { console.error('Error in chat completion:', error) dispatch( @@ -402,6 +380,9 @@ export const sendMessage = } }) } + // 等待所有请求完成,设置loading + await queue.onIdle() + dispatch(setTopicLoading({ topicId: topic.id, loading: false })) } catch (error: any) { console.error('Error in sendMessage:', error) dispatch(setError(error.message)) @@ -425,8 +406,7 @@ export const resendMessage = const assistantMessage = topicMessages.find((m) => m.role === 'assistant' && m.askId === message.id) return dispatch( - sendMessage(message.content, assistant, topic, { - resendUserMessage: message, + sendMessage(message, assistant, topic, { resendAssistantMessage: assistantMessage }) ) @@ -434,55 +414,38 @@ export const resendMessage = // 如果是助手消息,找到对应的用户消息 const userMessage = topicMessages.find((m) => m.id === message.askId && m.role === 'user') - console.log('topicMessages,topicMessages', topicMessages) if (!userMessage) { console.error('Cannot find original user message to resend') return dispatch(setError('Cannot find original user message to resend')) } if (isMentionModel) { - // @ - return dispatch( - sendMessage(userMessage.content, assistant, topic, { - resendUserMessage: userMessage - }) - ) + // @,追加助手消息 + return dispatch(sendMessage(userMessage, assistant, topic, { isMentionModel })) } dispatch( - sendMessage(userMessage.content, assistant, topic, { - resendUserMessage: userMessage, + sendMessage(userMessage, assistant, topic, { resendAssistantMessage: message }) ) } catch (error: any) { console.error('Error in resendMessage:', error) dispatch(setError(error.message)) - } finally { - dispatch(setTopicLoading({ topicId: topic.id, loading: false })) } } // Modified loadTopicMessages thunk export const loadTopicMessagesThunk = (topic: Topic) => async (dispatch: AppDispatch) => { + // 设置会话的loading状态 + dispatch(setTopicLoading({ topicId: topic.id, loading: true })) + dispatch(setCurrentTopic(topic)) try { - // 设置会话的loading状态 - dispatch(setTopicLoading({ topicId: topic.id, loading: true })) - dispatch(setCurrentTopic(topic)) - try { - // 使用 getTopic 获取会话对象 - const topicWithDB = await TopicManager.getTopic(topic.id) - if (topicWithDB) { - // 如果数据库中有会话,加载消息,保存会话 - dispatch(loadTopicMessages({ topicId: topic.id, messages: topicWithDB.messages })) - } - // else { - // // 如果找不到,可以将当前会话设为 null - // dispatch(setCurrentTopic(null)) - // } - } catch (error) { - console.error('Failed to get complete topic:', error) - dispatch(setCurrentTopic(null)) + // 使用 getTopic 获取会话对象 + const topicWithDB = await TopicManager.getTopic(topic.id) + if (topicWithDB) { + // 如果数据库中有会话,加载消息,保存会话 + dispatch(loadTopicMessages({ topicId: topic.id, messages: topicWithDB.messages })) } } catch (error) { dispatch(setError(error instanceof Error ? error.message : 'Failed to load messages')) @@ -491,7 +454,6 @@ export const loadTopicMessagesThunk = (topic: Topic) => async (dispatch: AppDisp dispatch(setTopicLoading({ topicId: topic.id, loading: false })) } } - // Modified clearMessages thunk export const clearTopicMessagesThunk = (topic: Topic) => async (dispatch: AppDispatch) => { try { @@ -521,9 +483,6 @@ export const clearTopicMessagesThunk = (topic: Topic) => async (dispatch: AppDis // 修改的 updateMessages thunk,同时更新缓存 export const updateMessages = (topic: Topic, messages: Message[]) => async (dispatch: AppDispatch) => { try { - // 设置会话的loading状态 - dispatch(setTopicLoading({ topicId: topic.id, loading: true })) - // 更新数据库 await db.topics.update(topic.id, { messages }) @@ -531,9 +490,6 @@ export const updateMessages = (topic: Topic, messages: Message[]) => async (disp dispatch(loadTopicMessages({ topicId: topic.id, messages })) } catch (error) { dispatch(setError(error instanceof Error ? error.message : 'Failed to update messages')) - } finally { - // 清除会话的loading状态 - dispatch(setTopicLoading({ topicId: topic.id, loading: false })) } }