mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-30 00:07:02 +02:00
feat: Make deleting and renaming conversations work end-to-end (no-changelog) (#20846)
This commit is contained in:
parent
ff309d249b
commit
aa1df2b568
|
|
@ -89,6 +89,10 @@ export class ChatHubEditMessageRequest extends Z.class({
|
|||
),
|
||||
}) {}
|
||||
|
||||
export class ChatHubChangeConversationTitleRequest extends Z.class({
|
||||
title: z.string(),
|
||||
}) {}
|
||||
|
||||
export type ChatHubMessageType = 'human' | 'ai' | 'system' | 'tool' | 'generic';
|
||||
export type ChatHubMessageState = 'active' | 'replaced';
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export {
|
|||
ChatHubSendMessageRequest,
|
||||
ChatHubRegenerateMessageRequest,
|
||||
ChatHubEditMessageRequest,
|
||||
ChatHubChangeConversationTitleRequest,
|
||||
type ChatMessageId,
|
||||
type ChatSessionId,
|
||||
type ChatHubMessageDto,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ import {
|
|||
ChatHubConversationResponse,
|
||||
ChatHubEditMessageRequest,
|
||||
ChatHubRegenerateMessageRequest,
|
||||
ChatHubChangeConversationTitleRequest,
|
||||
} from '@n8n/api-types';
|
||||
import { Logger } from '@n8n/backend-common';
|
||||
import { AuthenticatedRequest } from '@n8n/db';
|
||||
import { RestController, Post, Body, GlobalScope, Get } from '@n8n/decorators';
|
||||
import { RestController, Post, Body, GlobalScope, Get, Delete } from '@n8n/decorators';
|
||||
import type { Response } from 'express';
|
||||
import { strict as assert } from 'node:assert';
|
||||
|
||||
|
|
@ -184,4 +185,25 @@ export class ChatHubController {
|
|||
if (!res.writableEnded) res.end();
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/conversations/:id/rename')
|
||||
@GlobalScope('chatHub:message')
|
||||
async updateConversationTitle(
|
||||
req: AuthenticatedRequest<{ id: string }>,
|
||||
_res: Response,
|
||||
@Body payload: ChatHubChangeConversationTitleRequest,
|
||||
): Promise<ChatHubConversationResponse> {
|
||||
await this.chatService.updateSessionTitle(req.user.id, req.params.id, payload.title);
|
||||
|
||||
return await this.chatService.getConversation(req.user.id, req.params.id);
|
||||
}
|
||||
|
||||
@Delete('/conversations/:id')
|
||||
@GlobalScope('chatHub:message')
|
||||
async deleteConversation(
|
||||
req: AuthenticatedRequest<{ id: string }>,
|
||||
_res: Response,
|
||||
): Promise<void> {
|
||||
await this.chatService.deleteSession(req.user.id, req.params.id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -943,7 +943,32 @@ export class ChatHubService {
|
|||
|
||||
async deleteAllSessions() {
|
||||
const result = await this.sessionRepository.deleteAll();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the title of a session
|
||||
*/
|
||||
async updateSessionTitle(userId: string, sessionId: ChatSessionId, title: string) {
|
||||
const session = await this.sessionRepository.getOneById(sessionId, userId);
|
||||
|
||||
if (!session) {
|
||||
throw new NotFoundError('Session not found');
|
||||
}
|
||||
|
||||
return await this.sessionRepository.updateChatTitle(sessionId, title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a session
|
||||
*/
|
||||
async deleteSession(userId: string, sessionId: ChatSessionId) {
|
||||
const session = await this.sessionRepository.getOneById(sessionId, userId);
|
||||
|
||||
if (!session) {
|
||||
throw new NotFoundError('Session not found');
|
||||
}
|
||||
|
||||
await this.sessionRepository.deleteChatHubSession(sessionId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import type {
|
|||
ChatHubConversationResponse,
|
||||
ChatHubRegenerateMessageRequest,
|
||||
ChatHubEditMessageRequest,
|
||||
ChatSessionId,
|
||||
} from '@n8n/api-types';
|
||||
import type { StructuredChunk } from './chat.types';
|
||||
|
||||
|
|
@ -83,9 +84,28 @@ export const fetchConversationsApi = async (
|
|||
return await makeRestApiRequest<ChatHubConversationsResponse>(context, 'GET', apiEndpoint);
|
||||
};
|
||||
|
||||
export const updateConversationTitleApi = async (
|
||||
context: IRestApiContext,
|
||||
conversationId: ChatSessionId,
|
||||
title: string,
|
||||
): Promise<ChatHubConversationResponse> => {
|
||||
const apiEndpoint = `/chat/conversations/${conversationId}/rename`;
|
||||
return await makeRestApiRequest<ChatHubConversationResponse>(context, 'POST', apiEndpoint, {
|
||||
title,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteConversationApi = async (
|
||||
context: IRestApiContext,
|
||||
conversationId: ChatSessionId,
|
||||
): Promise<void> => {
|
||||
const apiEndpoint = `/chat/conversations/${conversationId}`;
|
||||
await makeRestApiRequest(context, 'DELETE', apiEndpoint);
|
||||
};
|
||||
|
||||
export const fetchSingleConversationApi = async (
|
||||
context: IRestApiContext,
|
||||
conversationId: string,
|
||||
conversationId: ChatSessionId,
|
||||
): Promise<ChatHubConversationResponse> => {
|
||||
const apiEndpoint = `/chat/conversations/${conversationId}`;
|
||||
return await makeRestApiRequest<ChatHubConversationResponse>(context, 'GET', apiEndpoint);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {
|
|||
regenerateMessageApi,
|
||||
fetchConversationsApi as fetchSessionsApi,
|
||||
fetchSingleConversationApi as fetchMessagesApi,
|
||||
updateConversationTitleApi,
|
||||
deleteConversationApi,
|
||||
} from './chat.api';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import type {
|
||||
|
|
@ -417,20 +419,18 @@ export const useChatStore = defineStore(CHAT_STORE, () => {
|
|||
);
|
||||
}
|
||||
|
||||
async function renameSession(sessionId: string, name: string) {
|
||||
// Optimistic update
|
||||
sessions.value = sessions.value.map((session) =>
|
||||
session.id === sessionId ? { ...session, title: name } : session,
|
||||
);
|
||||
async function renameSession(sessionId: ChatSessionId, title: string) {
|
||||
const updated = await updateConversationTitleApi(rootStore.restApiContext, sessionId, title);
|
||||
|
||||
// TODO: call the endpoint
|
||||
sessions.value = sessions.value.map((session) =>
|
||||
session.id === sessionId ? updated.session : session,
|
||||
);
|
||||
}
|
||||
|
||||
async function deleteSession(sessionId: string) {
|
||||
// Optimistic update
|
||||
sessions.value = sessions.value.filter((session) => session.id !== sessionId);
|
||||
async function deleteSession(sessionId: ChatSessionId) {
|
||||
await deleteConversationApi(rootStore.restApiContext, sessionId);
|
||||
|
||||
// TODO: call the endpoint
|
||||
sessions.value = sessions.value.filter((session) => session.id !== sessionId);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -53,8 +53,12 @@ function handleCancelRename() {
|
|||
}
|
||||
|
||||
async function handleConfirmRename(sessionId: string, newLabel: string) {
|
||||
await chatStore.renameSession(sessionId, newLabel);
|
||||
renamingSessionId.value = undefined;
|
||||
try {
|
||||
await chatStore.renameSession(sessionId, newLabel);
|
||||
renamingSessionId.value = undefined;
|
||||
} catch (error) {
|
||||
toast.showError(error, 'Could not update the conversation title.');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteSession(sessionId: string) {
|
||||
|
|
@ -71,8 +75,16 @@ async function handleDeleteSession(sessionId: string) {
|
|||
return;
|
||||
}
|
||||
|
||||
await chatStore.deleteSession(sessionId);
|
||||
toast.showMessage({ type: 'success', title: 'Conversation is deleted' });
|
||||
try {
|
||||
await chatStore.deleteSession(sessionId);
|
||||
toast.showMessage({ type: 'success', title: 'Conversation is deleted' });
|
||||
|
||||
if (sessionId === currentSessionId.value) {
|
||||
void router.push({ name: CHAT_VIEW });
|
||||
}
|
||||
} catch (error) {
|
||||
toast.showError(error, 'Could not delete the conversation');
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user