mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 07:19:02 +08:00
feat: implement user message creation via Data API in StreamingService
- Added a new method `createUserMessage` in StreamingService to handle user message creation through the Data API, generating server-side message IDs while preserving client-generated block IDs. - Updated `sendMessage` thunk to utilize the new method for normal topics, ensuring proper message handling based on the active agent session. - Refactored block conversion logic to streamline the process of preparing message data for the API.
This commit is contained in:
parent
e6f85ba9fc
commit
b1de7283dc
@ -24,7 +24,7 @@ import { loggerService } from '@logger'
|
||||
import type { Message, MessageBlock } from '@renderer/types/newMessage'
|
||||
import { AssistantMessageStatus, MessageBlockStatus } from '@renderer/types/newMessage'
|
||||
import { isAgentSessionTopicId } from '@renderer/utils/agentSession'
|
||||
import type { UpdateMessageDto } from '@shared/data/api/schemas/messages'
|
||||
import type { CreateMessageDto, UpdateMessageDto } from '@shared/data/api/schemas/messages'
|
||||
import type { MessageDataBlock, MessageStats } from '@shared/data/types/message'
|
||||
|
||||
import { dbService } from '../db'
|
||||
@ -465,8 +465,64 @@ class StreamingService {
|
||||
return nextGroupId
|
||||
}
|
||||
|
||||
// ============ User Message Creation ============
|
||||
|
||||
/**
|
||||
* Create a user message via Data API
|
||||
*
|
||||
* The message ID is generated by the server, not locally.
|
||||
* Block IDs remain client-generated for Redux store use.
|
||||
*
|
||||
* TRADEOFF: Not passing parentId - Data API will use topic.activeNodeId as parent.
|
||||
* In multi-window/multi-branch scenarios, this may cause incorrect associations
|
||||
* if activeNodeId was changed by another window.
|
||||
* TODO: In the future, parentId should come from the full message tree
|
||||
* maintained in the topic UI, not from topic.activeNodeId.
|
||||
*
|
||||
* @param topicId - Topic ID
|
||||
* @param message - Renderer format message (message.id will be ignored, server generates ID)
|
||||
* @param blocks - Renderer format blocks (block IDs preserved for Redux)
|
||||
* @returns Message with server-generated ID and original block IDs
|
||||
*/
|
||||
async createUserMessage(topicId: string, message: Message, blocks: MessageBlock[]): Promise<Message> {
|
||||
// Convert blocks to MessageDataBlock format (remove id, status, messageId)
|
||||
const dataBlocks = this.convertBlocksToDataFormat(blocks)
|
||||
|
||||
// Build CreateMessageDto (parentId omitted - API uses topic.activeNodeId)
|
||||
const createDto: CreateMessageDto = {
|
||||
role: 'user',
|
||||
data: { blocks: dataBlocks },
|
||||
status: 'success',
|
||||
traceId: message.traceId ?? undefined
|
||||
}
|
||||
|
||||
// POST to Data API - server generates message ID
|
||||
const sharedMessage = await dataApiService.post(`/topics/${topicId}/messages`, { body: createDto })
|
||||
|
||||
logger.debug('Created user message via Data API', { topicId, messageId: sharedMessage.id })
|
||||
|
||||
// Return message with server ID, preserving other fields from original message
|
||||
return {
|
||||
...message,
|
||||
id: sharedMessage.id, // Use server-generated ID
|
||||
blocks: blocks.map((b) => b.id) // Preserve client-generated block IDs
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Internal Methods ============
|
||||
|
||||
/**
|
||||
* Convert renderer MessageBlock[] to shared MessageDataBlock[]
|
||||
* Removes renderer-specific fields: id, status, messageId
|
||||
*/
|
||||
private convertBlocksToDataFormat(blocks: MessageBlock[]): MessageDataBlock[] {
|
||||
return blocks.map((block) => {
|
||||
// oxlint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { id, status, messageId, ...blockData } = block as MessageBlock & { messageId?: string }
|
||||
return blockData as unknown as MessageDataBlock
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert session data to database update payload
|
||||
*
|
||||
@ -530,16 +586,8 @@ class StreamingService {
|
||||
// TRADEOFF: Using 'as unknown as' because renderer's MessageBlockType and shared's BlockType
|
||||
// are structurally identical but TypeScript treats them as incompatible enums.
|
||||
const dataBlocks: MessageDataBlock[] = blocks.map((block) => {
|
||||
// Extract only the fields that belong to MessageDataBlock
|
||||
const {
|
||||
id: _id,
|
||||
status: _blockStatus,
|
||||
messageId: _messageId,
|
||||
...blockData
|
||||
} = block as MessageBlock & {
|
||||
messageId?: string
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { id, status, messageId, ...blockData } = block as MessageBlock & { messageId?: string }
|
||||
return blockData as unknown as MessageDataBlock
|
||||
})
|
||||
|
||||
|
||||
@ -978,8 +978,18 @@ export const sendMessage =
|
||||
userMessage.agentSessionId = activeAgentSession.agentSessionId
|
||||
}
|
||||
|
||||
await saveMessageAndBlocksToDB(topicId, userMessage, userMessageBlocks)
|
||||
dispatch(newMessagesActions.addMessage({ topicId, message: userMessage }))
|
||||
let finalUserMessage: Message
|
||||
|
||||
if (activeAgentSession) {
|
||||
// Agent session: keep existing Dexie logic
|
||||
await saveMessageAndBlocksToDB(topicId, userMessage, userMessageBlocks)
|
||||
finalUserMessage = userMessage
|
||||
} else {
|
||||
// Normal topic: use Data API, get server-generated message ID
|
||||
finalUserMessage = await streamingService.createUserMessage(topicId, userMessage, userMessageBlocks)
|
||||
}
|
||||
|
||||
dispatch(newMessagesActions.addMessage({ topicId, message: finalUserMessage }))
|
||||
if (userMessageBlocks.length > 0) {
|
||||
dispatch(upsertManyBlocks(userMessageBlocks))
|
||||
}
|
||||
@ -989,7 +999,7 @@ export const sendMessage =
|
||||
|
||||
if (activeAgentSession) {
|
||||
const assistantMessage = createAssistantMessage(assistant.id, topicId, {
|
||||
askId: userMessage.id,
|
||||
askId: finalUserMessage.id,
|
||||
model: assistant.model,
|
||||
traceId: userMessage.traceId
|
||||
})
|
||||
@ -1005,25 +1015,25 @@ export const sendMessage =
|
||||
assistant,
|
||||
assistantMessage,
|
||||
agentSession: activeAgentSession,
|
||||
userMessageId: userMessage.id
|
||||
userMessageId: finalUserMessage.id
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const mentionedModels = userMessage.mentions
|
||||
const mentionedModels = finalUserMessage.mentions
|
||||
|
||||
if (mentionedModels && mentionedModels.length > 0) {
|
||||
await dispatchMultiModelResponses(dispatch, getState, topicId, userMessage, assistant, mentionedModels)
|
||||
await dispatchMultiModelResponses(dispatch, getState, topicId, finalUserMessage, assistant, mentionedModels)
|
||||
} else {
|
||||
// Create message via Data API for normal topics
|
||||
const createDto: CreateMessageDto = {
|
||||
parentId: userMessage.id,
|
||||
parentId: finalUserMessage.id,
|
||||
role: 'assistant',
|
||||
data: { blocks: [] },
|
||||
status: 'pending',
|
||||
siblingsGroupId: 0,
|
||||
assistantId: assistant.id,
|
||||
modelId: assistant.model?.id,
|
||||
traceId: userMessage.traceId ?? undefined
|
||||
traceId: finalUserMessage.traceId ?? undefined
|
||||
}
|
||||
|
||||
const sharedMessage = await dataApiService.post(`/topics/${topicId}/messages`, { body: createDto })
|
||||
|
||||
Loading…
Reference in New Issue
Block a user