feat: Add planning step to AI workflow builder (no-changelog) (#18737)

Co-authored-by: Eugene Molodkin <eugene@n8n.io>
This commit is contained in:
oleg
2025-09-01 16:28:19 +02:00
committed by GitHub
parent caeaa679c6
commit 94f0048f02
31 changed files with 2977 additions and 1281 deletions

View File

@@ -24,6 +24,7 @@ interface Props {
};
messages?: ChatUI.AssistantMessage[];
streaming?: boolean;
disabled?: boolean;
loadingMessage?: string;
sessionId?: string;
title?: string;
@@ -66,7 +67,9 @@ function normalizeMessages(messages: ChatUI.AssistantMessage[]): ChatUI.Assistan
// filter out these messages so that tool collapsing works correctly
function filterOutHiddenMessages(messages: ChatUI.AssistantMessage[]): ChatUI.AssistantMessage[] {
return messages.filter((message) => Boolean(getSupportedMessageComponent(message.type)));
return messages.filter(
(message) => Boolean(getSupportedMessageComponent(message.type)) || message.type === 'custom',
);
}
function collapseToolMessages(messages: ChatUI.AssistantMessage[]): ChatUI.AssistantMessage[] {
@@ -165,7 +168,7 @@ const sessionEnded = computed(() => {
});
const sendDisabled = computed(() => {
return !textInputValue.value || props.streaming || sessionEnded.value;
return !textInputValue.value || props.streaming || sessionEnded.value || props.disabled;
});
const showPlaceholder = computed(() => {
@@ -226,6 +229,13 @@ watch(
},
{ immediate: true, deep: true },
);
// Expose focusInput method to parent components
defineExpose({
focusInput: () => {
chatInput.value?.focus();
},
});
</script>
<template>
@@ -265,7 +275,11 @@ watch(
@code-replace="() => emit('codeReplace', i)"
@code-undo="() => emit('codeUndo', i)"
@feedback="onRateMessage"
/>
>
<template v-if="$slots['custom-message']" #custom-message="customMessageProps">
<slot name="custom-message" v-bind="customMessageProps" />
</template>
</MessageWrapper>
<div
v-if="lastMessageQuickReplies.length && i === normalizedMessages.length - 1"
@@ -336,8 +350,8 @@ watch(
ref="chatInput"
v-model="textInputValue"
class="ignore-key-press-node-creator ignore-key-press-canvas"
:class="{ [$style.disabled]: sessionEnded || streaming }"
:disabled="sessionEnded || streaming"
:class="{ [$style.disabled]: sessionEnded || streaming || disabled }"
:disabled="sessionEnded || streaming || disabled"
:placeholder="placeholder ?? t('assistantChat.inputPlaceholder')"
rows="1"
wrap="hard"

View File

@@ -31,16 +31,27 @@ const messageComponent = computed<Component | null>(() => {
</script>
<template>
<component
:is="messageComponent"
v-if="messageComponent"
:message="message"
:is-first-of-role="isFirstOfRole"
:user="user"
:streaming="streaming"
:is-last-message="isLastMessage"
@code-replace="emit('codeReplace')"
@code-undo="emit('codeUndo')"
@feedback="(feedback: RatingFeedback) => emit('feedback', feedback)"
/>
<div>
<component
:is="messageComponent"
v-if="messageComponent"
:message="message"
:is-first-of-role="isFirstOfRole"
:user="user"
:streaming="streaming"
:is-last-message="isLastMessage"
@code-replace="emit('codeReplace')"
@code-undo="emit('codeUndo')"
@feedback="(feedback: RatingFeedback) => emit('feedback', feedback)"
/>
<slot
v-else-if="message.type === 'custom'"
name="custom-message"
:message="message"
:is-first-of-role="isFirstOfRole"
:user="user"
:streaming="streaming"
:is-last-message="isLastMessage"
/>
</div>
</template>

View File

@@ -23,6 +23,7 @@ export function getSupportedMessageComponent(type: ChatUI.AssistantMessage['type
return ToolMessage;
case 'agent-suggestion':
case 'workflow-updated':
case 'custom':
return null;
default:
return null;

View File

@@ -87,6 +87,15 @@ export namespace ChatUI {
}>;
}
export interface CustomMessage {
id?: string;
role: 'assistant' | 'user';
type: 'custom';
message?: string;
customType: string;
data: unknown;
}
type MessagesWithReplies = (
| TextMessage
| CodeDiffMessage
@@ -106,6 +115,7 @@ export namespace ChatUI {
| AgentSuggestionMessage
| WorkflowUpdatedMessage
| ToolMessage
| CustomMessage
) & {
id?: string;
read?: boolean;
@@ -186,6 +196,12 @@ export function isToolMessage(
return msg.type === 'tool';
}
export function isCustomMessage(
msg: ChatUI.AssistantMessage,
): msg is ChatUI.CustomMessage & { id?: string; read?: boolean } {
return msg.type === 'custom';
}
// Helper to ensure message has required id and read properties
export function hasRequiredProps<T extends ChatUI.AssistantMessage>(
msg: T,