mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Implement Ready to Run Workflows V2 experiment (no-changelog) (#19468)
This commit is contained in:
@@ -169,6 +169,7 @@ import IconLucideSettings from '~icons/lucide/settings';
|
||||
import IconLucideShare from '~icons/lucide/share';
|
||||
import IconLucideSlidersHorizontal from '~icons/lucide/sliders-horizontal';
|
||||
import IconLucideSmile from '~icons/lucide/smile';
|
||||
import IconLucideSparkles from '~icons/lucide/sparkles';
|
||||
import IconLucideSquare from '~icons/lucide/square';
|
||||
import IconLucideSquareCheck from '~icons/lucide/square-check';
|
||||
import IconLucideSquarePen from '~icons/lucide/square-pen';
|
||||
@@ -583,6 +584,7 @@ export const updatedIconSet = {
|
||||
share: IconLucideShare,
|
||||
'sliders-horizontal': IconLucideSlidersHorizontal,
|
||||
smile: IconLucideSmile,
|
||||
sparkles: IconLucideSparkles,
|
||||
square: IconLucideSquare,
|
||||
'square-check': IconLucideSquareCheck,
|
||||
'square-pen': IconLucideSquarePen,
|
||||
|
||||
@@ -2713,6 +2713,7 @@
|
||||
"workflows.empty.preBuiltAgents": "Try a pre-built agent",
|
||||
"workflows.empty.shared-with-me": "No {resource} has been shared with you",
|
||||
"workflows.empty.shared-with-me.link": "<a href=\"#\">Back to Personal</a>",
|
||||
"workflows.empty.readyToRunV2": "Try an AI workflow",
|
||||
"workflows.list.easyAI": "Test the power of AI in n8n with this simple AI Agent Workflow",
|
||||
"workflows.list.error.fetching": "Error fetching workflows",
|
||||
"workflows.shareModal.title": "Share '{name}'",
|
||||
|
||||
@@ -34,5 +34,6 @@ export const STORES = {
|
||||
AI_TEMPLATES_STARTER_COLLECTION: 'aiTemplatesStarterCollection',
|
||||
PERSONALIZED_TEMPLATES: 'personalizedTemplates',
|
||||
EXPERIMENT_READY_TO_RUN_WORKFLOWS: 'readyToRunWorkflows',
|
||||
EXPERIMENT_READY_TO_RUN_WORKFLOWS_V2: 'readyToRunWorkflowsV2',
|
||||
EXPERIMENT_TEMPLATE_RECO_V2: 'templateRecoV2',
|
||||
} as const;
|
||||
|
||||
@@ -21,6 +21,7 @@ import type { IUser } from 'n8n-workflow';
|
||||
import { type IconOrEmoji, isIconOrEmoji } from '@n8n/design-system/components/N8nIconPicker/types';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { PROJECT_DATA_STORES } from '@/features/dataStore/constants';
|
||||
import ReadyToRunV2Button from '@/experiments/readyToRunWorkflowsV2/components/ReadyToRunV2Button.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -32,6 +33,10 @@ const uiStore = useUIStore();
|
||||
|
||||
const projectPages = useProjectPages();
|
||||
|
||||
const props = defineProps<{
|
||||
hasActiveCallouts?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
createFolder: [];
|
||||
}>();
|
||||
@@ -341,18 +346,21 @@ const onSelect = (action: string) => {
|
||||
:disabled="!sourceControlStore.preferences.branchReadOnly"
|
||||
:content="i18n.baseText('readOnlyEnv.cantAdd.any')"
|
||||
>
|
||||
<ProjectCreateResource
|
||||
data-test-id="add-resource-buttons"
|
||||
:actions="menu"
|
||||
:disabled="sourceControlStore.preferences.branchReadOnly"
|
||||
@action="onSelect"
|
||||
>
|
||||
<N8nButton
|
||||
data-test-id="add-resource-workflow"
|
||||
v-bind="createWorkflowButton"
|
||||
@click="onSelect(ACTION_TYPES.WORKFLOW)"
|
||||
/>
|
||||
</ProjectCreateResource>
|
||||
<div style="display: flex; gap: var(--spacing-xs); align-items: center">
|
||||
<ReadyToRunV2Button :has-active-callouts="props.hasActiveCallouts" />
|
||||
<ProjectCreateResource
|
||||
data-test-id="add-resource-buttons"
|
||||
:actions="menu"
|
||||
:disabled="sourceControlStore.preferences.branchReadOnly"
|
||||
@action="onSelect"
|
||||
>
|
||||
<N8nButton
|
||||
data-test-id="add-resource-workflow"
|
||||
v-bind="createWorkflowButton"
|
||||
@click="onSelect(ACTION_TYPES.WORKFLOW)"
|
||||
/>
|
||||
</ProjectCreateResource>
|
||||
</div>
|
||||
</N8nTooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
|
||||
import { codeNodeEditorEventBus, globalLinkActionsEventBus } from '@/event-bus';
|
||||
import { useAITemplatesStarterCollectionStore } from '@/experiments/aiTemplatesStarterCollection/stores/aiTemplatesStarterCollection.store';
|
||||
import { useReadyToRunWorkflowsStore } from '@/experiments/readyToRunWorkflows/stores/readyToRunWorkflows.store';
|
||||
import { useReadyToRunWorkflowsV2Store } from '@/experiments/readyToRunWorkflowsV2/stores/readyToRunWorkflowsV2.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
@@ -48,6 +49,7 @@ export async function executionFinished(
|
||||
const uiStore = useUIStore();
|
||||
const aiTemplatesStarterCollectionStore = useAITemplatesStarterCollectionStore();
|
||||
const readyToRunWorkflowsStore = useReadyToRunWorkflowsStore();
|
||||
const readyToRunWorkflowsV2Store = useReadyToRunWorkflowsV2Store();
|
||||
|
||||
workflowsStore.lastAddedExecutingNode = null;
|
||||
|
||||
@@ -76,6 +78,15 @@ export async function executionFinished(
|
||||
);
|
||||
} else if (templateId.startsWith('37_onboarding_experiments_batch_aug11')) {
|
||||
readyToRunWorkflowsStore.trackExecuteWorkflow(templateId.split('-').pop() ?? '', data.status);
|
||||
} else if (
|
||||
templateId === 'ready-to-run-ai-workflow-v1' ||
|
||||
templateId === 'ready-to-run-ai-workflow-v2'
|
||||
) {
|
||||
if (data.status === 'success') {
|
||||
readyToRunWorkflowsV2Store.trackExecuteAiWorkflowSuccess();
|
||||
} else {
|
||||
readyToRunWorkflowsV2Store.trackExecuteAiWorkflow(data.status);
|
||||
}
|
||||
} else if (isPrebuiltAgentTemplateId(templateId)) {
|
||||
telemetry.track('User executed pre-built Agent', {
|
||||
template: templateId,
|
||||
|
||||
@@ -810,6 +810,13 @@ export const TEMPLATE_RECO_V2 = {
|
||||
variant: 'variant',
|
||||
};
|
||||
|
||||
export const READY_TO_RUN_V2_EXPERIMENT = {
|
||||
name: '042_ready-to-run-worfklow_v2',
|
||||
control: 'control',
|
||||
variant1: 'variant-1-singlebox',
|
||||
variant2: 'variant-2-twoboxes',
|
||||
};
|
||||
|
||||
export const EXPERIMENTS_TO_TRACK = [
|
||||
WORKFLOW_BUILDER_EXPERIMENT.name,
|
||||
EXTRA_TEMPLATE_LINKS_EXPERIMENT.name,
|
||||
@@ -818,6 +825,7 @@ export const EXPERIMENTS_TO_TRACK = [
|
||||
BATCH_11AUG_EXPERIMENT.name,
|
||||
PRE_BUILT_AGENTS_EXPERIMENT.name,
|
||||
TEMPLATE_RECO_V2.name,
|
||||
READY_TO_RUN_V2_EXPERIMENT.name,
|
||||
];
|
||||
|
||||
export const MFA_FORM = {
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { N8nButton } from '@n8n/design-system';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { getResourcePermissions } from '@n8n/permissions';
|
||||
import { useProjectPages } from '@/composables/useProjectPages';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
import { useFoldersStore } from '@/stores/folders.store';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useReadyToRunWorkflowsV2Store } from '../stores/readyToRunWorkflowsV2.store';
|
||||
|
||||
const props = defineProps<{
|
||||
hasActiveCallouts?: boolean;
|
||||
}>();
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
const projectPages = useProjectPages();
|
||||
const projectsStore = useProjectsStore();
|
||||
const sourceControlStore = useSourceControlStore();
|
||||
const foldersStore = useFoldersStore();
|
||||
const readyToRunWorkflowsV2Store = useReadyToRunWorkflowsV2Store();
|
||||
|
||||
const projectPermissions = computed(() => {
|
||||
return getResourcePermissions(
|
||||
projectsStore.currentProject?.scopes ?? projectsStore.personalProject?.scopes,
|
||||
);
|
||||
});
|
||||
|
||||
const showButton = computed(() => {
|
||||
return (
|
||||
readyToRunWorkflowsV2Store.getButtonVisibility(
|
||||
foldersStore.totalWorkflowCount > 0, // Has workflows
|
||||
projectPermissions.value.workflow.create,
|
||||
sourceControlStore.preferences.branchReadOnly,
|
||||
) && !props.hasActiveCallouts // Hide when callouts are shown
|
||||
);
|
||||
});
|
||||
|
||||
const handleClick = async () => {
|
||||
const projectId = projectPages.isOverviewSubPage
|
||||
? projectsStore.personalProject?.id
|
||||
: (route.params.projectId as string);
|
||||
|
||||
try {
|
||||
await readyToRunWorkflowsV2Store.claimCreditsAndOpenWorkflow(
|
||||
'button',
|
||||
route.params.folderId as string,
|
||||
projectId,
|
||||
);
|
||||
} catch (error) {
|
||||
toast.showError(error, i18n.baseText('generic.error'));
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nButton
|
||||
v-if="showButton"
|
||||
data-test-id="ready-to-run-v2-button"
|
||||
type="secondary"
|
||||
icon="sparkles"
|
||||
:loading="readyToRunWorkflowsV2Store.claimingCredits"
|
||||
:disabled="
|
||||
sourceControlStore.preferences.branchReadOnly || readyToRunWorkflowsV2Store.claimingCredits
|
||||
"
|
||||
@click="handleClick"
|
||||
>
|
||||
{{ i18n.baseText('workflows.empty.readyToRunV2') }}
|
||||
</N8nButton>
|
||||
</template>
|
||||
@@ -0,0 +1,222 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { N8nCard, N8nHeading, N8nText, N8nIcon } from '@n8n/design-system';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
import { getResourcePermissions } from '@n8n/permissions';
|
||||
import { useProjectPages } from '@/composables/useProjectPages';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useReadyToRunWorkflowsV2Store } from '../stores/readyToRunWorkflowsV2.store';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
const route = useRoute();
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
const usersStore = useUsersStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const sourceControlStore = useSourceControlStore();
|
||||
const projectPages = useProjectPages();
|
||||
const readyToRunWorkflowsV2Store = useReadyToRunWorkflowsV2Store();
|
||||
|
||||
const currentUser = computed(() => usersStore.currentUser ?? ({} as IUser));
|
||||
const personalProject = computed(() => projectsStore.personalProject);
|
||||
const readOnlyEnv = computed(() => sourceControlStore.preferences.branchReadOnly);
|
||||
|
||||
const projectPermissions = computed(() => {
|
||||
return getResourcePermissions(
|
||||
projectsStore.currentProject?.scopes ?? personalProject.value?.scopes,
|
||||
);
|
||||
});
|
||||
|
||||
const emptyListDescription = computed(() => {
|
||||
if (readOnlyEnv.value) {
|
||||
return i18n.baseText('workflows.empty.description.readOnlyEnv');
|
||||
} else if (!projectPermissions.value.workflow.create) {
|
||||
return i18n.baseText('workflows.empty.description.noPermission');
|
||||
} else {
|
||||
return i18n.baseText('workflows.empty.description');
|
||||
}
|
||||
});
|
||||
|
||||
const showReadyToRunV2Card = computed(() => {
|
||||
return readyToRunWorkflowsV2Store.getCardVisibility(
|
||||
projectPermissions.value.workflow.create,
|
||||
readOnlyEnv.value,
|
||||
false, // loading is false in simplified layout
|
||||
);
|
||||
});
|
||||
|
||||
const handleReadyToRunV2Click = async () => {
|
||||
const projectId = projectPages.isOverviewSubPage
|
||||
? personalProject.value?.id
|
||||
: (route.params.projectId as string);
|
||||
|
||||
try {
|
||||
await readyToRunWorkflowsV2Store.claimCreditsAndOpenWorkflow(
|
||||
'card',
|
||||
route.params.folderId as string,
|
||||
projectId,
|
||||
);
|
||||
} catch (error) {
|
||||
toast.showError(error, i18n.baseText('generic.error'));
|
||||
}
|
||||
};
|
||||
|
||||
const addWorkflow = () => {
|
||||
emit('click:add');
|
||||
};
|
||||
|
||||
const emit = defineEmits<{
|
||||
'click:add': [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="$style.simplifiedLayout">
|
||||
<div :class="$style.content">
|
||||
<div :class="$style.welcome">
|
||||
<N8nHeading tag="h1" size="2xlarge" :class="$style.welcomeTitle">
|
||||
{{
|
||||
currentUser.firstName
|
||||
? i18n.baseText('workflows.empty.heading', {
|
||||
interpolate: { name: currentUser.firstName },
|
||||
})
|
||||
: i18n.baseText('workflows.empty.heading.userNotSetup')
|
||||
}}
|
||||
</N8nHeading>
|
||||
<N8nText size="large" color="text-base" :class="$style.welcomeDescription">
|
||||
{{ emptyListDescription }}
|
||||
</N8nText>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!readOnlyEnv && projectPermissions.workflow.create"
|
||||
:class="$style.actionsContainer"
|
||||
>
|
||||
<N8nCard
|
||||
v-if="showReadyToRunV2Card"
|
||||
:class="$style.actionCard"
|
||||
hoverable
|
||||
data-test-id="ready-to-run-v2-card"
|
||||
@click="handleReadyToRunV2Click"
|
||||
>
|
||||
<div :class="$style.cardContent">
|
||||
<N8nIcon
|
||||
:class="$style.cardIcon"
|
||||
icon="sparkles"
|
||||
color="foreground-dark"
|
||||
:stroke-width="1.5"
|
||||
/>
|
||||
<N8nText size="large" class="mt-xs">
|
||||
{{ i18n.baseText('workflows.empty.readyToRunV2') }}
|
||||
</N8nText>
|
||||
</div>
|
||||
</N8nCard>
|
||||
|
||||
<N8nCard
|
||||
:class="$style.actionCard"
|
||||
hoverable
|
||||
data-test-id="new-workflow-card"
|
||||
@click="addWorkflow"
|
||||
>
|
||||
<div :class="$style.cardContent">
|
||||
<N8nIcon
|
||||
:class="$style.cardIcon"
|
||||
icon="file"
|
||||
color="foreground-dark"
|
||||
:stroke-width="1.5"
|
||||
/>
|
||||
<N8nText size="large" class="mt-xs">
|
||||
{{ i18n.baseText('workflows.empty.startFromScratch') }}
|
||||
</N8nText>
|
||||
</div>
|
||||
</N8nCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.simplifiedLayout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: var(--spacing-l);
|
||||
left: var(--spacing-l);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: 600px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome {
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.welcomeTitle {
|
||||
margin-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.welcomeDescription {
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.actionsContainer {
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.actionCard {
|
||||
width: 192px;
|
||||
height: 230px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.cardIcon svg {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cardContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-m);
|
||||
}
|
||||
|
||||
.cardIcon {
|
||||
font-size: 48px;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
|
||||
svg {
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
import { useFoldersStore } from '@/stores/folders.store';
|
||||
import { useProjectPages } from '@/composables/useProjectPages';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
/**
|
||||
* Determines if the instance is truly empty and should show the simplified layout
|
||||
*/
|
||||
export function useEmptyStateDetection() {
|
||||
const foldersStore = useFoldersStore();
|
||||
const projectPages = useProjectPages();
|
||||
const route = useRoute();
|
||||
|
||||
/**
|
||||
* Checks if the current state qualifies as "truly empty"
|
||||
* - No workflows exist in the instance
|
||||
* - User is on the main workflows view (not in a specific folder)
|
||||
* - User is on overview page or personal project workflows
|
||||
* - No search filters are applied
|
||||
* - Not currently refreshing data
|
||||
*/
|
||||
const isTrulyEmpty = (currentRoute: RouteLocationNormalized = route) => {
|
||||
const hasNoWorkflows = foldersStore.totalWorkflowCount === 0;
|
||||
const isNotInSpecificFolder = !currentRoute.params?.folderId;
|
||||
const isMainWorkflowsPage = projectPages.isOverviewSubPage || !projectPages.isSharedSubPage;
|
||||
|
||||
// Check for any search or filter parameters that would indicate filtering is active
|
||||
const hasSearchQuery = !!currentRoute.query?.search;
|
||||
const hasFilters = !!(
|
||||
currentRoute.query?.status ||
|
||||
currentRoute.query?.tags ||
|
||||
currentRoute.query?.showArchived ||
|
||||
currentRoute.query?.homeProject
|
||||
);
|
||||
|
||||
return (
|
||||
hasNoWorkflows &&
|
||||
isNotInSpecificFolder &&
|
||||
isMainWorkflowsPage &&
|
||||
!hasSearchQuery &&
|
||||
!hasFilters
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if we're in a state where the simplified layout should be shown
|
||||
* This matches the logic from ResourcesListLayout's showEmptyState computed property
|
||||
*/
|
||||
const shouldShowSimplifiedLayout = (
|
||||
currentRoute: RouteLocationNormalized,
|
||||
isFeatureEnabled: boolean,
|
||||
loading: boolean,
|
||||
) => {
|
||||
// Don't show simplified layout if loading or feature is disabled
|
||||
if (loading || !isFeatureEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isTrulyEmpty(currentRoute);
|
||||
};
|
||||
|
||||
return {
|
||||
isTrulyEmpty,
|
||||
shouldShowSimplifiedLayout,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { READY_TO_RUN_V2_EXPERIMENT, VIEWS } from '@/constants';
|
||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||
import { useCredentialsStore } from '@/stores/credentials.store';
|
||||
import { usePostHog } from '@/stores/posthog.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import { OPEN_AI_API_CREDENTIAL_TYPE } from 'n8n-workflow';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter, type RouteLocationNormalized } from 'vue-router';
|
||||
import { READY_TO_RUN_WORKFLOW_V1 } from '../workflows/ai-workflow';
|
||||
import { READY_TO_RUN_WORKFLOW_V2 } from '../workflows/ai-workflow-v2';
|
||||
import { useEmptyStateDetection } from '../composables/useEmptyStateDetection';
|
||||
|
||||
const LOCAL_STORAGE_CREDENTIAL_KEY = 'N8N_READY_TO_RUN_V2_OPENAI_CREDENTIAL_ID';
|
||||
|
||||
export const useReadyToRunWorkflowsV2Store = defineStore(
|
||||
STORES.EXPERIMENT_READY_TO_RUN_WORKFLOWS_V2,
|
||||
() => {
|
||||
const telemetry = useTelemetry();
|
||||
const i18n = useI18n();
|
||||
const toast = useToast();
|
||||
const router = useRouter();
|
||||
const credentialsStore = useCredentialsStore();
|
||||
const usersStore = useUsersStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
const posthogStore = usePostHog();
|
||||
const cloudPlanStore = useCloudPlanStore();
|
||||
|
||||
const isFeatureEnabled = computed(() => {
|
||||
const variant = posthogStore.getVariant(READY_TO_RUN_V2_EXPERIMENT.name);
|
||||
return (
|
||||
(variant === READY_TO_RUN_V2_EXPERIMENT.variant1 ||
|
||||
variant === READY_TO_RUN_V2_EXPERIMENT.variant2) &&
|
||||
cloudPlanStore.userIsTrialing
|
||||
);
|
||||
});
|
||||
|
||||
const claimedCredentialIdRef = useLocalStorage(LOCAL_STORAGE_CREDENTIAL_KEY, '');
|
||||
|
||||
const claimingCredits = ref(false);
|
||||
|
||||
const userHasOpenAiCredentialAlready = computed(
|
||||
() =>
|
||||
!!credentialsStore.allCredentials.filter(
|
||||
(credential) => credential.type === OPEN_AI_API_CREDENTIAL_TYPE,
|
||||
).length,
|
||||
);
|
||||
|
||||
const userHasClaimedAiCreditsAlready = computed(
|
||||
() => !!usersStore.currentUser?.settings?.userClaimedAiCredits,
|
||||
);
|
||||
|
||||
const userCanClaimOpenAiCredits = computed(() => {
|
||||
return (
|
||||
settingsStore.isAiCreditsEnabled &&
|
||||
!userHasOpenAiCredentialAlready.value &&
|
||||
!userHasClaimedAiCreditsAlready.value
|
||||
);
|
||||
});
|
||||
|
||||
const getCurrentVariant = () => {
|
||||
return posthogStore.getVariant(READY_TO_RUN_V2_EXPERIMENT.name);
|
||||
};
|
||||
|
||||
const trackExecuteAiWorkflow = (status: string) => {
|
||||
const variant = getCurrentVariant();
|
||||
telemetry.track('User executed ready to run AI workflow', {
|
||||
status,
|
||||
variant,
|
||||
});
|
||||
};
|
||||
|
||||
const trackExecuteAiWorkflowSuccess = () => {
|
||||
const variant = getCurrentVariant();
|
||||
telemetry.track('User executed ready to run AI workflow successfully', {
|
||||
variant,
|
||||
});
|
||||
};
|
||||
|
||||
const claimFreeAiCredits = async (projectId?: string) => {
|
||||
claimingCredits.value = true;
|
||||
|
||||
try {
|
||||
const credential = await credentialsStore.claimFreeAiCredits(projectId);
|
||||
|
||||
if (usersStore?.currentUser?.settings) {
|
||||
usersStore.currentUser.settings.userClaimedAiCredits = true;
|
||||
}
|
||||
|
||||
claimedCredentialIdRef.value = credential.id;
|
||||
|
||||
telemetry.track('User claimed OpenAI credits');
|
||||
return credential;
|
||||
} catch (e) {
|
||||
toast.showError(
|
||||
e,
|
||||
i18n.baseText('freeAi.credits.showError.claim.title'),
|
||||
i18n.baseText('freeAi.credits.showError.claim.message'),
|
||||
);
|
||||
throw e;
|
||||
} finally {
|
||||
claimingCredits.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openAiWorkflow = async (source: 'card' | 'button', parentFolderId?: string) => {
|
||||
const variant = getCurrentVariant();
|
||||
telemetry.track('User opened ready to run AI workflow', {
|
||||
source,
|
||||
variant,
|
||||
});
|
||||
|
||||
const workflow =
|
||||
variant === READY_TO_RUN_V2_EXPERIMENT.variant2
|
||||
? READY_TO_RUN_WORKFLOW_V2
|
||||
: READY_TO_RUN_WORKFLOW_V1;
|
||||
|
||||
await router.push({
|
||||
name: VIEWS.TEMPLATE_IMPORT,
|
||||
params: { id: workflow.meta?.templateId },
|
||||
query: { fromJson: 'true', parentFolderId },
|
||||
});
|
||||
};
|
||||
|
||||
const claimCreditsAndOpenWorkflow = async (
|
||||
source: 'card' | 'button',
|
||||
parentFolderId?: string,
|
||||
projectId?: string,
|
||||
) => {
|
||||
await claimFreeAiCredits(projectId);
|
||||
await openAiWorkflow(source, parentFolderId);
|
||||
};
|
||||
|
||||
const getCardVisibility = (
|
||||
canCreate: boolean | undefined,
|
||||
readOnlyEnv: boolean,
|
||||
loading: boolean,
|
||||
) => {
|
||||
return (
|
||||
!loading &&
|
||||
isFeatureEnabled.value &&
|
||||
userCanClaimOpenAiCredits.value &&
|
||||
!readOnlyEnv &&
|
||||
canCreate
|
||||
);
|
||||
};
|
||||
|
||||
const getButtonVisibility = (
|
||||
hasWorkflows: boolean,
|
||||
canCreate: boolean | undefined,
|
||||
readOnlyEnv: boolean,
|
||||
) => {
|
||||
return (
|
||||
isFeatureEnabled.value &&
|
||||
userCanClaimOpenAiCredits.value &&
|
||||
!readOnlyEnv &&
|
||||
canCreate &&
|
||||
hasWorkflows
|
||||
);
|
||||
};
|
||||
|
||||
const { shouldShowSimplifiedLayout } = useEmptyStateDetection();
|
||||
|
||||
const getSimplifiedLayoutVisibility = (route: RouteLocationNormalized, loading: boolean) => {
|
||||
return shouldShowSimplifiedLayout(route, isFeatureEnabled.value, loading);
|
||||
};
|
||||
|
||||
return {
|
||||
isFeatureEnabled,
|
||||
claimingCredits,
|
||||
userCanClaimOpenAiCredits,
|
||||
claimFreeAiCredits,
|
||||
openAiWorkflow,
|
||||
claimCreditsAndOpenWorkflow,
|
||||
getCardVisibility,
|
||||
getButtonVisibility,
|
||||
getSimplifiedLayoutVisibility,
|
||||
trackExecuteAiWorkflow,
|
||||
trackExecuteAiWorkflowSuccess,
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ApplicationError, deepCopy, OPEN_AI_API_CREDENTIAL_TYPE } from 'n8n-workflow';
|
||||
import type { WorkflowDataWithTemplateId } from '@/Interface';
|
||||
import { isWorkflowDataWithTemplateId } from '@/utils/templates/typeGuards';
|
||||
import { READY_TO_RUN_WORKFLOW_V1 } from '../workflows/ai-workflow';
|
||||
import { READY_TO_RUN_WORKFLOW_V2 } from '../workflows/ai-workflow-v2';
|
||||
|
||||
const getWorkflowJson = (json: unknown): WorkflowDataWithTemplateId => {
|
||||
if (!isWorkflowDataWithTemplateId(json)) {
|
||||
throw new ApplicationError('Invalid workflow template JSON structure');
|
||||
}
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Injects OpenAI credentials into workflow template if available in localStorage
|
||||
*/
|
||||
const injectOpenAiCredentialIntoWorkflow = (
|
||||
workflow: WorkflowDataWithTemplateId,
|
||||
): WorkflowDataWithTemplateId => {
|
||||
const credentialId = localStorage.getItem('N8N_READY_TO_RUN_V2_OPENAI_CREDENTIAL_ID');
|
||||
|
||||
if (!credentialId) {
|
||||
return workflow;
|
||||
}
|
||||
|
||||
const clonedWorkflow = deepCopy(workflow);
|
||||
|
||||
if (clonedWorkflow.nodes) {
|
||||
const openAiNode = clonedWorkflow.nodes.find((node) => node.name === 'OpenAI Model');
|
||||
if (openAiNode) {
|
||||
openAiNode.credentials ??= {};
|
||||
openAiNode.credentials[OPEN_AI_API_CREDENTIAL_TYPE] = {
|
||||
id: credentialId,
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return clonedWorkflow;
|
||||
};
|
||||
|
||||
export const getReadyToRunAIWorkflows = (): WorkflowDataWithTemplateId[] => {
|
||||
return [
|
||||
injectOpenAiCredentialIntoWorkflow(getWorkflowJson(READY_TO_RUN_WORKFLOW_V1)),
|
||||
injectOpenAiCredentialIntoWorkflow(getWorkflowJson(READY_TO_RUN_WORKFLOW_V2)),
|
||||
];
|
||||
};
|
||||
@@ -0,0 +1,253 @@
|
||||
import type { WorkflowDataCreate } from '@n8n/rest-api-client';
|
||||
|
||||
export const READY_TO_RUN_WORKFLOW_V2: WorkflowDataCreate = {
|
||||
name: 'AI Agent workflow',
|
||||
meta: { templateId: 'ready-to-run-ai-workflow-v2' },
|
||||
nodes: [
|
||||
{
|
||||
parameters: {
|
||||
url: 'https://www.theverge.com/rss/index.xml',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.rssFeedReadTool',
|
||||
typeVersion: 1.2,
|
||||
position: [-16, 768],
|
||||
id: '303e9b4e-cc4e-4d8a-8ede-7550f070d212',
|
||||
name: 'Get Tech News',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
toolDescription: 'Reads the news',
|
||||
url: '=https://feeds.bbci.co.uk/news/world/rss.xml',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.rssFeedReadTool',
|
||||
typeVersion: 1.2,
|
||||
position: [112, 768],
|
||||
id: '4090a753-f131-40b1-87c3-cf74d5a7e325',
|
||||
name: 'Get World News',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
rule: {
|
||||
interval: [
|
||||
{
|
||||
triggerAtHour: 7,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'n8n-nodes-base.scheduleTrigger',
|
||||
typeVersion: 1.2,
|
||||
position: [-560, 752],
|
||||
id: '651543b5-0213-433f-8760-57d62b8d6d64',
|
||||
name: 'Run every day at 7AM',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
assignments: {
|
||||
assignments: [
|
||||
{
|
||||
id: '85b5c530-2c13-4424-ab83-05979bc879a5',
|
||||
name: 'output',
|
||||
value: '={{ $json.output }}',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 3.4,
|
||||
position: [160, 544],
|
||||
id: '99f7bb9e-f8c0-43ca-a9a8-a76634ac9611',
|
||||
name: 'Output',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {},
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [-560, 544],
|
||||
id: 'a0390291-6794-4673-9a6a-5c3d3a5d9e4b',
|
||||
name: 'Click ‘Execute workflow’ to run',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content: '## ⚡ Start here:',
|
||||
height: 224,
|
||||
width: 224,
|
||||
color: 7,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [-624, 480],
|
||||
id: 'fac5929f-e065-4474-96b1-7bcc06834238',
|
||||
name: 'Sticky Note',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
model: {
|
||||
__rl: true,
|
||||
mode: 'list',
|
||||
value: 'gpt-4.1-mini',
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
|
||||
typeVersion: 1.2,
|
||||
position: [-272, 768],
|
||||
id: 'b16482e8-0d48-4426-aa93-c3fee11dd3cd',
|
||||
name: 'OpenAI Model',
|
||||
notesInFlow: true,
|
||||
credentials: {},
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content: '@[youtube](cMyOkQ4N-5M)',
|
||||
height: 512,
|
||||
width: 902,
|
||||
color: 7,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [-352, -96],
|
||||
id: 'ec65e69e-77fa-4912-a4af-49e0a248e2c8',
|
||||
name: 'Sticky Note3',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
promptType: 'define',
|
||||
text: '=Summarize world news and tech news from the last 24 hours. \nSkip your comments. \nThe titles should be "World news:" and "Tech news:" \nToday is {{ $today }}',
|
||||
options: {},
|
||||
},
|
||||
type: '@n8n/n8n-nodes-langchain.agent',
|
||||
typeVersion: 2.2,
|
||||
position: [-272, 544],
|
||||
id: '084d56aa-d157-4964-9073-b36d9d9589c5',
|
||||
name: 'AI Summary Agent',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content: '### Double click here to see the results:',
|
||||
height: 240,
|
||||
width: 192,
|
||||
color: 7,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [112, 464],
|
||||
id: 'a4b7a69a-0db8-4b9b-a81d-fd83378043a3',
|
||||
name: 'Sticky Note1',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content:
|
||||
'### 📰 Daily AI Summary\n\n\nThis workflow gets the latest news and asks AI to summarize it for you.\n\n⭐ Bonus: Send the summary via email by connecting your Gmail account\n\n▶ Watch the video to get started ',
|
||||
height: 272,
|
||||
width: 224,
|
||||
color: 5,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [-624, 32],
|
||||
id: '74d80857-5e63-47a8-8e86-8ecd10fd5f9e',
|
||||
name: 'Sticky Note2',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
subject: 'Your news daily summary',
|
||||
emailType: 'text',
|
||||
message: '={{ $json.output }}',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.gmail',
|
||||
typeVersion: 2.1,
|
||||
position: [432, 544],
|
||||
id: '45625d0d-bf26-4379-9eed-7bbc8e5d87a5',
|
||||
name: 'Send summary by email',
|
||||
webhookId: '093b04f1-5e78-4926-9863-1b100d6f2ead',
|
||||
notesInFlow: true,
|
||||
credentials: {},
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
'Get Tech News': {
|
||||
ai_tool: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_tool',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Get World News': {
|
||||
ai_tool: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_tool',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Run every day at 7AM': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Click ‘Execute workflow’ to run': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'OpenAI Model': {
|
||||
ai_languageModel: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_languageModel',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'AI Summary Agent': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'Output',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
Output: {
|
||||
main: [[]],
|
||||
},
|
||||
},
|
||||
pinData: {},
|
||||
};
|
||||
@@ -0,0 +1,240 @@
|
||||
import type { WorkflowDataCreate } from '@n8n/rest-api-client';
|
||||
|
||||
export const READY_TO_RUN_WORKFLOW_V1: WorkflowDataCreate = {
|
||||
name: 'AI Agent workflow',
|
||||
meta: { templateId: 'ready-to-run-ai-workflow-v1' },
|
||||
nodes: [
|
||||
{
|
||||
parameters: {
|
||||
url: 'https://www.theverge.com/rss/index.xml',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.rssFeedReadTool',
|
||||
typeVersion: 1.2,
|
||||
position: [-16, 768],
|
||||
id: '303e9b4e-cc4e-4d8a-8ede-7550f070d212',
|
||||
name: 'Get Tech News',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
toolDescription: 'Reads the news',
|
||||
url: '=https://feeds.bbci.co.uk/news/world/rss.xml',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.rssFeedReadTool',
|
||||
typeVersion: 1.2,
|
||||
position: [112, 768],
|
||||
id: '4090a753-f131-40b1-87c3-cf74d5a7e325',
|
||||
name: 'Get World News',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
rule: {
|
||||
interval: [
|
||||
{
|
||||
triggerAtHour: 7,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'n8n-nodes-base.scheduleTrigger',
|
||||
typeVersion: 1.2,
|
||||
position: [-560, 752],
|
||||
id: '651543b5-0213-433f-8760-57d62b8d6d64',
|
||||
name: 'Run every day at 7AM',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
assignments: {
|
||||
assignments: [
|
||||
{
|
||||
id: '85b5c530-2c13-4424-ab83-05979bc879a5',
|
||||
name: 'output',
|
||||
value: '={{ $json.output }}',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.set',
|
||||
typeVersion: 3.4,
|
||||
position: [160, 544],
|
||||
id: '99f7bb9e-f8c0-43ca-a9a8-a76634ac9611',
|
||||
name: 'Output',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {},
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [-560, 544],
|
||||
id: 'a0390291-6794-4673-9a6a-5c3d3a5d9e4b',
|
||||
name: 'Click ‘Execute workflow’ to run',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content: '## ⚡ Start here:',
|
||||
height: 240,
|
||||
width: 224,
|
||||
color: 7,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [-624, 464],
|
||||
id: 'fac5929f-e065-4474-96b1-7bcc06834238',
|
||||
name: 'Sticky Note',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
model: {
|
||||
__rl: true,
|
||||
mode: 'list',
|
||||
value: 'gpt-4.1-mini',
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
|
||||
typeVersion: 1.2,
|
||||
position: [-272, 768],
|
||||
id: 'b16482e8-0d48-4426-aa93-c3fee11dd3cd',
|
||||
name: 'OpenAI Model',
|
||||
notesInFlow: true,
|
||||
credentials: {},
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
promptType: 'define',
|
||||
text: '=Summarize world news and tech news from the last 24 hours. \nSkip your comments. \nThe titles should be "World news:" and "Tech news:" \nToday is {{ $today }}',
|
||||
options: {},
|
||||
},
|
||||
type: '@n8n/n8n-nodes-langchain.agent',
|
||||
typeVersion: 2.2,
|
||||
position: [-272, 544],
|
||||
id: '084d56aa-d157-4964-9073-b36d9d9589c5',
|
||||
name: 'AI Summary Agent',
|
||||
notesInFlow: true,
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content: '### Double click here to see the results:',
|
||||
height: 240,
|
||||
width: 192,
|
||||
color: 7,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [112, 464],
|
||||
id: 'a4b7a69a-0db8-4b9b-a81d-fd83378043a3',
|
||||
name: 'Sticky Note1',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
content:
|
||||
'### 📰 Daily AI Summary\n\n\nThis workflow gets the latest news and asks AI to summarize it for you.\n\n⭐ Bonus: Send the summary via email by connecting your Gmail account\n\n\n\n@[youtube](cMyOkQ4N-5M)',
|
||||
height: 432,
|
||||
width: 384,
|
||||
color: 5,
|
||||
},
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
typeVersion: 1,
|
||||
position: [-1152, 464],
|
||||
id: '74d80857-5e63-47a8-8e86-8ecd10fd5f9e',
|
||||
name: 'Sticky Note2',
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
subject: 'Your news daily summary',
|
||||
emailType: 'text',
|
||||
message: '={{ $json.output }}',
|
||||
options: {},
|
||||
},
|
||||
type: 'n8n-nodes-base.gmail',
|
||||
typeVersion: 2.1,
|
||||
position: [432, 544],
|
||||
id: '45625d0d-bf26-4379-9eed-7bbc8e5d87a5',
|
||||
name: 'Send summary by email',
|
||||
webhookId: '093b04f1-5e78-4926-9863-1b100d6f2ead',
|
||||
notesInFlow: true,
|
||||
credentials: {},
|
||||
notes: 'Double-click to open',
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
'Get Tech News': {
|
||||
ai_tool: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_tool',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Get World News': {
|
||||
ai_tool: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_tool',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Run every day at 7AM': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'Click ‘Execute workflow’ to run': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'OpenAI Model': {
|
||||
ai_languageModel: [
|
||||
[
|
||||
{
|
||||
node: 'AI Summary Agent',
|
||||
type: 'ai_languageModel',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
'AI Summary Agent': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: 'Output',
|
||||
type: 'main',
|
||||
index: 0,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
Output: {
|
||||
main: [[]],
|
||||
},
|
||||
},
|
||||
pinData: {},
|
||||
};
|
||||
@@ -62,6 +62,7 @@ vi.mock('@/composables/useDocumentTitle', () => ({
|
||||
|
||||
const mockDebounce = {
|
||||
callDebounced: vi.fn((fn) => fn()),
|
||||
debounce: vi.fn(),
|
||||
};
|
||||
vi.mock('@/composables/useDebounce', () => ({
|
||||
useDebounce: vi.fn(() => mockDebounce),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApplicationError, type INodeTypeNameVersion } from 'n8n-workflow';
|
||||
import type { WorkflowDataWithTemplateId } from '@/Interface';
|
||||
import { isWorkflowDataWithTemplateId } from '@/utils/templates/typeGuards';
|
||||
import { getReadyToRunAIWorkflows } from '@/experiments/readyToRunWorkflowsV2/utils/workflowSamples';
|
||||
|
||||
/* eslint-disable import-x/extensions */
|
||||
import easyAiStarterJson from '@/utils/templates/samples/easy_ai_starter.json';
|
||||
@@ -192,6 +193,7 @@ export const getSampleWorkflowByTemplateId = (
|
||||
const workflows = [
|
||||
getEasyAiWorkflowJson(),
|
||||
getRagStarterWorkflowJson(),
|
||||
...getReadyToRunAIWorkflows(),
|
||||
...getPrebuiltAgents().map((agent) => agent.template),
|
||||
...getTutorialTemplates().map((tutorial) => tutorial.template),
|
||||
];
|
||||
|
||||
@@ -26,8 +26,10 @@ import SuggestedWorkflowCard from '@/experiments/personalizedTemplates/component
|
||||
import SuggestedWorkflows from '@/experiments/personalizedTemplates/components/SuggestedWorkflows.vue';
|
||||
import { usePersonalizedTemplatesStore } from '@/experiments/personalizedTemplates/stores/personalizedTemplates.store';
|
||||
import { useReadyToRunWorkflowsStore } from '@/experiments/readyToRunWorkflows/stores/readyToRunWorkflows.store';
|
||||
import { useReadyToRunWorkflowsV2Store } from '@/experiments/readyToRunWorkflowsV2/stores/readyToRunWorkflowsV2.store';
|
||||
import TemplateRecommendationV2 from '@/experiments/templateRecoV2/components/TemplateRecommendationV2.vue';
|
||||
import { usePersonalizedTemplatesV2Store } from '@/experiments/templateRecoV2/stores/templateRecoV2.store';
|
||||
import SimplifiedEmptyLayout from '@/experiments/readyToRunWorkflowsV2/components/SimplifiedEmptyLayout.vue';
|
||||
import InsightsSummary from '@/features/insights/components/InsightsSummary.vue';
|
||||
import { useInsightsStore } from '@/features/insights/insights.store';
|
||||
import type {
|
||||
@@ -124,6 +126,7 @@ const templatesStore = useTemplatesStore();
|
||||
const aiStarterTemplatesStore = useAITemplatesStarterCollectionStore();
|
||||
const personalizedTemplatesStore = usePersonalizedTemplatesStore();
|
||||
const readyToRunWorkflowsStore = useReadyToRunWorkflowsStore();
|
||||
const readyToRunWorkflowsV2Store = useReadyToRunWorkflowsV2Store();
|
||||
const personalizedTemplatesV2Store = usePersonalizedTemplatesV2Store();
|
||||
|
||||
const documentTitle = useDocumentTitle();
|
||||
@@ -437,6 +440,19 @@ const showPersonalizedTemplates = computed(
|
||||
() => !loading.value && personalizedTemplatesStore.isFeatureEnabled(),
|
||||
);
|
||||
|
||||
const shouldUseSimplifiedLayout = computed(() => {
|
||||
return readyToRunWorkflowsV2Store.getSimplifiedLayoutVisibility(route, loading.value);
|
||||
});
|
||||
|
||||
const hasActiveCallouts = computed(() => {
|
||||
return (
|
||||
showPrebuiltAgentsCallout.value ||
|
||||
showAIStarterCollectionCallout.value ||
|
||||
showPersonalizedTemplates.value ||
|
||||
showReadyToRunWorkflowsCallout.value
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* WATCHERS, STORE SUBSCRIPTIONS AND EVENT BUS HANDLERS
|
||||
*/
|
||||
@@ -1713,7 +1729,10 @@ const onNameSubmit = async (name: string) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SimplifiedEmptyLayout v-if="shouldUseSimplifiedLayout" @click:add="addWorkflow" />
|
||||
|
||||
<ResourcesListLayout
|
||||
v-else
|
||||
v-model:filters="filters"
|
||||
resource-key="workflows"
|
||||
type="list-paginated"
|
||||
@@ -1735,7 +1754,10 @@ const onNameSubmit = async (name: string) => {
|
||||
@mouseleave="folderHelpers.resetDropTarget"
|
||||
>
|
||||
<template #header>
|
||||
<ProjectHeader @create-folder="createFolderInCurrent">
|
||||
<ProjectHeader
|
||||
:has-active-callouts="hasActiveCallouts"
|
||||
@create-folder="createFolderInCurrent"
|
||||
>
|
||||
<InsightsSummary
|
||||
v-if="showInsights"
|
||||
:loading="insightsStore.weeklySummary.isLoading"
|
||||
|
||||
Reference in New Issue
Block a user