@burdenoff/fluidgrids-fe-sdk 2026.1111.16 → 2026.1111.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/AiStudioPage-kJHSsIcj.js +774 -0
- package/dist/ApprovalInboxPage-dbBDSJ-f.js +558 -0
- package/dist/DataSinkDashboardPage-1h2QsMzH.js +270 -0
- package/dist/DataTablesPage-BZy2z0LB.js +1690 -0
- package/dist/EntityTagPicker-BqRNsLCQ.js +135 -0
- package/dist/{ExecutionDetailPage-D-0_POAf.js → ExecutionDetailPage-BdcZmLT4.js} +799 -4420
- package/dist/ExecutionDetailPage-DvQ667OE.d.ts +10 -0
- package/dist/ExecutionsListPage-BCUJoAIE.js +2742 -0
- package/dist/{FailureTriagePanel-BHiduL7A.js → FailureTriagePanel-B9hDQymi.js} +4 -97
- package/dist/{FluidGridsProvider-DmvFADFa.js → FluidGridsProvider-dvZSNtSW.js} +10 -350
- package/dist/HistoryPage-BLoRpJ1R.d.ts +6 -0
- package/dist/{HistoryPage-BbUgxx1I.js → HistoryPage-U0tqupBN.js} +6 -4
- package/dist/HumanRequestCard-D-ooVPDn.js +439 -0
- package/dist/{InsightsPage-s6f5bU-8.js → InsightsPage-CALo16eB.js} +17 -328
- package/dist/ManagedAiPolicyAuthority-BxwJ3ptd.js +40 -0
- package/dist/{ManagedAiPolicyAuthority-CGux_DaV.d.ts → ManagedAiPolicyAuthority-DM3t3wNr.d.ts} +1 -5
- package/dist/ManagedAiPolicyAuthorityContext-CNkxIOZ1.js +10 -0
- package/dist/{MissingNodesInstallModal-B-9gHy2K.js → MissingNodesInstallModal-CMybsI8h.js} +3 -2
- package/dist/{NodesPage-ClvhUfbh.js → NodesPage-Bf5PhfBK.js} +7 -3
- package/dist/NodesPage-DEbnz9iU.d.ts +6 -0
- package/dist/{PublishCustomNodeAction-D2F7oN0S.js → PublishCustomNodeAction-5XXnSA3H.js} +3 -45
- package/dist/QuotaExhaustedDialog-7IFSaLyi.js +54 -0
- package/dist/RunnerStatusPill-Ctg4TJcP.js +187 -0
- package/dist/RunnersPage-CLCOCclc.js +467 -0
- package/dist/{ManagedAiPolicyAuthority-DsSaz-vg.js → SettingsPage-CgkRlCM5.js} +19 -50
- package/dist/SettingsPage-DMHQFj5-.d.ts +9 -0
- package/dist/TagFilterSelect-DCBOH6Ou.js +41 -0
- package/dist/TaskQueuesPage-9VgSnLdL.js +951 -0
- package/dist/TemplateCard-DsC0KNH1.js +1416 -0
- package/dist/{TemplateConfigurationModal-D_Mvgxqa.js → TemplateConfigurationModal-BhGWIzpm.js} +3 -2
- package/dist/{TemplateDetailPanel-DfFfUcOe.js → TemplateDetailPanel-D4cUVr-m.js} +3 -3
- package/dist/TemplatePreview-GthzCOSo.js +231 -0
- package/dist/TemplatesPage-BG3H-6v6.d.ts +10 -0
- package/dist/{TemplatesPage-Cku13bav.js → TemplatesPage-CsDNT54N.js} +11 -228
- package/dist/VariablesPage-67WR_lVE.js +949 -0
- package/dist/{VersionDiffDialog-Dq2xlPFI.js → VersionDiffDialog-DSN0mU0f.js} +2 -1
- package/dist/WorkflowBuilder-DEi_9YOb.js +555 -0
- package/dist/{WorkflowSearchRedirect-CaoOOpEl.js → WorkflowBuilder-iPqXgbYr.js} +19 -585
- package/dist/{WorkflowProposalCard-8Vr56kQa.js → WorkflowProposalCard-qrEeNyYG.js} +4 -405
- package/dist/WorkflowSearchRedirect-DwDNZ_q5.js +36 -0
- package/dist/{WorkflowShareDialog-6RRDe-_v.js → WorkflowShareDialog-B_gn968c.js} +6 -52
- package/dist/{WorkflowsList-B2j1Pehb.js → WorkflowsList-CA20dVIl.js} +11 -6
- package/dist/WorkflowsList-CcNDQge5.d.ts +6 -0
- package/dist/agent-form-Bfvm32j1.js +83 -0
- package/dist/{agentTools-DJ5o2ekg.js → agentTools-CkRJB7r5.js} +3 -2
- package/dist/{ai-usage-D5p7ZeRR.js → ai-usage-glCwHyQz.js} +1 -1
- package/dist/api-BrCHB5oA.js +224 -0
- package/dist/api-C8e0iV-Q.js +55 -0
- package/dist/button-DG7y_FVJ.js +48 -0
- package/dist/{canvasNoteTools-D3zUdEeg.js → canvasNoteTools-CQIc7Oxg.js} +2 -2
- package/dist/client-C9XSzSXr.js +1147 -0
- package/dist/{codec-client-DstqyzTN.js → codec-client-CJ95Rmqo.js} +1 -1
- package/dist/constants-B2lhWNoN.js +202 -0
- package/dist/constants-XdvUxeWK.d.ts +162 -0
- package/dist/dialog-Cjb4lB4D.js +105 -0
- package/dist/executions.d.ts +1 -1
- package/dist/executions.js +2 -1
- package/dist/feature-flags-DM5myWaD.js +296 -0
- package/dist/flags.d.ts +3 -0
- package/dist/flags.js +4 -0
- package/dist/{flow-builder-DEzT8TQu.js → flow-builder-y2Y1iG4Y.js} +70 -1731
- package/dist/{node-endpoint-configs-DHFGA5MQ.js → graphql-operation-types-kX-MMjgW.js} +1 -1194
- package/dist/{graphql-types-XcDLtFwU.js → graphql-types-CDKVWH2R.js} +2 -202
- package/dist/history.d.ts +1 -1
- package/dist/history.js +1 -1
- package/dist/index-72XFgvbn.d.ts +132 -0
- package/dist/index-BcdtKBM-.d.ts +56 -0
- package/dist/{index-B3BWqafN.d.ts → index-DjiFgEQl.d.ts} +5 -131
- package/dist/{index-DRf25-D3.d.ts → index-W2o__Nm6.d.ts} +32 -214
- package/dist/index.d.ts +13 -9
- package/dist/index.js +50 -5300
- package/dist/{node-detail-view-LNqeIqN-.js → node-detail-view-B91kXzIy.js} +11 -8
- package/dist/node-endpoint-configs-B5bjNMDW.js +37 -0
- package/dist/node-registry-refresh-Dy0uRg9f.js +84 -0
- package/dist/nodes.d.ts +1 -1
- package/dist/nodes.js +1 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -1
- package/dist/{quick-config-panel-D3ogsaFL.js → quick-config-panel-DggPl1vY.js} +2 -2
- package/dist/rolldown-runtime-hDbaFkMy.js +18 -0
- package/dist/run-display-BycKYSU0.js +46 -0
- package/dist/run-list-links-ByYC0OlV.js +98 -0
- package/dist/settings.d.ts +2 -1
- package/dist/settings.js +2 -1
- package/dist/shared-CMfn30xT.js +196 -0
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +2 -1
- package/dist/theme-provider-B3mIuz2d.js +11 -0
- package/dist/{useAgentDefinitions-NFtLytDR.js → useAgentDefinitions-Dpz0HDTq.js} +2 -1
- package/dist/useDurableParity-CzUQMFJ3.js +415 -0
- package/dist/useEntityTags-DhnsZcTl.js +236 -0
- package/dist/{useExecutions-DAzeWHS6.js → useExecutions-9aLuOt1c.js} +4 -3
- package/dist/{useObservability-COCZ0hUS.js → useObservability-CfEw3ZFD.js} +2 -2
- package/dist/{useRunHistory-CsYUbouO.js → useRunHistory-B1xJDwl1.js} +4 -3
- package/dist/{useWorkflows-w4Dx2fF2.js → useWorkflows-BlVVu8NM.js} +3 -2
- package/dist/utils-uaL5UVxb.js +65 -0
- package/dist/workflow-builder.d.ts +1 -1
- package/dist/workflow-builder.js +4 -2
- package/dist/workflow-host.d.ts +6 -0
- package/dist/workflow-host.js +9 -0
- package/dist/workflow-pages.d.ts +55 -0
- package/dist/workflow-pages.js +113 -0
- package/dist/workflows-list.d.ts +1 -1
- package/dist/workflows-list.js +1 -1
- package/package.json +13 -1
- package/dist/ExecutionDetailPage-MvsnOnE1.d.ts +0 -4
- package/dist/HistoryPage-BqUizaFv.d.ts +0 -3
- package/dist/NodesPage-C97-9K2Y.d.ts +0 -3
- package/dist/TemplatesPage-D0qnXzHh.d.ts +0 -4
- package/dist/WorkflowsList-0d5GSKdh.d.ts +0 -3
- package/dist/agent-form-BvMWhQ42.js +0 -496
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
|
|
3
|
+
import { t as ThemeProvider } from "./theme-provider-B3mIuz2d.js";
|
|
4
|
+
import { a as useAiWorkflowTemplates, i as enrichDraftNodes, n as AI_DRAFT_MAX_PROMPT_LENGTH, r as draftToCanvas, t as FlowBuilderPage } from "./flow-builder-y2Y1iG4Y.js";
|
|
5
|
+
import { Et as classifyCreateRefusal, I as buildOperationSeed, Tt as builderPathOf, u as ensureNodeTypesLoaded } from "./FluidGridsProvider-dvZSNtSW.js";
|
|
6
|
+
import { _ as throwIfResultError, n as getFluidGridsClient } from "./client-C9XSzSXr.js";
|
|
7
|
+
import { a as notifyFluidGridsAiCreditsUpdated, t as AiCreditBalanceSlot } from "./aiCreditsBridge-BuE92tKo.js";
|
|
8
|
+
import { Pr as WorkflowGenerationDocument, y as CancelWorkflowGenerationDocument, yt as GenerateWorkflowFromPromptDocument } from "./graphql-operation-types-kX-MMjgW.js";
|
|
9
|
+
import { n as useCreateWorkflow } from "./useWorkflows-BlVVu8NM.js";
|
|
10
|
+
import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
|
|
11
|
+
import { f as BILLING_PLANS_PATH } from "./fluidgridsEvents-CNdcrUNN.js";
|
|
12
|
+
import { O as CreateWorkflowRefusalDialog } from "./WorkflowShareDialog-B_gn968c.js";
|
|
13
|
+
import { s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-B2lhWNoN.js";
|
|
14
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
15
|
+
import { useNavigate } from "react-router";
|
|
16
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
17
|
+
import { toast } from "sonner";
|
|
18
|
+
import { Badge, Button, Card, CardContent, CardHeader, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea } from "@burdenoff/fe-libs/ui";
|
|
19
|
+
import { ErrorBoundary } from "@burdenoff/fe-libs/shared/components";
|
|
20
|
+
import { v4 } from "uuid";
|
|
21
|
+
import "@xyflow/react/dist/style.css";
|
|
22
|
+
import { CheckCircle2, Lightbulb, Loader2, MessageCircleQuestion, Pencil, RefreshCw, Sparkles, Square, TriangleAlert } from "lucide-react";
|
|
23
|
+
|
|
24
|
+
// src/hooks/useWorkflowGeneration.ts
|
|
25
|
+
/**
|
|
26
|
+
* useWorkflowGeneration — drives one natural-language workflow generation for
|
|
27
|
+
* AI Studio (BOFF-4089): start it with `generateWorkflowFromPrompt`, poll
|
|
28
|
+
* `workflowGeneration(id)` while it is PROCESSING, and cancel it.
|
|
29
|
+
*
|
|
30
|
+
* A generation never saves anything. Its draft is shown for review and saved
|
|
31
|
+
* as a new DRAFT workflow only when the user chooses "Edit in builder".
|
|
32
|
+
*/
|
|
33
|
+
/** How often a PROCESSING generation is polled. */
|
|
34
|
+
const GENERATION_POLL_INTERVAL_MS = 3e3;
|
|
35
|
+
/** Stop polling after this long; the server closes a stuck generation after 20 minutes. */
|
|
36
|
+
const GENERATION_MAX_WAIT_MS = 36e4;
|
|
37
|
+
function messageOf(error) {
|
|
38
|
+
return error instanceof Error ? error.message : String(error);
|
|
39
|
+
}
|
|
40
|
+
function useWorkflowGeneration() {
|
|
41
|
+
const [generation, setGeneration] = useState(null);
|
|
42
|
+
const [isWorking, setIsWorking] = useState(false);
|
|
43
|
+
const [error, setError] = useState(null);
|
|
44
|
+
const [timedOut, setTimedOut] = useState(false);
|
|
45
|
+
const runRef = useRef(0);
|
|
46
|
+
const timerRef = useRef(null);
|
|
47
|
+
const idRef = useRef(null);
|
|
48
|
+
const clearTimer = useCallback(() => {
|
|
49
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
50
|
+
timerRef.current = null;
|
|
51
|
+
}, []);
|
|
52
|
+
useEffect(() => () => {
|
|
53
|
+
runRef.current++;
|
|
54
|
+
clearTimer();
|
|
55
|
+
}, [clearTimer]);
|
|
56
|
+
const reset = useCallback(() => {
|
|
57
|
+
runRef.current++;
|
|
58
|
+
clearTimer();
|
|
59
|
+
idRef.current = null;
|
|
60
|
+
setGeneration(null);
|
|
61
|
+
setIsWorking(false);
|
|
62
|
+
setError(null);
|
|
63
|
+
setTimedOut(false);
|
|
64
|
+
}, [clearTimer]);
|
|
65
|
+
const poll = useCallback(async (run, id, startedAt) => {
|
|
66
|
+
if (run !== runRef.current) return;
|
|
67
|
+
try {
|
|
68
|
+
const result = throwIfResultError(await getFluidGridsClient().query({
|
|
69
|
+
query: WorkflowGenerationDocument,
|
|
70
|
+
variables: { id },
|
|
71
|
+
fetchPolicy: "network-only"
|
|
72
|
+
}));
|
|
73
|
+
if (run !== runRef.current) return;
|
|
74
|
+
const next = result.data?.workflowGeneration ?? null;
|
|
75
|
+
if (next) setGeneration(next);
|
|
76
|
+
if (next && String(next.status) === "PROCESSING") {
|
|
77
|
+
if (Date.now() - startedAt < 36e4) {
|
|
78
|
+
timerRef.current = setTimeout(() => void poll(run, id, startedAt), GENERATION_POLL_INTERVAL_MS);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
setTimedOut(true);
|
|
82
|
+
} else notifyFluidGridsAiCreditsUpdated();
|
|
83
|
+
setIsWorking(false);
|
|
84
|
+
} catch (pollError) {
|
|
85
|
+
if (run !== runRef.current) return;
|
|
86
|
+
setError(messageOf(pollError));
|
|
87
|
+
setIsWorking(false);
|
|
88
|
+
}
|
|
89
|
+
}, []);
|
|
90
|
+
return {
|
|
91
|
+
generation,
|
|
92
|
+
isWorking,
|
|
93
|
+
error,
|
|
94
|
+
timedOut,
|
|
95
|
+
start: useCallback(async (input) => {
|
|
96
|
+
const run = ++runRef.current;
|
|
97
|
+
clearTimer();
|
|
98
|
+
setGeneration(null);
|
|
99
|
+
setError(null);
|
|
100
|
+
setTimedOut(false);
|
|
101
|
+
setIsWorking(true);
|
|
102
|
+
try {
|
|
103
|
+
const result = throwIfResultError(await getFluidGridsClient().mutate({
|
|
104
|
+
mutation: GenerateWorkflowFromPromptDocument,
|
|
105
|
+
variables: { input: {
|
|
106
|
+
prompt: input.prompt,
|
|
107
|
+
...input.template ? { template: input.template } : {},
|
|
108
|
+
...input.baseDefinitionJson ? { baseDefinitionJson: input.baseDefinitionJson } : {}
|
|
109
|
+
} }
|
|
110
|
+
}));
|
|
111
|
+
if (run !== runRef.current) return;
|
|
112
|
+
const id = result.data?.generateWorkflowFromPrompt.id;
|
|
113
|
+
if (!id) throw new Error("The generation did not start");
|
|
114
|
+
idRef.current = id;
|
|
115
|
+
await poll(run, id, Date.now());
|
|
116
|
+
} catch (startError) {
|
|
117
|
+
if (run !== runRef.current) return;
|
|
118
|
+
setError(messageOf(startError));
|
|
119
|
+
setIsWorking(false);
|
|
120
|
+
}
|
|
121
|
+
}, [clearTimer, poll]),
|
|
122
|
+
cancel: useCallback(async () => {
|
|
123
|
+
const id = idRef.current;
|
|
124
|
+
const run = ++runRef.current;
|
|
125
|
+
clearTimer();
|
|
126
|
+
if (!id) {
|
|
127
|
+
setIsWorking(false);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const result = throwIfResultError(await getFluidGridsClient().mutate({
|
|
132
|
+
mutation: CancelWorkflowGenerationDocument,
|
|
133
|
+
variables: { id }
|
|
134
|
+
}));
|
|
135
|
+
if (run !== runRef.current) return;
|
|
136
|
+
const cancelled = result.data?.cancelWorkflowGeneration;
|
|
137
|
+
if (cancelled) setGeneration((current) => current ? {
|
|
138
|
+
...current,
|
|
139
|
+
status: cancelled.status,
|
|
140
|
+
message: cancelled.message
|
|
141
|
+
} : current);
|
|
142
|
+
} catch (cancelError) {
|
|
143
|
+
if (run !== runRef.current) return;
|
|
144
|
+
setError(messageOf(cancelError));
|
|
145
|
+
} finally {
|
|
146
|
+
if (run === runRef.current) setIsWorking(false);
|
|
147
|
+
}
|
|
148
|
+
}, [clearTimer]),
|
|
149
|
+
reset
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/modules/ai-studio/lib/ai-studio.ts
|
|
154
|
+
/** The version every workflow saved from AI Studio starts at. */
|
|
155
|
+
const AI_STUDIO_DRAFT_VERSION = "v1";
|
|
156
|
+
const AI_STUDIO_EXAMPLES = [
|
|
157
|
+
{
|
|
158
|
+
id: "payment-to-crm",
|
|
159
|
+
labelKey: "fluidgrids.aiStudio.examples.paymentToCrm.label",
|
|
160
|
+
label: "Payment to CRM and chat",
|
|
161
|
+
promptKey: "fluidgrids.aiStudio.examples.paymentToCrm.prompt",
|
|
162
|
+
prompt: "When a new Stripe payment succeeds, add the customer to HubSpot and post a summary to Slack #sales."
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "daily-digest",
|
|
166
|
+
labelKey: "fluidgrids.aiStudio.examples.dailyDigest.label",
|
|
167
|
+
label: "Daily order digest",
|
|
168
|
+
promptKey: "fluidgrids.aiStudio.examples.dailyDigest.prompt",
|
|
169
|
+
prompt: "Every weekday at 9am, fetch new orders from our API, keep the ones over 500 and post a summary to a webhook. Log the rest."
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "webhook-triage",
|
|
173
|
+
labelKey: "fluidgrids.aiStudio.examples.webhookTriage.label",
|
|
174
|
+
label: "Triage incoming requests",
|
|
175
|
+
promptKey: "fluidgrids.aiStudio.examples.webhookTriage.prompt",
|
|
176
|
+
prompt: "When a webhook receives a support request, check the priority field. Send urgent requests to our escalation API and log everything else."
|
|
177
|
+
}
|
|
178
|
+
];
|
|
179
|
+
const AI_STUDIO_REFINEMENTS = [
|
|
180
|
+
{
|
|
181
|
+
id: "error-handling",
|
|
182
|
+
labelKey: "fluidgrids.aiStudio.refinements.errorHandling",
|
|
183
|
+
label: "Add error handling",
|
|
184
|
+
instruction: "Add error handling: when a step that calls an external service fails, route the failure to a separate branch that logs the error instead of stopping silently."
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: "notify-on-failure",
|
|
188
|
+
labelKey: "fluidgrids.aiStudio.refinements.notifyOnFailure",
|
|
189
|
+
label: "Notify on failure",
|
|
190
|
+
instruction: "Notify someone when the workflow fails: add a step that sends the error details to a notification webhook."
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "log-result",
|
|
194
|
+
labelKey: "fluidgrids.aiStudio.refinements.logResult",
|
|
195
|
+
label: "Log the result",
|
|
196
|
+
instruction: "Add a final step that logs a short summary of what the workflow did."
|
|
197
|
+
}
|
|
198
|
+
];
|
|
199
|
+
function clamp(text) {
|
|
200
|
+
return text.length > 4e3 ? text.slice(0, AI_DRAFT_MAX_PROMPT_LENGTH) : text;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* The request for a refinement of the current draft: the original request
|
|
204
|
+
* plus the change. The draft itself goes to the server as
|
|
205
|
+
* `baseDefinitionJson`, so the model edits it rather than starting over.
|
|
206
|
+
*/
|
|
207
|
+
function buildRefinementPrompt(originalPrompt, instruction) {
|
|
208
|
+
return clamp(`${originalPrompt.trim()}\n\nChange: ${instruction.trim()}`);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The request after the user answered some of the model's clarifying
|
|
212
|
+
* questions. Unanswered questions are left out; null when nothing was
|
|
213
|
+
* answered, so the caller keeps the button disabled.
|
|
214
|
+
*/
|
|
215
|
+
function buildAnsweredPrompt(originalPrompt, questions, answers) {
|
|
216
|
+
const lines = questions.flatMap((question, index) => {
|
|
217
|
+
const answer = (answers[index] ?? "").trim();
|
|
218
|
+
return answer === "" ? [] : [`- ${question} ${answer}`];
|
|
219
|
+
});
|
|
220
|
+
if (lines.length === 0) return null;
|
|
221
|
+
return clamp(`${originalPrompt.trim()}\n\nAnswers to your questions:\n${lines.join("\n")}`);
|
|
222
|
+
}
|
|
223
|
+
function isRecord(value) {
|
|
224
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
225
|
+
}
|
|
226
|
+
/** The draft's nodes as readable steps; entries without an id or type are skipped. */
|
|
227
|
+
function draftSteps(definitionJson) {
|
|
228
|
+
const data = isRecord(definitionJson) && isRecord(definitionJson.data) ? definitionJson.data : {};
|
|
229
|
+
return (Array.isArray(data.nodes) ? data.nodes : []).filter(isRecord).flatMap((node) => {
|
|
230
|
+
if (typeof node.id !== "string" || typeof node.type !== "string") return [];
|
|
231
|
+
const nodeData = isRecord(node.data) ? node.data : {};
|
|
232
|
+
let label = node.id;
|
|
233
|
+
if (typeof nodeData.label === "string" && nodeData.label.trim() !== "") label = nodeData.label;
|
|
234
|
+
else if (typeof node.label === "string" && node.label.trim() !== "") label = node.label;
|
|
235
|
+
return [{
|
|
236
|
+
id: node.id,
|
|
237
|
+
label,
|
|
238
|
+
type: node.type
|
|
239
|
+
}];
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The `createWorkflowDefinition` input that saves a draft as a new DRAFT
|
|
244
|
+
* workflow under `key`. Same shape the server writes when a proposal is
|
|
245
|
+
* applied as a new workflow: the generated definition with its key replaced.
|
|
246
|
+
* Nothing is activated.
|
|
247
|
+
*/
|
|
248
|
+
function draftCreateInput(draft, key) {
|
|
249
|
+
const name = draft.name.trim() || "Generated workflow";
|
|
250
|
+
const description = draft.description.trim() || name;
|
|
251
|
+
return {
|
|
252
|
+
workflowKey: key,
|
|
253
|
+
version: "v1",
|
|
254
|
+
name,
|
|
255
|
+
description,
|
|
256
|
+
definitionJson: {
|
|
257
|
+
...draft.definitionJson,
|
|
258
|
+
key,
|
|
259
|
+
name,
|
|
260
|
+
description
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/** The page phase for a generation status (the wire enum's string). */
|
|
265
|
+
function phaseOf(status, working) {
|
|
266
|
+
if (working || status === "PROCESSING") return "generating";
|
|
267
|
+
if (status === "COMPLETED") return "ready";
|
|
268
|
+
if (status === "FAILED") return "failed";
|
|
269
|
+
if (status === "CANCELLED") return "cancelled";
|
|
270
|
+
return "idle";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// src/modules/ai-studio/pages/AiStudioPage.tsx
|
|
274
|
+
/**
|
|
275
|
+
* AI Studio (BOFF-4089) — describe an automation in plain English and review
|
|
276
|
+
* the workflow draft the model builds for it, before anything is saved.
|
|
277
|
+
*
|
|
278
|
+
* - Left: the request, optional starting pattern, starter examples, the
|
|
279
|
+
* AI-credit balance, one-click refinements of the current draft and the
|
|
280
|
+
* model's clarifying questions (answer them and regenerate).
|
|
281
|
+
* - Right: the draft on the real builder canvas in read-only mode, its
|
|
282
|
+
* explanation, the settings still to fill in, and the two ways forward:
|
|
283
|
+
* "Edit in builder" saves the draft as a new DRAFT workflow (never
|
|
284
|
+
* activated) and opens it; "Regenerate" asks again from scratch.
|
|
285
|
+
*
|
|
286
|
+
* Generation runs on workflow-svc (`generateWorkflowFromPrompt`, polled via
|
|
287
|
+
* `workflowGeneration`); this page only drives and renders it.
|
|
288
|
+
*/
|
|
289
|
+
var AiStudioPage_exports = /* @__PURE__ */ __exportAll({ AiStudioPage: () => AiStudioPage });
|
|
290
|
+
const NO_TEMPLATE = "__none__";
|
|
291
|
+
/** The read-only canvas for a draft, with the palette metadata a drop would give each node. */
|
|
292
|
+
function DraftCanvas({ generationId, name, definitionJson }) {
|
|
293
|
+
const tr = useTr();
|
|
294
|
+
const [workflow, setWorkflow] = useState(null);
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
let active = true;
|
|
297
|
+
const build = (seeded) => {
|
|
298
|
+
const canvas = draftToCanvas(definitionJson);
|
|
299
|
+
return {
|
|
300
|
+
id: generationId,
|
|
301
|
+
workflowKey: generationId,
|
|
302
|
+
version: "preview",
|
|
303
|
+
name,
|
|
304
|
+
nodes: seeded ? enrichDraftNodes(canvas.nodes, buildOperationSeed) : canvas.nodes,
|
|
305
|
+
edges: canvas.edges,
|
|
306
|
+
sidebar: {
|
|
307
|
+
active: "none",
|
|
308
|
+
panels: { nodeProperties: { selectedNode: null } }
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
};
|
|
312
|
+
ensureNodeTypesLoaded(draftToCanvas(definitionJson).nodes.map((node) => node.type)).then(() => {
|
|
313
|
+
if (active) setWorkflow(build(true));
|
|
314
|
+
}).catch(() => {
|
|
315
|
+
if (active) setWorkflow(build(false));
|
|
316
|
+
});
|
|
317
|
+
return () => {
|
|
318
|
+
active = false;
|
|
319
|
+
};
|
|
320
|
+
}, [
|
|
321
|
+
definitionJson,
|
|
322
|
+
generationId,
|
|
323
|
+
name
|
|
324
|
+
]);
|
|
325
|
+
if (!workflow) return /* @__PURE__ */ jsxs("div", {
|
|
326
|
+
className: "flex h-full items-center justify-center gap-2 text-sm text-muted-foreground",
|
|
327
|
+
children: [/* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }), tr("fluidgrids.aiStudio.preview.loading", "Loading preview…")]
|
|
328
|
+
});
|
|
329
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
330
|
+
fallback: () => /* @__PURE__ */ jsx("div", {
|
|
331
|
+
className: "flex h-full items-center justify-center p-6 text-center text-sm text-muted-foreground",
|
|
332
|
+
children: tr("fluidgrids.aiStudio.preview.renderError", "The preview could not be drawn. The draft can still be opened in the builder.")
|
|
333
|
+
}),
|
|
334
|
+
children: /* @__PURE__ */ jsx(FlowBuilderPage, {
|
|
335
|
+
workflow,
|
|
336
|
+
readOnly: true
|
|
337
|
+
})
|
|
338
|
+
}) });
|
|
339
|
+
}
|
|
340
|
+
function AiStudioPage() {
|
|
341
|
+
const tr = useTr();
|
|
342
|
+
const navigate = useNavigate();
|
|
343
|
+
const generation = useWorkflowGeneration();
|
|
344
|
+
const templates = useAiWorkflowTemplates(true);
|
|
345
|
+
const { createWorkflow } = useCreateWorkflow();
|
|
346
|
+
const { allowed: canGenerate } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.AI_WORKFLOW_CREATE);
|
|
347
|
+
const { allowed: canCreate } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_CREATE);
|
|
348
|
+
const [prompt, setPrompt] = useState("");
|
|
349
|
+
const [template, setTemplate] = useState(NO_TEMPLATE);
|
|
350
|
+
const [answers, setAnswers] = useState({});
|
|
351
|
+
const [saving, setSaving] = useState(false);
|
|
352
|
+
const [refusal, setRefusal] = useState(null);
|
|
353
|
+
const requestRef = useRef(null);
|
|
354
|
+
const current = generation.generation;
|
|
355
|
+
const status = current ? String(current.status) : void 0;
|
|
356
|
+
const phase = phaseOf(status, generation.isWorking);
|
|
357
|
+
const draft = phase === "ready" ? current?.draft ?? null : null;
|
|
358
|
+
const questions = draft?.questions ?? [];
|
|
359
|
+
const warnings = current?.validation?.warnings ?? [];
|
|
360
|
+
const errors = current?.validation?.errors ?? [];
|
|
361
|
+
const steps = useMemo(() => draft ? draftSteps(draft.definitionJson) : [], [draft]);
|
|
362
|
+
const trimmed = prompt.trim();
|
|
363
|
+
const working = phase === "generating";
|
|
364
|
+
const answeredPrompt = requestRef.current ? buildAnsweredPrompt(requestRef.current.prompt, questions, answers) : null;
|
|
365
|
+
const run = useCallback((input) => {
|
|
366
|
+
setAnswers({});
|
|
367
|
+
generation.start(input);
|
|
368
|
+
}, [generation]);
|
|
369
|
+
const handleGenerate = useCallback(() => {
|
|
370
|
+
if (!trimmed) return;
|
|
371
|
+
const request = {
|
|
372
|
+
prompt: trimmed,
|
|
373
|
+
...template !== NO_TEMPLATE ? { template } : {}
|
|
374
|
+
};
|
|
375
|
+
requestRef.current = request;
|
|
376
|
+
run(request);
|
|
377
|
+
}, [
|
|
378
|
+
run,
|
|
379
|
+
template,
|
|
380
|
+
trimmed
|
|
381
|
+
]);
|
|
382
|
+
const handleRegenerate = useCallback(() => {
|
|
383
|
+
if (!requestRef.current) return;
|
|
384
|
+
run(requestRef.current);
|
|
385
|
+
}, [run]);
|
|
386
|
+
const handleRefine = useCallback((instruction) => {
|
|
387
|
+
if (!draft || !requestRef.current) return;
|
|
388
|
+
const refined = buildRefinementPrompt(requestRef.current.prompt, instruction);
|
|
389
|
+
requestRef.current = {
|
|
390
|
+
...requestRef.current,
|
|
391
|
+
prompt: refined
|
|
392
|
+
};
|
|
393
|
+
setPrompt(refined);
|
|
394
|
+
run({
|
|
395
|
+
prompt: refined,
|
|
396
|
+
baseDefinitionJson: draft.definitionJson
|
|
397
|
+
});
|
|
398
|
+
}, [draft, run]);
|
|
399
|
+
const handleAnswer = useCallback(() => {
|
|
400
|
+
if (!draft || !requestRef.current || !answeredPrompt) return;
|
|
401
|
+
requestRef.current = {
|
|
402
|
+
...requestRef.current,
|
|
403
|
+
prompt: answeredPrompt
|
|
404
|
+
};
|
|
405
|
+
setPrompt(answeredPrompt);
|
|
406
|
+
run({
|
|
407
|
+
prompt: answeredPrompt,
|
|
408
|
+
baseDefinitionJson: draft.definitionJson
|
|
409
|
+
});
|
|
410
|
+
}, [
|
|
411
|
+
answeredPrompt,
|
|
412
|
+
draft,
|
|
413
|
+
run
|
|
414
|
+
]);
|
|
415
|
+
const handleEditInBuilder = useCallback(async () => {
|
|
416
|
+
if (!draft) return;
|
|
417
|
+
setSaving(true);
|
|
418
|
+
let reported = null;
|
|
419
|
+
try {
|
|
420
|
+
const created = await createWorkflow(draftCreateInput({
|
|
421
|
+
name: draft.name,
|
|
422
|
+
description: draft.description,
|
|
423
|
+
definitionJson: draft.definitionJson
|
|
424
|
+
}, v4()), { onError: (error) => {
|
|
425
|
+
reported = error;
|
|
426
|
+
} });
|
|
427
|
+
if (!created?.workflowKey || !created.version) {
|
|
428
|
+
setRefusal(classifyCreateRefusal(reported));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
toast.success(tr("fluidgrids.aiStudio.savedToast", "Draft saved as a new workflow"), { description: tr("fluidgrids.aiStudio.savedToastDescription", "Nothing is live yet. Fill in the settings, run a test, then activate it.") });
|
|
432
|
+
navigate(builderPathOf(created, ".."));
|
|
433
|
+
} finally {
|
|
434
|
+
setSaving(false);
|
|
435
|
+
}
|
|
436
|
+
}, [
|
|
437
|
+
createWorkflow,
|
|
438
|
+
draft,
|
|
439
|
+
navigate,
|
|
440
|
+
tr
|
|
441
|
+
]);
|
|
442
|
+
const showExamples = phase === "idle" && !requestRef.current;
|
|
443
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
444
|
+
className: "min-h-full bg-background-primary p-4 sm:p-6",
|
|
445
|
+
"data-testid": "ai-studio-page",
|
|
446
|
+
children: [
|
|
447
|
+
/* @__PURE__ */ jsxs("header", {
|
|
448
|
+
className: "mb-6 flex flex-col gap-1",
|
|
449
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
450
|
+
className: "flex items-center gap-3",
|
|
451
|
+
children: [
|
|
452
|
+
/* @__PURE__ */ jsx(Sparkles, {
|
|
453
|
+
className: "size-6 text-action-primary-bg",
|
|
454
|
+
"aria-hidden": true
|
|
455
|
+
}),
|
|
456
|
+
/* @__PURE__ */ jsx("h1", {
|
|
457
|
+
className: "text-2xl font-semibold text-text-primary sm:text-3xl",
|
|
458
|
+
children: tr("fluidgrids.aiStudio.title", "AI Studio")
|
|
459
|
+
}),
|
|
460
|
+
/* @__PURE__ */ jsx(Badge, {
|
|
461
|
+
variant: "outline",
|
|
462
|
+
children: tr("fluidgrids.aiStudio.beta", "Beta")
|
|
463
|
+
})
|
|
464
|
+
]
|
|
465
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
466
|
+
className: "text-sm text-text-secondary",
|
|
467
|
+
children: tr("fluidgrids.aiStudio.subtitle", "Describe your automation in plain English. AI builds a draft workflow you review before anything is saved.")
|
|
468
|
+
})]
|
|
469
|
+
}),
|
|
470
|
+
/* @__PURE__ */ jsxs("div", {
|
|
471
|
+
className: "grid gap-6 lg:grid-cols-[minmax(0,2fr)_minmax(0,3fr)]",
|
|
472
|
+
children: [/* @__PURE__ */ jsxs(Card, {
|
|
473
|
+
className: "h-fit",
|
|
474
|
+
children: [/* @__PURE__ */ jsx(CardHeader, {
|
|
475
|
+
className: "pb-2",
|
|
476
|
+
children: /* @__PURE__ */ jsx("h2", {
|
|
477
|
+
className: "text-base font-semibold text-text-primary",
|
|
478
|
+
children: tr("fluidgrids.aiStudio.describeTitle", "Describe your automation")
|
|
479
|
+
})
|
|
480
|
+
}), /* @__PURE__ */ jsxs(CardContent, {
|
|
481
|
+
className: "flex flex-col gap-4",
|
|
482
|
+
children: [
|
|
483
|
+
/* @__PURE__ */ jsxs("div", {
|
|
484
|
+
className: "flex flex-col gap-1.5",
|
|
485
|
+
children: [
|
|
486
|
+
/* @__PURE__ */ jsx(Label, {
|
|
487
|
+
htmlFor: "ai-studio-prompt",
|
|
488
|
+
className: "sr-only",
|
|
489
|
+
children: tr("fluidgrids.aiStudio.promptLabel", "What should the workflow do?")
|
|
490
|
+
}),
|
|
491
|
+
/* @__PURE__ */ jsx(Textarea, {
|
|
492
|
+
id: "ai-studio-prompt",
|
|
493
|
+
"data-testid": "ai-studio-prompt",
|
|
494
|
+
rows: 6,
|
|
495
|
+
maxLength: AI_DRAFT_MAX_PROMPT_LENGTH,
|
|
496
|
+
value: prompt,
|
|
497
|
+
disabled: working,
|
|
498
|
+
placeholder: tr("fluidgrids.aiStudio.promptPlaceholder", "When a new Stripe payment succeeds, add the customer to HubSpot and post a summary to Slack #sales."),
|
|
499
|
+
onChange: (event) => setPrompt(event.target.value)
|
|
500
|
+
}),
|
|
501
|
+
/* @__PURE__ */ jsxs("span", {
|
|
502
|
+
className: "self-end text-xs text-text-secondary",
|
|
503
|
+
children: [
|
|
504
|
+
prompt.length,
|
|
505
|
+
"/",
|
|
506
|
+
AI_DRAFT_MAX_PROMPT_LENGTH
|
|
507
|
+
]
|
|
508
|
+
})
|
|
509
|
+
]
|
|
510
|
+
}),
|
|
511
|
+
templates.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
512
|
+
className: "flex flex-col gap-1.5",
|
|
513
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
514
|
+
htmlFor: "ai-studio-template",
|
|
515
|
+
children: tr("fluidgrids.aiStudio.templateLabel", "Starting pattern (optional)")
|
|
516
|
+
}), /* @__PURE__ */ jsxs(Select, {
|
|
517
|
+
value: template,
|
|
518
|
+
onValueChange: setTemplate,
|
|
519
|
+
disabled: working,
|
|
520
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
521
|
+
id: "ai-studio-template",
|
|
522
|
+
"data-testid": "ai-studio-template",
|
|
523
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
524
|
+
}), /* @__PURE__ */ jsxs(SelectContent, { children: [/* @__PURE__ */ jsx(SelectItem, {
|
|
525
|
+
value: NO_TEMPLATE,
|
|
526
|
+
children: tr("fluidgrids.aiStudio.noTemplate", "No pattern")
|
|
527
|
+
}), templates.map((option) => /* @__PURE__ */ jsx(SelectItem, {
|
|
528
|
+
value: option.id,
|
|
529
|
+
children: option.name
|
|
530
|
+
}, option.id))] })]
|
|
531
|
+
})]
|
|
532
|
+
}),
|
|
533
|
+
/* @__PURE__ */ jsx(AiCreditBalanceSlot, { testId: "ai-studio-credit-balance" }),
|
|
534
|
+
/* @__PURE__ */ jsxs("div", {
|
|
535
|
+
className: "flex flex-wrap gap-2",
|
|
536
|
+
children: [/* @__PURE__ */ jsxs(Button, {
|
|
537
|
+
"data-testid": "ai-studio-generate",
|
|
538
|
+
onClick: handleGenerate,
|
|
539
|
+
disabled: !trimmed || working || !canGenerate,
|
|
540
|
+
title: canGenerate ? void 0 : tr("fluidgrids.aiStudio.noPermission", "Your role cannot generate workflows with AI"),
|
|
541
|
+
className: "flex grow gap-2",
|
|
542
|
+
children: [working ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Sparkles, { className: "size-4" }), working ? tr("fluidgrids.aiStudio.generating", "Generating…") : tr("fluidgrids.aiStudio.generate", "Generate workflow")]
|
|
543
|
+
}), working && /* @__PURE__ */ jsxs(Button, {
|
|
544
|
+
variant: "outline",
|
|
545
|
+
"data-testid": "ai-studio-cancel",
|
|
546
|
+
onClick: () => void generation.cancel(),
|
|
547
|
+
className: "flex gap-2",
|
|
548
|
+
children: [/* @__PURE__ */ jsx(Square, { className: "size-4" }), tr("fluidgrids.aiStudio.stop", "Stop")]
|
|
549
|
+
})]
|
|
550
|
+
}),
|
|
551
|
+
showExamples && /* @__PURE__ */ jsxs("section", {
|
|
552
|
+
className: "flex flex-col gap-2",
|
|
553
|
+
"data-testid": "ai-studio-examples",
|
|
554
|
+
children: [/* @__PURE__ */ jsxs("h3", {
|
|
555
|
+
className: "flex items-center gap-2 text-sm font-medium text-text-primary",
|
|
556
|
+
children: [/* @__PURE__ */ jsx(Lightbulb, {
|
|
557
|
+
className: "size-4",
|
|
558
|
+
"aria-hidden": true
|
|
559
|
+
}), tr("fluidgrids.aiStudio.examplesTitle", "Try an example")]
|
|
560
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
561
|
+
className: "flex flex-wrap gap-2",
|
|
562
|
+
children: AI_STUDIO_EXAMPLES.map((example) => /* @__PURE__ */ jsx(Button, {
|
|
563
|
+
variant: "outline",
|
|
564
|
+
size: "sm",
|
|
565
|
+
"data-testid": `ai-studio-example-${example.id}`,
|
|
566
|
+
onClick: () => setPrompt(tr(example.promptKey, example.prompt)),
|
|
567
|
+
children: tr(example.labelKey, example.label)
|
|
568
|
+
}, example.id))
|
|
569
|
+
})]
|
|
570
|
+
}),
|
|
571
|
+
draft && /* @__PURE__ */ jsxs("section", {
|
|
572
|
+
className: "flex flex-col gap-2",
|
|
573
|
+
"data-testid": "ai-studio-suggestions",
|
|
574
|
+
children: [/* @__PURE__ */ jsxs("h3", {
|
|
575
|
+
className: "flex items-center gap-2 text-sm font-medium text-text-primary",
|
|
576
|
+
children: [/* @__PURE__ */ jsx(Lightbulb, {
|
|
577
|
+
className: "size-4",
|
|
578
|
+
"aria-hidden": true
|
|
579
|
+
}), tr("fluidgrids.aiStudio.suggestionsTitle", "Suggestions")]
|
|
580
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
581
|
+
className: "flex flex-wrap gap-2",
|
|
582
|
+
children: AI_STUDIO_REFINEMENTS.map((refinement) => /* @__PURE__ */ jsx(Button, {
|
|
583
|
+
variant: "outline",
|
|
584
|
+
size: "sm",
|
|
585
|
+
disabled: !canGenerate,
|
|
586
|
+
"data-testid": `ai-studio-refine-${refinement.id}`,
|
|
587
|
+
onClick: () => handleRefine(refinement.instruction),
|
|
588
|
+
children: tr(refinement.labelKey, refinement.label)
|
|
589
|
+
}, refinement.id))
|
|
590
|
+
})]
|
|
591
|
+
}),
|
|
592
|
+
questions.length > 0 && /* @__PURE__ */ jsxs("section", {
|
|
593
|
+
className: "flex flex-col gap-3",
|
|
594
|
+
"data-testid": "ai-studio-questions",
|
|
595
|
+
children: [
|
|
596
|
+
/* @__PURE__ */ jsxs("h3", {
|
|
597
|
+
className: "flex items-center gap-2 text-sm font-medium text-text-primary",
|
|
598
|
+
children: [/* @__PURE__ */ jsx(MessageCircleQuestion, {
|
|
599
|
+
className: "size-4",
|
|
600
|
+
"aria-hidden": true
|
|
601
|
+
}), tr("fluidgrids.aiStudio.questionsTitle", "AI clarifying questions")]
|
|
602
|
+
}),
|
|
603
|
+
/* @__PURE__ */ jsx("p", {
|
|
604
|
+
className: "text-xs text-text-secondary",
|
|
605
|
+
children: tr("fluidgrids.aiStudio.questionsHint", "The draft uses sensible defaults. Answer any of these to refine it.")
|
|
606
|
+
}),
|
|
607
|
+
/* @__PURE__ */ jsx("ol", {
|
|
608
|
+
className: "flex flex-col gap-3",
|
|
609
|
+
children: questions.map((question, index) => /* @__PURE__ */ jsxs("li", {
|
|
610
|
+
className: "flex flex-col gap-1.5",
|
|
611
|
+
children: [/* @__PURE__ */ jsxs(Label, {
|
|
612
|
+
htmlFor: `ai-studio-answer-${index}`,
|
|
613
|
+
className: "flex gap-2 font-normal text-text-primary",
|
|
614
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
615
|
+
className: "flex size-5 shrink-0 items-center justify-center rounded-full bg-action-primary-bg text-xs text-action-primary-text",
|
|
616
|
+
children: index + 1
|
|
617
|
+
}), /* @__PURE__ */ jsx("span", { children: question })]
|
|
618
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
619
|
+
id: `ai-studio-answer-${index}`,
|
|
620
|
+
"data-testid": `ai-studio-answer-${index}`,
|
|
621
|
+
value: answers[index] ?? "",
|
|
622
|
+
placeholder: tr("fluidgrids.aiStudio.answerPlaceholder", "Your answer (optional)"),
|
|
623
|
+
onChange: (event) => setAnswers((previous) => ({
|
|
624
|
+
...previous,
|
|
625
|
+
[index]: event.target.value
|
|
626
|
+
}))
|
|
627
|
+
})]
|
|
628
|
+
}, question))
|
|
629
|
+
}),
|
|
630
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
631
|
+
variant: "secondary",
|
|
632
|
+
"data-testid": "ai-studio-answer-submit",
|
|
633
|
+
disabled: !answeredPrompt || !canGenerate,
|
|
634
|
+
onClick: handleAnswer,
|
|
635
|
+
className: "flex gap-2 self-start",
|
|
636
|
+
children: [/* @__PURE__ */ jsx(RefreshCw, { className: "size-4" }), tr("fluidgrids.aiStudio.answerSubmit", "Update the draft with my answers")]
|
|
637
|
+
})
|
|
638
|
+
]
|
|
639
|
+
})
|
|
640
|
+
]
|
|
641
|
+
})]
|
|
642
|
+
}), /* @__PURE__ */ jsxs(Card, {
|
|
643
|
+
className: "flex min-w-0 flex-col",
|
|
644
|
+
"data-testid": "ai-studio-draft",
|
|
645
|
+
children: [/* @__PURE__ */ jsxs(CardHeader, {
|
|
646
|
+
className: "flex flex-row flex-wrap items-center justify-between gap-2 pb-2",
|
|
647
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
648
|
+
className: "flex min-w-0 items-center gap-2",
|
|
649
|
+
children: [/* @__PURE__ */ jsx(Badge, {
|
|
650
|
+
variant: "secondary",
|
|
651
|
+
children: tr("fluidgrids.aiStudio.draftBadge", "Draft")
|
|
652
|
+
}), /* @__PURE__ */ jsx("h2", {
|
|
653
|
+
className: "truncate text-base font-semibold text-text-primary",
|
|
654
|
+
"data-testid": "ai-studio-draft-name",
|
|
655
|
+
children: draft?.name ?? (working ? tr("fluidgrids.aiStudio.draftGenerating", "Your workflow is being generated") : tr("fluidgrids.aiStudio.draftEmpty", "No draft yet"))
|
|
656
|
+
})]
|
|
657
|
+
}), draft && /* @__PURE__ */ jsx(Badge, {
|
|
658
|
+
variant: "outline",
|
|
659
|
+
"data-testid": "ai-studio-step-count",
|
|
660
|
+
children: tr("fluidgrids.aiStudio.stepCount", "{{count}} steps", { count: draft.nodeCount })
|
|
661
|
+
})]
|
|
662
|
+
}), /* @__PURE__ */ jsxs(CardContent, {
|
|
663
|
+
className: "flex flex-col gap-4",
|
|
664
|
+
children: [
|
|
665
|
+
/* @__PURE__ */ jsx("div", {
|
|
666
|
+
className: "relative h-[360px] w-full overflow-hidden rounded-lg border border-border-secondary sm:h-[440px]",
|
|
667
|
+
children: draft && current ? /* @__PURE__ */ jsx(DraftCanvas, {
|
|
668
|
+
generationId: current.id,
|
|
669
|
+
name: draft.name,
|
|
670
|
+
definitionJson: draft.definitionJson
|
|
671
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
672
|
+
className: "flex h-full flex-col items-center justify-center gap-3 p-6 text-center text-sm text-text-secondary",
|
|
673
|
+
"data-testid": `ai-studio-state-${phase}`,
|
|
674
|
+
children: working ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Loader2, { className: "size-6 animate-spin" }), /* @__PURE__ */ jsx("p", { children: tr("fluidgrids.aiStudio.generatingHint", "Picking nodes, connecting them and checking the result. This usually takes under a minute.") })] }) : phase === "failed" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
675
|
+
/* @__PURE__ */ jsx(TriangleAlert, { className: "size-6 text-status-error-text" }),
|
|
676
|
+
/* @__PURE__ */ jsx("p", {
|
|
677
|
+
role: "alert",
|
|
678
|
+
className: "text-status-error-text",
|
|
679
|
+
children: current?.message ?? tr("fluidgrids.aiStudio.failed", "The draft could not be generated.")
|
|
680
|
+
}),
|
|
681
|
+
errors.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
682
|
+
className: "max-w-md list-disc text-left text-xs",
|
|
683
|
+
children: errors.slice(0, 5).map((entry) => /* @__PURE__ */ jsx("li", { children: entry }, entry))
|
|
684
|
+
})
|
|
685
|
+
] }) : phase === "cancelled" ? /* @__PURE__ */ jsx("p", { children: tr("fluidgrids.aiStudio.cancelled", "Generation stopped. Change the request or generate again.") }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Sparkles, {
|
|
686
|
+
className: "size-6",
|
|
687
|
+
"aria-hidden": true
|
|
688
|
+
}), /* @__PURE__ */ jsx("p", { children: tr("fluidgrids.aiStudio.emptyHint", "Your draft appears here: a trigger and the steps that follow, ready to review.") })] })
|
|
689
|
+
})
|
|
690
|
+
}),
|
|
691
|
+
(generation.error || generation.timedOut) && /* @__PURE__ */ jsx("div", {
|
|
692
|
+
role: "alert",
|
|
693
|
+
className: "rounded-md border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text",
|
|
694
|
+
"data-testid": "ai-studio-error",
|
|
695
|
+
children: generation.timedOut ? tr("fluidgrids.aiStudio.timedOut", "The draft is taking longer than expected. Try again in a moment.") : generation.error
|
|
696
|
+
}),
|
|
697
|
+
draft && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
698
|
+
draft.explanation && /* @__PURE__ */ jsx("p", {
|
|
699
|
+
className: "text-sm text-text-secondary",
|
|
700
|
+
"data-testid": "ai-studio-explanation",
|
|
701
|
+
children: draft.explanation
|
|
702
|
+
}),
|
|
703
|
+
steps.length > 0 && /* @__PURE__ */ jsx("ol", {
|
|
704
|
+
className: "flex flex-col gap-1 text-sm",
|
|
705
|
+
"data-testid": "ai-studio-steps",
|
|
706
|
+
children: steps.map((step, index) => /* @__PURE__ */ jsxs("li", {
|
|
707
|
+
className: "flex items-center gap-2 text-text-primary",
|
|
708
|
+
children: [
|
|
709
|
+
/* @__PURE__ */ jsx(CheckCircle2, {
|
|
710
|
+
className: "size-4 shrink-0 text-status-success-text",
|
|
711
|
+
"aria-hidden": true
|
|
712
|
+
}),
|
|
713
|
+
/* @__PURE__ */ jsxs("span", {
|
|
714
|
+
className: "text-text-secondary",
|
|
715
|
+
children: [index + 1, "."]
|
|
716
|
+
}),
|
|
717
|
+
/* @__PURE__ */ jsx("span", {
|
|
718
|
+
className: "truncate",
|
|
719
|
+
children: step.label
|
|
720
|
+
})
|
|
721
|
+
]
|
|
722
|
+
}, step.id))
|
|
723
|
+
}),
|
|
724
|
+
warnings.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
725
|
+
className: "rounded-md border border-status-warning-border bg-status-warning-bg-subtle p-3 text-sm text-status-warning-text",
|
|
726
|
+
"data-testid": "ai-studio-warnings",
|
|
727
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
728
|
+
className: "font-medium",
|
|
729
|
+
children: tr("fluidgrids.aiStudio.warningsTitle", "Fill in before activating")
|
|
730
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
731
|
+
className: "mt-1 list-disc pl-5 text-xs",
|
|
732
|
+
children: warnings.map((warning) => /* @__PURE__ */ jsx("li", { children: warning }, warning))
|
|
733
|
+
})]
|
|
734
|
+
})
|
|
735
|
+
] }),
|
|
736
|
+
/* @__PURE__ */ jsxs("div", {
|
|
737
|
+
className: "flex flex-wrap items-center gap-2",
|
|
738
|
+
children: [
|
|
739
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
740
|
+
"data-testid": "ai-studio-edit-in-builder",
|
|
741
|
+
disabled: !draft || saving || working || !canCreate,
|
|
742
|
+
onClick: () => void handleEditInBuilder(),
|
|
743
|
+
className: "flex gap-2",
|
|
744
|
+
children: [saving ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Pencil, { className: "size-4" }), tr("fluidgrids.aiStudio.editInBuilder", "Edit in builder")]
|
|
745
|
+
}),
|
|
746
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
747
|
+
variant: "outline",
|
|
748
|
+
"data-testid": "ai-studio-regenerate",
|
|
749
|
+
disabled: !requestRef.current || working || !canGenerate,
|
|
750
|
+
onClick: handleRegenerate,
|
|
751
|
+
className: "flex gap-2",
|
|
752
|
+
children: [/* @__PURE__ */ jsx(RefreshCw, { className: "size-4" }), tr("fluidgrids.aiStudio.regenerate", "Regenerate")]
|
|
753
|
+
}),
|
|
754
|
+
/* @__PURE__ */ jsx("span", {
|
|
755
|
+
className: "text-xs text-text-secondary sm:ml-auto",
|
|
756
|
+
children: tr("fluidgrids.aiStudio.betaNote", "AI Studio is in beta. Outputs may vary.")
|
|
757
|
+
})
|
|
758
|
+
]
|
|
759
|
+
})
|
|
760
|
+
]
|
|
761
|
+
})]
|
|
762
|
+
})]
|
|
763
|
+
}),
|
|
764
|
+
/* @__PURE__ */ jsx(CreateWorkflowRefusalDialog, {
|
|
765
|
+
surface: "builder-create",
|
|
766
|
+
refusal,
|
|
767
|
+
onClose: () => setRefusal(null),
|
|
768
|
+
onUpgrade: () => navigate(BILLING_PLANS_PATH)
|
|
769
|
+
})
|
|
770
|
+
]
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export { AiStudioPage_exports as n, AiStudioPage as t };
|