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