mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 07:19:02 +08:00
feat(Messages): implement MESSAGE_CREATED event for immediate message updates
- Added a new MESSAGE_CREATED event to EventService to facilitate optimistic UI updates when a user sends a message. - Enhanced the Messages component to listen for MESSAGE_CREATED events, ensuring new messages appear instantly in the UI. - Updated StreamingService to create new session objects for cache notifications, improving reactivity in the message display. - Emitted MESSAGE_CREATED event in the sendMessage thunk to trigger cache updates for user messages. This commit improves user experience by ensuring timely updates in the message list during interactions.
This commit is contained in:
parent
16596fd9bb
commit
47f20c5663
@ -123,6 +123,33 @@ const Messages: React.FC<MessagesProps> = ({ assistant, topic, setActiveTopic, o
|
||||
return unifiedResult.groupedMessages.filter((group) => !group.some((item) => streamingSet.has(item.message.id)))
|
||||
}, [isDataApiPath, unifiedResult.groupedMessages, sessionIds])
|
||||
|
||||
// NOTE: [v2 Migration] Listen for MESSAGE_CREATED event to refresh data when user sends a message.
|
||||
// This ensures user messages appear immediately in the list.
|
||||
useEffect(() => {
|
||||
if (!isDataApiPath) return
|
||||
|
||||
const handler = async ({
|
||||
topicId: eventTopicId
|
||||
}: {
|
||||
message: Message
|
||||
blocks: MessageBlock[]
|
||||
topicId: string
|
||||
}) => {
|
||||
if (eventTopicId !== topic.id) return
|
||||
|
||||
// Refresh DataApi to show the new user message
|
||||
if (unifiedResult.mutate) {
|
||||
await unifiedResult.mutate()
|
||||
}
|
||||
}
|
||||
|
||||
EventEmitter.on(EVENT_NAMES.MESSAGE_CREATED, handler)
|
||||
return () => {
|
||||
EventEmitter.off(EVENT_NAMES.MESSAGE_CREATED, handler)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- mutate is stable (useCallback wrapped)
|
||||
}, [isDataApiPath, topic.id, unifiedResult.mutate])
|
||||
|
||||
// NOTE: [v2 Migration] Listen for STREAMING_FINALIZED event to handle DataApi refresh and cache clearing.
|
||||
// TRADEOFF: Event-driven ensures mutate() completes before cache clears, preventing UI flicker.
|
||||
useEffect(() => {
|
||||
|
||||
@ -28,5 +28,8 @@ export const EVENT_NAMES = {
|
||||
CHANGE_TOPIC: 'CHANGE_TOPIC',
|
||||
// NOTE: [v2 Migration] Used for streaming->API transition.
|
||||
// Emitted by StreamingService.finalize() to signal UI hooks to refresh data and clear cache.
|
||||
STREAMING_FINALIZED: 'streaming:finalized'
|
||||
STREAMING_FINALIZED: 'streaming:finalized',
|
||||
// NOTE: [v2 Migration] Emitted when a message is created and saved to DB.
|
||||
// Used by Messages.tsx to optimistically update SWR cache for immediate UI display.
|
||||
MESSAGE_CREATED: 'message:created'
|
||||
}
|
||||
|
||||
@ -343,10 +343,20 @@ class StreamingService {
|
||||
|
||||
// Merge changes - use type assertion since we're updating the same block type
|
||||
const updatedBlock = { ...existingBlock, ...changes } as MessageBlock
|
||||
session.blocks[blockId] = updatedBlock
|
||||
|
||||
// Update caches
|
||||
cacheService.set(getSessionKey(messageId), session, SESSION_TTL)
|
||||
// IMPORTANT: Create new session object to trigger CacheService notification.
|
||||
// CacheService uses Object.is() for value comparison - same reference = no notification.
|
||||
// Without this, useCache subscribers won't re-render on updates.
|
||||
const updatedSession: StreamingSession = {
|
||||
...session,
|
||||
blocks: {
|
||||
...session.blocks,
|
||||
[blockId]: updatedBlock
|
||||
}
|
||||
}
|
||||
|
||||
// Update caches with new object references
|
||||
cacheService.set(getSessionKey(messageId), updatedSession, SESSION_TTL)
|
||||
cacheService.set(getBlockKey(blockId), updatedBlock, SESSION_TTL)
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ 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 { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import FileManager from '@renderer/services/FileManager'
|
||||
import { BlockManager } from '@renderer/services/messageStreaming/BlockManager'
|
||||
import { createCallbacks } from '@renderer/services/messageStreaming/callbacks'
|
||||
@ -931,6 +932,7 @@ export const sendMessage =
|
||||
|
||||
const stateBeforeSend = getState()
|
||||
let activeAgentSession = agentSession ?? findExistingAgentSessionContext(stateBeforeSend, topicId, assistant.id)
|
||||
|
||||
if (activeAgentSession) {
|
||||
const derivedSession = findExistingAgentSessionContext(stateBeforeSend, topicId, assistant.id)
|
||||
if (derivedSession?.agentSessionId && derivedSession.agentSessionId !== activeAgentSession.agentSessionId) {
|
||||
@ -950,6 +952,13 @@ export const sendMessage =
|
||||
} else {
|
||||
// Normal topic: use Data API, get server-generated message ID
|
||||
finalUserMessage = await streamingService.createUserMessage(topicId, userMessage, userMessageBlocks)
|
||||
|
||||
// NOTE: [v2 Migration] Emit event for Messages.tsx to optimistically update SWR cache
|
||||
EventEmitter.emit(EVENT_NAMES.MESSAGE_CREATED, {
|
||||
message: finalUserMessage,
|
||||
blocks: userMessageBlocks,
|
||||
topicId
|
||||
})
|
||||
}
|
||||
|
||||
dispatch(newMessagesActions.addMessage({ topicId, message: finalUserMessage }))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user