feat(editor): Add AI Starter pack experiment (no-changelog) (#17497)

This commit is contained in:
Milorad FIlipović
2025-07-21 13:43:40 +02:00
committed by GitHub
parent 79306b040e
commit 5ddf290795
10 changed files with 964 additions and 2 deletions

View File

@@ -69,6 +69,7 @@ import debounce from 'lodash/debounce';
import { type IUser, PROJECT_ROOT } from 'n8n-workflow';
import { computed, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
import { type LocationQueryRaw, useRoute, useRouter } from 'vue-router';
import { useAITemplatesStarterCollectionStore } from '@/experiments/aiTemplatesStarterCollection/stores/aiTemplatesStarterCollection.store';
const SEARCH_DEBOUNCE_TIME = 300;
const FILTERS_DEBOUNCE_TIME = 100;
@@ -112,6 +113,7 @@ const foldersStore = useFoldersStore();
const usageStore = useUsageStore();
const insightsStore = useInsightsStore();
const templatesStore = useTemplatesStore();
const aiStarterTemplatesStore = useAITemplatesStarterCollectionStore();
const documentTitle = useDocumentTitle();
const { callDebounced } = useDebounce();
@@ -369,6 +371,19 @@ const showRegisteredCommunityCTA = computed(
() => isSelfHostedDeployment.value && !foldersEnabled.value && canUserRegisterCommunityPlus.value,
);
const showAIStarterCollectionCallout = computed(() => {
return (
!loading.value &&
aiStarterTemplatesStore.isFeatureEnabled &&
!aiStarterTemplatesStore.calloutDismissed &&
!readOnlyEnv.value &&
// We want to show the callout only if the user has permissions to create folders and workflows
// but also on the overview page
(projectPages.isOverviewSubPage ||
(hasPermissionToCreateFolders.value && hasPermissionToCreateWorkflows.value))
);
});
/**
* WATCHERS, STORE SUBSCRIPTIONS AND EVENT BUS HANDLERS
*/
@@ -778,6 +793,45 @@ function isValidProjectId(projectId: string) {
return projectsStore.availableProjects.some((project) => project.id === projectId);
}
const createAIStarterWorkflows = async (source: 'card' | 'callout') => {
try {
const projectId = projectPages.isOverviewSubPage
? personalProject.value?.id
: (route.params.projectId as string);
if (typeof projectId !== 'string') {
toast.showError(new Error(), i18n.baseText('workflows.ai.starter.collection.error'));
return;
}
const newFolder = await aiStarterTemplatesStore.createStarterWorkflows(
projectId,
currentFolderId.value ?? undefined,
);
// If we are on the overview page, navigate to the new folder
if (projectPages.isOverviewSubPage) {
await router.push({
name: VIEWS.PROJECTS_FOLDERS,
params: { projectId, folderId: newFolder.id },
});
} else {
// If we are in a specific folder, just add the new folder to the list
workflowsAndFolders.value.unshift({
id: newFolder.id,
name: newFolder.name,
resource: 'folder',
createdAt: newFolder.createdAt,
updatedAt: newFolder.updatedAt,
subFolderCount: 0,
workflowCount: 3,
parentFolder: newFolder.parentFolder,
});
}
aiStarterTemplatesStore.trackUserCreatedStarterCollection(source);
} catch (error) {
toast.showError(error, i18n.baseText('workflows.ai.starter.collection.error'));
return;
}
};
const openAIWorkflow = async (source: string) => {
dismissEasyAICallout();
telemetry.track('User clicked test AI workflow', {
@@ -793,6 +847,11 @@ const openAIWorkflow = async (source: string) => {
});
};
const dismissStarterCollectionCallout = () => {
aiStarterTemplatesStore.dismissCallout();
aiStarterTemplatesStore.trackUserDismissedCallout();
};
const dismissEasyAICallout = () => {
easyAICalloutVisible.value = false;
};
@@ -1610,7 +1669,34 @@ const onNameSubmit = async (name: string) => {
</template>
<template #callout>
<N8nCallout
v-if="!loading && showEasyAIWorkflowCallout && easyAICalloutVisible"
v-if="showAIStarterCollectionCallout"
theme="secondary"
icon="gift"
:class="$style['easy-ai-workflow-callout']"
>
{{ i18n.baseText('workflows.ai.starter.collection.callout') }}
<template #trailingContent>
<div :class="$style['callout-trailing-content']">
<N8nButton
data-test-id="easy-ai-button"
size="small"
type="secondary"
@click="createAIStarterWorkflows('callout')"
>
{{ i18n.baseText('generic.startNow') }}
</N8nButton>
<N8nIcon
size="small"
icon="x"
:title="i18n.baseText('generic.dismiss')"
class="clickable"
@click="dismissStarterCollectionCallout"
/>
</div>
</template>
</N8nCallout>
<N8nCallout
v-else-if="!loading && showEasyAIWorkflowCallout && easyAICalloutVisible"
theme="secondary"
icon="bot"
:class="$style['easy-ai-workflow-callout']"
@@ -1804,7 +1890,26 @@ const onNameSubmit = async (name: string) => {
</div>
</N8nCard>
<N8nCard
v-if="showEasyAIWorkflowCallout"
v-if="showAIStarterCollectionCallout"
:class="$style.emptyStateCard"
hoverable
data-test-id="easy-ai-workflow-card"
@click="createAIStarterWorkflows('card')"
>
<div :class="$style.emptyStateCardContent">
<N8nIcon
:class="$style.emptyStateCardIcon"
:stroke-width="1.5"
icon="gift"
color="foreground-dark"
/>
<N8nText size="large" class="mt-xs pl-2xs pr-2xs">
{{ i18n.baseText('workflows.ai.starter.collection.card') }}
</N8nText>
</div>
</N8nCard>
<N8nCard
v-else-if="showEasyAIWorkflowCallout"
:class="$style.emptyStateCard"
hoverable
data-test-id="easy-ai-workflow-card"