chore(editor): Clean up AI workflow builder header (#26037)

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Tuukka Kantola
2026-02-23 12:21:54 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 71194488aa
commit 0c29bb038f
6 changed files with 96 additions and 244 deletions
@@ -11,7 +11,7 @@ import AssistantIcon from '../AskAssistantIcon/AssistantIcon.vue';
import AssistantText from '../AskAssistantText/AssistantText.vue';
import InlineAskAssistantButton from '../InlineAskAssistantButton/InlineAskAssistantButton.vue';
import N8nButton from '../N8nButton';
import N8nIcon from '../N8nIcon';
import N8nIconButton from '../N8nIconButton';
import N8nPromptInput from '../N8nPromptInput';
import N8nPromptInputSuggestions from '../N8nPromptInputSuggestions';
import N8nScrollArea from '../N8nScrollArea/N8nScrollArea.vue';
@@ -465,13 +465,16 @@ defineExpose({
<AssistantIcon size="large" />
<AssistantText size="large" :text="t('assistantChat.aiAssistantLabel')" />
</div>
<span :class="$style.betaTag">{{ t('assistantChat.aiAssistantBetaLabel') }}</span>
</div>
<slot name="header" />
</div>
<div :class="$style.back" data-test-id="close-chat-button" @click="onClose">
<N8nIcon icon="arrow-right" color="text-base" />
</div>
<N8nIconButton
icon="x"
variant="ghost"
size="large"
data-test-id="close-chat-button"
@click="onClose"
/>
</div>
<div :class="$style.body">
<div v-if="normalizedMessages?.length || loadingMessage" :class="$style.messages">
@@ -708,11 +711,12 @@ defineExpose({
.header {
height: 65px; // same as header height in editor
padding: 0 var(--spacing--lg);
padding: 0 var(--spacing--sm) 0 var(--spacing--lg);
background-color: var(--color--background--light-3);
border: var(--border);
border-top: 0;
display: flex;
align-items: center;
div {
display: flex;
@@ -724,12 +728,6 @@ defineExpose({
}
}
.betaTag {
color: var(--color--text);
font-size: var(--font-size--2xs);
font-weight: var(--font-weight--bold);
}
.body {
background-color: var(--color--background--light-2);
border: var(--border);
@@ -830,10 +828,6 @@ defineExpose({
}
}
.back:hover {
cursor: pointer;
}
.quickReplies {
margin-top: var(--spacing--sm);
@@ -26,25 +26,20 @@ exports[`AskAssistantChat > limits maximum input length when maxCharacterLength
text="assistantChat.aiAssistantLabel"
/>
</div>
<span
class="betaTag"
>
assistantChat.aiAssistantBetaLabel
</span>
</div>
</div>
<div
class="back"
<n8n-icon-button-stub
class=""
data-test-id="close-chat-button"
>
<n8n-icon-stub
color="text-base"
icon="arrow-right"
spin="false"
/>
</div>
disabled="false"
icon="x"
icononly="true"
loading="false"
size="large"
variant="ghost"
/>
</div>
<div
class="body"
@@ -131,25 +126,20 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
text="assistantChat.aiAssistantLabel"
/>
</div>
<span
class="betaTag"
>
assistantChat.aiAssistantBetaLabel
</span>
</div>
</div>
<div
class="back"
<n8n-icon-button-stub
class=""
data-test-id="close-chat-button"
>
<n8n-icon-stub
color="text-base"
icon="arrow-right"
spin="false"
/>
</div>
disabled="false"
icon="x"
icononly="true"
loading="false"
size="large"
variant="ghost"
/>
</div>
<div
class="body"
@@ -379,25 +369,20 @@ exports[`AskAssistantChat > renders default placeholder chat correctly 1`] = `
text="assistantChat.aiAssistantLabel"
/>
</div>
<span
class="betaTag"
>
assistantChat.aiAssistantBetaLabel
</span>
</div>
</div>
<div
class="back"
<n8n-icon-button-stub
class=""
data-test-id="close-chat-button"
>
<n8n-icon-stub
color="text-base"
icon="arrow-right"
spin="false"
/>
</div>
disabled="false"
icon="x"
icononly="true"
loading="false"
size="large"
variant="ghost"
/>
</div>
<div
class="body"
@@ -484,25 +469,20 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
text="assistantChat.aiAssistantLabel"
/>
</div>
<span
class="betaTag"
>
assistantChat.aiAssistantBetaLabel
</span>
</div>
</div>
<div
class="back"
<n8n-icon-button-stub
class=""
data-test-id="close-chat-button"
>
<n8n-icon-stub
color="text-base"
icon="arrow-right"
spin="false"
/>
</div>
disabled="false"
icon="x"
icononly="true"
loading="false"
size="large"
variant="ghost"
/>
</div>
<div
class="body"
@@ -641,25 +621,20 @@ exports[`AskAssistantChat > renders error message correctly with retry button 1`
text="assistantChat.aiAssistantLabel"
/>
</div>
<span
class="betaTag"
>
assistantChat.aiAssistantBetaLabel
</span>
</div>
</div>
<div
class="back"
<n8n-icon-button-stub
class=""
data-test-id="close-chat-button"
>
<n8n-icon-stub
color="text-base"
icon="arrow-right"
spin="false"
/>
</div>
disabled="false"
icon="x"
icononly="true"
loading="false"
size="large"
variant="ghost"
/>
</div>
<div
class="body"
@@ -779,25 +754,20 @@ exports[`AskAssistantChat > renders message with code snippet 1`] = `
text="assistantChat.aiAssistantLabel"
/>
</div>
<span
class="betaTag"
>
assistantChat.aiAssistantBetaLabel
</span>
</div>
</div>
<div
class="back"
<n8n-icon-button-stub
class=""
data-test-id="close-chat-button"
>
<n8n-icon-stub
color="text-base"
icon="arrow-right"
spin="false"
/>
</div>
disabled="false"
icon="x"
icononly="true"
loading="false"
size="large"
variant="ghost"
/>
</div>
<div
class="body"
@@ -917,25 +887,20 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = `
text="assistantChat.aiAssistantLabel"
/>
</div>
<span
class="betaTag"
>
assistantChat.aiAssistantBetaLabel
</span>
</div>
</div>
<div
class="back"
<n8n-icon-button-stub
class=""
data-test-id="close-chat-button"
>
<n8n-icon-stub
color="text-base"
icon="arrow-right"
spin="false"
/>
</div>
disabled="false"
icon="x"
icononly="true"
loading="false"
size="large"
variant="ghost"
/>
</div>
<div
class="body"
@@ -247,7 +247,7 @@
"aiAssistant.assistant": "Assistant",
"aiAssistant.tabs.ask": "Ask",
"aiAssistant.tabs.build": "Build",
"aiAssistant.reducedHelp.chat.notice": "You have opted not to share actual data values. As a result, AI responses will be less accurate and context-aware.",
"aiAssistant.reducedHelp.chat.notice": "Your admin turned off sending actual data values to the AI. Responses may be less accurate.",
"aiAssistant.tabs.builder.disabled.tooltip": "AI Builder is disabled because sending data values to AI is turned off. Enable it in AI Usage settings to use AI Builder.",
"aiAssistant.builder.mode": "AI Builder",
"aiAssistant.builder.placeholder": "Ask n8n to build...",
@@ -30,13 +30,12 @@ import { canvasEventBus } from '@/features/workflows/canvas/canvas.eventBus';
import { useErrorHandler } from '@/app/composables/useErrorHandler';
import type { WorkflowDataUpdate } from '@n8n/rest-api-client/api/workflows';
import shuffle from 'lodash/shuffle';
import AISettingsButton from '@/features/ai/assistant/components/Chat/AISettingsButton.vue';
import { useAssistantStore } from '@/features/ai/assistant/assistant.store';
import { useSettingsStore } from '@/app/stores/settings.store';
import { useChatPanelStateStore } from '@/features/ai/assistant/chatPanelState.store';
import { useReviewChanges } from '@/features/ai/assistant/composables/useReviewChanges';
import { N8nAskAssistantChat } from '@n8n/design-system';
import { N8nAskAssistantChat, N8nInfoTip } from '@n8n/design-system';
import BuildModeEmptyState from './BuildModeEmptyState.vue';
import {
isPlanModePlanMessage,
@@ -59,11 +58,11 @@ const workflowHistoryStore = useWorkflowHistoryStore();
const historyStore = useHistoryStore();
const collaborationStore = useCollaborationStore();
const workflowAutosaveStore = useWorkflowSaveStore();
const settingsStore = useSettingsStore();
const telemetry = useTelemetry();
const slots = useSlots();
const workflowsStore = useWorkflowsStore();
const assistantStore = useAssistantStore();
const settingsStore = useSettingsStore();
const chatPanelStateStore = useChatPanelStateStore();
const router = useRouter();
const i18n = useI18n();
@@ -101,12 +100,9 @@ watch(
},
);
const showSettingsButton = computed(() => {
return assistantStore.canManageAISettings;
});
const allowSendingParameterValues = computed(
() => settingsStore.settings.ai.allowSendingParameterValues,
const showUsabilityNotice = computed(
() =>
assistantStore.canManageAISettings && !settingsStore.settings.ai.allowSendingParameterValues,
);
const shouldShowNotificationBanner = computed(() => {
@@ -532,15 +528,11 @@ defineExpose({
<template #focused-nodes-chips="{ message }">
<MessageFocusedNodesChips :focused-node-names="message.focusedNodeNames" />
</template>
<template #header>
<div :class="{ [$style.header]: true, [$style['with-slot']]: !!slots.header }">
<slot name="header" />
<AISettingsButton
v-if="showSettingsButton"
:show-usability-notice="!allowSendingParameterValues"
:disabled="builderStore.streaming"
/>
</div>
<template v-if="slots.header || showUsabilityNotice" #header>
<slot name="header" />
<N8nInfoTip v-if="showUsabilityNotice" theme="warning" type="tooltip">
<span>{{ i18n.baseText('aiAssistant.reducedHelp.chat.notice') }}</span>
</N8nInfoTip>
</template>
<template #inputHeader>
<ReviewChangesBanner
@@ -667,17 +659,6 @@ defineExpose({
width: 100%;
}
.header {
display: flex;
justify-content: end;
align-items: center;
flex: 1;
&.with-slot {
justify-content: space-between;
}
}
.newWorkflowButtonWrapper {
display: flex;
flex-direction: column;
@@ -1,69 +0,0 @@
<script setup lang="ts">
import { useI18n } from '@n8n/i18n';
import { N8nLink, N8nTooltip, N8nIcon, N8nInfoTip } from '@n8n/design-system';
import { VIEWS } from '@/app/constants';
type Props = {
disabled?: boolean;
showUsabilityNotice?: boolean;
};
const props = withDefaults(defineProps<Props>(), {
disabled: false,
showUsabilityNotice: false,
});
const i18n = useI18n();
</script>
<template>
<div :class="$style.container" data-test-id="ai-settings-button">
<N8nInfoTip v-if="props.showUsabilityNotice" theme="warning" type="tooltip">
<span>{{ i18n.baseText('aiAssistant.reducedHelp.chat.notice') }}</span>
</N8nInfoTip>
<N8nTooltip :content="i18n.baseText('settings.ai.button.tooltip')" :disabled="props.disabled">
<N8nLink
:to="props.disabled ? undefined : { name: VIEWS.AI_SETTINGS }"
:aria-label="i18n.baseText('settings.ai.button.tooltip')"
:class="{ [$style.link]: true, [$style.disabled]: props.disabled }"
>
<N8nIcon icon="settings" size="large" color="text-light" />
</N8nLink>
</N8nTooltip>
</div>
</template>
<style lang="scss" module>
.container {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing--2xs);
}
.link {
display: flex;
align-items: center;
justify-content: center;
margin-right: var(--spacing--4xs);
svg {
color: var(--color--text);
transition: color 0.2s ease-in-out;
}
&:hover svg {
color: var(--color--text--shade-1);
}
:global(.n8n-text) {
display: flex;
}
&.disabled {
pointer-events: none;
svg {
color: var(--color--text--tint-2);
}
}
}
</style>
@@ -1,13 +1,12 @@
<script lang="ts" setup>
import { useAssistantStore } from '@/features/ai/assistant/assistant.store';
import { useUsersStore } from '@/features/settings/users/users.store';
import { useSettingsStore } from '@/app/stores/settings.store';
import { computed, ref, useSlots } from 'vue';
import { N8nAskAssistantChat } from '@n8n/design-system';
import AISettingsButton from '@/features/ai/assistant/components/Chat/AISettingsButton.vue';
import { N8nAskAssistantChat, N8nInfoTip } from '@n8n/design-system';
import { useTelemetry } from '@/app/composables/useTelemetry';
import { injectWorkflowState } from '@/app/composables/useWorkflowState';
import { useI18n } from '@n8n/i18n';
import { useSettingsStore } from '@/app/stores/settings.store';
import AskModeEmptyState from './AskModeEmptyState.vue';
const emit = defineEmits<{
@@ -15,19 +14,15 @@ const emit = defineEmits<{
}>();
const assistantStore = useAssistantStore();
const settingsStore = useSettingsStore();
const workflowState = injectWorkflowState();
const usersStore = useUsersStore();
const settingsStore = useSettingsStore();
const telemetry = useTelemetry();
const slots = useSlots();
const i18n = useI18n();
const n8nChatRef = ref<InstanceType<typeof N8nAskAssistantChat>>();
const allowSendingParameterValues = computed(
() => settingsStore.settings.ai.allowSendingParameterValues,
);
const user = computed(() => ({
firstName: usersStore.currentUser?.firstName ?? '',
lastName: usersStore.currentUser?.lastName ?? '',
@@ -35,9 +30,10 @@ const user = computed(() => ({
const loadingMessage = computed(() => assistantStore.assistantThinkingMessage);
const showSettingsButton = computed(() => {
return assistantStore.canManageAISettings;
});
const showUsabilityNotice = computed(
() =>
assistantStore.canManageAISettings && !settingsStore.settings.ai.allowSendingParameterValues,
);
async function onUserMessage(content: string, quickReplyType?: string, isFeedback = false) {
// If there is no current session running, initialize the support chat session
@@ -99,15 +95,11 @@ defineExpose({
@code-replace="onCodeReplace"
@code-undo="undoCodeDiff"
>
<template #header>
<div :class="{ [$style.header]: true, [$style['with-slot']]: !!slots.header }">
<slot name="header" />
<AISettingsButton
v-if="showSettingsButton"
:show-usability-notice="!allowSendingParameterValues"
:disabled="assistantStore.streaming"
/>
</div>
<template v-if="slots.header || showUsabilityNotice" #header>
<slot name="header" />
<N8nInfoTip v-if="showUsabilityNotice" theme="warning" type="tooltip">
<span>{{ i18n.baseText('aiAssistant.reducedHelp.chat.notice') }}</span>
</N8nInfoTip>
</template>
<template #placeholder>
<AskModeEmptyState />
@@ -121,15 +113,4 @@ defineExpose({
height: 100%;
width: 100%;
}
.header {
display: flex;
justify-content: end;
align-items: center;
flex: 1;
&.with-slot {
justify-content: space-between;
}
}
</style>