mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-29 15:57:00 +02:00
fix(ai-builder): Hide the excute and refine dialog in the workflow builder if task was aborted (#21355)
This commit is contained in:
parent
589c072b67
commit
f79d968151
|
|
@ -4,7 +4,7 @@ import { computed, nextTick, onUnmounted, ref, useCssModule, watch } from 'vue';
|
|||
import MessageWrapper from './messages/MessageWrapper.vue';
|
||||
import { useI18n } from '../../composables/useI18n';
|
||||
import type { ChatUI, RatingFeedback, WorkflowSuggestion } from '../../types/assistant';
|
||||
import { isToolMessage } from '../../types/assistant';
|
||||
import { isTaskAbortedMessage, isToolMessage } from '../../types/assistant';
|
||||
import AssistantIcon from '../AskAssistantIcon/AssistantIcon.vue';
|
||||
import AssistantLoadingMessage from '../AskAssistantLoadingMessage/AssistantLoadingMessage.vue';
|
||||
import AssistantText from '../AskAssistantText/AssistantText.vue';
|
||||
|
|
@ -374,11 +374,8 @@ function getMessageStyles(message: ChatUI.AssistantMessage, messageCount: number
|
|||
}
|
||||
|
||||
function getMessageColor(message: ChatUI.AssistantMessage): string | undefined {
|
||||
if (message.type === 'text' && message.role === 'assistant') {
|
||||
const isTaskAbortedMessage = message.content === t('aiAssistant.builder.streamAbortedMessage');
|
||||
if (isTaskAbortedMessage) {
|
||||
return 'var(--color--text)';
|
||||
}
|
||||
if (isTaskAbortedMessage(message)) {
|
||||
return 'var(--color--text)';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ export namespace ChatUI {
|
|||
codeSnippet?: string;
|
||||
}
|
||||
|
||||
export interface TaskAbortedMessage extends Omit<TextMessage, 'role' | 'codeSnippet'> {
|
||||
role: 'assistant';
|
||||
aborted: true;
|
||||
}
|
||||
|
||||
export interface SummaryBlock {
|
||||
role: 'assistant';
|
||||
type: 'block';
|
||||
|
|
@ -107,6 +112,7 @@ export namespace ChatUI {
|
|||
|
||||
export type AssistantMessage = (
|
||||
| TextMessage
|
||||
| TaskAbortedMessage
|
||||
| MessagesWithReplies
|
||||
| ErrorMessage
|
||||
| EndSessionMessage
|
||||
|
|
@ -131,7 +137,13 @@ export type RatingFeedback = { rating?: 'up' | 'down'; feedback?: string };
|
|||
export function isTextMessage(
|
||||
msg: ChatUI.AssistantMessage,
|
||||
): msg is ChatUI.TextMessage & { id?: string; read?: boolean; quickReplies?: ChatUI.QuickReply[] } {
|
||||
return msg.type === 'text';
|
||||
return msg.type === 'text' && !('aborted' in msg);
|
||||
}
|
||||
|
||||
export function isTaskAbortedMessage(
|
||||
msg: ChatUI.AssistantMessage,
|
||||
): msg is ChatUI.TaskAbortedMessage & { id?: string; read?: boolean } {
|
||||
return msg.type === 'text' && 'aborted' in msg && msg.aborted;
|
||||
}
|
||||
|
||||
export function isSummaryBlock(msg: ChatUI.AssistantMessage): msg is ChatUI.SummaryBlock & {
|
||||
|
|
|
|||
|
|
@ -647,9 +647,9 @@ describe('AI Builder store', () => {
|
|||
expect(builderStore.chatMessages[0].role).toBe('user');
|
||||
expect(builderStore.chatMessages[1].role).toBe('assistant');
|
||||
expect(builderStore.chatMessages[1].type).toBe('text');
|
||||
expect((builderStore.chatMessages[1] as ChatUI.TextMessage).content).toBe(
|
||||
'aiAssistant.builder.streamAbortedMessage',
|
||||
);
|
||||
const abortedMessage = builderStore.chatMessages[1] as ChatUI.TaskAbortedMessage;
|
||||
expect(abortedMessage.content).toBe('aiAssistant.builder.streamAbortedMessage');
|
||||
expect(abortedMessage.aborted).toBe(true);
|
||||
|
||||
// Verify streaming state was reset
|
||||
expect(builderStore.streaming).toBe(false);
|
||||
|
|
@ -774,9 +774,9 @@ describe('AI Builder store', () => {
|
|||
const assistantMessages = builderStore.chatMessages.filter((msg) => msg.role === 'assistant');
|
||||
expect(assistantMessages).toHaveLength(1);
|
||||
expect(assistantMessages[0].type).toBe('text');
|
||||
expect((assistantMessages[0] as ChatUI.TextMessage).content).toBe(
|
||||
'aiAssistant.builder.streamAbortedMessage',
|
||||
);
|
||||
const abortedMessage = assistantMessages[0] as ChatUI.TaskAbortedMessage;
|
||||
expect(abortedMessage.content).toBe('aiAssistant.builder.streamAbortedMessage');
|
||||
expect(abortedMessage.aborted).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ export const useBuilderStore = defineStore(STORES.BUILDER, () => {
|
|||
const userMsg = createAssistantMessage(
|
||||
locale.baseText('aiAssistant.builder.streamAbortedMessage'),
|
||||
'aborted-streaming',
|
||||
{ aborted: true },
|
||||
);
|
||||
chatMessages.value = [...chatMessages.value, userMsg];
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -675,8 +675,7 @@ describe('AskAssistantBuild', () => {
|
|||
},
|
||||
});
|
||||
|
||||
// User cancels generation - this adds a locale message for aborted task
|
||||
// In tests, i18n.baseText returns the key itself
|
||||
// User cancels generation - this adds an aborted message
|
||||
builderStore.$patch({
|
||||
chatMessages: [
|
||||
{ id: '1', role: 'user', type: 'text', content: testMessage },
|
||||
|
|
@ -684,7 +683,8 @@ describe('AskAssistantBuild', () => {
|
|||
id: '2',
|
||||
role: 'assistant',
|
||||
type: 'text',
|
||||
content: 'aiAssistant.builder.streamAbortedMessage',
|
||||
content: 'Task aborted',
|
||||
aborted: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -1025,7 +1025,6 @@ describe('AskAssistantBuild', () => {
|
|||
});
|
||||
|
||||
// Add cancellation message to chat
|
||||
// In tests, i18n.baseText returns the key itself
|
||||
builderStore.$patch({
|
||||
chatMessages: [
|
||||
{ id: '1', role: 'user', type: 'text', content: 'Create workflow from canvas' },
|
||||
|
|
@ -1033,7 +1032,8 @@ describe('AskAssistantBuild', () => {
|
|||
id: '2',
|
||||
role: 'assistant',
|
||||
type: 'text',
|
||||
content: 'aiAssistant.builder.streamAbortedMessage',
|
||||
content: 'Task aborted',
|
||||
aborted: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -1299,6 +1299,53 @@ describe('AskAssistantBuild', () => {
|
|||
// Verify the ExecuteMessage component should NOT be rendered
|
||||
expect(queryByTestId('execute-message-component')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should hide ExecuteMessage component when task is aborted after workflow update', async () => {
|
||||
// Setup: workflow with nodes
|
||||
workflowsStore.$patch({
|
||||
workflow: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1',
|
||||
name: 'Start',
|
||||
type: 'n8n-nodes-base.start',
|
||||
position: [0, 0],
|
||||
typeVersion: 1,
|
||||
parameters: {},
|
||||
} as INodeUi,
|
||||
],
|
||||
connections: {},
|
||||
},
|
||||
});
|
||||
|
||||
const { queryByTestId } = renderComponent();
|
||||
|
||||
// Simulate workflow update message followed by task aborted message
|
||||
builderStore.$patch({
|
||||
streaming: false,
|
||||
chatMessages: [
|
||||
{ id: '1', role: 'user', type: 'text', content: 'Create a workflow' },
|
||||
{
|
||||
id: '2',
|
||||
role: 'assistant',
|
||||
type: 'workflow-updated',
|
||||
codeSnippet: JSON.stringify({ nodes: [], connections: {} }),
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
role: 'assistant',
|
||||
type: 'text',
|
||||
content: 'Task aborted',
|
||||
aborted: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// Verify the ExecuteMessage component should NOT be rendered
|
||||
expect(queryByTestId('execute-message-component')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple canvas generations correctly', async () => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
|||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useWorkflowSaving } from '@/composables/useWorkflowSaving';
|
||||
import type { RatingFeedback, WorkflowSuggestion } from '@n8n/design-system/types/assistant';
|
||||
import { isWorkflowUpdatedMessage } from '@n8n/design-system/types/assistant';
|
||||
import { isTaskAbortedMessage, isWorkflowUpdatedMessage } from '@n8n/design-system/types/assistant';
|
||||
import { nodeViewEventBus } from '@/event-bus';
|
||||
import ExecuteMessage from './ExecuteMessage.vue';
|
||||
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||
|
|
@ -61,16 +61,19 @@ const showExecuteMessage = computed(() => {
|
|||
(msg.type === 'tool' && msg.toolName === 'update_node_parameters'),
|
||||
);
|
||||
|
||||
// Check if there's an error message after the last workflow update
|
||||
const hasErrorAfterUpdate = builderStore.chatMessages
|
||||
.slice(builderUpdatedWorkflowMessageIndex + 1)
|
||||
.some((msg) => msg.type === 'error');
|
||||
// Check if there's an error message or task aborted message after the last workflow update
|
||||
const messagesAfterUpdate = builderStore.chatMessages.slice(
|
||||
builderUpdatedWorkflowMessageIndex + 1,
|
||||
);
|
||||
const hasErrorAfterUpdate = messagesAfterUpdate.some((msg) => msg.type === 'error');
|
||||
const hasTaskAbortedAfterUpdate = messagesAfterUpdate.some((msg) => isTaskAbortedMessage(msg));
|
||||
|
||||
return (
|
||||
!builderStore.streaming &&
|
||||
workflowsStore.workflow.nodes.length > 0 &&
|
||||
builderUpdatedWorkflowMessageIndex > -1 &&
|
||||
!hasErrorAfterUpdate
|
||||
!hasErrorAfterUpdate &&
|
||||
!hasTaskAbortedAfterUpdate
|
||||
);
|
||||
});
|
||||
const creditsQuota = computed(() => builderStore.creditsQuota);
|
||||
|
|
@ -251,12 +254,7 @@ watch(
|
|||
// Check if the generation completed successfully (no error or cancellation)
|
||||
const lastMessage = builderStore.chatMessages[builderStore.chatMessages.length - 1];
|
||||
const successful =
|
||||
lastMessage &&
|
||||
lastMessage.type !== 'error' &&
|
||||
!(
|
||||
lastMessage.type === 'text' &&
|
||||
lastMessage.content === i18n.baseText('aiAssistant.builder.streamAbortedMessage')
|
||||
);
|
||||
lastMessage && lastMessage.type !== 'error' && !isTaskAbortedMessage(lastMessage);
|
||||
|
||||
builderStore.initialGeneration = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -318,7 +318,31 @@ export function useBuilderMessages() {
|
|||
};
|
||||
}
|
||||
|
||||
function createAssistantMessage(content: string, id: string): ChatUI.AssistantMessage {
|
||||
function createAssistantMessage(
|
||||
content: string,
|
||||
id: string,
|
||||
options: { aborted: true },
|
||||
): ChatUI.TaskAbortedMessage;
|
||||
function createAssistantMessage(
|
||||
content: string,
|
||||
id: string,
|
||||
options?: { aborted?: false },
|
||||
): ChatUI.TextMessage;
|
||||
function createAssistantMessage(
|
||||
content: string,
|
||||
id: string,
|
||||
options?: { aborted?: boolean },
|
||||
): ChatUI.AssistantMessage {
|
||||
if (options?.aborted) {
|
||||
return {
|
||||
id,
|
||||
role: 'assistant',
|
||||
type: 'text',
|
||||
content,
|
||||
read: true,
|
||||
aborted: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
id,
|
||||
role: 'assistant',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user