perf: optimize/message performance (#3181)
* feat: Add message pause and resume functionality - Implement pauseMessage and pauseMessages methods in useMessageOperations hook - Update Inputbar to use new pauseMessages method for stopping message generation - Remove deprecated pause-related code from ApiService and store - Simplify message generation and pause logic across providers - Enhance message state management with more granular control over streaming messages * feat: Enhance topic management with sequence-based sorting and lazy loading - Add sequence field to topics for better sorting - Implement lazy loading mechanism for topic messages - Modify Redux store to support per-topic loading states - Update database schema to use sequence as an auto-incrementing primary key - Optimize message initialization and retrieval process * refactor(database): Enhance topic management with timestamps and upgrade logic - Modify database schema to include createdAt and updatedAt for topics - Add database hooks for automatic timestamp handling - Refactor topic upgrade process to support new timestamp fields - Remove redundant upgradesV6.ts file - Update topic retrieval to use updatedAt for sorting - Improve database consistency and tracking of topic modifications * refactor: Streamline message state management and remove unused code - Remove commented-out code in multiple components - Delete initializeMessagesState thunk from messages store - Simplify message sending and streaming logic - Remove unnecessary console logs - Optimize MessageStream component with memo - Using loading to control message generation within a single session - Lift the restriction on not being able to switch topics in message generation * refactor(database): Remove version 6 database version and hooks - Remove version 6 database schema definition - Delete automatic timestamp hooks for topics - Clean up unused database upgrade and hook code * refactor(Messages): Optimize message state management and remove redundant code - Remove duplicate imports and redundant code blocks - Simplify message sending and streaming logic in messages store - Enhance throttling mechanism for message updates - Remove commented-out code and unused function parameters - Improve error handling and loading state management - Optimize message synchronization with database * fix:console
This commit is contained in:
parent
a25c0e657b
commit
c69c750144
@ -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<FileType, 'id'>
|
||||
@ -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
|
||||
|
||||
@ -37,6 +37,7 @@ export async function upgradeToV5(tx: Transaction): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个 topic 添加时间戳,兼容老数据,默认按照最新的时间戳来,不确定是否要加
|
||||
export async function upgradeToV6(tx: Transaction): Promise<void> {
|
||||
const topics = await tx.table('topics').toArray()
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Props> = ({ 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<NodeJS.Timeout>()
|
||||
@ -140,14 +140,27 @@ const Inputbar: FC<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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)
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +52,6 @@ const MessageStream: React.FC<MessageStreamProps> = ({
|
||||
// 在hooks调用后进行条件判断
|
||||
const isStreaming = !!(streamMessage && streamMessage.id === _message.id)
|
||||
const message = isStreaming ? streamMessage : regularMessage
|
||||
console.log('isStreaming', isStreaming)
|
||||
return (
|
||||
<MessageStreamContainer>
|
||||
<MessageItem
|
||||
|
||||
@ -67,7 +67,7 @@ const Topics: FC<Props> = ({ 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)
|
||||
}, [])
|
||||
|
||||
@ -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<Props> = ({ 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 () => {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user