diff --git a/packages/cli/src/modules/chat-hub/chat-hub.controller.ts b/packages/cli/src/modules/chat-hub/chat-hub.controller.ts index f242ad9201c..726cfb40a72 100644 --- a/packages/cli/src/modules/chat-hub/chat-hub.controller.ts +++ b/packages/cli/src/modules/chat-hub/chat-hub.controller.ts @@ -119,7 +119,7 @@ export class ChatHubController { this.logger.debug(`Chat edit request received: ${JSON.stringify(payload)}`); try { - await this.chatService.editHumanMessage(res, req.user, { + await this.chatService.editMessage(res, req.user, { ...payload, sessionId, editId, diff --git a/packages/cli/src/modules/chat-hub/chat-hub.service.ts b/packages/cli/src/modules/chat-hub/chat-hub.service.ts index 4b73d72570c..7af3032734f 100644 --- a/packages/cli/src/modules/chat-hub/chat-hub.service.ts +++ b/packages/cli/src/modules/chat-hub/chat-hub.service.ts @@ -62,6 +62,7 @@ import type { import { ChatHubMessageRepository } from './chat-message.repository'; import { ChatHubSessionRepository } from './chat-session.repository'; import { getMaxContextWindowTokens } from './context-limits'; +import { ChatHubSession } from './chat-hub-session.entity'; const providerNodeTypeMapping: Record = { openai: { @@ -404,8 +405,8 @@ export class ChatHubService { } } - async editHumanMessage(res: Response, user: User, payload: EditMessagePayload) { - const { sessionId, editId, messageId, message, replyId } = payload; + async editMessage(res: Response, user: User, payload: EditMessagePayload) { + const { sessionId, editId } = payload; const selectedModel: ModelWithCredentials = { ...payload.model, credentialId: this.getCredentialId(payload.model.provider, payload.credentials), @@ -414,10 +415,24 @@ export class ChatHubService { const session = await this.getChatSession(user, sessionId); const messageToEdit = await this.getChatMessage(session.id, editId); - if (messageToEdit.type !== 'human') { - throw new BadRequestError('Can only edit human messages'); + if (messageToEdit.type === 'human') { + await this.editHumanMessage(res, user, payload, session, messageToEdit, selectedModel); + } else if (messageToEdit.type === 'ai') { + await this.editAIMessage(payload.message, editId); + } else { + throw new BadRequestError('Only human and AI messages can be edited'); } + } + private async editHumanMessage( + res: Response, + user: User, + payload: EditMessagePayload, + session: ChatHubSession, + messageToEdit: ChatHubMessage, + selectedModel: ModelWithCredentials, + ) { + const { sessionId, messageId, message, replyId } = payload; const messages = Object.fromEntries((session.messages ?? []).map((m) => [m.id, m])); const history = this.buildMessageHistory(messages, messageToEdit.previousMessageId); @@ -456,6 +471,11 @@ export class ChatHubService { } } + private async editAIMessage(content: string, messageId: ChatMessageId) { + // AI edits just change the original message without revisioning + await this.messageRepository.updateChatMessage(messageId, { content }); + } + async regenerateAIMessage(res: Response, user: User, payload: RegenerateMessagePayload) { const { sessionId, retryId, replyId } = payload; diff --git a/packages/cli/src/modules/chat-hub/chat-message.repository.ts b/packages/cli/src/modules/chat-hub/chat-message.repository.ts index b8142e4192a..0f7ad06afcd 100644 --- a/packages/cli/src/modules/chat-hub/chat-message.repository.ts +++ b/packages/cli/src/modules/chat-hub/chat-message.repository.ts @@ -26,7 +26,7 @@ export class ChatHubMessageRepository extends Repository { async updateChatMessage( id: ChatMessageId, - fields: { status: ChatHubMessageStatus; content?: string }, + fields: Partial<{ status: ChatHubMessageStatus; content: string }>, trx?: EntityManager, ) { return await withTransaction(this.manager, trx, async (em) => { diff --git a/packages/frontend/editor-ui/src/features/ai/chatHub/ChatView.vue b/packages/frontend/editor-ui/src/features/ai/chatHub/ChatView.vue index 2243212cc70..5c1079ea7ba 100644 --- a/packages/frontend/editor-ui/src/features/ai/chatHub/ChatView.vue +++ b/packages/frontend/editor-ui/src/features/ai/chatHub/ChatView.vue @@ -263,7 +263,7 @@ function handleCancelEditMessage() { } function handleEditMessage(message: ChatHubMessageDto) { - if (chatStore.isResponding || message.type !== 'human' || !selectedModel.value) { + if (chatStore.isResponding || !['human', 'ai'].includes(message.type) || !selectedModel.value) { return; } diff --git a/packages/frontend/editor-ui/src/features/ai/chatHub/chat.store.ts b/packages/frontend/editor-ui/src/features/ai/chatHub/chat.store.ts index c152847c000..7f712e93ded 100644 --- a/packages/frontend/editor-ui/src/features/ai/chatHub/chat.store.ts +++ b/packages/frontend/editor-ui/src/features/ai/chatHub/chat.store.ts @@ -187,6 +187,20 @@ export const useChatStore = defineStore(CHAT_STORE, () => { conversation.activeMessageChain = computeActiveChain(conversation.messages, message.id); } + function replaceMessageContent( + sessionId: ChatSessionId, + messageId: ChatMessageId, + content: string, + ) { + const conversation = ensureConversation(sessionId); + const message = conversation.messages[messageId]; + if (!message) { + throw new Error(`Message with ID ${messageId} not found in session ${sessionId}`); + } + + message.content = content; + } + function appendMessage(sessionId: ChatSessionId, messageId: ChatMessageId, chunk: string) { const conversation = ensureConversation(sessionId); const message = conversation.messages[messageId]; @@ -392,7 +406,7 @@ export const useChatStore = defineStore(CHAT_STORE, () => { function editMessage( sessionId: ChatSessionId, editId: ChatMessageId, - message: string, + content: string, model: ChatHubConversationModel, credentials: ChatHubSendMessageRequest['credentials'], ) { @@ -400,27 +414,32 @@ export const useChatStore = defineStore(CHAT_STORE, () => { const replyId = uuidv4(); const conversation = ensureConversation(sessionId); - const previousMessageId = conversation.messages[editId]?.previousMessageId ?? null; + const message = conversation.messages[editId]; + const previousMessageId = message?.previousMessageId ?? null; - addMessage(sessionId, { - id: messageId, - sessionId, - type: 'human', - name: 'User', - content: message, - provider: null, - model: null, - workflowId: null, - executionId: null, - status: 'success', - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - previousMessageId, - retryOfMessageId: null, - revisionOfMessageId: editId, - responses: [], - alternatives: [], - }); + if (message?.type === 'human') { + addMessage(sessionId, { + id: messageId, + sessionId, + type: 'human', + name: message.name ?? 'User', + content, + provider: null, + model: null, + workflowId: null, + executionId: null, + status: 'success', + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + previousMessageId, + retryOfMessageId: null, + revisionOfMessageId: editId, + responses: [], + alternatives: [], + }); + } else if (message?.type === 'ai') { + replaceMessageContent(sessionId, editId, content); + } editMessageApi( rootStore.restApiContext, @@ -430,7 +449,7 @@ export const useChatStore = defineStore(CHAT_STORE, () => { model, messageId, replyId, - message, + message: content, credentials, }, (chunk: StructuredChunk) => onStreamMessage(sessionId, chunk, replyId, messageId, null), diff --git a/packages/frontend/editor-ui/src/features/ai/chatHub/components/ChatMessage.vue b/packages/frontend/editor-ui/src/features/ai/chatHub/components/ChatMessage.vue index 34bdcbc79f9..2814cf3187e 100644 --- a/packages/frontend/editor-ui/src/features/ai/chatHub/components/ChatMessage.vue +++ b/packages/frontend/editor-ui/src/features/ai/chatHub/components/ChatMessage.vue @@ -72,7 +72,7 @@ function handleCancelEdit() { } function handleConfirmEdit() { - if (message.type === 'ai' || !editedText.value.trim()) { + if (!editedText.value.trim()) { return; }