feat(editor): Move AI Assistant button to canvas action buttons (#16879)

This commit is contained in:
Daria
2025-07-10 13:33:30 +03:00
committed by GitHub
parent 3edadb5a75
commit 2294c3d71b
12 changed files with 135 additions and 83 deletions

View File

@@ -35,8 +35,8 @@ describe('AI Assistant::enabled', () => {
}); });
it('renders placeholder UI', () => { it('renders placeholder UI', () => {
aiAssistant.getters.askAssistantFloatingButton().should('be.visible'); aiAssistant.getters.askAssistantCanvasActionButton().should('be.visible');
aiAssistant.getters.askAssistantFloatingButton().click(); aiAssistant.getters.askAssistantCanvasActionButton().click();
aiAssistant.getters.askAssistantChat().should('be.visible'); aiAssistant.getters.askAssistantChat().should('be.visible');
aiAssistant.getters.placeholderMessage().should('be.visible'); aiAssistant.getters.placeholderMessage().should('be.visible');
aiAssistant.getters.chatInput().should('be.visible'); aiAssistant.getters.chatInput().should('be.visible');
@@ -47,7 +47,7 @@ describe('AI Assistant::enabled', () => {
}); });
it('should resize assistant chat up', () => { it('should resize assistant chat up', () => {
aiAssistant.getters.askAssistantFloatingButton().click(); aiAssistant.getters.askAssistantCanvasActionButton().click();
aiAssistant.getters.askAssistantSidebarResizer().should('be.visible'); aiAssistant.getters.askAssistantSidebarResizer().should('be.visible');
aiAssistant.getters.askAssistantChat().should('be.visible'); aiAssistant.getters.askAssistantChat().should('be.visible');
aiAssistant.getters.askAssistantChat().then((element) => { aiAssistant.getters.askAssistantChat().then((element) => {
@@ -64,7 +64,7 @@ describe('AI Assistant::enabled', () => {
}); });
it('should resize assistant chat down', () => { it('should resize assistant chat down', () => {
aiAssistant.getters.askAssistantFloatingButton().click(); aiAssistant.getters.askAssistantCanvasActionButton().click();
aiAssistant.getters.askAssistantSidebarResizer().should('be.visible'); aiAssistant.getters.askAssistantSidebarResizer().should('be.visible');
aiAssistant.getters.askAssistantChat().should('be.visible'); aiAssistant.getters.askAssistantChat().should('be.visible');
aiAssistant.getters.askAssistantChat().then((element) => { aiAssistant.getters.askAssistantChat().then((element) => {
@@ -300,11 +300,11 @@ describe('AI Assistant::enabled', () => {
} }
}); });
}).as('chatRequest'); }).as('chatRequest');
aiAssistant.actions.openChat(); aiAssistant.actions.openChatFromCanvas();
aiAssistant.actions.sendMessage('Hello'); aiAssistant.actions.sendMessage('Hello');
cy.wait('@chatRequest'); cy.wait('@chatRequest');
aiAssistant.actions.closeChat(); aiAssistant.actions.closeChat();
aiAssistant.actions.openChat(); aiAssistant.actions.openChatFromCanvas();
// After closing and reopening the chat, all messages should be still there // After closing and reopening the chat, all messages should be still there
aiAssistant.getters.chatMessagesAll().should('have.length', 2); aiAssistant.getters.chatMessagesAll().should('have.length', 2);
// End the session // End the session
@@ -313,7 +313,7 @@ describe('AI Assistant::enabled', () => {
aiAssistant.getters.chatMessagesSystem().should('have.length', 1); aiAssistant.getters.chatMessagesSystem().should('have.length', 1);
aiAssistant.getters.chatMessagesSystem().first().should('contain.text', 'session has ended'); aiAssistant.getters.chatMessagesSystem().first().should('contain.text', 'session has ended');
aiAssistant.actions.closeChat(); aiAssistant.actions.closeChat();
aiAssistant.actions.openChat(); aiAssistant.actions.openChatFromCanvas();
// Now, session should be reset // Now, session should be reset
aiAssistant.getters.placeholderMessage().should('be.visible'); aiAssistant.getters.placeholderMessage().should('be.visible');
}); });
@@ -324,7 +324,7 @@ describe('AI Assistant::enabled', () => {
fixture: 'aiAssistant/responses/simple_message_response.json', fixture: 'aiAssistant/responses/simple_message_response.json',
}).as('chatRequest'); }).as('chatRequest');
wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
aiAssistant.actions.openChat(); aiAssistant.actions.openChatFromCanvas();
aiAssistant.actions.sendMessage('Hello'); aiAssistant.actions.sendMessage('Hello');
wf.actions.openNode(SCHEDULE_TRIGGER_NODE_NAME); wf.actions.openNode(SCHEDULE_TRIGGER_NODE_NAME);
ndv.getters.nodeExecuteButton().click(); ndv.getters.nodeExecuteButton().click();
@@ -339,7 +339,7 @@ describe('AI Assistant::enabled', () => {
}).as('chatRequest'); }).as('chatRequest');
wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME); wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
aiAssistant.actions.openChat(); aiAssistant.actions.openChatFromCanvas();
nodeCreatorFeature.actions.openNodeCreator(); nodeCreatorFeature.actions.openNodeCreator();
aiAssistant.getters.chatInput().type('Hello{Enter}'); aiAssistant.getters.chatInput().type('Hello{Enter}');
@@ -488,8 +488,8 @@ describe('General help', () => {
fixture: 'aiAssistant/responses/code_snippet_response.json', fixture: 'aiAssistant/responses/code_snippet_response.json',
}).as('chatRequest'); }).as('chatRequest');
aiAssistant.getters.askAssistantFloatingButton().should('be.visible'); aiAssistant.getters.askAssistantCanvasActionButton().should('be.visible');
aiAssistant.getters.askAssistantFloatingButton().click(); aiAssistant.getters.askAssistantCanvasActionButton().click();
aiAssistant.getters.askAssistantChat().should('be.visible'); aiAssistant.getters.askAssistantChat().should('be.visible');
aiAssistant.getters.placeholderMessage().should('be.visible'); aiAssistant.getters.placeholderMessage().should('be.visible');
aiAssistant.getters.chatInput().should('be.visible'); aiAssistant.getters.chatInput().should('be.visible');
@@ -537,7 +537,7 @@ describe('General help', () => {
fixture: 'aiAssistant/responses/simple_message_response.json', fixture: 'aiAssistant/responses/simple_message_response.json',
}).as('chatRequest'); }).as('chatRequest');
aiAssistant.getters.askAssistantFloatingButton().click(); aiAssistant.getters.askAssistantCanvasActionButton().click();
aiAssistant.actions.sendMessage('What is wrong with this workflow?'); aiAssistant.actions.sendMessage('What is wrong with this workflow?');
cy.wait('@chatRequest').then((interception) => { cy.wait('@chatRequest').then((interception) => {
@@ -557,7 +557,7 @@ describe('General help', () => {
fixture: 'aiAssistant/responses/simple_message_response.json', fixture: 'aiAssistant/responses/simple_message_response.json',
}).as('chatRequest'); }).as('chatRequest');
aiAssistant.getters.askAssistantFloatingButton().click(); aiAssistant.getters.askAssistantCanvasActionButton().click();
wf.getters.zoomToFitButton().click(); wf.getters.zoomToFitButton().click();
aiAssistant.actions.sendMessage('What is wrong with this workflow?'); aiAssistant.actions.sendMessage('What is wrong with this workflow?');

View File

@@ -13,6 +13,7 @@ export class AIAssistant extends BasePage {
getters = { getters = {
askAssistantFloatingButton: () => cy.getByTestId('ask-assistant-floating-button'), askAssistantFloatingButton: () => cy.getByTestId('ask-assistant-floating-button'),
askAssistantCanvasActionButton: () => cy.getByTestId('ask-assistant-canvas-action-button'),
askAssistantSidebar: () => cy.getByTestId('ask-assistant-sidebar'), askAssistantSidebar: () => cy.getByTestId('ask-assistant-sidebar'),
askAssistantSidebarResizer: () => askAssistantSidebarResizer: () =>
this.getters.askAssistantSidebar().find('[class^=_resizer][data-dir=left]').first(), this.getters.askAssistantSidebar().find('[class^=_resizer][data-dir=left]').first(),
@@ -53,7 +54,11 @@ export class AIAssistant extends BasePage {
this.getters.closeChatButton().click(); this.getters.closeChatButton().click();
this.getters.askAssistantChat().should('not.be.visible'); this.getters.askAssistantChat().should('not.be.visible');
}, },
openChat: () => { openChatFromCanvas: () => {
this.getters.askAssistantCanvasActionButton().click();
this.getters.askAssistantChat().should('be.visible');
},
openChatFromNdv: () => {
this.getters.askAssistantFloatingButton().click(); this.getters.askAssistantFloatingButton().click();
this.getters.askAssistantChat().should('be.visible'); this.getters.askAssistantChat().should('be.visible');
}, },

View File

@@ -4,7 +4,6 @@ import { ref } from 'vue';
import { useI18n } from '../../composables/useI18n'; import { useI18n } from '../../composables/useI18n';
import AssistantIcon from '../AskAssistantIcon/AssistantIcon.vue'; import AssistantIcon from '../AskAssistantIcon/AssistantIcon.vue';
import AssistantText from '../AskAssistantText/AssistantText.vue'; import AssistantText from '../AskAssistantText/AssistantText.vue';
import BetaTag from '../BetaTag/BetaTag.vue';
const { t } = useI18n(); const { t } = useI18n();
@@ -48,9 +47,6 @@ function onMouseLeave() {
" "
/> />
</div> </div>
<div>
<BetaTag />
</div>
</div> </div>
</button> </button>
</template> </template>
@@ -92,7 +88,6 @@ function onMouseLeave() {
.text { .text {
position: absolute; position: absolute;
top: -1px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: end; align-items: end;

View File

@@ -21,13 +21,6 @@ exports[`AskAssistantButton > renders button with unread messages correctly 1`]
Ask Assistant Ask Assistant
</span> </span>
</div> </div>
<div>
<div
class="beta"
>
beta
</div>
</div>
</div> </div>
</button> </button>
</div> </div>
@@ -83,13 +76,6 @@ exports[`AskAssistantButton > renders default button correctly 1`] = `
Ask Assistant Ask Assistant
</span> </span>
</div> </div>
<div>
<div
class="beta"
>
beta
</div>
</div>
</div> </div>
</button> </button>
</div> </div>

View File

@@ -16,7 +16,6 @@ import type { ChatUI } from '../../types/assistant';
import AssistantIcon from '../AskAssistantIcon/AssistantIcon.vue'; import AssistantIcon from '../AskAssistantIcon/AssistantIcon.vue';
import AssistantLoadingMessage from '../AskAssistantLoadingMessage/AssistantLoadingMessage.vue'; import AssistantLoadingMessage from '../AskAssistantLoadingMessage/AssistantLoadingMessage.vue';
import AssistantText from '../AskAssistantText/AssistantText.vue'; import AssistantText from '../AskAssistantText/AssistantText.vue';
import BetaTag from '../BetaTag/BetaTag.vue';
import InlineAskAssistantButton from '../InlineAskAssistantButton/InlineAskAssistantButton.vue'; import InlineAskAssistantButton from '../InlineAskAssistantButton/InlineAskAssistantButton.vue';
import N8nButton from '../N8nButton'; import N8nButton from '../N8nButton';
import N8nIcon from '../N8nIcon'; import N8nIcon from '../N8nIcon';
@@ -123,7 +122,6 @@ function onSubmitFeedback(feedback: string) {
<AssistantIcon size="large" /> <AssistantIcon size="large" />
<AssistantText size="large" :text="title" /> <AssistantText size="large" :text="title" />
</div> </div>
<BetaTag />
<slot name="header" /> <slot name="header" />
</div> </div>
<div :class="$style.back" data-test-id="close-chat-button" @click="onClose"> <div :class="$style.back" data-test-id="close-chat-button" @click="onClose">

View File

@@ -54,11 +54,6 @@ exports[`AskAssistantChat > does not render retry button if no error is present
AI Assistant AI Assistant
</span> </span>
</div> </div>
<div
class="beta"
>
beta
</div>
</div> </div>
@@ -247,11 +242,6 @@ exports[`AskAssistantChat > renders chat with messages correctly 1`] = `
AI Assistant AI Assistant
</span> </span>
</div> </div>
<div
class="beta"
>
beta
</div>
</div> </div>
@@ -1064,11 +1054,6 @@ exports[`AskAssistantChat > renders default placeholder chat correctly 1`] = `
AI Assistant AI Assistant
</span> </span>
</div> </div>
<div
class="beta"
>
beta
</div>
</div> </div>
@@ -1250,11 +1235,6 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
AI Assistant AI Assistant
</span> </span>
</div> </div>
<div
class="beta"
>
beta
</div>
</div> </div>
@@ -1520,11 +1500,6 @@ exports[`AskAssistantChat > renders error message correctly with retry button 1`
AI Assistant AI Assistant
</span> </span>
</div> </div>
<div
class="beta"
>
beta
</div>
</div> </div>
@@ -1725,11 +1700,6 @@ exports[`AskAssistantChat > renders message with code snippet 1`] = `
AI Assistant AI Assistant
</span> </span>
</div> </div>
<div
class="beta"
>
beta
</div>
</div> </div>
@@ -1988,11 +1958,6 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = `
AI Assistant AI Assistant
</span> </span>
</div> </div>
<div
class="beta"
>
beta
</div>
</div> </div>

View File

@@ -195,6 +195,7 @@
"aiAssistant.prompts.currentView.credentialsList": "The user is currently looking at the list of credentials.", "aiAssistant.prompts.currentView.credentialsList": "The user is currently looking at the list of credentials.",
"aiAssistant.prompts.currentView.executionsView": "The user is currently looking at the list of executions for the currently open workflow.", "aiAssistant.prompts.currentView.executionsView": "The user is currently looking at the list of executions for the currently open workflow.",
"aiAssistant.prompts.currentView.workflowEditor": "The user is currently looking at the current workflow in n8n editor, without any specific node selected.", "aiAssistant.prompts.currentView.workflowEditor": "The user is currently looking at the current workflow in n8n editor, without any specific node selected.",
"aiAssistant.tooltip": "Ask Assistant",
"banners.confirmEmail.message.1": "To secure your account and prevent future access issues, please confirm your", "banners.confirmEmail.message.1": "To secure your account and prevent future access issues, please confirm your",
"banners.confirmEmail.message.2": "email address.", "banners.confirmEmail.message.2": "email address.",
"banners.confirmEmail.button": "Confirm email", "banners.confirmEmail.button": "Confirm email",

View File

@@ -38,7 +38,10 @@ useHistoryHelper(route);
const loading = ref(true); const loading = ref(true);
const defaultLocale = computed(() => rootStore.defaultLocale); const defaultLocale = computed(() => rootStore.defaultLocale);
const isDemoMode = computed(() => route.name === VIEWS.DEMO); const isDemoMode = computed(() => route.name === VIEWS.DEMO);
const showAssistantButton = computed(() => assistantStore.canShowAssistantButtonsOnCanvas); const showAssistantFloatingButton = computed(
() =>
assistantStore.canShowAssistantButtonsOnCanvas && !assistantStore.hideAssistantFloatingButton,
);
const hasContentFooter = ref(false); const hasContentFooter = ref(false);
const appGrid = ref<Element | null>(null); const appGrid = ref<Element | null>(null);
@@ -129,7 +132,7 @@ watch(
<Modals /> <Modals />
</div> </div>
<Telemetry /> <Telemetry />
<AskAssistantFloatingButton v-if="showAssistantButton" /> <AskAssistantFloatingButton v-if="showAssistantFloatingButton" />
</div> </div>
<AssistantsHub /> <AssistantsHub />
</div> </div>

View File

@@ -38,7 +38,11 @@ const onClick = () => {
<template> <template>
<div <div
v-if="assistantStore.canShowAssistantButtonsOnCanvas && !assistantStore.isAssistantOpen" v-if="
assistantStore.canShowAssistantButtonsOnCanvas &&
!assistantStore.isAssistantOpen &&
!assistantStore.hideAssistantFloatingButton
"
:class="$style.container" :class="$style.container"
data-test-id="ask-assistant-floating-button" data-test-id="ask-assistant-floating-button"
:style="{ '--canvas-panel-height-offset': `${logsStore.height}px` }" :style="{ '--canvas-panel-height-offset': `${logsStore.height}px` }"
@@ -64,16 +68,9 @@ const onClick = () => {
<style lang="scss" module> <style lang="scss" module>
.container { .container {
position: absolute; position: absolute;
bottom: calc(var(--canvas-panel-height-offset, 0px) + var(--spacing-s)); bottom: var(--spacing-2xl);
right: var(--spacing-s); right: var(--spacing-s);
z-index: var(--z-index-ask-assistant-floating-button); z-index: var(--z-index-ask-assistant-floating-button);
/* Prevent overlap with 'Execute Workflow' / 'Open Chat' buttons on small screens */
@include mixins.breakpoint('sm-only') {
bottom: calc(
var(--canvas-panel-height-offset, 0px) + var(--spacing-s) + var(--spacing-xs) + 42px
);
}
} }
.tooltip { .tooltip {

View File

@@ -19,7 +19,9 @@ import type {
} from '@/Interface'; } from '@/Interface';
import { useActions } from './NodeCreator/composables/useActions'; import { useActions } from './NodeCreator/composables/useActions';
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue'; import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
import AssistantIcon from '@n8n/design-system/components/AskAssistantIcon/AssistantIcon.vue';
import { useI18n } from '@n8n/i18n'; import { useI18n } from '@n8n/i18n';
import { useAssistantStore } from '@/stores/assistant.store';
type Props = { type Props = {
nodeViewScale: number; nodeViewScale: number;
@@ -44,6 +46,7 @@ const uiStore = useUIStore();
const focusPanelStore = useFocusPanelStore(); const focusPanelStore = useFocusPanelStore();
const posthogStore = usePostHog(); const posthogStore = usePostHog();
const i18n = useI18n(); const i18n = useI18n();
const assistantStore = useAssistantStore();
const { getAddedNodesAndConnections } = useActions(); const { getAddedNodesAndConnections } = useActions();
@@ -82,6 +85,17 @@ function nodeTypeSelected(value: NodeTypeSelectedPayload[]) {
emit('addNodes', getAddedNodesAndConnections(value)); emit('addNodes', getAddedNodesAndConnections(value));
closeNodeCreator(true); closeNodeCreator(true);
} }
function onAskAssistantButtonClick() {
if (!assistantStore.chatWindowOpen)
assistantStore.trackUserOpenedAssistant({
source: 'canvas',
task: 'placeholder',
has_existing_session: !assistantStore.isSessionEnded,
});
assistantStore.toggleChatOpen();
}
</script> </script>
<template> <template>
@@ -125,6 +139,24 @@ function nodeTypeSelected(value: NodeTypeSelectedPayload[]) {
@click="focusPanelStore.toggleFocusPanel" @click="focusPanelStore.toggleFocusPanel"
/> />
</KeyboardShortcutTooltip> </KeyboardShortcutTooltip>
<n8n-tooltip placement="left">
<template #content> {{ i18n.baseText('aiAssistant.tooltip') }}</template>
<n8n-button
v-if="assistantStore.canShowAssistantButtonsOnCanvas"
type="tertiary"
size="large"
square
:class="$style.icon"
data-test-id="ask-assistant-canvas-action-button"
@click="onAskAssistantButtonClick"
>
<template #default>
<div>
<AssistantIcon size="large" />
</div>
</template>
</n8n-button>
</n8n-tooltip>
</div> </div>
<Suspense> <Suspense>
<LazyNodeCreator <LazyNodeCreator
@@ -146,4 +178,14 @@ function nodeTypeSelected(value: NodeTypeSelectedPayload[]) {
padding: var(--spacing-s); padding: var(--spacing-s);
pointer-events: all !important; pointer-events: all !important;
} }
.icon {
display: inline-flex;
justify-content: center;
align-items: center;
svg {
display: block;
}
}
</style> </style>

View File

@@ -7,6 +7,7 @@ import {
MIN_CHAT_WIDTH, MIN_CHAT_WIDTH,
useAssistantStore, useAssistantStore,
} from '@/stores/assistant.store'; } from '@/stores/assistant.store';
import { useWorkflowsStore } from './workflows.store';
import type { ChatRequest } from '@/types/assistant.types'; import type { ChatRequest } from '@/types/assistant.types';
import { usePostHog } from './posthog.store'; import { usePostHog } from './posthog.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
@@ -43,11 +44,12 @@ const setAssistantEnabled = (enabled: boolean) => {
}; };
let currentRouteName = ENABLED_VIEWS[0]; let currentRouteName = ENABLED_VIEWS[0];
let currentRouteParams = {};
vi.mock('vue-router', () => ({ vi.mock('vue-router', () => ({
useRoute: vi.fn(() => useRoute: vi.fn(() =>
reactive({ reactive({
path: '/', path: '/',
params: {}, params: currentRouteParams,
name: currentRouteName, name: currentRouteName,
}), }),
), ),
@@ -58,6 +60,7 @@ vi.mock('vue-router', () => ({
describe('AI Assistant store', () => { describe('AI Assistant store', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
currentRouteParams = {};
setActivePinia(createPinia()); setActivePinia(createPinia());
settingsStore = useSettingsStore(); settingsStore = useSettingsStore();
settingsStore.setSettings( settingsStore.setSettings(
@@ -305,7 +308,7 @@ describe('AI Assistant store', () => {
[VIEWS.PROJECTS_CREDENTIALS, VIEWS.TEMPLATE_SETUP, VIEWS.CREDENTIALS].forEach((view) => { [VIEWS.PROJECTS_CREDENTIALS, VIEWS.TEMPLATE_SETUP, VIEWS.CREDENTIALS].forEach((view) => {
it(`should show assistant if on ${view} page`, () => { it(`should show assistant if on ${view} page`, () => {
currentRouteName = VIEWS.PROJECTS_CREDENTIALS; currentRouteName = view;
const assistantStore = useAssistantStore(); const assistantStore = useAssistantStore();
setAssistantEnabled(true); setAssistantEnabled(true);
@@ -315,6 +318,48 @@ describe('AI Assistant store', () => {
}); });
}); });
[
{ view: VIEWS.WORKFLOW, nodeId: 'nodeId' },
{ view: VIEWS.NEW_WORKFLOW },
{ view: VIEWS.EXECUTION_DEBUG },
].forEach(({ view, nodeId }) => {
it(`should show ai assistant floating button if on ${view} page`, () => {
currentRouteName = view;
currentRouteParams = nodeId ? { nodeId } : {};
const workflowsStore = useWorkflowsStore();
workflowsStore.activeNode = () => ({
id: 'test-node',
name: 'Test Node',
type: 'test',
typeVersion: 1,
position: [0, 0],
parameters: {},
});
const assistantStore = useAssistantStore();
setAssistantEnabled(true);
expect(assistantStore.canShowAssistantButtonsOnCanvas).toBe(true);
expect(assistantStore.hideAssistantFloatingButton).toBe(false);
});
});
[{ view: VIEWS.WORKFLOW }, { view: VIEWS.NEW_WORKFLOW }].forEach(({ view }) => {
it(`should hide ai assistant floating button if on canvas of ${view} page`, () => {
currentRouteName = view;
const workflowsStore = useWorkflowsStore();
workflowsStore.activeNode = () => null;
const assistantStore = useAssistantStore();
setAssistantEnabled(true);
expect(assistantStore.canShowAssistantButtonsOnCanvas).toBe(true);
expect(assistantStore.hideAssistantFloatingButton).toBe(true);
});
});
it('should initialize assistant chat session on node error', async () => { it('should initialize assistant chat session on node error', async () => {
const context: ChatRequest.ErrorContext = { const context: ChatRequest.ErrorContext = {
error: { error: {

View File

@@ -113,6 +113,11 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
const canShowAssistantButtonsOnCanvas = computed( const canShowAssistantButtonsOnCanvas = computed(
() => isAssistantEnabled.value && EDITABLE_CANVAS_VIEWS.includes(route.name as VIEWS), () => isAssistantEnabled.value && EDITABLE_CANVAS_VIEWS.includes(route.name as VIEWS),
); );
const hideAssistantFloatingButton = computed(
() =>
(route.name === VIEWS.WORKFLOW || route.name === VIEWS.NEW_WORKFLOW) &&
!workflowsStore.activeNode(),
);
const unreadCount = computed( const unreadCount = computed(
() => () =>
@@ -161,6 +166,14 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
}, ASK_AI_SLIDE_OUT_DURATION_MS + 50); }, ASK_AI_SLIDE_OUT_DURATION_MS + 50);
} }
function toggleChatOpen() {
if (chatWindowOpen.value) {
closeChat();
} else {
openChat();
}
}
function addAssistantMessages(newMessages: ChatRequest.MessageResponse[], id: string) { function addAssistantMessages(newMessages: ChatRequest.MessageResponse[], id: string) {
const read = chatWindowOpen.value; const read = chatWindowOpen.value;
const messages = [...chatMessages.value].filter( const messages = [...chatMessages.value].filter(
@@ -814,6 +827,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
return { return {
isAssistantEnabled, isAssistantEnabled,
canShowAssistantButtonsOnCanvas, canShowAssistantButtonsOnCanvas,
hideAssistantFloatingButton,
chatWidth, chatWidth,
chatMessages, chatMessages,
unreadCount, unreadCount,
@@ -827,6 +841,7 @@ export const useAssistantStore = defineStore(STORES.ASSISTANT, () => {
trackUserOpenedAssistant, trackUserOpenedAssistant,
closeChat, closeChat,
openChat, openChat,
toggleChatOpen,
updateWindowWidth, updateWindowWidth,
isNodeErrorActive, isNodeErrorActive,
initErrorHelper, initErrorHelper,