@burdenoff/fluidgrids-fe-sdk 2026.1111.15 → 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-DO38vZ_F.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-B9RR_CI_.js → FluidGridsProvider-dvZSNtSW.js} +28 -350
- package/dist/HistoryPage-BLoRpJ1R.d.ts +6 -0
- package/dist/{HistoryPage-CA42pU85.js → HistoryPage-U0tqupBN.js} +6 -4
- package/dist/HumanRequestCard-D-ooVPDn.js +439 -0
- package/dist/{InsightsPage-CBoN_Mj9.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-Dm0nMBXM.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-CKihmbJE.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-Bs_V-E4u.js → TemplateConfigurationModal-BhGWIzpm.js} +3 -2
- package/dist/{TemplateDetailPanel-MlT39VLG.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-CHVpWBuw.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-3gyHcYY2.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-0Ai1HED-.js → WorkflowShareDialog-B_gn968c.js} +6 -52
- package/dist/{WorkflowsList-CJ3KmMHX.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-B1IK1JEO.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-eewVHVuy.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-C_3sY3pZ.js → flow-builder-y2Y1iG4Y.js} +182 -1821
- 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-CAws5qjg.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-DOzNkSVV.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-DkS0FTfw.js → useExecutions-9aLuOt1c.js} +4 -3
- package/dist/{useObservability-BkxhHZsX.js → useObservability-CfEw3ZFD.js} +2 -2
- package/dist/{useRunHistory-DHehG09Z.js → useRunHistory-B1xJDwl1.js} +4 -3
- package/dist/{useWorkflows-DyhtoVwp.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
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
|
|
2
3
|
import { A as nodeAccent, B as NODE_TYPE_DRAG_DATA_FORMAT, G as isTriggerOperation, H as useFlowStore, M as isImageIcon, T as resolveInputHandles, U as useIsValidConnection, V as STICKY_NOTE_TYPE, g as toCodeMirrorDiagnostics, j as NodeTypeIcon, r as CODE_WORKFLOW_NODE_TYPE, z as NODE_OPERATION_DRAG_DATA_FORMAT } from "./node-operation-selection--oy-yEq_.js";
|
|
3
|
-
import { $ as isStringOptionList, $t as inAppLeaveTarget,
|
|
4
|
+
import { $ as isStringOptionList, $t as inAppLeaveTarget, An as newConnectionProviderKey, At as useSaveStatusStore, B as useRunGuard, Bt as shouldReleaseForIdle, Ct as useSaveWorkflow, D as InstallWorkflowNodeDocument, Dn as connectionsForAdapter, E as InstallPurchasedNodeDocument, F as getNodeMetadata, Fn as useBuilderReadOnly, Ft as leaseCallQueue, G as nodeSubtitle, Gt as sessionBeforeReload, H as asRecord, Ht as EDIT_SESSION_ID, I as buildOperationSeed, It as leaseCallSequence, J as paramsFromOperation, Jt as AUTOSAVE_DEBOUNCE_MS, K as nodeTitle, Kt as rememberOwnStatusChange, Lt as phaseAfterAcquire, M as UninstallOrganizationPrivateNodeDocument, Mn as getNodeDetail, Mt as useEditLeaseStore, N as UninstallWorkflowNodeDocument, Nn as buildStickyNode, Nt as bannerOf, On as isOptionalAdapter, Ot as refusalOfQuota, Pn as BuilderReadOnlyProvider, Pt as isLocked, R as useInsertNode, Rt as phaseAfterTakeOver, S as CreateStoreOrderDocument, St as useFlowValidator, T as InstallOrganizationPrivateNodeDocument, U as coerceNumber, Ut as forgetSessionForReload, V as workflowParameterIssues, Vt as tickAction, Wt as rememberSessionForReload, X as useNodeConfig, Xt as canAutosave, Yt as AUTOSAVE_RETRY_DELAYS_MS, _t as leavingLosesChanges, a as NODES_METADATA, at as casesFromRules, b as BrowseWorkflowNodesDocument, bt as watchLeavingLosesChanges, cn as parseParameterValue, dt as useGuardedNavigate, en as resettledBaseline, et as optionListText, f as isNodeConfigsInitialized, fn as isParamVisible, ft as useManualSave, g as registerWorkflowNodeTypes, gn as useNodeRegistryRevision, gt as editingLeaseLocked, hn as useCanvasEdges, ht as editStateFor, i as AVAILABLE_NODES, it as SwitchRulesEditor, jn as replaceAdapterConnections, jt as useEditLeaseLocked, k as OrganizationPrivateNodesDocument, kn as matchesAdapterProvider, kt as saveIndicatorOf, l as ensureNodeConfigsLoaded, ln as structuredParameterType, lt as boundFieldsOf, m as refetchStoreNodes, mn as openRunQuotaUpgradePrompt, mt as departingSave, nn as workflowFingerprint, nt as resolveLoadOptionsSpec, o as NODE_CONFIGS, ot as FieldTree, p as onNodeTypesUpdate, pn as useFluidGridsAnalytics, pt as autosaveAllowedFor, q as optionText, qt as rememberRevision, rn as useNodeExecution, rt as ConditionTreeBuilder, s as NODE_TYPES, sn as formatParameterValue, st as parseMappingValue, t as FluidGridsContext, tn as saveFailureActionOf, tt as optionListValues, ut as missingRequiredParameters, v as scheduleNodePaletteLoad, vn as loadDefinitionJson, vt as savedEverythingBeforeLeaving, w as GetNodeInstallableVersionsDocument, xt as isFlowComplete, yt as touchesSavedFields, z as MissingParametersDialog, zt as releasesOnClose } from "./FluidGridsProvider-dvZSNtSW.js";
|
|
4
5
|
import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
6
|
+
import { c as useDurableParityEnabled, d as usePaletteGates, f as usePhaseCFlags, l as useFluidGridsFlag, m as useWorkflowTestsEnabled, s as useDurableFunctionNodesEnabled, t as FLUIDGRIDS_FLAGS } from "./feature-flags-DM5myWaD.js";
|
|
7
|
+
import { _ as throwIfResultError, d as emitFluidGridsEvent, m as describeOperationError, n as getFluidGridsClient, r as getFluidGridsClientSafe, s as waitForFluidGridsClient, x as getFluidGridsConfig } from "./client-C9XSzSXr.js";
|
|
8
|
+
import { L as triggerSchedule, T as pauseSchedule, t as backfillSchedule, u as describeSchedule, z as unpauseSchedule } from "./api-BrCHB5oA.js";
|
|
9
|
+
import { i as NativeSelect, o as StatusPill, t as FieldError, u as useResource } from "./shared-CMfn30xT.js";
|
|
10
|
+
import { CF as WorkflowExportFormat, Eo as CodeReplaceStatus, PD as ScheduleType, QF as WorkflowTestExpectedStatus, SF as WorkflowEnvironment, VF as WorkflowScheduleOverlapPolicy, xu as DrainageStatus, yP as VersioningBehavior } from "./graphql-types-CDKVWH2R.js";
|
|
11
|
+
import { a as SdkPermissionGate, i as SdkPermissionButton, s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-B2lhWNoN.js";
|
|
12
|
+
import { a as useConnections, r as useAgentsUsingConnection, t as useAgentDefinitionList, wt as useRunFromNodeEligibilityQuery } from "./useAgentDefinitions-Dpz0HDTq.js";
|
|
13
|
+
import { r as describeRule, s as isAgentNodeType } from "./agent-form-Bfvm32j1.js";
|
|
14
|
+
import { An as SetWorkflowRolloutDocument, Ar as WorkflowEditLeaseDocument, Bn as TakeOverWorkflowEditLeaseDocument, Cr as WorkflowCallInputsDocument, Ct as ListenForWebhookTestEventDocument, Er as WorkflowDefinitionDocument, Fr as WorkflowGenerationTemplatesDocument, Gt as PushWorkflowToGitDocument, Ir as WorkflowGitCommitsDocument, Jt as RejectWorkflowPromotionDocument, Lr as WorkflowGitDiffDocument, Mr as WorkflowEnvironmentsDocument, Nt as NodeUsageStatusDocument, Rn as StopWebhookTestListenerDocument, Rr as WorkflowGitPushesDocument, St as LinkWorkflowGitRepositoryDocument, Ti as WorkspaceRunsQuotaDocument, Ur as WorkflowPromotionRequestsDocument, Vt as PreviewScheduleDocument, X as DeployWorkflowVersionToDevDocument, Xt as ReleaseWorkflowEditLeaseDocument, Yn as UnlinkWorkflowGitRepositoryDocument, Zt as RemoveNodeFromWorkflowsDocument, an as RestoreWorkflowFromGitDocument, b as CancelWorkflowPromotionDocument, bi as WorkflowVersionDrainageDocument, jt as NodeSchemaIntrospectionDocument, l as ApproveWorkflowPromotionDocument, pr as WebhookTestListenerDocument, q as DeleteWorkflowRolloutDocument, r as ActivateWorkflowVersionCheckedDocument, sr as UpdateWorkflowEnvironmentPolicyDocument, t as AcquireWorkflowEditLeaseDocument, tn as RequestWorkflowPromotionDocument, vr as WorkflowAuthoringDocument, xi as WorkflowVersioningDocument, zr as WorkflowGitRepositoryDocument } from "./graphql-operation-types-kX-MMjgW.js";
|
|
15
|
+
import { f as BILLING_PLANS_PATH, g as collectExtensions, m as RUNS_QUOTA_NAME, o as emitUpgradeClicked, t as emitNodeInstalled } from "./fluidgridsEvents-CNdcrUNN.js";
|
|
16
|
+
import { C as rowsFromRules, S as readAssertRulesSetting, _ as ASSERT_OPERATORS, a as TEST_STATUS_TONE_CLASS, b as normaliseAssertRules, c as draftToInput, d as isTestRunOpen, f as nextSortOrder, g as testStatusTone, h as testStatusLabel, i as useWorkflowTests, l as emptyTestCaseDraft, m as testRunSummaryText, n as useWorkflowProposal, p as pinnedOutputsFromCanvas, s as draftFromTestCase, t as WorkflowProposalCard, u as enabledCaseIds, v as describeAssertRuleProblem, w as ruleFromRow, x as operatorSpec, y as emptyAssertRuleRow } from "./WorkflowProposalCard-qrEeNyYG.js";
|
|
11
17
|
import { t as cn$1 } from "./cn-QqidFIgr.js";
|
|
12
|
-
import {
|
|
18
|
+
import { _ as storeNodeInstallVersion, a as useTemplateImportFlow, d as filterStoreNodesForPalette, f as getStoreNodeAvailability, g as storeNodeDisplayVersion, h as isStoreNodeUpdateAvailable, k as filterPaletteNodes, m as getStoreNodeType, p as getStoreNodeIcon, t as TemplateCard, u as useWorkflowTemplates, v as useInstalledNodes, y as useGlobalStoreClient } from "./TemplateCard-DsC0KNH1.js";
|
|
19
|
+
import { a as useRunners, t as RunnerStatusPill } from "./RunnerStatusPill-Ctg4TJcP.js";
|
|
20
|
+
import { a as DialogFooter$1, d as Input$1, i as DialogDescription$1, l as DialogTitle$1, o as DialogHeader$1, r as DialogContent$1, t as Dialog$1 } from "./dialog-Cjb4lB4D.js";
|
|
21
|
+
import { t as Button$2 } from "./button-DG7y_FVJ.js";
|
|
22
|
+
import { t as PublishCustomNodeAction } from "./PublishCustomNodeAction-5XXnSA3H.js";
|
|
13
23
|
import { n as useAgentMarksStore } from "./marksStore-DGCXjTmZ.js";
|
|
14
|
-
import { O as CreateWorkflowRefusalDialog, b as lineOfNode, h as fileBaseName, i as useWorkflowCodeExport, n as downloadWorkflowCodeExport, t as WorkflowShareDialog, v as isLosslessReport, w as sortFidelityItems, x as orderedExportFiles, y as isLossyFidelityItem } from "./WorkflowShareDialog-
|
|
24
|
+
import { O as CreateWorkflowRefusalDialog, b as lineOfNode, h as fileBaseName, i as useWorkflowCodeExport, n as downloadWorkflowCodeExport, t as WorkflowShareDialog, v as isLosslessReport, w as sortFidelityItems, x as orderedExportFiles, y as isLossyFidelityItem } from "./WorkflowShareDialog-B_gn968c.js";
|
|
25
|
+
import { t as EntityTagPicker } from "./EntityTagPicker-BqRNsLCQ.js";
|
|
15
26
|
import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
|
|
16
|
-
import { a as useDeactivateWorkflow, d as useRestoreWorkflowVersion, o as useDeleteWorkflow, t as useActivateWorkflow, v as useWorkflowVersions } from "./useWorkflows-
|
|
17
|
-
import { n as VersionLifecycleBar, r as countLifecycle, t as VersionDiffDialog } from "./VersionDiffDialog-
|
|
27
|
+
import { a as useDeactivateWorkflow, d as useRestoreWorkflowVersion, o as useDeleteWorkflow, t as useActivateWorkflow, v as useWorkflowVersions } from "./useWorkflows-BlVVu8NM.js";
|
|
28
|
+
import { n as VersionLifecycleBar, r as countLifecycle, t as VersionDiffDialog } from "./VersionDiffDialog-DSN0mU0f.js";
|
|
29
|
+
import { t as buildNodeDisplayMap } from "./run-display-BycKYSU0.js";
|
|
18
30
|
import { n as useRequestedProposal } from "./useRequestedProposal-B7uPXfZB.js";
|
|
19
31
|
import { t as AiCreditBalanceSlot } from "./aiCreditsBridge-BuE92tKo.js";
|
|
20
32
|
import * as React$1 from "react";
|
|
21
|
-
import
|
|
33
|
+
import { Suspense, createContext, lazy, memo, useCallback, useContext, useDeferredValue, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
22
34
|
import { Link, useNavigate, useParams } from "react-router";
|
|
23
35
|
import { useMutation, useQuery } from "@apollo/client/react";
|
|
24
|
-
import { gql } from "@apollo/client/core";
|
|
25
36
|
import { create } from "zustand";
|
|
26
37
|
import { Background, Controls, EdgeLabelRenderer, Handle, MarkerType, MiniMap, NodeToolbar, Panel, Position, ReactFlow, ReactFlowProvider, ViewportPortal, addEdge, getBezierPath, useEdgesState, useInternalNode, useNodes, useNodesState, useOnSelectionChange, useReactFlow, useStore } from "@xyflow/react";
|
|
27
38
|
import { Icon } from "@iconify/react";
|
|
@@ -32,15 +43,14 @@ import { produce } from "immer";
|
|
|
32
43
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
33
44
|
import { toast } from "sonner";
|
|
34
45
|
import { edgeColor, nodeCategoryColor } from "@burdenoff/fe-libs/shared/utils";
|
|
35
|
-
import { gql
|
|
46
|
+
import { gql } from "@apollo/client";
|
|
36
47
|
import { shallow, useShallow } from "zustand/shallow";
|
|
37
48
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, Label, Popover, PopoverContent, PopoverTrigger, ResponsiveDialog, ResponsiveDialogContent, ResponsiveDialogDescription, ResponsiveDialogFooter, ResponsiveDialogHeader, ResponsiveDialogTitle, ScrollArea, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, Toaster as Toaster$1, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@burdenoff/fe-libs/ui";
|
|
38
49
|
import { useEventBus } from "@burdenoff/fe-libs/shared/events";
|
|
39
50
|
import { Button as Button$1 } from "@burdenoff/fe-libs/button";
|
|
40
51
|
import { ThemeProvider } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
41
52
|
import "@xyflow/react/dist/style.css";
|
|
42
|
-
import {
|
|
43
|
-
import { AlertCircle, AlertTriangle, ArrowRight, BadgeCheck, Braces, Check, CheckCircle2, ChevronDown, ChevronRight, CircleHelp, Code, Copy, DollarSign, Download, ExternalLink, Eye, FlaskConical, GitBranch, GitCommitHorizontal, GitCompare, GitCompareArrows, Hash, History, Info, Keyboard, Layers, LayoutGrid, Link2, List, Loader2, Maximize2, Package, Pencil, Play, Plus, PlusCircle, Redo2, RefreshCw, Rocket, RotateCcw, Search, Share2, SlidersHorizontal, Sparkles, Star, Trash2, Type, Undo2, Unlink, Upload, Workflow, X, XIcon } from "lucide-react";
|
|
53
|
+
import { AlertCircle, AlertTriangle, ArrowRight, BadgeCheck, Braces, Check, CheckCircle2, ChevronDown, ChevronRight, CircleHelp, Code, Copy, DollarSign, Download, ExternalLink, Eye, FlaskConical, GitBranch, GitCommitHorizontal, GitCompare, GitCompareArrows, Hash, History, Info, Keyboard, Layers, LayoutGrid, Link2, List, Loader2, Maximize2, Package, Pencil, Play, Plus, PlusCircle, Redo2, RefreshCw, Rocket, RotateCcw, Search, Share2, SlidersHorizontal, Sparkles, Star, Trash2, Type, Undo2, Unlink, Upload, Workflow, X } from "lucide-react";
|
|
44
54
|
import { ConnectionDialog, ProviderIcon } from "@burdenoff/fe-libs/connections";
|
|
45
55
|
import { isEmpty } from "radash";
|
|
46
56
|
import { formatDistanceToNow } from "date-fns";
|
|
@@ -49,7 +59,6 @@ import { lintGutter, setDiagnostics } from "@codemirror/lint";
|
|
|
49
59
|
import cronstrue from "cronstrue";
|
|
50
60
|
import { digest } from "ohash";
|
|
51
61
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
52
|
-
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
53
62
|
import { animate, easeOut } from "popmotion";
|
|
54
63
|
import { createPortal } from "react-dom";
|
|
55
64
|
import { GraphQLEnumType, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLUnionType, buildSchema } from "graphql";
|
|
@@ -57,134 +66,6 @@ import { useAgentTools } from "@burdenoff/fe-libs/shared/agent-tools";
|
|
|
57
66
|
import { useUnsavedChanges } from "@burdenoff/fe-libs/shared/hooks";
|
|
58
67
|
import { buildActorFallback, useActorProfiles } from "@burdenoff/fe-libs/shared";
|
|
59
68
|
|
|
60
|
-
// src/feature-flags/durable-nodes.ts
|
|
61
|
-
/**
|
|
62
|
-
* Palette gating for the durable-function nodes (BOFF-9905).
|
|
63
|
-
*
|
|
64
|
-
* wspace-workflow-svc only seeds the `internal:code-workflow` and
|
|
65
|
-
* `internal:wait-for-signal` manifests while `DURABLE_NODE_MANIFESTS_ENABLED`
|
|
66
|
-
* is set, and refuses durable work while the `fluidgrids.durable-functions`
|
|
67
|
-
* flag is off for the workspace. Once the manifests are seeded for every
|
|
68
|
-
* workspace, the builder still has to hide the two nodes from the palettes of
|
|
69
|
-
* workspaces where the flag is off. This module is that client-side filter; it
|
|
70
|
-
* mirrors the server-side gate and is never the gate itself.
|
|
71
|
-
*
|
|
72
|
-
* Hiding only affects what can be picked from a palette. A workflow that
|
|
73
|
-
* already contains one of these nodes keeps rendering and running unchanged.
|
|
74
|
-
*/
|
|
75
|
-
/** Node types offered only while durable functions are enabled. */
|
|
76
|
-
const DURABLE_FUNCTION_NODE_TYPES = /* @__PURE__ */ new Set(["internal:code-workflow", "internal:wait-for-signal"]);
|
|
77
|
-
/**
|
|
78
|
-
* The run-control nodes of Phase C (BOFF-9906, DC15). wspace-workflow-svc
|
|
79
|
-
* seeds their manifests only while `RUN_CONTROL_NODE_MANIFESTS_ENABLED` is
|
|
80
|
-
* set, which is turned on after this filter ships; the builder then still
|
|
81
|
-
* hides each one where its flag is off: Signal Run needs run control, Set
|
|
82
|
-
* Search Attributes needs search attributes.
|
|
83
|
-
*/
|
|
84
|
-
const RUN_CONTROL_NODE_TYPES = /* @__PURE__ */ new Set(["internal:signal-run", "internal:set-search-attributes"]);
|
|
85
|
-
/**
|
|
86
|
-
* The Assert node of the workflow test suites (BOFF-9960). wspace-workflow-svc
|
|
87
|
-
* seeds its manifest for every workspace, so the builder hides it wherever the
|
|
88
|
-
* `fluidgrids.workflow-tests` flag is off.
|
|
89
|
-
*/
|
|
90
|
-
const WORKFLOW_TEST_NODE_TYPES = /* @__PURE__ */ new Set(["internal:assert"]);
|
|
91
|
-
/** Whether a workflow node type is one of the Phase C run-control nodes. */
|
|
92
|
-
function isRunControlNodeType(nodeType) {
|
|
93
|
-
return !!nodeType && RUN_CONTROL_NODE_TYPES.has(nodeType);
|
|
94
|
-
}
|
|
95
|
-
/** Normalises the boolean (Phase B) or object form of the palette gates. */
|
|
96
|
-
function paletteGatesOf(gates) {
|
|
97
|
-
if (typeof gates === "boolean") return {
|
|
98
|
-
durableFunctions: gates,
|
|
99
|
-
runControl: false,
|
|
100
|
-
searchAttributes: false,
|
|
101
|
-
workflowTests: false
|
|
102
|
-
};
|
|
103
|
-
return {
|
|
104
|
-
durableFunctions: gates?.durableFunctions === true,
|
|
105
|
-
runControl: gates?.runControl === true,
|
|
106
|
-
searchAttributes: gates?.searchAttributes === true,
|
|
107
|
-
workflowTests: gates?.workflowTests === true
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function hiddenBy(nodeType, gates) {
|
|
111
|
-
if (DURABLE_FUNCTION_NODE_TYPES.has(nodeType)) return !gates.durableFunctions;
|
|
112
|
-
if (nodeType === "internal:signal-run") return !gates.runControl;
|
|
113
|
-
if (nodeType === "internal:set-search-attributes") return !gates.searchAttributes;
|
|
114
|
-
if (WORKFLOW_TEST_NODE_TYPES.has(nodeType)) return !gates.workflowTests;
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
/** Whether a workflow node type is one of the durable-function nodes. */
|
|
118
|
-
function isDurableFunctionNodeType(nodeType) {
|
|
119
|
-
return !!nodeType && DURABLE_FUNCTION_NODE_TYPES.has(nodeType);
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Whether a node type must be left out of the builder palettes. Fails closed:
|
|
123
|
-
* a family the caller does not say is enabled is not.
|
|
124
|
-
*/
|
|
125
|
-
function isNodeTypeHiddenFromPalette(nodeType, gates = false) {
|
|
126
|
-
return !!nodeType && hiddenBy(nodeType, paletteGatesOf(gates));
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Drops the palette entries that must stay hidden. Returns the input array
|
|
130
|
-
* itself when nothing is hidden, so the canvas palettes keep the exact list
|
|
131
|
-
* (and references) they had before this filter existed.
|
|
132
|
-
*/
|
|
133
|
-
function filterPaletteNodes(nodes, gates = false) {
|
|
134
|
-
const resolved = paletteGatesOf(gates);
|
|
135
|
-
if (!nodes.some((node) => hiddenBy(node.type, resolved))) return nodes;
|
|
136
|
-
return nodes.filter((node) => !hiddenBy(node.type, resolved));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// src/ui/input.tsx
|
|
140
|
-
const Input$1 = forwardRef(({ className, type, ...props }, ref) => {
|
|
141
|
-
return /* @__PURE__ */ jsx("input", {
|
|
142
|
-
type,
|
|
143
|
-
className: cn$1("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
144
|
-
ref,
|
|
145
|
-
...props
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
Input$1.displayName = "Input";
|
|
149
|
-
|
|
150
|
-
// src/ui/card.tsx
|
|
151
|
-
const Card$1 = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
152
|
-
ref,
|
|
153
|
-
className: cn$1("rounded-lg border bg-card text-card-foreground shadow-sm transition-all duration-200 ease-out hover:shadow-md", className),
|
|
154
|
-
...props
|
|
155
|
-
}));
|
|
156
|
-
Card$1.displayName = "Card";
|
|
157
|
-
const CardHeader$1 = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
158
|
-
ref,
|
|
159
|
-
className: cn$1("flex flex-col space-y-1.5 p-6", className),
|
|
160
|
-
...props
|
|
161
|
-
}));
|
|
162
|
-
CardHeader$1.displayName = "CardHeader";
|
|
163
|
-
const CardTitle$1 = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("h3", {
|
|
164
|
-
ref,
|
|
165
|
-
className: cn$1("text-2xl font-semibold leading-none tracking-tight", className),
|
|
166
|
-
...props
|
|
167
|
-
}));
|
|
168
|
-
CardTitle$1.displayName = "CardTitle";
|
|
169
|
-
const CardDescription$1 = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("p", {
|
|
170
|
-
ref,
|
|
171
|
-
className: cn$1("text-sm text-muted-foreground", className),
|
|
172
|
-
...props
|
|
173
|
-
}));
|
|
174
|
-
CardDescription$1.displayName = "CardDescription";
|
|
175
|
-
const CardContent$1 = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
176
|
-
ref,
|
|
177
|
-
className: cn$1("p-6 pt-0", className),
|
|
178
|
-
...props
|
|
179
|
-
}));
|
|
180
|
-
CardContent$1.displayName = "CardContent";
|
|
181
|
-
const CardFooter = forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", {
|
|
182
|
-
ref,
|
|
183
|
-
className: cn$1("flex items-center p-6 pt-0", className),
|
|
184
|
-
...props
|
|
185
|
-
}));
|
|
186
|
-
CardFooter.displayName = "CardFooter";
|
|
187
|
-
|
|
188
69
|
// src/components/shared/FluidGridsMark.tsx
|
|
189
70
|
/**
|
|
190
71
|
* FluidGridsMark — the small "Powered by FluidGrids" mark that every workflow
|
|
@@ -2030,7 +1911,7 @@ function WebhookTestListenerControls({ nodeId }) {
|
|
|
2030
1911
|
}
|
|
2031
1912
|
|
|
2032
1913
|
// src/builder/components/ndv/workflow-picker-field.tsx
|
|
2033
|
-
const LIST_WORKFLOWS_QUERY = gql
|
|
1914
|
+
const LIST_WORKFLOWS_QUERY = gql`
|
|
2034
1915
|
query ListWorkflowDefinitionsForPicker($limit: Int, $offset: Int) {
|
|
2035
1916
|
workflowDefinitions(input: { limit: $limit, offset: $offset }) {
|
|
2036
1917
|
items {
|
|
@@ -2464,7 +2345,7 @@ function InputField({ field, mapping, inputMapping, onChange, testId }) {
|
|
|
2464
2345
|
}
|
|
2465
2346
|
|
|
2466
2347
|
// src/hooks/load-node-options-request.ts
|
|
2467
|
-
const LOAD_NODE_OPTIONS = gql
|
|
2348
|
+
const LOAD_NODE_OPTIONS = gql`
|
|
2468
2349
|
query LoadNodeOptions(
|
|
2469
2350
|
$nodeType: String!
|
|
2470
2351
|
$nodeVersion: String!
|
|
@@ -3546,7 +3427,7 @@ function defaultScheduleRule() {
|
|
|
3546
3427
|
function isScheduleRuleField(value) {
|
|
3547
3428
|
return typeof value === "string" && SCHEDULE_RULE_FIELDS.some((field) => field === value);
|
|
3548
3429
|
}
|
|
3549
|
-
function isRecord$
|
|
3430
|
+
function isRecord$5(value) {
|
|
3550
3431
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3551
3432
|
}
|
|
3552
3433
|
function toNumber(value) {
|
|
@@ -3645,10 +3526,10 @@ function rulesFromLegacyState(state) {
|
|
|
3645
3526
|
* array and the legacy fields, and always yields at least one rule.
|
|
3646
3527
|
*/
|
|
3647
3528
|
function normalizeScheduleState(state) {
|
|
3648
|
-
const source = isRecord$
|
|
3529
|
+
const source = isRecord$5(state) ? state : {};
|
|
3649
3530
|
let rules;
|
|
3650
3531
|
if (Array.isArray(source.rules)) {
|
|
3651
|
-
rules = source.rules.filter(isRecord$
|
|
3532
|
+
rules = source.rules.filter(isRecord$5).map(normalizeScheduleRule);
|
|
3652
3533
|
if (rules.length === 0) rules = [defaultScheduleRule()];
|
|
3653
3534
|
} else rules = rulesFromLegacyState(source);
|
|
3654
3535
|
const timezone = toTrimmedString(source.timezone) || "UTC";
|
|
@@ -3663,7 +3544,7 @@ function normalizeScheduleState(state) {
|
|
|
3663
3544
|
}
|
|
3664
3545
|
/** Whether the saved state predates `rules[]` (so the editor is showing a conversion). */
|
|
3665
3546
|
function isLegacyScheduleState(state) {
|
|
3666
|
-
if (!isRecord$
|
|
3547
|
+
if (!isRecord$5(state) || Array.isArray(state.rules)) return false;
|
|
3667
3548
|
return state.mode !== void 0 || toTrimmedString(state.cronExpression) !== "" || toNumber(state.interval) !== void 0;
|
|
3668
3549
|
}
|
|
3669
3550
|
function inRange(value, min, max) {
|
|
@@ -4313,7 +4194,7 @@ const TOOL_APPROVAL_RULES = [
|
|
|
4313
4194
|
function isRule(value) {
|
|
4314
4195
|
return typeof value === "string" && TOOL_APPROVAL_RULES.includes(value);
|
|
4315
4196
|
}
|
|
4316
|
-
function isRecord$
|
|
4197
|
+
function isRecord$4(value) {
|
|
4317
4198
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4318
4199
|
}
|
|
4319
4200
|
function parsed(value) {
|
|
@@ -4327,11 +4208,11 @@ function parsed(value) {
|
|
|
4327
4208
|
/** The rows of a stored setting; rows with an unknown rule read as `ask`. */
|
|
4328
4209
|
function readToolApprovalRows(value) {
|
|
4329
4210
|
const raw = parsed(value);
|
|
4330
|
-
if (Array.isArray(raw)) return raw.filter(isRecord$
|
|
4211
|
+
if (Array.isArray(raw)) return raw.filter(isRecord$4).map((row) => ({
|
|
4331
4212
|
toolName: typeof row.toolName === "string" ? row.toolName : "",
|
|
4332
4213
|
rule: isRule(row.rule) ? row.rule : "ask"
|
|
4333
4214
|
}));
|
|
4334
|
-
if (isRecord$
|
|
4215
|
+
if (isRecord$4(raw)) return Object.entries(raw).map(([toolName, rule]) => ({
|
|
4335
4216
|
toolName,
|
|
4336
4217
|
rule: isRule(rule) ? rule : "ask"
|
|
4337
4218
|
}));
|
|
@@ -4348,9 +4229,9 @@ function connectedToolNames(agentId, nodes, edges) {
|
|
|
4348
4229
|
for (const edge of edges) {
|
|
4349
4230
|
if (edge.source !== agentId || edge.sourceHandle !== "Tool") continue;
|
|
4350
4231
|
const node = edge.target ? byId.get(edge.target) : void 0;
|
|
4351
|
-
const args = isRecord$
|
|
4352
|
-
const state = isRecord$
|
|
4353
|
-
const name = isRecord$
|
|
4232
|
+
const args = isRecord$4(node?.data) ? node.data.args : void 0;
|
|
4233
|
+
const state = isRecord$4(args) ? args.state : void 0;
|
|
4234
|
+
const name = isRecord$4(state) ? state.toolName : void 0;
|
|
4354
4235
|
if (typeof name === "string" && name.trim()) names.add(name.trim());
|
|
4355
4236
|
}
|
|
4356
4237
|
return [...names];
|
|
@@ -4923,7 +4804,7 @@ function TimezoneSelect({ value, onChange, className, testId }) {
|
|
|
4923
4804
|
/** Builder field types the trigger manifests give the two picker fields. */
|
|
4924
4805
|
const PLATFORM_EVENT_MODULE_FIELD = "platform-event-module";
|
|
4925
4806
|
const PLATFORM_EVENT_RECORD_TYPE_FIELD = "platform-event-record-type";
|
|
4926
|
-
const PLATFORM_EVENT_SOURCES_QUERY = gql
|
|
4807
|
+
const PLATFORM_EVENT_SOURCES_QUERY = gql`
|
|
4927
4808
|
query PlatformEventSources {
|
|
4928
4809
|
platformEventSources {
|
|
4929
4810
|
module
|
|
@@ -4937,17 +4818,17 @@ const PLATFORM_EVENT_SOURCES_QUERY = gql$1`
|
|
|
4937
4818
|
/** The list hardly changes; one read per few minutes is plenty. */
|
|
4938
4819
|
const CACHE_TTL_MS$1 = 18e4;
|
|
4939
4820
|
const caches$1 = /* @__PURE__ */ new WeakMap();
|
|
4940
|
-
function isRecord$
|
|
4821
|
+
function isRecord$3(value) {
|
|
4941
4822
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4942
4823
|
}
|
|
4943
4824
|
/** The well-formed sources of a `platformEventSources` response; anything else is dropped. */
|
|
4944
4825
|
function readPlatformEventSources(data) {
|
|
4945
|
-
const list = isRecord$
|
|
4826
|
+
const list = isRecord$3(data) ? data.platformEventSources : void 0;
|
|
4946
4827
|
if (!Array.isArray(list)) return [];
|
|
4947
4828
|
const sources = [];
|
|
4948
4829
|
for (const item of list) {
|
|
4949
|
-
if (!isRecord$
|
|
4950
|
-
const recordTypes = Array.isArray(item.recordTypes) ? item.recordTypes.flatMap((type) => isRecord$
|
|
4830
|
+
if (!isRecord$3(item) || typeof item.module !== "string") continue;
|
|
4831
|
+
const recordTypes = Array.isArray(item.recordTypes) ? item.recordTypes.flatMap((type) => isRecord$3(type) && typeof type.name === "string" ? [{
|
|
4951
4832
|
name: type.name,
|
|
4952
4833
|
actions: Array.isArray(type.actions) ? type.actions.filter((action) => typeof action === "string") : []
|
|
4953
4834
|
}] : []) : [];
|
|
@@ -5196,7 +5077,7 @@ function PlatformEventSourceSelect({ kind, value, module = "", required = false,
|
|
|
5196
5077
|
// src/builder/components/ndv/timecampus-time-triggers.ts
|
|
5197
5078
|
/** Builder field type the TimeCampus Event manifest gives its `triggerIds` field. */
|
|
5198
5079
|
const TIMECAMPUS_TIME_TRIGGER_FIELD = "timecampus-time-trigger";
|
|
5199
|
-
const MY_TIME_TRIGGERS_QUERY = gql
|
|
5080
|
+
const MY_TIME_TRIGGERS_QUERY = gql`
|
|
5200
5081
|
query FluidGridsTimeCampusMyTimeTriggers {
|
|
5201
5082
|
timeCampusMyTimeTriggers {
|
|
5202
5083
|
id
|
|
@@ -5214,16 +5095,16 @@ const MY_TIME_TRIGGERS_QUERY = gql$1`
|
|
|
5214
5095
|
*/
|
|
5215
5096
|
const CACHE_TTL_MS = 3e4;
|
|
5216
5097
|
const caches = /* @__PURE__ */ new WeakMap();
|
|
5217
|
-
function isRecord$
|
|
5098
|
+
function isRecord$2(value) {
|
|
5218
5099
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5219
5100
|
}
|
|
5220
5101
|
/** The well-formed triggers of a `timeCampusMyTimeTriggers` response, sorted by name. */
|
|
5221
5102
|
function readTimeTriggers(data) {
|
|
5222
|
-
const list = isRecord$
|
|
5103
|
+
const list = isRecord$2(data) ? data.timeCampusMyTimeTriggers : void 0;
|
|
5223
5104
|
if (!Array.isArray(list)) return [];
|
|
5224
5105
|
const triggers = [];
|
|
5225
5106
|
for (const item of list) {
|
|
5226
|
-
if (!isRecord$
|
|
5107
|
+
if (!isRecord$2(item) || typeof item.id !== "string" || !item.id) continue;
|
|
5227
5108
|
triggers.push({
|
|
5228
5109
|
id: item.id,
|
|
5229
5110
|
name: typeof item.name === "string" && item.name.trim() ? item.name.trim() : item.id,
|
|
@@ -5537,24 +5418,6 @@ function TimeCampusTriggerSelect({ value, onChange, className, testId, label, in
|
|
|
5537
5418
|
});
|
|
5538
5419
|
}
|
|
5539
5420
|
|
|
5540
|
-
// src/hooks/store/useGlobalStoreClient.ts
|
|
5541
|
-
/**
|
|
5542
|
-
* Hook to get the Global Store Apollo client
|
|
5543
|
-
*
|
|
5544
|
-
* Priority:
|
|
5545
|
-
* 1. FluidGridsProvider context (if available)
|
|
5546
|
-
* 2. Module-level singleton set via setGlobalStoreClient()
|
|
5547
|
-
*
|
|
5548
|
-
* @throws Error if no global client is available from either source
|
|
5549
|
-
*/
|
|
5550
|
-
function useGlobalStoreClient() {
|
|
5551
|
-
const context = useContext(FluidGridsContext);
|
|
5552
|
-
if (context?.globalClient) return context.globalClient;
|
|
5553
|
-
const singletonClient = getGlobalStoreClient();
|
|
5554
|
-
if (singletonClient) return singletonClient;
|
|
5555
|
-
throw new Error("useGlobalStoreClient: No global client available. Either wrap your component tree with <FluidGridsProvider globalClient={...}> or call setGlobalStoreClient(client) before rendering.");
|
|
5556
|
-
}
|
|
5557
|
-
|
|
5558
5421
|
// src/modules/channels/hooks/useChannels.ts
|
|
5559
5422
|
/**
|
|
5560
5423
|
* Custom hooks for fetching channels from global-channel-svc.
|
|
@@ -5563,7 +5426,7 @@ function useGlobalStoreClient() {
|
|
|
5563
5426
|
* Channels represent configured connections to communication platforms
|
|
5564
5427
|
* (Slack, Teams, Discord, Email, SMS, etc.).
|
|
5565
5428
|
*/
|
|
5566
|
-
const CHANNEL_SUMMARY_FIELDS = gql
|
|
5429
|
+
const CHANNEL_SUMMARY_FIELDS = gql`
|
|
5567
5430
|
fragment ChannelSummaryFields on Channel {
|
|
5568
5431
|
id
|
|
5569
5432
|
name
|
|
@@ -5577,7 +5440,7 @@ const CHANNEL_SUMMARY_FIELDS = gql$1`
|
|
|
5577
5440
|
createdAt
|
|
5578
5441
|
}
|
|
5579
5442
|
`;
|
|
5580
|
-
const CHANNEL_DETAIL_FIELDS = gql
|
|
5443
|
+
const CHANNEL_DETAIL_FIELDS = gql`
|
|
5581
5444
|
fragment ChannelDetailFields on Channel {
|
|
5582
5445
|
id
|
|
5583
5446
|
name
|
|
@@ -5599,7 +5462,7 @@ const CHANNEL_DETAIL_FIELDS = gql$1`
|
|
|
5599
5462
|
updatedAt
|
|
5600
5463
|
}
|
|
5601
5464
|
`;
|
|
5602
|
-
const GET_CHANNELS = gql
|
|
5465
|
+
const GET_CHANNELS = gql`
|
|
5603
5466
|
${CHANNEL_SUMMARY_FIELDS}
|
|
5604
5467
|
query GetChannels(
|
|
5605
5468
|
$filter: ChannelFilterInput
|
|
@@ -5614,7 +5477,7 @@ const GET_CHANNELS = gql$1`
|
|
|
5614
5477
|
}
|
|
5615
5478
|
}
|
|
5616
5479
|
`;
|
|
5617
|
-
const GET_CHANNEL = gql
|
|
5480
|
+
const GET_CHANNEL = gql`
|
|
5618
5481
|
${CHANNEL_DETAIL_FIELDS}
|
|
5619
5482
|
query GetChannel($id: ID!) {
|
|
5620
5483
|
getChannel(id: $id) {
|
|
@@ -5863,7 +5726,7 @@ function channelAuthUrl(providerKey, returnTo, nodeId) {
|
|
|
5863
5726
|
* panel: pick a communication channel, see its status, start the platform's
|
|
5864
5727
|
* OAuth flow, and link an integration connection to the channel.
|
|
5865
5728
|
*/
|
|
5866
|
-
const LINK_INTEGRATION_MUTATION = gql
|
|
5729
|
+
const LINK_INTEGRATION_MUTATION = gql`
|
|
5867
5730
|
mutation LinkIntegrationConnection(
|
|
5868
5731
|
$integrationConnectionId: ID!
|
|
5869
5732
|
$channelId: ID!
|
|
@@ -8048,184 +7911,6 @@ function NodeParamsForm({ config, nodeType, testIdPrefix = "ndv" }) {
|
|
|
8048
7911
|
] });
|
|
8049
7912
|
}
|
|
8050
7913
|
|
|
8051
|
-
// src/modules/runners/hooks/useRunners.ts
|
|
8052
|
-
/**
|
|
8053
|
-
* Self-hosted runners (wspace-sandbox-svc): list, add, rotate token, remove.
|
|
8054
|
-
*
|
|
8055
|
-
* A runner is a program the customer installs on their own machine; workflow
|
|
8056
|
-
* nodes whose "Run on" names it execute there, in a local sandbox. Mirrors the
|
|
8057
|
-
* connections/variables hook pattern (shared Apollo client, no generated hooks).
|
|
8058
|
-
*/
|
|
8059
|
-
const RUNNER_FIELDS = gql$1`
|
|
8060
|
-
fragment SandboxRunnerFields on SandboxRunner {
|
|
8061
|
-
id
|
|
8062
|
-
name
|
|
8063
|
-
status
|
|
8064
|
-
hostname
|
|
8065
|
-
os
|
|
8066
|
-
arch
|
|
8067
|
-
driver
|
|
8068
|
-
version
|
|
8069
|
-
capacity
|
|
8070
|
-
privateNetwork {
|
|
8071
|
-
target
|
|
8072
|
-
ports
|
|
8073
|
-
nodes
|
|
8074
|
-
}
|
|
8075
|
-
tokenPrefix
|
|
8076
|
-
lastSeenAt
|
|
8077
|
-
connectedAt
|
|
8078
|
-
createdBy
|
|
8079
|
-
createdAt
|
|
8080
|
-
}
|
|
8081
|
-
`;
|
|
8082
|
-
const LIST_RUNNERS = gql$1`
|
|
8083
|
-
${RUNNER_FIELDS}
|
|
8084
|
-
query SandboxRunners {
|
|
8085
|
-
sandboxRunners(context: {}) {
|
|
8086
|
-
...SandboxRunnerFields
|
|
8087
|
-
}
|
|
8088
|
-
}
|
|
8089
|
-
`;
|
|
8090
|
-
const CREATE_RUNNER = gql$1`
|
|
8091
|
-
${RUNNER_FIELDS}
|
|
8092
|
-
mutation CreateSandboxRunner($name: String!) {
|
|
8093
|
-
createSandboxRunner(input: { name: $name, context: {} }) {
|
|
8094
|
-
token
|
|
8095
|
-
bridgeUrl
|
|
8096
|
-
runner {
|
|
8097
|
-
...SandboxRunnerFields
|
|
8098
|
-
}
|
|
8099
|
-
}
|
|
8100
|
-
}
|
|
8101
|
-
`;
|
|
8102
|
-
const ROTATE_RUNNER_TOKEN = gql$1`
|
|
8103
|
-
${RUNNER_FIELDS}
|
|
8104
|
-
mutation RotateSandboxRunnerToken($id: ID!) {
|
|
8105
|
-
rotateSandboxRunnerToken(id: $id, context: {}) {
|
|
8106
|
-
token
|
|
8107
|
-
bridgeUrl
|
|
8108
|
-
runner {
|
|
8109
|
-
...SandboxRunnerFields
|
|
8110
|
-
}
|
|
8111
|
-
}
|
|
8112
|
-
}
|
|
8113
|
-
`;
|
|
8114
|
-
const DELETE_RUNNER = gql$1`
|
|
8115
|
-
mutation DeleteSandboxRunner($id: ID!) {
|
|
8116
|
-
deleteSandboxRunner(id: $id, context: {})
|
|
8117
|
-
}
|
|
8118
|
-
`;
|
|
8119
|
-
/** Where the installer is published (fluidgrids-runner release workflow). */
|
|
8120
|
-
const RUNNER_INSTALL_SCRIPT_URL = "https://www.fluidgrids.ai/downloads/runner/install.sh";
|
|
8121
|
-
/**
|
|
8122
|
-
* The one-line install command shown after creating a runner. It carries NO
|
|
8123
|
-
* token: the installer asks for it (input hidden), so the token never lands in
|
|
8124
|
-
* shell history or the machine's process list.
|
|
8125
|
-
*/
|
|
8126
|
-
function runnerInstallCommand(bridgeUrl) {
|
|
8127
|
-
const base = `curl -fsSL ${RUNNER_INSTALL_SCRIPT_URL} | sh`;
|
|
8128
|
-
return bridgeUrl && bridgeUrl !== "wss://graphqlworkspaces.burdenoff.com/runner-bridge" ? `${base} -s -- --url ${bridgeUrl}` : base;
|
|
8129
|
-
}
|
|
8130
|
-
/** The runner's built-in default; omitted from the command when it matches. */
|
|
8131
|
-
const DEFAULT_BRIDGE_URL = "wss://graphqlworkspaces.burdenoff.com/runner-bridge";
|
|
8132
|
-
/**
|
|
8133
|
-
* Runners in the current workspace. Polls while mounted so ONLINE/OFFLINE
|
|
8134
|
-
* stays current (a runner heartbeats every 20 s).
|
|
8135
|
-
*/
|
|
8136
|
-
function useRunners(options) {
|
|
8137
|
-
const [runners, setRunners] = useState([]);
|
|
8138
|
-
const [loading, setLoading] = useState(!options?.skip);
|
|
8139
|
-
const [error, setError] = useState(null);
|
|
8140
|
-
const mounted = useRef(true);
|
|
8141
|
-
const fetchRunners = useCallback(async () => {
|
|
8142
|
-
if (options?.skip) return;
|
|
8143
|
-
try {
|
|
8144
|
-
const result = await getFluidGridsClient().query({
|
|
8145
|
-
query: LIST_RUNNERS,
|
|
8146
|
-
fetchPolicy: "network-only"
|
|
8147
|
-
});
|
|
8148
|
-
if (!mounted.current) return;
|
|
8149
|
-
setRunners(result.data?.sandboxRunners ?? []);
|
|
8150
|
-
setError(null);
|
|
8151
|
-
} catch (err) {
|
|
8152
|
-
if (mounted.current) setError(err);
|
|
8153
|
-
} finally {
|
|
8154
|
-
if (mounted.current) setLoading(false);
|
|
8155
|
-
}
|
|
8156
|
-
}, [options?.skip]);
|
|
8157
|
-
useEffect(() => {
|
|
8158
|
-
mounted.current = true;
|
|
8159
|
-
fetchRunners();
|
|
8160
|
-
const pollMs = options?.pollMs ?? 1e4;
|
|
8161
|
-
const timer = pollMs > 0 ? setInterval(fetchRunners, pollMs) : null;
|
|
8162
|
-
return () => {
|
|
8163
|
-
mounted.current = false;
|
|
8164
|
-
if (timer) clearInterval(timer);
|
|
8165
|
-
};
|
|
8166
|
-
}, [fetchRunners, options?.pollMs]);
|
|
8167
|
-
return {
|
|
8168
|
-
runners,
|
|
8169
|
-
loading,
|
|
8170
|
-
error,
|
|
8171
|
-
refetch: fetchRunners
|
|
8172
|
-
};
|
|
8173
|
-
}
|
|
8174
|
-
function useRunnerActions() {
|
|
8175
|
-
const [busy, setBusy] = useState(false);
|
|
8176
|
-
const run = useCallback(async (fn) => {
|
|
8177
|
-
setBusy(true);
|
|
8178
|
-
try {
|
|
8179
|
-
return await fn();
|
|
8180
|
-
} finally {
|
|
8181
|
-
setBusy(false);
|
|
8182
|
-
}
|
|
8183
|
-
}, []);
|
|
8184
|
-
return {
|
|
8185
|
-
createRunner: useCallback((name) => run(async () => {
|
|
8186
|
-
const result = throwIfResultError(await getFluidGridsClient().mutate({
|
|
8187
|
-
mutation: CREATE_RUNNER,
|
|
8188
|
-
variables: { name }
|
|
8189
|
-
}));
|
|
8190
|
-
if (!result.data?.createSandboxRunner) throw new Error("No runner returned");
|
|
8191
|
-
return result.data.createSandboxRunner;
|
|
8192
|
-
}), [run]),
|
|
8193
|
-
rotateToken: useCallback((id) => run(async () => {
|
|
8194
|
-
const result = throwIfResultError(await getFluidGridsClient().mutate({
|
|
8195
|
-
mutation: ROTATE_RUNNER_TOKEN,
|
|
8196
|
-
variables: { id }
|
|
8197
|
-
}));
|
|
8198
|
-
if (!result.data?.rotateSandboxRunnerToken) throw new Error("No runner returned");
|
|
8199
|
-
return result.data.rotateSandboxRunnerToken;
|
|
8200
|
-
}), [run]),
|
|
8201
|
-
deleteRunner: useCallback((id) => run(async () => {
|
|
8202
|
-
return throwIfResultError(await getFluidGridsClient().mutate({
|
|
8203
|
-
mutation: DELETE_RUNNER,
|
|
8204
|
-
variables: { id }
|
|
8205
|
-
})).data?.deleteSandboxRunner === true;
|
|
8206
|
-
}), [run]),
|
|
8207
|
-
busy
|
|
8208
|
-
};
|
|
8209
|
-
}
|
|
8210
|
-
|
|
8211
|
-
// src/modules/runners/components/RunnerStatusPill.tsx
|
|
8212
|
-
const STYLES = {
|
|
8213
|
-
ONLINE: "border-status-success-border bg-status-success-bg-subtle text-status-success-text",
|
|
8214
|
-
OFFLINE: "border-border-default bg-bg-sunken text-text-muted",
|
|
8215
|
-
NEVER_CONNECTED: "border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text"
|
|
8216
|
-
};
|
|
8217
|
-
/** Online / Offline / Not connected yet. */
|
|
8218
|
-
function RunnerStatusPill({ status, className }) {
|
|
8219
|
-
const tr = useTr();
|
|
8220
|
-
const label = status === "ONLINE" ? tr("fluidgrids.runners.online", "Online") : status === "OFFLINE" ? tr("fluidgrids.runners.offline", "Offline") : tr("fluidgrids.runners.neverConnected", "Not connected yet");
|
|
8221
|
-
return /* @__PURE__ */ jsx("span", {
|
|
8222
|
-
className: cn("inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium", STYLES[status], className),
|
|
8223
|
-
"data-testid": "runner-status",
|
|
8224
|
-
"data-status": status,
|
|
8225
|
-
children: label
|
|
8226
|
-
});
|
|
8227
|
-
}
|
|
8228
|
-
|
|
8229
7914
|
// src/modules/runners/components/RunOnSection.tsx
|
|
8230
7915
|
/**
|
|
8231
7916
|
* NDV Settings → "Run on": FluidGrids Cloud (default) or one of the
|
|
@@ -9330,45 +9015,6 @@ function useStoreNodes(options = {}) {
|
|
|
9330
9015
|
};
|
|
9331
9016
|
}
|
|
9332
9017
|
|
|
9333
|
-
// src/hooks/store/useInstalledNodes.ts
|
|
9334
|
-
/**
|
|
9335
|
-
* Hook to get installed nodes for the current workspace
|
|
9336
|
-
*
|
|
9337
|
-
* Fetches installations from the Store service to show "Already Installed" status
|
|
9338
|
-
* in the Store popup and display installed count.
|
|
9339
|
-
*/
|
|
9340
|
-
function useInstalledNodes() {
|
|
9341
|
-
const client = useGlobalStoreClient();
|
|
9342
|
-
const workspaceId = useContext(FluidGridsContext)?.workspaceId ?? getFluidGridsConfig().workspaceId;
|
|
9343
|
-
const { data, loading, error, refetch } = useQuery(GetMyNodeInstallationsDocument, {
|
|
9344
|
-
client,
|
|
9345
|
-
variables: {
|
|
9346
|
-
workspaceId: workspaceId || "",
|
|
9347
|
-
limit: 100,
|
|
9348
|
-
offset: 0
|
|
9349
|
-
},
|
|
9350
|
-
skip: !workspaceId,
|
|
9351
|
-
fetchPolicy: "cache-and-network"
|
|
9352
|
-
});
|
|
9353
|
-
const installations = data?.myStoreAppInstallations?.items || [];
|
|
9354
|
-
const activeInstallations = installations.filter((item) => item.status === "ACTIVE");
|
|
9355
|
-
return {
|
|
9356
|
-
installations,
|
|
9357
|
-
installedAppIds: new Set(activeInstallations.map((item) => item.productId)),
|
|
9358
|
-
installationsMap: new Map(activeInstallations.map((item) => [item.productId, {
|
|
9359
|
-
installedVersion: item.version ?? void 0,
|
|
9360
|
-
installationId: item.id,
|
|
9361
|
-
installedAt: item.installedAt
|
|
9362
|
-
}])),
|
|
9363
|
-
deprecatedInstallations: activeInstallations.filter((item) => item.productStatus === "UNLISTED"),
|
|
9364
|
-
installedNodeTypes: /* @__PURE__ */ new Set(),
|
|
9365
|
-
totalCount: data?.myStoreAppInstallations?.total || 0,
|
|
9366
|
-
loading,
|
|
9367
|
-
error,
|
|
9368
|
-
refetch
|
|
9369
|
-
};
|
|
9370
|
-
}
|
|
9371
|
-
|
|
9372
9018
|
// src/hooks/store/useRefreshAvailableNodes.ts
|
|
9373
9019
|
/**
|
|
9374
9020
|
* Hook to refresh the available nodes list after installing/uninstalling nodes.
|
|
@@ -9395,138 +9041,6 @@ function useRefreshAvailableNodes() {
|
|
|
9395
9041
|
}, []) };
|
|
9396
9042
|
}
|
|
9397
9043
|
|
|
9398
|
-
// src/builder/components/flow-builder/components/store/store-node-availability.ts
|
|
9399
|
-
/**
|
|
9400
|
-
* Operations a user may actually pick for a node type: adapter operations
|
|
9401
|
-
* (supplyModel/supplyTool/…) and `internal` ones (the AI Agent's planStep) are
|
|
9402
|
-
* executor plumbing, chosen automatically, and must not count as user choices.
|
|
9403
|
-
*/
|
|
9404
|
-
function userSelectableOperationCount(nodeType) {
|
|
9405
|
-
if (!nodeType) return 0;
|
|
9406
|
-
return (NODE_CONFIGS.find((c) => c.type === nodeType)?.operations ?? []).filter((op) => !op.isAdapterOperation && !op.internal).length;
|
|
9407
|
-
}
|
|
9408
|
-
/**
|
|
9409
|
-
* Resolve the workflow node type for a store node.
|
|
9410
|
-
*
|
|
9411
|
-
* Installed nodes can fall back to the historical slug-based type when older
|
|
9412
|
-
* manifests did not yet include `manifest.nodeType`.
|
|
9413
|
-
*/
|
|
9414
|
-
function getStoreNodeType(node, isInstalled) {
|
|
9415
|
-
return node.manifest?.nodeType ?? (isInstalled ? `store/${node.slug}` : void 0);
|
|
9416
|
-
}
|
|
9417
|
-
/**
|
|
9418
|
-
* A node is considered prebuilt when it is already present in the workflow
|
|
9419
|
-
* registry/available nodes list even though the workspace does not have a store
|
|
9420
|
-
* installation record for it.
|
|
9421
|
-
*/
|
|
9422
|
-
function getStoreNodeAvailability(node, isInstalled, options = {}) {
|
|
9423
|
-
const nodeType = getStoreNodeType(node, isInstalled);
|
|
9424
|
-
if (isNodeTypeHiddenFromPalette(nodeType, options.durableFunctionsEnabled)) return {
|
|
9425
|
-
nodeType,
|
|
9426
|
-
isPrebuilt: false,
|
|
9427
|
-
isDraggable: false,
|
|
9428
|
-
isHidden: true
|
|
9429
|
-
};
|
|
9430
|
-
const isInternalBuiltin = !!nodeType && nodeType.startsWith("internal:");
|
|
9431
|
-
const isRegistryBacked = !!nodeType && AVAILABLE_NODES.some((availableNode) => availableNode.type === nodeType && (availableNode.publishType === "BUILTIN" || availableNode.publishType === void 0));
|
|
9432
|
-
const isPrebuilt = !isInstalled && (isInternalBuiltin || isRegistryBacked);
|
|
9433
|
-
return {
|
|
9434
|
-
nodeType,
|
|
9435
|
-
isPrebuilt,
|
|
9436
|
-
isDraggable: !!nodeType && (isInstalled || isPrebuilt) && userSelectableOperationCount(nodeType) <= 1,
|
|
9437
|
-
isHidden: false
|
|
9438
|
-
};
|
|
9439
|
-
}
|
|
9440
|
-
/**
|
|
9441
|
-
* The icon a store listing shows.
|
|
9442
|
-
*
|
|
9443
|
-
* Listings for the built-in nodes are seeded without an icon, so every one of
|
|
9444
|
-
* them rendered as a letter tile in the Node Store while the canvas showed the
|
|
9445
|
-
* node's real icon (BOFF-7651). The registry already carries that icon from
|
|
9446
|
-
* the node's manifest, so a listing without its own falls back to it. A
|
|
9447
|
-
* listing's own icon always wins; `null` means "no icon anywhere", and the
|
|
9448
|
-
* caller draws its letter tile.
|
|
9449
|
-
*/
|
|
9450
|
-
function getStoreNodeIcon(listingIcon, nodeType) {
|
|
9451
|
-
if (listingIcon) return listingIcon;
|
|
9452
|
-
if (!nodeType) return null;
|
|
9453
|
-
return AVAILABLE_NODES.find((availableNode) => availableNode.type === nodeType)?.icon || null;
|
|
9454
|
-
}
|
|
9455
|
-
/**
|
|
9456
|
-
* The version to show for a store node: the node's own version from its
|
|
9457
|
-
* manifest (`nodeVersion`, then `identity.version`), falling back to the
|
|
9458
|
-
* product's `manifestVersion`. The product field is usually the manifest
|
|
9459
|
-
* *format* version ("1.0"), so showing it read as "v1.0" for every node
|
|
9460
|
-
* (BOFF-11456).
|
|
9461
|
-
*/
|
|
9462
|
-
function storeNodeDisplayVersion(manifest, manifestVersion) {
|
|
9463
|
-
const record = typeof manifest === "string" ? parseManifest(manifest) : manifest;
|
|
9464
|
-
if (typeof record === "object" && record !== null) {
|
|
9465
|
-
const { nodeVersion, identity } = record;
|
|
9466
|
-
if (typeof nodeVersion === "string" && nodeVersion.trim()) return nodeVersion.trim();
|
|
9467
|
-
const identityVersion = identity?.version;
|
|
9468
|
-
if (typeof identityVersion === "string" && identityVersion.trim()) return identityVersion.trim();
|
|
9469
|
-
}
|
|
9470
|
-
return manifestVersion?.trim() || void 0;
|
|
9471
|
-
}
|
|
9472
|
-
const MAJOR_MINOR_ONLY = /^\d+\.\d+$/;
|
|
9473
|
-
/**
|
|
9474
|
-
* Node versions are full semver: node images are tagged `major.minor.patch`
|
|
9475
|
-
* and the sandbox picks one by the exact version a workflow node pins. A bare
|
|
9476
|
-
* `major.minor` (the manifest format version, "1.0") therefore means
|
|
9477
|
-
* `major.minor.0` (BOFF-11496). Other values come back trimmed.
|
|
9478
|
-
*/
|
|
9479
|
-
function canonicalNodeVersion(version) {
|
|
9480
|
-
const trimmed = version.trim();
|
|
9481
|
-
return MAJOR_MINOR_ONLY.test(trimmed) ? `${trimmed}.0` : trimmed;
|
|
9482
|
-
}
|
|
9483
|
-
/**
|
|
9484
|
-
* The version a store node installs as: its own version from the manifest,
|
|
9485
|
-
* canonical, `1.0.0` when the product names none. Installs used to send the
|
|
9486
|
-
* product's `manifestVersion` ("1.0"), which no node image carries
|
|
9487
|
-
* (BOFF-11496).
|
|
9488
|
-
*/
|
|
9489
|
-
function storeNodeInstallVersion(manifest, manifestVersion) {
|
|
9490
|
-
return canonicalNodeVersion(storeNodeDisplayVersion(manifest, manifestVersion) ?? "1.0.0");
|
|
9491
|
-
}
|
|
9492
|
-
/**
|
|
9493
|
-
* Whether an installed store node has a newer version: the installation's
|
|
9494
|
-
* recorded version against the node's own current version, both canonical, so
|
|
9495
|
-
* an installation recorded as "1.0" is not "outdated" against 1.0.0
|
|
9496
|
-
* (BOFF-11496).
|
|
9497
|
-
*/
|
|
9498
|
-
function isStoreNodeUpdateAvailable(installedVersion, manifest, manifestVersion) {
|
|
9499
|
-
if (!installedVersion?.trim()) return false;
|
|
9500
|
-
const latest = storeNodeDisplayVersion(manifest, manifestVersion);
|
|
9501
|
-
if (!latest) return false;
|
|
9502
|
-
return canonicalNodeVersion(installedVersion) !== canonicalNodeVersion(latest);
|
|
9503
|
-
}
|
|
9504
|
-
function parseManifest(text) {
|
|
9505
|
-
try {
|
|
9506
|
-
return JSON.parse(text);
|
|
9507
|
-
} catch {
|
|
9508
|
-
return;
|
|
9509
|
-
}
|
|
9510
|
-
}
|
|
9511
|
-
/**
|
|
9512
|
-
* Whether a store product must be left out of the builder's store listings
|
|
9513
|
-
* because it is a gated node (durable-function or run-control) whose flag is
|
|
9514
|
-
* off.
|
|
9515
|
-
* Fails closed when `durableFunctionsEnabled` is omitted.
|
|
9516
|
-
*/
|
|
9517
|
-
function isStoreNodeHiddenFromPalette(node, durableFunctionsEnabled = false) {
|
|
9518
|
-
return isNodeTypeHiddenFromPalette(getStoreNodeType(node, false), durableFunctionsEnabled);
|
|
9519
|
-
}
|
|
9520
|
-
/**
|
|
9521
|
-
* Drops the store products that must stay hidden from the builder. Returns
|
|
9522
|
-
* the input array itself when nothing is hidden.
|
|
9523
|
-
*/
|
|
9524
|
-
function filterStoreNodesForPalette(nodes, durableFunctionsEnabled = false) {
|
|
9525
|
-
const hidden = (node) => isStoreNodeHiddenFromPalette(node, durableFunctionsEnabled);
|
|
9526
|
-
if (!nodes.some(hidden)) return nodes;
|
|
9527
|
-
return nodes.filter((node) => !hidden(node));
|
|
9528
|
-
}
|
|
9529
|
-
|
|
9530
9044
|
// src/hooks/store/useInstallNode.ts
|
|
9531
9045
|
/** The configured JSON scalar contract for an installation manifest is an object. */
|
|
9532
9046
|
function isManifestRecord(value) {
|
|
@@ -9731,995 +9245,116 @@ const useUninstallNode = () => {
|
|
|
9731
9245
|
const useNodeUsageInWorkflows = () => {
|
|
9732
9246
|
return {
|
|
9733
9247
|
checkNodeUsage: useCallback(async (workspaceId, nodeType, nodeVersion = "1.0.0", options) => {
|
|
9734
|
-
const client = getFluidGridsClient();
|
|
9735
|
-
try {
|
|
9736
|
-
const result = await client.query({
|
|
9737
|
-
query: NodeUsageStatusDocument,
|
|
9738
|
-
variables: {
|
|
9739
|
-
nodeType,
|
|
9740
|
-
nodeVersion,
|
|
9741
|
-
allVersions: options?.allVersions ?? false
|
|
9742
|
-
},
|
|
9743
|
-
fetchPolicy: "network-only"
|
|
9744
|
-
});
|
|
9745
|
-
if (result.error) throw result.error;
|
|
9746
|
-
const data = result.data?.nodeUsageStatus;
|
|
9747
|
-
if (!data) return {
|
|
9748
|
-
isUsed: false,
|
|
9749
|
-
workflows: []
|
|
9750
|
-
};
|
|
9751
|
-
const workflows = data.workflows.map((wf) => ({
|
|
9752
|
-
id: wf.workflowId,
|
|
9753
|
-
name: wf.workflowName,
|
|
9754
|
-
version: wf.workflowKey
|
|
9755
|
-
}));
|
|
9756
|
-
return {
|
|
9757
|
-
isUsed: data.isInUse,
|
|
9758
|
-
workflows
|
|
9759
|
-
};
|
|
9760
|
-
} catch (err) {
|
|
9761
|
-
const error = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to check node usage");
|
|
9762
|
-
console.error("[useNodeUsageInWorkflows] Error:", error);
|
|
9763
|
-
throw error;
|
|
9764
|
-
}
|
|
9765
|
-
}, []),
|
|
9766
|
-
loading: false,
|
|
9767
|
-
error: null
|
|
9768
|
-
};
|
|
9769
|
-
};
|
|
9770
|
-
|
|
9771
|
-
// src/hooks/store/useRemoveNodeFromWorkflows.ts
|
|
9772
|
-
/**
|
|
9773
|
-
* Hook for removing a node from workflows before uninstallation
|
|
9774
|
-
* Calls the wspace-workflow-svc GraphQL mutation
|
|
9775
|
-
*/
|
|
9776
|
-
const useRemoveNodeFromWorkflows = () => {
|
|
9777
|
-
return { removeNodeFromWorkflows: useCallback(async (nodeType, nodeVersion, workflowIds, options) => {
|
|
9778
|
-
const client = getFluidGridsClient();
|
|
9779
|
-
try {
|
|
9780
|
-
const result = throwIfResultError(await client.mutate({
|
|
9781
|
-
mutation: RemoveNodeFromWorkflowsDocument,
|
|
9782
|
-
variables: {
|
|
9783
|
-
nodeType,
|
|
9784
|
-
nodeVersion,
|
|
9785
|
-
workflowIds,
|
|
9786
|
-
allVersions: options?.allVersions ?? false
|
|
9787
|
-
}
|
|
9788
|
-
}));
|
|
9789
|
-
if (result.error) throw new Error(result.error.message);
|
|
9790
|
-
const data = result.data?.removeNodeFromWorkflows;
|
|
9791
|
-
if (!data) throw new Error("No data returned from removeNodeFromWorkflows");
|
|
9792
|
-
return {
|
|
9793
|
-
success: data.success,
|
|
9794
|
-
removedFromWorkflows: data.removedFromWorkflows,
|
|
9795
|
-
deactivatedWorkflows: data.deactivatedWorkflows,
|
|
9796
|
-
cleanedUsageEntries: data.cleanedUsageEntries
|
|
9797
|
-
};
|
|
9798
|
-
} catch (err) {
|
|
9799
|
-
const error = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to remove node from workflows");
|
|
9800
|
-
console.error("[useRemoveNodeFromWorkflows] Error:", error);
|
|
9801
|
-
throw error;
|
|
9802
|
-
}
|
|
9803
|
-
}, []) };
|
|
9804
|
-
};
|
|
9805
|
-
|
|
9806
|
-
// src/hooks/store/useWorkflowTemplates.ts
|
|
9807
|
-
/**
|
|
9808
|
-
* Hook to browse workflow templates from the Store
|
|
9809
|
-
*
|
|
9810
|
-
* Fetches WORKFLOW type products from the global store service
|
|
9811
|
-
* with rich preview data (screenshots, videos, etc.)
|
|
9812
|
-
*
|
|
9813
|
-
* @param searchTerm - Optional search query
|
|
9814
|
-
* @param limit - Number of templates per page (default: 20)
|
|
9815
|
-
* @param enabled - Whether to enable the query (default: true). Set to false to skip the query.
|
|
9816
|
-
* @param category - Optional store category to filter by (exact match)
|
|
9817
|
-
*/
|
|
9818
|
-
function useWorkflowTemplates(searchTerm, limit = 20, enabled = true, category) {
|
|
9819
|
-
const client = useGlobalStoreClient();
|
|
9820
|
-
const context = useContext(FluidGridsContext);
|
|
9821
|
-
const workspaceId = context?.workspaceId ?? getFluidGridsConfig().workspaceId;
|
|
9822
|
-
const organizationId = context?.organizationId;
|
|
9823
|
-
const contextHeaders = useMemo(() => {
|
|
9824
|
-
const headers = {};
|
|
9825
|
-
if (organizationId) headers["x-organization-id"] = organizationId;
|
|
9826
|
-
if (workspaceId) headers["x-workspace-id"] = workspaceId;
|
|
9827
|
-
return headers;
|
|
9828
|
-
}, [organizationId, workspaceId]);
|
|
9829
|
-
const buildVariables = (offset) => ({ input: {
|
|
9830
|
-
filter: {
|
|
9831
|
-
type: "WORKFLOW",
|
|
9832
|
-
status: "PUBLISHED",
|
|
9833
|
-
search: searchTerm,
|
|
9834
|
-
...category ? { category } : {}
|
|
9835
|
-
},
|
|
9836
|
-
pagination: {
|
|
9837
|
-
limit,
|
|
9838
|
-
offset
|
|
9839
|
-
},
|
|
9840
|
-
sortBy: searchTerm ? "RELEVANCE" : "NEWEST"
|
|
9841
|
-
} });
|
|
9842
|
-
const { data, loading, error, fetchMore, refetch } = useQuery(BrowseWorkflowTemplatesDocument, {
|
|
9843
|
-
client,
|
|
9844
|
-
variables: buildVariables(0),
|
|
9845
|
-
fetchPolicy: "cache-and-network",
|
|
9846
|
-
skip: !enabled,
|
|
9847
|
-
context: Object.keys(contextHeaders).length > 0 ? { headers: contextHeaders } : void 0
|
|
9848
|
-
});
|
|
9849
|
-
const templates = data?.browseStore?.products || [];
|
|
9850
|
-
const totalCount = data?.browseStore?.totalCount || 0;
|
|
9851
|
-
const hasMore = data?.browseStore?.hasMore || false;
|
|
9852
|
-
const loadMore = () => {
|
|
9853
|
-
if (!hasMore || loading) return;
|
|
9854
|
-
return fetchMore({
|
|
9855
|
-
variables: buildVariables(templates.length),
|
|
9856
|
-
context: Object.keys(contextHeaders).length > 0 ? { headers: contextHeaders } : void 0,
|
|
9857
|
-
updateQuery: (prev, { fetchMoreResult }) => {
|
|
9858
|
-
if (!fetchMoreResult) return prev;
|
|
9859
|
-
return { browseStore: {
|
|
9860
|
-
...fetchMoreResult.browseStore,
|
|
9861
|
-
products: [...prev.browseStore?.products || [], ...fetchMoreResult.browseStore?.products || []]
|
|
9862
|
-
} };
|
|
9863
|
-
}
|
|
9864
|
-
});
|
|
9865
|
-
};
|
|
9866
|
-
return {
|
|
9867
|
-
templates,
|
|
9868
|
-
totalCount,
|
|
9869
|
-
hasMore,
|
|
9870
|
-
loading,
|
|
9871
|
-
error,
|
|
9872
|
-
loadMore,
|
|
9873
|
-
refetch
|
|
9874
|
-
};
|
|
9875
|
-
}
|
|
9876
|
-
|
|
9877
|
-
// src/hooks/store/useCreateOrder.ts
|
|
9878
|
-
/**
|
|
9879
|
-
* Hook to create a store order for paid products
|
|
9880
|
-
* This creates a PENDING order that will be completed after payment
|
|
9881
|
-
*/
|
|
9882
|
-
function useCreateOrder() {
|
|
9883
|
-
const globalClient = useGlobalStoreClient();
|
|
9884
|
-
const [creating, setCreating] = useState(false);
|
|
9885
|
-
const organizationId = useContext(FluidGridsContext)?.organizationId;
|
|
9886
|
-
const [createOrderMutation] = useMutation(CreateStoreOrderDocument, {
|
|
9887
|
-
client: globalClient,
|
|
9888
|
-
context: organizationId ? { headers: { "x-organization-id": organizationId } } : void 0
|
|
9889
|
-
});
|
|
9890
|
-
return {
|
|
9891
|
-
createOrder: useCallback(async (input) => {
|
|
9892
|
-
setCreating(true);
|
|
9893
|
-
try {
|
|
9894
|
-
console.log("[useCreateOrder] Creating order with input:", input);
|
|
9895
|
-
const result = await createOrderMutation({ variables: { input } });
|
|
9896
|
-
if (!result.data?.createStoreOrder?.id) throw new Error("Failed to create order - no order ID returned");
|
|
9897
|
-
const order = result.data.createStoreOrder;
|
|
9898
|
-
console.log("[useCreateOrder] Order created successfully:", order);
|
|
9899
|
-
setCreating(false);
|
|
9900
|
-
return {
|
|
9901
|
-
order,
|
|
9902
|
-
success: true
|
|
9903
|
-
};
|
|
9904
|
-
} catch (error) {
|
|
9905
|
-
console.error("[useCreateOrder] Failed to create order:", error);
|
|
9906
|
-
const errorMessage = error instanceof Error ? error.message : "Failed to create order";
|
|
9907
|
-
setCreating(false);
|
|
9908
|
-
return {
|
|
9909
|
-
success: false,
|
|
9910
|
-
error: errorMessage
|
|
9911
|
-
};
|
|
9912
|
-
}
|
|
9913
|
-
}, [createOrderMutation]),
|
|
9914
|
-
creating
|
|
9915
|
-
};
|
|
9916
|
-
}
|
|
9917
|
-
|
|
9918
|
-
// src/hooks/store/useTemplateDetail.ts
|
|
9919
|
-
const GET_WORKFLOW_TEMPLATE_DETAIL = gql`
|
|
9920
|
-
query GetWorkflowTemplateDetail($id: ID!) {
|
|
9921
|
-
product(id: $id) {
|
|
9922
|
-
id
|
|
9923
|
-
name
|
|
9924
|
-
slug
|
|
9925
|
-
type
|
|
9926
|
-
description
|
|
9927
|
-
longDescription
|
|
9928
|
-
icon
|
|
9929
|
-
screenshots
|
|
9930
|
-
videoUrls
|
|
9931
|
-
rating
|
|
9932
|
-
reviewCount
|
|
9933
|
-
downloads
|
|
9934
|
-
viewCount
|
|
9935
|
-
price
|
|
9936
|
-
pricingModel
|
|
9937
|
-
currency
|
|
9938
|
-
featured
|
|
9939
|
-
content
|
|
9940
|
-
manifest
|
|
9941
|
-
manifestVersion
|
|
9942
|
-
authorName
|
|
9943
|
-
license
|
|
9944
|
-
category
|
|
9945
|
-
tags
|
|
9946
|
-
compatibleProducts
|
|
9947
|
-
publisher {
|
|
9948
|
-
id
|
|
9949
|
-
name
|
|
9950
|
-
email
|
|
9951
|
-
websiteUrl
|
|
9952
|
-
logoUrl
|
|
9953
|
-
isVerified
|
|
9954
|
-
}
|
|
9955
|
-
createdAt
|
|
9956
|
-
updatedAt
|
|
9957
|
-
publishedAt
|
|
9958
|
-
}
|
|
9959
|
-
}
|
|
9960
|
-
`;
|
|
9961
|
-
/**
|
|
9962
|
-
* Hook to lazily fetch full details of a workflow template from the store.
|
|
9963
|
-
*
|
|
9964
|
-
* Unlike `useWorkflowTemplates` which fetches browse-level metadata,
|
|
9965
|
-
* this hook fetches the full product detail including the `content` field
|
|
9966
|
-
* which contains the workflow definitionJson.
|
|
9967
|
-
*
|
|
9968
|
-
* Uses a manual query (not useQuery) to only fetch when explicitly requested.
|
|
9969
|
-
*/
|
|
9970
|
-
function useTemplateDetail() {
|
|
9971
|
-
const client = useGlobalStoreClient();
|
|
9972
|
-
const [loading, setLoading] = useState(false);
|
|
9973
|
-
const [error, setError] = useState(null);
|
|
9974
|
-
const context = useContext(FluidGridsContext);
|
|
9975
|
-
const workspaceId = context?.workspaceId ?? getFluidGridsConfig().workspaceId;
|
|
9976
|
-
const organizationId = context?.organizationId;
|
|
9977
|
-
const contextHeaders = useMemo(() => {
|
|
9978
|
-
const headers = {};
|
|
9979
|
-
if (organizationId) headers["x-organization-id"] = organizationId;
|
|
9980
|
-
if (workspaceId) headers["x-workspace-id"] = workspaceId;
|
|
9981
|
-
return headers;
|
|
9982
|
-
}, [organizationId, workspaceId]);
|
|
9983
|
-
return {
|
|
9984
|
-
fetchTemplateDetail: useCallback(async (templateId) => {
|
|
9985
|
-
setLoading(true);
|
|
9986
|
-
setError(null);
|
|
9987
|
-
try {
|
|
9988
|
-
const result = await client.query({
|
|
9989
|
-
query: GET_WORKFLOW_TEMPLATE_DETAIL,
|
|
9990
|
-
variables: { id: templateId },
|
|
9991
|
-
fetchPolicy: "network-only",
|
|
9992
|
-
context: Object.keys(contextHeaders).length > 0 ? { headers: contextHeaders } : void 0
|
|
9993
|
-
});
|
|
9994
|
-
if (result.error) throw result.error;
|
|
9995
|
-
return result.data?.product ?? null;
|
|
9996
|
-
} catch (err) {
|
|
9997
|
-
const fetchError = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to fetch template detail");
|
|
9998
|
-
setError(fetchError);
|
|
9999
|
-
console.error("[useTemplateDetail] Error:", err);
|
|
10000
|
-
return null;
|
|
10001
|
-
} finally {
|
|
10002
|
-
setLoading(false);
|
|
10003
|
-
}
|
|
10004
|
-
}, [client, contextHeaders]),
|
|
10005
|
-
loading,
|
|
10006
|
-
error
|
|
10007
|
-
};
|
|
10008
|
-
}
|
|
10009
|
-
|
|
10010
|
-
// src/utils/template-input-applicator.ts
|
|
10011
|
-
/**
|
|
10012
|
-
* Template Input Applicator
|
|
10013
|
-
*
|
|
10014
|
-
* Walks a workflow definitionJson and replaces ${variableName} placeholders
|
|
10015
|
-
* with user-provided values. Used during template import to apply
|
|
10016
|
-
* user-configurable inputs before creating the workflow.
|
|
10017
|
-
*/
|
|
10018
|
-
/**
|
|
10019
|
-
* Deep-clone an object using structured clone.
|
|
10020
|
-
* Falls back to JSON parse/stringify for environments without structuredClone.
|
|
10021
|
-
*/
|
|
10022
|
-
function deepClone(obj) {
|
|
10023
|
-
if (typeof structuredClone === "function") return structuredClone(obj);
|
|
10024
|
-
return JSON.parse(JSON.stringify(obj));
|
|
10025
|
-
}
|
|
10026
|
-
/**
|
|
10027
|
-
* Apply template input values to a definitionJson by replacing
|
|
10028
|
-
* ${variableName} placeholders in string values throughout the object tree.
|
|
10029
|
-
*
|
|
10030
|
-
* @param definitionJson - The workflow definition JSON to process
|
|
10031
|
-
* @param inputs - Map of variable names to user-provided values
|
|
10032
|
-
* @returns A new definitionJson with placeholders replaced
|
|
10033
|
-
*
|
|
10034
|
-
* @example
|
|
10035
|
-
* ```ts
|
|
10036
|
-
* const definition = {
|
|
10037
|
-
* nodes: [{
|
|
10038
|
-
* data: { url: "${slack_webhook_url}", message: "Hello ${name}" }
|
|
10039
|
-
* }]
|
|
10040
|
-
* };
|
|
10041
|
-
* const result = applyTemplateInputs(definition, {
|
|
10042
|
-
* slack_webhook_url: "https://hooks.slack.com/...",
|
|
10043
|
-
* name: "World"
|
|
10044
|
-
* });
|
|
10045
|
-
* // result.nodes[0].data.url === "https://hooks.slack.com/..."
|
|
10046
|
-
* // result.nodes[0].data.message === "Hello World"
|
|
10047
|
-
* ```
|
|
10048
|
-
*/
|
|
10049
|
-
function applyTemplateInputs(definitionJson, inputs) {
|
|
10050
|
-
if (!inputs || Object.keys(inputs).length === 0) return definitionJson;
|
|
10051
|
-
return walkAndReplace(deepClone(definitionJson), inputs);
|
|
10052
|
-
}
|
|
10053
|
-
/**
|
|
10054
|
-
* Recursively walk an object/array and replace ${variableName} placeholders
|
|
10055
|
-
* in string values.
|
|
10056
|
-
*/
|
|
10057
|
-
function walkAndReplace(obj, inputs) {
|
|
10058
|
-
if (typeof obj === "string") return replaceVariables(obj, inputs);
|
|
10059
|
-
if (Array.isArray(obj)) return obj.map((item) => walkAndReplace(item, inputs));
|
|
10060
|
-
if (obj !== null && typeof obj === "object") {
|
|
10061
|
-
const result = {};
|
|
10062
|
-
for (const [key, value] of Object.entries(obj)) result[key] = walkAndReplace(value, inputs);
|
|
10063
|
-
return result;
|
|
10064
|
-
}
|
|
10065
|
-
return obj;
|
|
10066
|
-
}
|
|
10067
|
-
/**
|
|
10068
|
-
* Replace ${variableName} patterns in a string with provided values.
|
|
10069
|
-
*
|
|
10070
|
-
* Supports:
|
|
10071
|
-
* - ${variableName} — standard replacement
|
|
10072
|
-
* - ${variableName || 'default'} — fallback to default if variable not provided
|
|
10073
|
-
*/
|
|
10074
|
-
function replaceVariables(str, inputs) {
|
|
10075
|
-
return str.replace(/\$\{([^}]+)\}/g, (_match, expression) => {
|
|
10076
|
-
const trimmed = expression.trim();
|
|
10077
|
-
const fallbackMatch = trimmed.match(/^(\w+)\s*\|\|\s*['"]?(.+?)['"]?$/);
|
|
10078
|
-
if (fallbackMatch) {
|
|
10079
|
-
const varName = fallbackMatch[1];
|
|
10080
|
-
const fallback = fallbackMatch[2];
|
|
10081
|
-
if (varName in inputs) return String(inputs[varName]);
|
|
10082
|
-
return fallback;
|
|
10083
|
-
}
|
|
10084
|
-
if (trimmed in inputs) return String(inputs[trimmed]);
|
|
10085
|
-
return _match;
|
|
10086
|
-
});
|
|
10087
|
-
}
|
|
10088
|
-
|
|
10089
|
-
// src/utils/template-import-flow.ts
|
|
10090
|
-
function isRecord$2(value) {
|
|
10091
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10092
|
-
}
|
|
10093
|
-
function graphOf(value) {
|
|
10094
|
-
if (!Array.isArray(value.nodes)) return null;
|
|
10095
|
-
return {
|
|
10096
|
-
nodes: value.nodes,
|
|
10097
|
-
edges: Array.isArray(value.edges) ? value.edges : [],
|
|
10098
|
-
name: typeof value.name === "string" ? value.name : void 0
|
|
10099
|
-
};
|
|
10100
|
-
}
|
|
10101
|
-
/**
|
|
10102
|
-
* The canvas of a WORKFLOW product that keeps it in its manifest instead of
|
|
10103
|
-
* `content.definitionJson`: either a flat `{ nodes, edges }` manifest, or the
|
|
10104
|
-
* Store Listing JSON editor's `manifest.jsonContent` string. Null when the
|
|
10105
|
-
* manifest holds neither.
|
|
10106
|
-
*/
|
|
10107
|
-
function manifestTemplateGraph(manifest) {
|
|
10108
|
-
if (!isRecord$2(manifest)) return null;
|
|
10109
|
-
const direct = graphOf(manifest);
|
|
10110
|
-
if (direct) return direct;
|
|
10111
|
-
if (typeof manifest.jsonContent !== "string") return null;
|
|
10112
|
-
try {
|
|
10113
|
-
const parsed = JSON.parse(manifest.jsonContent);
|
|
10114
|
-
return isRecord$2(parsed) ? graphOf(parsed) : null;
|
|
10115
|
-
} catch {
|
|
10116
|
-
return null;
|
|
10117
|
-
}
|
|
10118
|
-
}
|
|
10119
|
-
/**
|
|
10120
|
-
* The template content a product carries: `content` when it holds a
|
|
10121
|
-
* `definitionJson`, otherwise a content built from the manifest's canvas,
|
|
10122
|
-
* with its required nodes worked out by `requiredNodeTypesOf` (a failed
|
|
10123
|
-
* lookup leaves them empty, which skips the node check). Null when the
|
|
10124
|
-
* product has no workflow at all.
|
|
10125
|
-
*/
|
|
10126
|
-
async function resolveTemplateContent(product, requiredNodeTypesOf) {
|
|
10127
|
-
const content = product.content;
|
|
10128
|
-
if (content?.definitionJson) return content;
|
|
10129
|
-
const graph = manifestTemplateGraph(product.manifest);
|
|
10130
|
-
if (!graph) return null;
|
|
10131
|
-
let requiredNodeTypes = [];
|
|
10132
|
-
try {
|
|
10133
|
-
requiredNodeTypes = await requiredNodeTypesOf(graph.nodes);
|
|
10134
|
-
} catch {
|
|
10135
|
-
requiredNodeTypes = [];
|
|
10136
|
-
}
|
|
10137
|
-
return {
|
|
10138
|
-
definitionJson: {
|
|
10139
|
-
nodes: graph.nodes,
|
|
10140
|
-
edges: graph.edges
|
|
10141
|
-
},
|
|
10142
|
-
requiredNodeTypes,
|
|
10143
|
-
defaultWorkflowName: graph.name ?? product.name
|
|
10144
|
-
};
|
|
10145
|
-
}
|
|
10146
|
-
/**
|
|
10147
|
-
* The canvas to create a copy from. Some templates store a whole API
|
|
10148
|
-
* response as their definition (`{ data: { workflowDefinition: {
|
|
10149
|
-
* definitionJson } } }`); the canvas is the inner `definitionJson`.
|
|
10150
|
-
*/
|
|
10151
|
-
function unwrapTemplateDefinition(definitionJson) {
|
|
10152
|
-
const data = definitionJson.data;
|
|
10153
|
-
if (!isRecord$2(data)) return definitionJson;
|
|
10154
|
-
const nested = isRecord$2(data.workflowDefinition) ? data.workflowDefinition.definitionJson : void 0;
|
|
10155
|
-
return isRecord$2(nested) ? nested : definitionJson;
|
|
10156
|
-
}
|
|
10157
|
-
/**
|
|
10158
|
-
* Whether "Use this template" must stop at the configuration dialog before
|
|
10159
|
-
* creating the copy: the template asks for inputs or connections, or one of
|
|
10160
|
-
* its nodes is not installed (free to install, paid, or not offered by the
|
|
10161
|
-
* store at all).
|
|
10162
|
-
*/
|
|
10163
|
-
function needsTemplateConfiguration(content, nodeCheck) {
|
|
10164
|
-
return (content.requiredInputs?.length ?? 0) > 0 || (content.requiredCredentialTypes?.length ?? 0) > 0 || nodeCheck.missing.length > 0 || nodeCheck.missingPaid.length > 0 || (nodeCheck.unavailable?.length ?? 0) > 0;
|
|
10165
|
-
}
|
|
10166
|
-
/** The free nodes to install before the copy is created. */
|
|
10167
|
-
function nodesToInstall(nodeCheck) {
|
|
10168
|
-
return (nodeCheck?.missing ?? []).filter((node) => !!node.storeProductId);
|
|
10169
|
-
}
|
|
10170
|
-
/**
|
|
10171
|
-
* Install nodes one at a time, in order, reporting progress before each.
|
|
10172
|
-
* One at a time on purpose: the dialog shows "installing i of n", and every
|
|
10173
|
-
* install writes the same workspace's installation records in the store.
|
|
10174
|
-
* A failed install is reported to `onFailure` and the rest still run, so the
|
|
10175
|
-
* copy can be created; the user installs the node later from the Node Store.
|
|
10176
|
-
* Resolves to the node types that installed.
|
|
10177
|
-
*/
|
|
10178
|
-
async function installNodesInOrder(nodes, install, onProgress, onFailure) {
|
|
10179
|
-
const installed = [];
|
|
10180
|
-
await nodes.reduce((previous, node, index) => previous.then(async () => {
|
|
10181
|
-
onProgress({
|
|
10182
|
-
current: index + 1,
|
|
10183
|
-
total: nodes.length,
|
|
10184
|
-
nodeName: node.displayName || node.nodeType
|
|
10185
|
-
});
|
|
10186
|
-
try {
|
|
10187
|
-
await install(node);
|
|
10188
|
-
installed.push(node.nodeType);
|
|
10189
|
-
} catch (error) {
|
|
10190
|
-
onFailure(node, error);
|
|
10191
|
-
}
|
|
10192
|
-
}), Promise.resolve());
|
|
10193
|
-
return installed;
|
|
10194
|
-
}
|
|
10195
|
-
|
|
10196
|
-
// src/hooks/store/useImportWorkflowTemplate.ts
|
|
10197
|
-
const IMPORT_WORKFLOW_TEMPLATE = gql`
|
|
10198
|
-
mutation ImportWorkflowTemplate($input: ImportWorkflowTemplateInput!) {
|
|
10199
|
-
importWorkflowTemplate(input: $input) {
|
|
10200
|
-
success
|
|
10201
|
-
message
|
|
10202
|
-
workflow {
|
|
10203
|
-
id
|
|
10204
|
-
workflowKey
|
|
10205
|
-
version
|
|
10206
|
-
name
|
|
10207
|
-
description
|
|
10208
|
-
status
|
|
10209
|
-
definitionJson
|
|
10210
|
-
tags
|
|
10211
|
-
category
|
|
10212
|
-
icon
|
|
10213
|
-
createdAt
|
|
10214
|
-
}
|
|
10215
|
-
}
|
|
10216
|
-
}
|
|
10217
|
-
`;
|
|
10218
|
-
const RECORD_DOWNLOAD = gql`
|
|
10219
|
-
mutation RecordDownload($productId: String!) {
|
|
10220
|
-
recordDownload(productId: $productId) {
|
|
10221
|
-
id
|
|
10222
|
-
downloadedAt
|
|
10223
|
-
}
|
|
10224
|
-
}
|
|
10225
|
-
`;
|
|
10226
|
-
const WORKFLOW_DEFINITIONS = gql`
|
|
10227
|
-
query WorkflowDefinitions($input: ListWorkflowDefinitionsInput!) {
|
|
10228
|
-
workflowDefinitions(input: $input) {
|
|
10229
|
-
items {
|
|
10230
|
-
id
|
|
10231
|
-
workflowKey
|
|
10232
|
-
name
|
|
10233
|
-
version
|
|
10234
|
-
status
|
|
10235
|
-
}
|
|
10236
|
-
total
|
|
10237
|
-
hasMore
|
|
10238
|
-
}
|
|
10239
|
-
}
|
|
10240
|
-
`;
|
|
10241
|
-
/**
|
|
10242
|
-
* Hook to orchestrate importing a workflow template from the store.
|
|
10243
|
-
*
|
|
10244
|
-
* It fetches template detail, parses content and creates the copy:
|
|
10245
|
-
* 1. Fetch full template detail from global store (lazy load content)
|
|
10246
|
-
* 2. Apply user-provided template inputs to definitionJson
|
|
10247
|
-
* 3. Create workflow via importWorkflowTemplate mutation (workspace gateway)
|
|
10248
|
-
* 4. Record the download and the installation in the store
|
|
10249
|
-
*
|
|
10250
|
-
* The node check, the configuration dialog and installing missing nodes are
|
|
10251
|
-
* done by `useTemplateImportFlow`, which every "Use this template" button
|
|
10252
|
-
* goes through (BOFF-11153).
|
|
10253
|
-
*/
|
|
10254
|
-
function useImportWorkflowTemplate() {
|
|
10255
|
-
const [importing, setImporting] = useState(false);
|
|
10256
|
-
const [error, setError] = useState(null);
|
|
10257
|
-
const { fetchTemplateDetail, loading: fetchingDetail } = useTemplateDetail();
|
|
10258
|
-
const globalClient = useGlobalStoreClient();
|
|
10259
|
-
const context = useContext(FluidGridsContext);
|
|
10260
|
-
const workspaceId = context?.workspaceId ?? getFluidGridsConfig().workspaceId;
|
|
10261
|
-
const bus = useEventBus();
|
|
10262
|
-
const getTemplateDetail = useCallback(async (templateId) => {
|
|
10263
|
-
return fetchTemplateDetail(templateId);
|
|
10264
|
-
}, [fetchTemplateDetail]);
|
|
10265
|
-
/**
|
|
10266
|
-
* Parse the template content from a StoreProduct.
|
|
10267
|
-
* Returns null if the product doesn't have valid workflow template content.
|
|
10268
|
-
*/
|
|
10269
|
-
const parseTemplateContent = useCallback((template) => {
|
|
10270
|
-
try {
|
|
10271
|
-
const content = template.content;
|
|
10272
|
-
if (!content?.definitionJson) {
|
|
10273
|
-
console.warn("[useImportWorkflowTemplate] Template has no definitionJson in content:", template.id);
|
|
10274
|
-
return null;
|
|
10275
|
-
}
|
|
10276
|
-
return content;
|
|
10277
|
-
} catch {
|
|
10278
|
-
console.error("[useImportWorkflowTemplate] Failed to parse template content:", template.id);
|
|
10279
|
-
return null;
|
|
10280
|
-
}
|
|
10281
|
-
}, []);
|
|
10282
|
-
return {
|
|
10283
|
-
/** Fetch full template content from store (lazy load) */
|
|
10284
|
-
getTemplateDetail,
|
|
10285
|
-
/** Parse WorkflowTemplateContent from a StoreProduct */
|
|
10286
|
-
parseTemplateContent,
|
|
10287
|
-
/** Full import: apply inputs -> create workflow -> record analytics */
|
|
10288
|
-
importTemplate: useCallback(async (template, options) => {
|
|
10289
|
-
setImporting(true);
|
|
10290
|
-
setError(null);
|
|
10291
|
-
try {
|
|
10292
|
-
const content = options?.content ?? parseTemplateContent(template);
|
|
10293
|
-
if (!content) throw new Error("Template does not contain valid workflow definition data");
|
|
10294
|
-
let definitionJson = unwrapTemplateDefinition(deepClone(content.definitionJson));
|
|
10295
|
-
if (options?.templateInputs) definitionJson = applyTemplateInputs(definitionJson, options.templateInputs);
|
|
10296
|
-
const config = getFluidGridsConfig();
|
|
10297
|
-
const importContext = {
|
|
10298
|
-
...options?.context || {},
|
|
10299
|
-
...config.productId ? { productId: config.productId } : {},
|
|
10300
|
-
...config.productName ? { productName: config.productName } : {}
|
|
10301
|
-
};
|
|
10302
|
-
const client = getFluidGridsClient();
|
|
10303
|
-
const importResult = throwIfResultError(await client.mutate({
|
|
10304
|
-
mutation: IMPORT_WORKFLOW_TEMPLATE,
|
|
10305
|
-
variables: { input: {
|
|
10306
|
-
definitionJson,
|
|
10307
|
-
name: options?.name || content.defaultWorkflowName || template.name,
|
|
10308
|
-
description: options?.description || template.description,
|
|
10309
|
-
sourceTemplateId: template.id,
|
|
10310
|
-
sourceTemplateName: template.name,
|
|
10311
|
-
tags: content.defaultTags || template.tags || [],
|
|
10312
|
-
category: content.defaultCategory || template.category || void 0,
|
|
10313
|
-
icon: template.icon || void 0,
|
|
10314
|
-
projectId: options?.projectId,
|
|
10315
|
-
context: importContext
|
|
10316
|
-
} },
|
|
10317
|
-
refetchQueries: [WORKFLOW_DEFINITIONS]
|
|
10318
|
-
})).data?.importWorkflowTemplate;
|
|
10319
|
-
if (!importResult?.success || !importResult.workflow) throw new Error(importResult?.message || "Failed to create workflow from template");
|
|
10320
|
-
try {
|
|
10321
|
-
throwIfResultError(await globalClient.mutate({
|
|
10322
|
-
mutation: RECORD_DOWNLOAD,
|
|
10323
|
-
variables: { productId: template.id }
|
|
10324
|
-
}));
|
|
10325
|
-
} catch {
|
|
10326
|
-
console.warn("[useImportWorkflowTemplate] Failed to record store download");
|
|
10327
|
-
}
|
|
10328
|
-
try {
|
|
10329
|
-
throwIfResultError(await globalClient.mutate({
|
|
10330
|
-
mutation: InstallWorkflowNodeDocument,
|
|
10331
|
-
variables: { input: {
|
|
10332
|
-
productId: template.id,
|
|
10333
|
-
workspaceId,
|
|
10334
|
-
organizationId: context?.tenantId ?? getFluidGridsConfig().tenantId,
|
|
10335
|
-
version: template.manifestVersion || "1.0.0",
|
|
10336
|
-
manifest: template.manifest,
|
|
10337
|
-
metadata: {
|
|
10338
|
-
type: "WORKFLOW",
|
|
10339
|
-
sourceTemplateId: template.id,
|
|
10340
|
-
sourceTemplateName: template.name,
|
|
10341
|
-
importedWorkflowKey: importResult.workflow.workflowKey,
|
|
10342
|
-
importedWorkflowVersion: importResult.workflow.version
|
|
10343
|
-
}
|
|
10344
|
-
} }
|
|
10345
|
-
}));
|
|
10346
|
-
} catch {
|
|
10347
|
-
console.warn("[useImportWorkflowTemplate] Failed to record app installation");
|
|
10348
|
-
}
|
|
10349
|
-
emitWorkflowCreated(bus, {
|
|
10350
|
-
workspaceId,
|
|
10351
|
-
workflowId: importResult.workflow.id,
|
|
10352
|
-
source: "template",
|
|
10353
|
-
definition: definitionJson
|
|
10354
|
-
});
|
|
10355
|
-
emitTemplateUsed(bus, {
|
|
10356
|
-
workspaceId,
|
|
10357
|
-
templateId: template.id,
|
|
10358
|
-
category: content.defaultCategory || template.category
|
|
10359
|
-
});
|
|
10360
|
-
return {
|
|
10361
|
-
success: true,
|
|
10362
|
-
workflow: importResult.workflow,
|
|
10363
|
-
message: importResult.message || "Template imported successfully",
|
|
10364
|
-
installedNodes: [],
|
|
10365
|
-
requiredCredentials: content.requiredCredentialTypes || []
|
|
10366
|
-
};
|
|
10367
|
-
} catch (err) {
|
|
10368
|
-
const importError = err instanceof Error ? err : /* @__PURE__ */ new Error("Template import failed");
|
|
10369
|
-
setError(importError);
|
|
10370
|
-
console.error("[useImportWorkflowTemplate] Import failed:", err);
|
|
10371
|
-
return {
|
|
10372
|
-
success: false,
|
|
10373
|
-
workflow: null,
|
|
10374
|
-
message: importError.message,
|
|
10375
|
-
installedNodes: [],
|
|
10376
|
-
requiredCredentials: [],
|
|
10377
|
-
error: importError
|
|
10378
|
-
};
|
|
10379
|
-
} finally {
|
|
10380
|
-
setImporting(false);
|
|
10381
|
-
}
|
|
10382
|
-
}, [
|
|
10383
|
-
parseTemplateContent,
|
|
10384
|
-
globalClient,
|
|
10385
|
-
workspaceId,
|
|
10386
|
-
context?.tenantId,
|
|
10387
|
-
bus
|
|
10388
|
-
]),
|
|
10389
|
-
/** Whether we're fetching template detail */
|
|
10390
|
-
fetchingDetail,
|
|
10391
|
-
/** Whether we're importing */
|
|
10392
|
-
importing,
|
|
10393
|
-
/** Last error */
|
|
10394
|
-
error
|
|
10395
|
-
};
|
|
10396
|
-
}
|
|
10397
|
-
|
|
10398
|
-
// src/utils/workflow-node-check.ts
|
|
10399
|
-
/**
|
|
10400
|
-
* Workflow node dependency checking.
|
|
10401
|
-
*
|
|
10402
|
-
* Shared logic for detecting node types used by a workflow that are not
|
|
10403
|
-
* installed in the current workspace, resolving them to store products, and
|
|
10404
|
-
* classifying them as installed / missing (free installable) / missingPaid /
|
|
10405
|
-
* unavailable (not offered by the store, so nothing can install them).
|
|
10406
|
-
*
|
|
10407
|
-
* Used by:
|
|
10408
|
-
* - Template import flow (desktop-sidebar-fragment)
|
|
10409
|
-
* - Shared-workflow copy flow (WorkflowItem)
|
|
10410
|
-
* - AI-assistant-created workflow open flow (WorkflowItem)
|
|
10411
|
-
*/
|
|
10412
|
-
/**
|
|
10413
|
-
* Canvas-only node types: they live on the React Flow canvas but are never
|
|
10414
|
-
* executed, never registered in NODE_CONFIGS and never published to the store.
|
|
10415
|
-
*
|
|
10416
|
-
* They must be excluded from the dependency check. A sticky note reaching it
|
|
10417
|
-
* was reported as `sticky-note v1.0 — Not installed` in the Missing Nodes
|
|
10418
|
-
* dialog on every open of an affected workflow, and because no store product
|
|
10419
|
-
* backs it the Install action could only ever be a no-op (BOFF-7267).
|
|
10420
|
-
*
|
|
10421
|
-
* Kept as a literal set rather than importing STICKY_NOTE_TYPE from the
|
|
10422
|
-
* builder so this module stays free of builder-component imports.
|
|
10423
|
-
*/
|
|
10424
|
-
const CANVAS_ONLY_NODE_TYPES = /* @__PURE__ */ new Set(["sticky-note"]);
|
|
10425
|
-
/**
|
|
10426
|
-
* Resolve the set of unique executable node types used by a workflow.
|
|
10427
|
-
*
|
|
10428
|
-
* Canvas-only annotations (see CANVAS_ONLY_NODE_TYPES) are skipped: they carry
|
|
10429
|
-
* no dependency, so including them produces a permanently unsatisfiable
|
|
10430
|
-
* "missing node".
|
|
10431
|
-
*/
|
|
10432
|
-
function getWorkflowNodeTypes(nodes) {
|
|
10433
|
-
return [...new Set(nodes.map((n) => n.type).filter((t) => Boolean(t)).filter((t) => !CANVAS_ONLY_NODE_TYPES.has(t)))];
|
|
10434
|
-
}
|
|
10435
|
-
/**
|
|
10436
|
-
* Node types per store lookup: global-store-svc answers for at most 100 at a
|
|
10437
|
-
* time. A workflow misses far fewer; more are asked in parallel requests.
|
|
10438
|
-
*/
|
|
10439
|
-
const STORE_NODE_LOOKUP_BATCH_SIZE = 100;
|
|
10440
|
-
/** The store context of FluidGrids node products, as the Node Store browses them. */
|
|
10441
|
-
const STORE_NODE_PRODUCT_CONTEXT = "fluidgrids";
|
|
10442
|
-
/**
|
|
10443
|
-
* The store products that offer any of `nodeTypes`, at most
|
|
10444
|
-
* `STORE_NODE_LOOKUP_BATCH_SIZE` types per request, the requests in parallel.
|
|
10445
|
-
* Blank and repeated types are asked once.
|
|
10446
|
-
*/
|
|
10447
|
-
async function fetchStoreNodeProducts(nodeTypes, fetchProducts) {
|
|
10448
|
-
const wanted = [...new Set(nodeTypes.filter(Boolean))];
|
|
10449
|
-
const batches = [];
|
|
10450
|
-
for (let i = 0; i < wanted.length; i += 100) batches.push(wanted.slice(i, i + 100));
|
|
10451
|
-
return (await Promise.all(batches.map(fetchProducts))).flatMap((products) => [...products ?? []]);
|
|
10452
|
-
}
|
|
10453
|
-
function publishedAtMs(product) {
|
|
10454
|
-
const ms = product.publishedAt ? Date.parse(product.publishedAt) : NaN;
|
|
10455
|
-
return Number.isNaN(ms) ? Number.NEGATIVE_INFINITY : ms;
|
|
10456
|
-
}
|
|
10457
|
-
/**
|
|
10458
|
-
* Order two store products that offer the same nodeType; a negative result
|
|
10459
|
-
* means `a` is the better match.
|
|
10460
|
-
*
|
|
10461
|
-
* 1. A product linked to a devportal application (`productId` set) wins. Only
|
|
10462
|
-
* those have an installable published version; the unlinked rows are
|
|
10463
|
-
* orphaned seed rows such as `app-gmail-reader-001` that cannot install.
|
|
10464
|
-
* 2. Then more downloads, 3. then the most recently published, 4. then the
|
|
10465
|
-
* product id, so the choice never depends on the order the store returned.
|
|
10466
|
-
*/
|
|
10467
|
-
function compareStoreNodeCandidates(a, b) {
|
|
10468
|
-
const aLinked = Boolean(a.productId);
|
|
10469
|
-
if (aLinked !== Boolean(b.productId)) return aLinked ? -1 : 1;
|
|
10470
|
-
const downloads = (b.downloads ?? 0) - (a.downloads ?? 0);
|
|
10471
|
-
if (downloads !== 0) return downloads;
|
|
10472
|
-
const aPublished = publishedAtMs(a);
|
|
10473
|
-
const bPublished = publishedAtMs(b);
|
|
10474
|
-
if (aPublished !== bPublished) return aPublished > bPublished ? -1 : 1;
|
|
10475
|
-
if (a.id === b.id) return 0;
|
|
10476
|
-
return a.id < b.id ? -1 : 1;
|
|
10477
|
-
}
|
|
10478
|
-
/**
|
|
10479
|
-
* Map nodeType -> best store product (see compareStoreNodeCandidates).
|
|
10480
|
-
* Pure function, independent of the order of `products`.
|
|
10481
|
-
*/
|
|
10482
|
-
function buildStoreNodeTypeMap(products) {
|
|
10483
|
-
const nodeTypeToProduct = /* @__PURE__ */ new Map();
|
|
10484
|
-
for (const product of products) {
|
|
10485
|
-
const nodeType = product.manifest?.nodeType;
|
|
10486
|
-
if (typeof nodeType !== "string" || !nodeType) continue;
|
|
10487
|
-
const current = nodeTypeToProduct.get(nodeType);
|
|
10488
|
-
if (!current || compareStoreNodeCandidates(product, current) < 0) nodeTypeToProduct.set(nodeType, product);
|
|
10489
|
-
}
|
|
10490
|
-
return nodeTypeToProduct;
|
|
10491
|
-
}
|
|
10492
|
-
/**
|
|
10493
|
-
* Ask the global store which products offer `nodeTypes` and map each type to the
|
|
10494
|
-
* best one (see compareStoreNodeCandidates). Only the asked types travel: the
|
|
10495
|
-
* check used to page through the whole node catalog for them (BOFF-11754).
|
|
10496
|
-
*/
|
|
10497
|
-
async function fetchStoreNodeTypeMap(nodeTypes) {
|
|
10498
|
-
const storeClient = getGlobalStoreClient();
|
|
10499
|
-
if (!storeClient || nodeTypes.length === 0) return /* @__PURE__ */ new Map();
|
|
10500
|
-
return buildStoreNodeTypeMap(await fetchStoreNodeProducts(nodeTypes, async (batch) => {
|
|
10501
|
-
return (await storeClient.query({
|
|
10502
|
-
query: StoreNodeProductsByTypeDocument,
|
|
10503
|
-
variables: {
|
|
10504
|
-
nodeTypes: batch,
|
|
10505
|
-
compatibleWith: STORE_NODE_PRODUCT_CONTEXT
|
|
10506
|
-
},
|
|
10507
|
-
fetchPolicy: "cache-first"
|
|
10508
|
-
})).data?.storeNodeProductsByType;
|
|
10509
|
-
}));
|
|
10510
|
-
}
|
|
10511
|
-
/**
|
|
10512
|
-
* One required-node entry of a template (BOFF-11496): a type the registry
|
|
10513
|
-
* already has is BUILTIN at its registry version; any other type is a STORE
|
|
10514
|
-
* node at the node's own version from its store product. Versions are
|
|
10515
|
-
* canonical, never the product's manifest format version ("1.0").
|
|
10516
|
-
*/
|
|
10517
|
-
function requiredNodeEntry(type, registered, product) {
|
|
10518
|
-
if (registered) return {
|
|
10519
|
-
nodeType: type,
|
|
10520
|
-
nodeVersion: canonicalNodeVersion(registered.version || "1.0.0"),
|
|
10521
|
-
publishType: "BUILTIN",
|
|
10522
|
-
displayName: type
|
|
10523
|
-
};
|
|
10524
|
-
return {
|
|
10525
|
-
nodeType: type,
|
|
10526
|
-
nodeVersion: storeNodeInstallVersion(product?.manifest, product?.manifestVersion),
|
|
10527
|
-
publishType: "STORE",
|
|
10528
|
-
storeProductId: product?.id,
|
|
10529
|
-
displayName: product?.name ?? type,
|
|
10530
|
-
requiresPurchase: product !== void 0 && product.pricingModel !== "FREE"
|
|
10531
|
-
};
|
|
10532
|
-
}
|
|
10533
|
-
/**
|
|
10534
|
-
* Build the list of required node types for a workflow.
|
|
10535
|
-
*
|
|
10536
|
-
* - BUILTIN / already-installed types are marked BUILTIN.
|
|
10537
|
-
* - Missing types are looked up in the store; if found, they are marked STORE
|
|
10538
|
-
* with storeProductId and requiresPurchase.
|
|
10539
|
-
* - Missing types not found in the store are still reported as STORE without
|
|
10540
|
-
* a storeProductId so the UI can show "not installed".
|
|
10541
|
-
*/
|
|
10542
|
-
async function buildRequiredNodeTypes(nodes) {
|
|
10543
|
-
const workflowNodeTypes = getWorkflowNodeTypes(nodes);
|
|
10544
|
-
if (workflowNodeTypes.length === 0) return [];
|
|
10545
|
-
const isRegistered = (type) => NODE_CONFIGS.some((c) => c.type === type);
|
|
10546
|
-
await ensureRegistryNodeTypesLoaded(workflowNodeTypes);
|
|
10547
|
-
const missingTypes = workflowNodeTypes.filter((type) => !isRegistered(type));
|
|
10548
|
-
let nodeTypeToProduct = /* @__PURE__ */ new Map();
|
|
10549
|
-
if (missingTypes.length > 0) [, nodeTypeToProduct] = await Promise.all([ensureInstalledNodeTypesLoaded(missingTypes), fetchStoreNodeTypeMap(missingTypes)]);
|
|
10550
|
-
return workflowNodeTypes.map((type) => {
|
|
10551
|
-
const registered = NODE_CONFIGS.find((c) => c.type === type);
|
|
10552
|
-
return requiredNodeEntry(type, registered ? { version: registered.defaultData.version } : void 0, nodeTypeToProduct.get(type));
|
|
10553
|
-
});
|
|
10554
|
-
}
|
|
10555
|
-
/**
|
|
10556
|
-
* Classify required nodes against the set of installed store app IDs.
|
|
10557
|
-
*
|
|
10558
|
-
* Pure function — easy to unit test.
|
|
10559
|
-
*/
|
|
10560
|
-
function buildNodeCheck(requiredNodeTypes, installedAppIds) {
|
|
10561
|
-
const installed = requiredNodeTypes.filter((n) => n.publishType === "BUILTIN" || n.storeProductId && installedAppIds.has(n.storeProductId));
|
|
10562
|
-
const notInstalled = requiredNodeTypes.filter((n) => n.publishType !== "BUILTIN" && (!n.storeProductId || !installedAppIds.has(n.storeProductId)));
|
|
10563
|
-
const unavailable = notInstalled.filter((n) => !n.storeProductId);
|
|
10564
|
-
const installable = notInstalled.filter((n) => !!n.storeProductId);
|
|
10565
|
-
const missing = installable.filter((n) => !n.requiresPurchase);
|
|
10566
|
-
const missingPaid = installable.filter((n) => !!n.requiresPurchase);
|
|
10567
|
-
return {
|
|
10568
|
-
installed,
|
|
10569
|
-
missing,
|
|
10570
|
-
missingPaid,
|
|
10571
|
-
unavailable,
|
|
10572
|
-
canImport: missingPaid.length === 0
|
|
9248
|
+
const client = getFluidGridsClient();
|
|
9249
|
+
try {
|
|
9250
|
+
const result = await client.query({
|
|
9251
|
+
query: NodeUsageStatusDocument,
|
|
9252
|
+
variables: {
|
|
9253
|
+
nodeType,
|
|
9254
|
+
nodeVersion,
|
|
9255
|
+
allVersions: options?.allVersions ?? false
|
|
9256
|
+
},
|
|
9257
|
+
fetchPolicy: "network-only"
|
|
9258
|
+
});
|
|
9259
|
+
if (result.error) throw result.error;
|
|
9260
|
+
const data = result.data?.nodeUsageStatus;
|
|
9261
|
+
if (!data) return {
|
|
9262
|
+
isUsed: false,
|
|
9263
|
+
workflows: []
|
|
9264
|
+
};
|
|
9265
|
+
const workflows = data.workflows.map((wf) => ({
|
|
9266
|
+
id: wf.workflowId,
|
|
9267
|
+
name: wf.workflowName,
|
|
9268
|
+
version: wf.workflowKey
|
|
9269
|
+
}));
|
|
9270
|
+
return {
|
|
9271
|
+
isUsed: data.isInUse,
|
|
9272
|
+
workflows
|
|
9273
|
+
};
|
|
9274
|
+
} catch (err) {
|
|
9275
|
+
const error = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to check node usage");
|
|
9276
|
+
console.error("[useNodeUsageInWorkflows] Error:", error);
|
|
9277
|
+
throw error;
|
|
9278
|
+
}
|
|
9279
|
+
}, []),
|
|
9280
|
+
loading: false,
|
|
9281
|
+
error: null
|
|
10573
9282
|
};
|
|
10574
|
-
}
|
|
9283
|
+
};
|
|
10575
9284
|
|
|
10576
|
-
// src/hooks/store/
|
|
10577
|
-
const LazyTemplateConfigurationModal = lazy(() => import("./TemplateConfigurationModal-Bs_V-E4u.js").then((m) => ({ default: m.TemplateConfigurationModal })));
|
|
9285
|
+
// src/hooks/store/useRemoveNodeFromWorkflows.ts
|
|
10578
9286
|
/**
|
|
10579
|
-
*
|
|
10580
|
-
*
|
|
10581
|
-
* (BOFF-11153). Before this, only the builder panel checked the template's
|
|
10582
|
-
* nodes and asked for its inputs; the two pages created the copy directly.
|
|
10583
|
-
*
|
|
10584
|
-
* 1. Load the template's full detail and resolve its content.
|
|
10585
|
-
* 2. Check its required nodes against the workspace's installed nodes.
|
|
10586
|
-
* 3. When it asks for inputs or connections, or a node is not installed,
|
|
10587
|
-
* show the configuration dialog; otherwise go straight on.
|
|
10588
|
-
* 4. Install the missing free nodes, create the copy with the user's inputs,
|
|
10589
|
-
* and open it (`onImported`).
|
|
9287
|
+
* Hook for removing a node from workflows before uninstallation
|
|
9288
|
+
* Calls the wspace-workflow-svc GraphQL mutation
|
|
10590
9289
|
*/
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
const { installedAppIds } = useInstalledNodes();
|
|
10595
|
-
const { getTemplateDetail, importTemplate } = useImportWorkflowTemplate();
|
|
10596
|
-
const [pending, setPending] = useState(null);
|
|
10597
|
-
const [busy, setBusy] = useState(false);
|
|
10598
|
-
const [installProgress, setInstallProgress] = useState(null);
|
|
10599
|
-
const installMissingNodes = useCallback(async (nodeCheck) => {
|
|
10600
|
-
const nodes = nodesToInstall(nodeCheck);
|
|
10601
|
-
const storeClient = getGlobalStoreClient();
|
|
10602
|
-
if (nodes.length === 0 || !storeClient) return;
|
|
10603
|
-
const config = getFluidGridsConfig();
|
|
10604
|
-
await installNodesInOrder(nodes, async (node) => {
|
|
10605
|
-
throwIfResultError(await storeClient.mutate({
|
|
10606
|
-
mutation: InstallWorkflowNodeDocument,
|
|
10607
|
-
variables: { input: {
|
|
10608
|
-
productId: node.storeProductId ?? "",
|
|
10609
|
-
workspaceId: config.workspaceId,
|
|
10610
|
-
organizationId: config.tenantId,
|
|
10611
|
-
version: node.nodeVersion,
|
|
10612
|
-
manifest: null
|
|
10613
|
-
} }
|
|
10614
|
-
}));
|
|
10615
|
-
emitNodeInstalled(bus, {
|
|
10616
|
-
workspaceId,
|
|
10617
|
-
nodeType: node.nodeType,
|
|
10618
|
-
source: "store"
|
|
10619
|
-
});
|
|
10620
|
-
}, setInstallProgress, (node, err) => console.warn(`[useTemplateImportFlow] Failed to install node ${node.nodeType}:`, err));
|
|
10621
|
-
setInstallProgress(null);
|
|
10622
|
-
}, [bus, workspaceId]);
|
|
10623
|
-
const createCopy = useCallback(async (template, content, nodeCheck, overrides) => {
|
|
10624
|
-
setBusy(true);
|
|
10625
|
-
try {
|
|
10626
|
-
await installMissingNodes(nodeCheck);
|
|
10627
|
-
toast.loading(tr("fluidgrids.builder.desktopSidebar.creatingWorkflowToast", "Creating workflow..."), { id: "template-import" });
|
|
10628
|
-
const result = await importTemplate(template, {
|
|
10629
|
-
content,
|
|
10630
|
-
name: overrides?.name,
|
|
10631
|
-
templateInputs: overrides?.templateInputs
|
|
10632
|
-
});
|
|
10633
|
-
toast.dismiss("template-import");
|
|
10634
|
-
if (result.success && result.workflow) {
|
|
10635
|
-
toast.success(tr("fluidgrids.builder.desktopSidebar.workflowCreatedToast", "Workflow created from template!"), { description: `"${result.workflow.name}" is ready to edit` });
|
|
10636
|
-
if ((content.requiredCredentialTypes?.length ?? 0) > 0) toast.info(tr("fluidgrids.builder.desktopSidebar.credentialsNeededToast", "Credentials needed"), {
|
|
10637
|
-
description: `Configure ${content.requiredCredentialTypes?.join(", ")} in the Credentials page`,
|
|
10638
|
-
duration: 6e3
|
|
10639
|
-
});
|
|
10640
|
-
onImported(result.workflow);
|
|
10641
|
-
return true;
|
|
10642
|
-
}
|
|
10643
|
-
if (onQuotaExhausted && isQuotaExhaustedFailure(result.error?.cause ?? result.error)) {
|
|
10644
|
-
onQuotaExhausted();
|
|
10645
|
-
return false;
|
|
10646
|
-
}
|
|
10647
|
-
toast.error(tr("fluidgrids.builder.desktopSidebar.importTemplateErrorToast", "Failed to import template"), { description: result.message || tr("fluidgrids.builder.desktopSidebar.importTemplateErrorFallback", "Unknown error during import") });
|
|
10648
|
-
return false;
|
|
10649
|
-
} catch (err) {
|
|
10650
|
-
toast.dismiss("template-import");
|
|
10651
|
-
toast.error(tr("fluidgrids.builder.desktopSidebar.importFailedToast", "Import failed"), { description: err instanceof Error ? err.message : tr("fluidgrids.builder.desktopSidebar.importFailedFallback", "Unknown error") });
|
|
10652
|
-
return false;
|
|
10653
|
-
} finally {
|
|
10654
|
-
setInstallProgress(null);
|
|
10655
|
-
setBusy(false);
|
|
10656
|
-
}
|
|
10657
|
-
}, [
|
|
10658
|
-
importTemplate,
|
|
10659
|
-
installMissingNodes,
|
|
10660
|
-
onImported,
|
|
10661
|
-
onQuotaExhausted,
|
|
10662
|
-
tr
|
|
10663
|
-
]);
|
|
10664
|
-
const start = useCallback(async (template) => {
|
|
10665
|
-
setBusy(true);
|
|
10666
|
-
toast.loading(tr("fluidgrids.builder.desktopSidebar.loadingTemplateToast", "Loading template..."), { id: "template-load" });
|
|
9290
|
+
const useRemoveNodeFromWorkflows = () => {
|
|
9291
|
+
return { removeNodeFromWorkflows: useCallback(async (nodeType, nodeVersion, workflowIds, options) => {
|
|
9292
|
+
const client = getFluidGridsClient();
|
|
10667
9293
|
try {
|
|
10668
|
-
const
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
return;
|
|
10687
|
-
}
|
|
10688
|
-
await createCopy(full, content, nodeCheck);
|
|
9294
|
+
const result = throwIfResultError(await client.mutate({
|
|
9295
|
+
mutation: RemoveNodeFromWorkflowsDocument,
|
|
9296
|
+
variables: {
|
|
9297
|
+
nodeType,
|
|
9298
|
+
nodeVersion,
|
|
9299
|
+
workflowIds,
|
|
9300
|
+
allVersions: options?.allVersions ?? false
|
|
9301
|
+
}
|
|
9302
|
+
}));
|
|
9303
|
+
if (result.error) throw new Error(result.error.message);
|
|
9304
|
+
const data = result.data?.removeNodeFromWorkflows;
|
|
9305
|
+
if (!data) throw new Error("No data returned from removeNodeFromWorkflows");
|
|
9306
|
+
return {
|
|
9307
|
+
success: data.success,
|
|
9308
|
+
removedFromWorkflows: data.removedFromWorkflows,
|
|
9309
|
+
deactivatedWorkflows: data.deactivatedWorkflows,
|
|
9310
|
+
cleanedUsageEntries: data.cleanedUsageEntries
|
|
9311
|
+
};
|
|
10689
9312
|
} catch (err) {
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
setBusy(false);
|
|
9313
|
+
const error = err instanceof Error ? err : /* @__PURE__ */ new Error("Failed to remove node from workflows");
|
|
9314
|
+
console.error("[useRemoveNodeFromWorkflows] Error:", error);
|
|
9315
|
+
throw error;
|
|
10694
9316
|
}
|
|
10695
|
-
}, [
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
9317
|
+
}, []) };
|
|
9318
|
+
};
|
|
9319
|
+
|
|
9320
|
+
// src/hooks/store/useCreateOrder.ts
|
|
9321
|
+
/**
|
|
9322
|
+
* Hook to create a store order for paid products
|
|
9323
|
+
* This creates a PENDING order that will be completed after payment
|
|
9324
|
+
*/
|
|
9325
|
+
function useCreateOrder() {
|
|
9326
|
+
const globalClient = useGlobalStoreClient();
|
|
9327
|
+
const [creating, setCreating] = useState(false);
|
|
9328
|
+
const organizationId = useContext(FluidGridsContext)?.organizationId;
|
|
9329
|
+
const [createOrderMutation] = useMutation(CreateStoreOrderDocument, {
|
|
9330
|
+
client: globalClient,
|
|
9331
|
+
context: organizationId ? { headers: { "x-organization-id": organizationId } } : void 0
|
|
9332
|
+
});
|
|
10705
9333
|
return {
|
|
10706
|
-
|
|
10707
|
-
|
|
10708
|
-
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
9334
|
+
createOrder: useCallback(async (input) => {
|
|
9335
|
+
setCreating(true);
|
|
9336
|
+
try {
|
|
9337
|
+
console.log("[useCreateOrder] Creating order with input:", input);
|
|
9338
|
+
const result = await createOrderMutation({ variables: { input } });
|
|
9339
|
+
if (!result.data?.createStoreOrder?.id) throw new Error("Failed to create order - no order ID returned");
|
|
9340
|
+
const order = result.data.createStoreOrder;
|
|
9341
|
+
console.log("[useCreateOrder] Order created successfully:", order);
|
|
9342
|
+
setCreating(false);
|
|
9343
|
+
return {
|
|
9344
|
+
order,
|
|
9345
|
+
success: true
|
|
9346
|
+
};
|
|
9347
|
+
} catch (error) {
|
|
9348
|
+
console.error("[useCreateOrder] Failed to create order:", error);
|
|
9349
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to create order";
|
|
9350
|
+
setCreating(false);
|
|
9351
|
+
return {
|
|
9352
|
+
success: false,
|
|
9353
|
+
error: errorMessage
|
|
9354
|
+
};
|
|
9355
|
+
}
|
|
9356
|
+
}, [createOrderMutation]),
|
|
9357
|
+
creating
|
|
10723
9358
|
};
|
|
10724
9359
|
}
|
|
10725
9360
|
|
|
@@ -12404,302 +11039,6 @@ function SidebarButtonItem({ children, className, active, ...props }) {
|
|
|
12404
11039
|
});
|
|
12405
11040
|
}
|
|
12406
11041
|
|
|
12407
|
-
// src/ui/dialog.tsx
|
|
12408
|
-
function Dialog$1({ ...props }) {
|
|
12409
|
-
return /* @__PURE__ */ jsx(DialogPrimitive.Root, {
|
|
12410
|
-
"data-slot": "dialog",
|
|
12411
|
-
...props
|
|
12412
|
-
});
|
|
12413
|
-
}
|
|
12414
|
-
function DialogTrigger$1({ ...props }) {
|
|
12415
|
-
return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, {
|
|
12416
|
-
"data-slot": "dialog-trigger",
|
|
12417
|
-
...props
|
|
12418
|
-
});
|
|
12419
|
-
}
|
|
12420
|
-
function DialogPortal({ ...props }) {
|
|
12421
|
-
return /* @__PURE__ */ jsx(DialogPrimitive.Portal, {
|
|
12422
|
-
"data-slot": "dialog-portal",
|
|
12423
|
-
...props
|
|
12424
|
-
});
|
|
12425
|
-
}
|
|
12426
|
-
function DialogClose({ ...props }) {
|
|
12427
|
-
return /* @__PURE__ */ jsx(DialogPrimitive.Close, {
|
|
12428
|
-
"data-slot": "dialog-close",
|
|
12429
|
-
...props
|
|
12430
|
-
});
|
|
12431
|
-
}
|
|
12432
|
-
function DialogOverlay({ className, ...props }) {
|
|
12433
|
-
return /* @__PURE__ */ jsx(DialogPrimitive.Overlay, {
|
|
12434
|
-
"data-slot": "dialog-overlay",
|
|
12435
|
-
className: cn$1("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-bg-overlay", className),
|
|
12436
|
-
...props
|
|
12437
|
-
});
|
|
12438
|
-
}
|
|
12439
|
-
function DialogContent$1({ className, children, showCloseButton = true, size = "default", ...props }) {
|
|
12440
|
-
const tr = useTr();
|
|
12441
|
-
return /* @__PURE__ */ jsxs(DialogPortal, {
|
|
12442
|
-
"data-slot": "dialog-portal",
|
|
12443
|
-
children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
|
|
12444
|
-
"data-slot": "dialog-content",
|
|
12445
|
-
className: cn$1("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none", {
|
|
12446
|
-
default: "sm:max-w-lg",
|
|
12447
|
-
lg: "sm:max-w-3xl",
|
|
12448
|
-
xl: "sm:max-w-[1400px] w-[90vw]",
|
|
12449
|
-
full: "sm:max-w-[95vw] w-[95vw]"
|
|
12450
|
-
}[size], className),
|
|
12451
|
-
...props,
|
|
12452
|
-
children: [children, showCloseButton && /* @__PURE__ */ jsxs(DialogPrimitive.Close, {
|
|
12453
|
-
"data-slot": "dialog-close",
|
|
12454
|
-
className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none",
|
|
12455
|
-
children: [/* @__PURE__ */ jsx(XIcon, { className: "size-4" }), /* @__PURE__ */ jsx("span", {
|
|
12456
|
-
className: "sr-only",
|
|
12457
|
-
children: tr("fluidgrids.ui.dialog.closeLabel", "Close")
|
|
12458
|
-
})]
|
|
12459
|
-
})]
|
|
12460
|
-
})]
|
|
12461
|
-
});
|
|
12462
|
-
}
|
|
12463
|
-
function DialogHeader$1({ className, ...props }) {
|
|
12464
|
-
return /* @__PURE__ */ jsx("div", {
|
|
12465
|
-
"data-slot": "dialog-header",
|
|
12466
|
-
className: cn$1("flex flex-col gap-2 text-center sm:text-left", className),
|
|
12467
|
-
...props
|
|
12468
|
-
});
|
|
12469
|
-
}
|
|
12470
|
-
function DialogFooter$1({ className, ...props }) {
|
|
12471
|
-
return /* @__PURE__ */ jsx("div", {
|
|
12472
|
-
"data-slot": "dialog-footer",
|
|
12473
|
-
className: cn$1("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
|
|
12474
|
-
...props
|
|
12475
|
-
});
|
|
12476
|
-
}
|
|
12477
|
-
function DialogTitle$1({ className, ...props }) {
|
|
12478
|
-
return /* @__PURE__ */ jsx(DialogPrimitive.Title, {
|
|
12479
|
-
"data-slot": "dialog-title",
|
|
12480
|
-
className: cn$1("text-lg leading-none font-semibold", className),
|
|
12481
|
-
...props
|
|
12482
|
-
});
|
|
12483
|
-
}
|
|
12484
|
-
function DialogDescription$1({ className, ...props }) {
|
|
12485
|
-
return /* @__PURE__ */ jsx(DialogPrimitive.Description, {
|
|
12486
|
-
"data-slot": "dialog-description",
|
|
12487
|
-
className: cn$1("text-muted-foreground text-sm", className),
|
|
12488
|
-
...props
|
|
12489
|
-
});
|
|
12490
|
-
}
|
|
12491
|
-
|
|
12492
|
-
// src/ui/badge.tsx
|
|
12493
|
-
const badgeVariants = cva("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", {
|
|
12494
|
-
variants: { variant: {
|
|
12495
|
-
default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
|
12496
|
-
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
12497
|
-
destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
|
12498
|
-
outline: "text-foreground",
|
|
12499
|
-
success: "border-transparent bg-status-success-bg-subtle text-status-success-text",
|
|
12500
|
-
warning: "border-transparent bg-status-warning-bg-subtle text-status-warning-text",
|
|
12501
|
-
info: "border-transparent bg-status-info-bg-subtle text-status-info-text"
|
|
12502
|
-
} },
|
|
12503
|
-
defaultVariants: { variant: "default" }
|
|
12504
|
-
});
|
|
12505
|
-
function Badge$1({ className, variant, ...props }) {
|
|
12506
|
-
return /* @__PURE__ */ jsx("div", {
|
|
12507
|
-
className: cn$1(badgeVariants({ variant }), className),
|
|
12508
|
-
...props
|
|
12509
|
-
});
|
|
12510
|
-
}
|
|
12511
|
-
|
|
12512
|
-
// src/utils/template-preview.ts
|
|
12513
|
-
const ICONIFY_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*:[a-z0-9]+(?:[-_][a-z0-9]+)*$/i;
|
|
12514
|
-
/** True when the value is an Iconify `prefix:name` icon, not a URL. */
|
|
12515
|
-
function isIconifyName(value) {
|
|
12516
|
-
return ICONIFY_NAME.test(value) && !/^(https?|data|blob):/i.test(value);
|
|
12517
|
-
}
|
|
12518
|
-
/**
|
|
12519
|
-
* The preview for a template: its first screenshot, else its icon, which is
|
|
12520
|
-
* shown as an image only when it is not an Iconify name.
|
|
12521
|
-
*/
|
|
12522
|
-
function templatePreview(template) {
|
|
12523
|
-
const screenshot = template.screenshots?.find((shot) => typeof shot === "string" && shot.trim() !== "");
|
|
12524
|
-
if (screenshot) return {
|
|
12525
|
-
kind: "image",
|
|
12526
|
-
src: screenshot
|
|
12527
|
-
};
|
|
12528
|
-
const icon = template.icon?.trim();
|
|
12529
|
-
if (!icon) return { kind: "none" };
|
|
12530
|
-
return isIconifyName(icon) ? {
|
|
12531
|
-
kind: "icon",
|
|
12532
|
-
name: icon
|
|
12533
|
-
} : {
|
|
12534
|
-
kind: "image",
|
|
12535
|
-
src: icon
|
|
12536
|
-
};
|
|
12537
|
-
}
|
|
12538
|
-
|
|
12539
|
-
// src/builder/components/flow-builder/components/templates/TemplateCard.tsx
|
|
12540
|
-
function formatNumber(count) {
|
|
12541
|
-
if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`;
|
|
12542
|
-
if (count >= 1e3) return `${(count / 1e3).toFixed(1)}k`;
|
|
12543
|
-
return count.toString();
|
|
12544
|
-
}
|
|
12545
|
-
/**
|
|
12546
|
-
* Template card component inspired by ComfyUI's template showcase
|
|
12547
|
-
* Features:
|
|
12548
|
-
* - Large preview image/screenshot
|
|
12549
|
-
* - Template name and description
|
|
12550
|
-
* - Author info
|
|
12551
|
-
* - Stats (downloads, views, rating)
|
|
12552
|
-
* - Featured badge
|
|
12553
|
-
* - Category/tags
|
|
12554
|
-
*/
|
|
12555
|
-
function TemplateCard({ template, onClick, onUseTemplate }) {
|
|
12556
|
-
const tr = useTr();
|
|
12557
|
-
const [imageError, setImageError] = React.useState(false);
|
|
12558
|
-
const preview = templatePreview(template);
|
|
12559
|
-
const previewImage = preview.kind === "image" ? preview.src : void 0;
|
|
12560
|
-
const hasPreview = Boolean(previewImage) && !imageError;
|
|
12561
|
-
const handleUseTemplate = (e) => {
|
|
12562
|
-
e.stopPropagation();
|
|
12563
|
-
onUseTemplate?.(template);
|
|
12564
|
-
};
|
|
12565
|
-
const handleImageError = () => {
|
|
12566
|
-
setImageError(true);
|
|
12567
|
-
};
|
|
12568
|
-
return /* @__PURE__ */ jsxs(Card$1, {
|
|
12569
|
-
className: cn$1("group overflow-hidden transition-all duration-300 cursor-pointer", "hover:shadow-xl hover:scale-[1.02] border-2 hover:border-primary/50"),
|
|
12570
|
-
onClick,
|
|
12571
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
12572
|
-
className: "relative aspect-video w-full overflow-hidden bg-gradient-to-br from-primary/5 to-primary/10",
|
|
12573
|
-
children: [
|
|
12574
|
-
hasPreview ? /* @__PURE__ */ jsx("img", {
|
|
12575
|
-
src: previewImage,
|
|
12576
|
-
alt: template.name,
|
|
12577
|
-
onError: handleImageError,
|
|
12578
|
-
className: "w-full h-full object-cover group-hover:scale-110 transition-transform duration-300"
|
|
12579
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
12580
|
-
className: "w-full h-full flex items-center justify-center",
|
|
12581
|
-
children: preview.kind === "icon" ? /* @__PURE__ */ jsx(Icon, {
|
|
12582
|
-
icon: preview.name,
|
|
12583
|
-
width: 64,
|
|
12584
|
-
height: 64,
|
|
12585
|
-
className: "text-primary/60",
|
|
12586
|
-
"aria-hidden": "true"
|
|
12587
|
-
}) : /* @__PURE__ */ jsx(Sparkles, { className: "size-16 text-primary/30" })
|
|
12588
|
-
}),
|
|
12589
|
-
template.featured && /* @__PURE__ */ jsx("div", {
|
|
12590
|
-
className: "absolute top-2 right-2",
|
|
12591
|
-
children: /* @__PURE__ */ jsxs(Badge$1, {
|
|
12592
|
-
variant: "default",
|
|
12593
|
-
className: "bg-status-warning-bg-subtle text-status-warning-text border border-status-warning-border shadow-lg",
|
|
12594
|
-
children: [/* @__PURE__ */ jsx(Star, { className: "size-3 mr-1 fill-current" }), tr("fluidgrids.builder.templateCard.featuredBadge", "Featured")]
|
|
12595
|
-
})
|
|
12596
|
-
}),
|
|
12597
|
-
/* @__PURE__ */ jsx("div", {
|
|
12598
|
-
className: "absolute top-2 left-2",
|
|
12599
|
-
children: template.pricingModel === "FREE" ? /* @__PURE__ */ jsx(Badge$1, {
|
|
12600
|
-
variant: "secondary",
|
|
12601
|
-
className: "bg-status-success-bg text-action-primary-text border-0",
|
|
12602
|
-
children: tr("fluidgrids.builder.templateCard.freeBadge", "Free")
|
|
12603
|
-
}) : /* @__PURE__ */ jsxs(Badge$1, {
|
|
12604
|
-
variant: "default",
|
|
12605
|
-
className: "bg-status-info-bg text-action-primary-text border-0",
|
|
12606
|
-
children: ["$", template.price?.toString() || "0"]
|
|
12607
|
-
})
|
|
12608
|
-
}),
|
|
12609
|
-
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-bg-overlay via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" })
|
|
12610
|
-
]
|
|
12611
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
12612
|
-
className: "p-4 space-y-3",
|
|
12613
|
-
children: [
|
|
12614
|
-
/* @__PURE__ */ jsx("h3", {
|
|
12615
|
-
className: "font-semibold text-base line-clamp-1 group-hover:text-primary transition-colors",
|
|
12616
|
-
children: template.name
|
|
12617
|
-
}),
|
|
12618
|
-
/* @__PURE__ */ jsx("p", {
|
|
12619
|
-
className: "text-sm text-muted-foreground line-clamp-2 min-h-[2.5rem]",
|
|
12620
|
-
children: template.description || tr("fluidgrids.builder.templateCard.noDescription", "No description available")
|
|
12621
|
-
}),
|
|
12622
|
-
/* @__PURE__ */ jsxs("div", {
|
|
12623
|
-
className: "flex items-center gap-2 text-xs text-muted-foreground",
|
|
12624
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
12625
|
-
className: "font-medium",
|
|
12626
|
-
children: template.authorName || template.publisher?.name || tr("fluidgrids.builder.templateCard.unknownAuthor", "Unknown")
|
|
12627
|
-
}), template.publisher?.isVerified && /* @__PURE__ */ jsx(Badge$1, {
|
|
12628
|
-
variant: "secondary",
|
|
12629
|
-
className: "text-xs px-1.5 py-0",
|
|
12630
|
-
children: "✓"
|
|
12631
|
-
})]
|
|
12632
|
-
}),
|
|
12633
|
-
/* @__PURE__ */ jsxs("div", {
|
|
12634
|
-
className: "flex items-center justify-between text-xs text-muted-foreground pt-2 border-t",
|
|
12635
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
12636
|
-
className: "flex items-center gap-3",
|
|
12637
|
-
children: template.rating && /* @__PURE__ */ jsxs("div", {
|
|
12638
|
-
className: "flex items-center gap-1",
|
|
12639
|
-
children: [
|
|
12640
|
-
/* @__PURE__ */ jsx(Star, { className: "size-3 fill-status-warning-bg text-status-warning-text" }),
|
|
12641
|
-
/* @__PURE__ */ jsx("span", {
|
|
12642
|
-
className: "font-medium",
|
|
12643
|
-
children: template.rating.toFixed(1)
|
|
12644
|
-
}),
|
|
12645
|
-
/* @__PURE__ */ jsxs("span", {
|
|
12646
|
-
className: "text-muted-foreground/60",
|
|
12647
|
-
children: [
|
|
12648
|
-
"(",
|
|
12649
|
-
template.reviewCount || 0,
|
|
12650
|
-
")"
|
|
12651
|
-
]
|
|
12652
|
-
})
|
|
12653
|
-
]
|
|
12654
|
-
})
|
|
12655
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
12656
|
-
className: "flex items-center gap-3",
|
|
12657
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
12658
|
-
className: "flex items-center gap-1",
|
|
12659
|
-
title: tr("fluidgrids.builder.templateCard.downloadsTooltip", "Downloads"),
|
|
12660
|
-
children: [/* @__PURE__ */ jsx(Download, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: formatNumber(template.downloads || 0) })]
|
|
12661
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
12662
|
-
className: "flex items-center gap-1",
|
|
12663
|
-
title: tr("fluidgrids.builder.templateCard.viewsTooltip", "Views"),
|
|
12664
|
-
children: [/* @__PURE__ */ jsx(Eye, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: formatNumber(template.viewCount || 0) })]
|
|
12665
|
-
})]
|
|
12666
|
-
})]
|
|
12667
|
-
}),
|
|
12668
|
-
(template.category || template.tags?.length > 0) && /* @__PURE__ */ jsxs("div", {
|
|
12669
|
-
className: "flex flex-wrap gap-1",
|
|
12670
|
-
children: [
|
|
12671
|
-
template.category && /* @__PURE__ */ jsx(Badge$1, {
|
|
12672
|
-
variant: "outline",
|
|
12673
|
-
className: "text-xs",
|
|
12674
|
-
children: template.category
|
|
12675
|
-
}),
|
|
12676
|
-
template.tags?.slice(0, 2).map((tag) => /* @__PURE__ */ jsx(Badge$1, {
|
|
12677
|
-
variant: "outline",
|
|
12678
|
-
className: "text-xs",
|
|
12679
|
-
children: tag
|
|
12680
|
-
}, tag)),
|
|
12681
|
-
template.tags?.length > 2 && /* @__PURE__ */ jsxs(Badge$1, {
|
|
12682
|
-
variant: "outline",
|
|
12683
|
-
className: "text-xs",
|
|
12684
|
-
children: ["+", template.tags.length - 2]
|
|
12685
|
-
})
|
|
12686
|
-
]
|
|
12687
|
-
}),
|
|
12688
|
-
/* @__PURE__ */ jsx(SdkPermissionButton, {
|
|
12689
|
-
permission: FLUIDGRIDS_PERMISSIONS.WORKFLOW_CREATE,
|
|
12690
|
-
deniedTooltip: tr("fluidgrids.templates.useDeniedTooltip", "You don't have permission to create workflows from templates"),
|
|
12691
|
-
children: /* @__PURE__ */ jsxs(Button$2, {
|
|
12692
|
-
size: "sm",
|
|
12693
|
-
className: "w-full mt-2",
|
|
12694
|
-
onClick: handleUseTemplate,
|
|
12695
|
-
children: [/* @__PURE__ */ jsx(Sparkles, { className: "size-3 mr-1" }), tr("fluidgrids.builder.templateCard.useTemplateButton", "Use Template")]
|
|
12696
|
-
})
|
|
12697
|
-
})
|
|
12698
|
-
]
|
|
12699
|
-
})]
|
|
12700
|
-
});
|
|
12701
|
-
}
|
|
12702
|
-
|
|
12703
11042
|
// src/builder/components/flow-builder/components/templates/TemplatesGrid.tsx
|
|
12704
11043
|
/**
|
|
12705
11044
|
* Grid layout for displaying workflow templates
|
|
@@ -12751,7 +11090,7 @@ function TemplatesGrid({ templates, loading, hasMore, onLoadMore, onTemplateClic
|
|
|
12751
11090
|
}
|
|
12752
11091
|
|
|
12753
11092
|
// src/builder/components/flow-builder/components/templates/TemplatesModal.tsx
|
|
12754
|
-
const LazyTemplateDetailPanel = lazy(() => import("./TemplateDetailPanel-
|
|
11093
|
+
const LazyTemplateDetailPanel = lazy(() => import("./TemplateDetailPanel-D4cUVr-m.js").then((m) => ({ default: m.TemplateDetailPanel })));
|
|
12755
11094
|
/**
|
|
12756
11095
|
* Modal for browsing and selecting workflow templates
|
|
12757
11096
|
* Features:
|
|
@@ -21078,6 +19417,26 @@ function takeBaseline() {
|
|
|
21078
19417
|
editRevision: flow.editRevision
|
|
21079
19418
|
});
|
|
21080
19419
|
}
|
|
19420
|
+
/**
|
|
19421
|
+
* Let the canvas settle again whenever the node registry redraws it
|
|
19422
|
+
* (BOFF-11754). The canvas is drawn before every node type on it is
|
|
19423
|
+
* registered, so a card can turn into the real one well after the load (an
|
|
19424
|
+
* installed store node read later, a node installed from the missing-nodes
|
|
19425
|
+
* dialog) and measure and normalise itself then. Until the person edits, that
|
|
19426
|
+
* moves the baseline instead of counting as unsaved work. `subscribe` is the
|
|
19427
|
+
* registry's update feed. Returns the unsubscribe function.
|
|
19428
|
+
*/
|
|
19429
|
+
function settleOnNodeRegistryUpdates(subscribe = onNodeTypesUpdate) {
|
|
19430
|
+
return subscribe(() => {
|
|
19431
|
+
const status = useSaveStatusStore.getState();
|
|
19432
|
+
const flow = useFlowStore.getState();
|
|
19433
|
+
const next = resettledBaseline(status.baseline, {
|
|
19434
|
+
generation: flow.loadGeneration,
|
|
19435
|
+
editRevision: flow.editRevision
|
|
19436
|
+
}, Date.now(), SETTLE_MS);
|
|
19437
|
+
if (next) status.actions.setBaseline(next);
|
|
19438
|
+
});
|
|
19439
|
+
}
|
|
21081
19440
|
/** Unsaved-change tracking, autosave with retries, and saves on the way out. */
|
|
21082
19441
|
function useAutosave(readOnly) {
|
|
21083
19442
|
const tr = useTr();
|
|
@@ -21159,6 +19518,7 @@ function useAutosave(readOnly) {
|
|
|
21159
19518
|
const unsubscribeStatus = useSaveStatusStore.subscribe((state, previous) => {
|
|
21160
19519
|
if (state.baseline !== previous.baseline || state.autosaveEnabled !== previous.autosaveEnabled) scheduleCheck();
|
|
21161
19520
|
});
|
|
19521
|
+
const unsubscribeRegistry = settleOnNodeRegistryUpdates();
|
|
21162
19522
|
const unsubscribeLease = useEditLeaseStore.subscribe((state, previous) => {
|
|
21163
19523
|
if (!isLocked(previous.phase) || isLocked(state.phase)) return;
|
|
21164
19524
|
if (status().failure?.conflict?.reason === "EDIT_LEASE_HELD") {
|
|
@@ -21170,6 +19530,7 @@ function useAutosave(readOnly) {
|
|
|
21170
19530
|
return () => {
|
|
21171
19531
|
unsubscribeFlow();
|
|
21172
19532
|
unsubscribeStatus();
|
|
19533
|
+
unsubscribeRegistry();
|
|
21173
19534
|
unsubscribeLease();
|
|
21174
19535
|
if (checkTimer) clearTimeout(checkTimer);
|
|
21175
19536
|
autosave.cancel();
|
|
@@ -27430,8 +25791,8 @@ function EditLeaseBanner() {
|
|
|
27430
25791
|
}
|
|
27431
25792
|
|
|
27432
25793
|
// src/builder/components/flow-builder/flow-builder.tsx
|
|
27433
|
-
const LazyNodeDetailView = lazy(() => import("./node-detail-view-
|
|
27434
|
-
const LazyQuickConfigPanel = lazy(() => import("./quick-config-panel-
|
|
25794
|
+
const LazyNodeDetailView = lazy(() => import("./node-detail-view-B91kXzIy.js").then((m) => ({ default: m.NodeDetailView })));
|
|
25795
|
+
const LazyQuickConfigPanel = lazy(() => import("./quick-config-panel-DggPl1vY.js").then((m) => ({ default: m.QuickConfigPanel })));
|
|
27435
25796
|
const edgeTypes = { deletable: CustomDeletableEdge };
|
|
27436
25797
|
const edgeOptions = {
|
|
27437
25798
|
type: "deletable",
|
|
@@ -27491,7 +25852,7 @@ const FlowBuilder = ({ readOnly = false }) => {
|
|
|
27491
25852
|
const [agentTools, setAgentTools] = useState([]);
|
|
27492
25853
|
useEffect(() => {
|
|
27493
25854
|
let cancelled = false;
|
|
27494
|
-
import("./agentTools-
|
|
25855
|
+
import("./agentTools-CkRJB7r5.js").then(({ createBuilderAgentTools }) => {
|
|
27495
25856
|
if (cancelled) return;
|
|
27496
25857
|
setAgentTools(createBuilderAgentTools({ durableFunctionsEnabled: () => paletteGatesRef.current }));
|
|
27497
25858
|
}).catch((error) => {
|
|
@@ -27546,7 +25907,7 @@ const FlowBuilder = ({ readOnly = false }) => {
|
|
|
27546
25907
|
const [noteTools, setNoteTools] = useState([]);
|
|
27547
25908
|
useEffect(() => {
|
|
27548
25909
|
let cancelled = false;
|
|
27549
|
-
import("./canvasNoteTools-
|
|
25910
|
+
import("./canvasNoteTools-CQIc7Oxg.js").then(({ createCanvasNoteTools }) => {
|
|
27550
25911
|
if (cancelled) return;
|
|
27551
25912
|
setNoteTools(createCanvasNoteTools({ focusNode: (nodeId) => fitViewRef.current({
|
|
27552
25913
|
nodes: [{ id: nodeId }],
|
|
@@ -27854,4 +26215,4 @@ const FlowBuilderPage = ({ workflow, readOnly = false }) => {
|
|
|
27854
26215
|
] });
|
|
27855
26216
|
};
|
|
27856
26217
|
|
|
27857
|
-
export {
|
|
26218
|
+
export { ConnectionSelector as A, FluidGridsMark as B, RunOnSection as C, PopoverContent$1 as D, Popover$1 as E, SchedulePanel as F, NodeEditorTabs as I, TabButton as L, ReadOnlyFieldset as M, useNodeEditorTab as N, PopoverTrigger$1 as O, SCHEDULE_NODE_TYPE as P, NodeInputFields as R, NodeSettingsPanel as S, NDV_Z_INDEX as T, JsonView as _, useAiWorkflowTemplates as a, NodeExecutionActions as b, RunsUsageMeter as c, readRunsQuota as d, runsQuotaLevel as f, notesKeyOf as g, useWorkflowReloadStore as h, enrichDraftNodes as i, ConnectionFormDialog as j, CodeEditorField as k, RunsUsageMeterView as l, releaseLeaseKeptForReload as m, AI_DRAFT_MAX_PROMPT_LENGTH as n, BUILDER_FULLSCREEN_ATTRIBUTE as o, summarizeRunsQuota as p, draftToCanvas as r, BUILDER_FULLSCREEN_EVENT as s, FlowBuilderPage as t, useRunsQuota as u, TableView as v, NodeParamsForm as w, NodeEditorTabBody as x, computeAttachPosition as y, useNodeSelectedEvent as z };
|