mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-01 01:07:04 +02:00
feat(core): Make it possible to edit AI messages (no-changelog) (#21000)
This commit is contained in:
parent
13614542eb
commit
ef9e32b27a
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<ChatHubProvider, INodeTypeNameVersion> = {
|
||||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export class ChatHubMessageRepository extends Repository<ChatHubMessage> {
|
|||
|
||||
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) => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ function handleCancelEdit() {
|
|||
}
|
||||
|
||||
function handleConfirmEdit() {
|
||||
if (message.type === 'ai' || !editedText.value.trim()) {
|
||||
if (!editedText.value.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user