PluginProbe
Extendify / 3.1.4
Extendify v3.1.4
3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 0.7.0 All 126 releases
extendify / src / Agent / state / workflows.js

workflows.js in Extendify 3.1.4, at src/Agent/state/workflows.js

153 lines 5.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { isAbilityWorkflow } from '@agent/lib/abilities';
2 import { isChangeSiteDesignWorkflowAvailable } from '@agent/lib/util';
3 import { AbilityRun } from '@agent/workflows/abilities/components/run';
4 import changeSiteDesignWorkflow from '@agent/workflows/theme/change-site-design';
5 import variationsWorkflow from '@agent/workflows/theme/change-theme-variation';
6 import { abilityWorkflows, workflows } from '@agent/workflows/workflows';
7 import { useQuickEditStore } from '@quick-edit/state/store';
8 import { deepMerge } from '@shared/lib/utils';
9 import { create } from 'zustand';
10 import { devtools, persist } from 'zustand/middleware';
11
12 const onboarding = window.extAgentData.chatHistory?.length === 0;
13 const agentResponse = isChangeSiteDesignWorkflowAvailable()
14 ? changeSiteDesignWorkflow.example?.agentResponse
15 : variationsWorkflow.example?.agentResponse;
16 const onboardingToolProps = {
17 ...agentResponse.whenFinishedTool,
18 agentResponse,
19 };
20
21 // Used to check case-insensitive matches for workflow examples
22 const collator = new Intl.Collator(undefined, {
23 sensitivity: 'base',
24 usage: 'search',
25 });
26
27 const state = (set, get) => ({
28 workflow: null,
29 // Data for the tool component that shows up at the end of a workflow
30 whenFinishedToolProps: null,
31 getWorkflow: () => {
32 const curr = get().workflow;
33 // Workflows may define a "parent" workflow via templateId
34 const currId = curr?.templateId || curr?.id;
35 const wf = workflows.find(({ id }) => id === currId);
36 if (!wf?.id) {
37 if (!curr) return null;
38 if (!isAbilityWorkflow(curr.id)) return curr;
39 // The backend never sends whenFinished, so this supplies the gate.
40 const ability = deepMerge(
41 { whenFinished: { component: AbilityRun } },
42 abilityWorkflows[curr.id] ?? {},
43 );
44 return { ...deepMerge(curr, ability), id: curr.id };
45 }
46 return { ...deepMerge(curr, wf || {}), id: curr?.id };
47 },
48 getWorkflowByExample: (example) => {
49 const wf = workflows.find(
50 ({ example: ex }) => collator.compare(ex?.text, example) === 0,
51 );
52 if (!wf?.id) return null;
53 return wf;
54 },
55 // Gets the workflows available to the user
56 // TODO: maybe we need to have a way to include a
57 // workflow regardless of the block being active?
58 getAvailableWorkflows: () => {
59 const wfs = workflows.filter(({ available }) => available());
60 // If a block is set, only include those with 'block'
61 const blockWorkflows = wfs.filter(({ requires }) =>
62 requires?.includes('block'),
63 );
64 if (useQuickEditStore.getState().agentBlock) return blockWorkflows;
65 // otherwise remove all of the above
66 return wfs.filter(({ id }) => !blockWorkflows.some((w) => w.id === id));
67 },
68 getWorkflowsByFeature: ({ requires } = {}) => {
69 if (!requires) return workflows.filter(({ available }) => available());
70 // e.g. requires: ['block']
71 return workflows.filter(
72 ({ available, requires: workflowRequires }) =>
73 available() &&
74 (!requires || workflowRequires?.some((s) => requires.includes(s))),
75 );
76 },
77 workflowData: null,
78 mergeWorkflowData: (data) => {
79 set((state) => {
80 if (!state.workflowData) return { workflowData: data };
81 return {
82 workflowData: { ...state.workflowData, ...data },
83 };
84 });
85 },
86 setWorkflow: (workflow) => {
87 const agentBlockCode = useQuickEditStore.getState().agentBlockCode;
88 const started = workflow
89 ? { ...workflow, startingPage: window.location.href }
90 : null;
91 set({
92 workflow: started,
93 // If a block is selected, add it to the workflow data
94 // previousContent is named this way for legacy reasons
95 workflowData: agentBlockCode ? { previousContent: agentBlockCode } : null,
96 whenFinishedToolProps: null,
97 });
98 if (!started) return;
99 const workflowStarted = get().getWorkflow();
100 window.dispatchEvent(
101 new CustomEvent('extendify-workflow::started', {
102 detail: workflowStarted,
103 }),
104 );
105 try {
106 workflowStarted?.onStarted?.(workflowStarted);
107 } catch {
108 // A workflow that can't read the page still has to start.
109 }
110 },
111 setWhenFinishedToolProps: (whenFinishedToolProps) =>
112 set({ whenFinishedToolProps }),
113 // Declared up front it would hide the workflow while nothing is staged.
114 // Not setWorkflow: that resets workflowData and drops the run's data.
115 requireBlock: () =>
116 set((state) =>
117 state.workflow
118 ? { workflow: { ...state.workflow, requires: ['block'] } }
119 : {},
120 ),
121 });
122
123 export const useWorkflowStore = create()(
124 persist(devtools(state, { name: 'Extendify Agent Workflows' }), {
125 name: `extendify-agent-workflows-${window.extSharedData.siteId}`,
126 merge: (persistedState, currentState) => {
127 // if we are in onboarding mode, add the starting workflow
128 if (onboarding) {
129 return {
130 ...currentState,
131 ...persistedState,
132 workflow: isChangeSiteDesignWorkflowAvailable()
133 ? changeSiteDesignWorkflow
134 : variationsWorkflow,
135 whenFinishedToolProps: onboardingToolProps,
136 };
137 }
138 const merged = { ...currentState, ...persistedState };
139 // A reload can't carry the staged block a block-patching workflow
140 // depends on, so a rehydrated-open one is always stale.
141 if (merged.workflow?.id === 'block-patching') {
142 return {
143 ...merged,
144 workflow: null,
145 whenFinishedToolProps: null,
146 workflowData: null,
147 };
148 }
149 return merged;
150 },
151 }),
152 );
153