orcheo-studio 0.22.2 → 0.23.1
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/.bumpversion.cfg +1 -1
- package/dist/assets/{arc-De9dLqyl.js → arc-CqnxfCHp.js} +1 -1
- package/dist/assets/{architectureDiagram-VXUJARFQ-hEslfmYm.js → architectureDiagram-VXUJARFQ-UGPIhA1j.js} +1 -1
- package/dist/assets/{blockDiagram-VD42YOAC-CyWSMWJa.js → blockDiagram-VD42YOAC-Cn6wka40.js} +1 -1
- package/dist/assets/{c4Diagram-YG6GDRKO-CxoTVJ47.js → c4Diagram-YG6GDRKO-BvPRNdur.js} +1 -1
- package/dist/assets/channel-FjxqoZWk.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-twftUw9o.js → chunk-4BX2VUAB-F_o1AmTw.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DQZDNQUo.js → chunk-55IACEB6-Cl075es2.js} +1 -1
- package/dist/assets/{chunk-B4BG7PRW-BYGOoNB5.js → chunk-B4BG7PRW-DcErQhy9.js} +1 -1
- package/dist/assets/{chunk-DI55MBZ5-qyPhcZF3.js → chunk-DI55MBZ5-CFxCndOp.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BgUDHGEG.js → chunk-FMBD7UC4-XOcF0fgy.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-DHlMh8NC.js → chunk-QN33PNHL-CjJH_Bvj.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Bfc8IV0O.js → chunk-QZHKN3VN-nLb-Go8u.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-BqwdftTm.js → chunk-TZMSLE5B-L07ovmT0.js} +1 -1
- package/dist/assets/classDiagram-2ON5EDUG-8JYNY9Ic.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-8JYNY9Ic.js +1 -0
- package/dist/assets/clone-NPVQC0Uc.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-g5agH7fS.js → cose-bilkent-S5V4N54A-B6IcYcMk.js} +1 -1
- package/dist/assets/{dagre-6UL2VRFP-ajLMaXPP.js → dagre-6UL2VRFP-CN7ADhef.js} +1 -1
- package/dist/assets/{diagram-PSM6KHXK-BndVcBhB.js → diagram-PSM6KHXK-3chqLyzk.js} +1 -1
- package/dist/assets/{diagram-QEK2KX5R-BFiIYeu7.js → diagram-QEK2KX5R-MrbKZWaG.js} +1 -1
- package/dist/assets/{diagram-S2PKOQOG-DXy-A9pn.js → diagram-S2PKOQOG-Dr7BijIx.js} +1 -1
- package/dist/assets/{erDiagram-Q2GNP2WA-D_Bac_58.js → erDiagram-Q2GNP2WA-Ck-v9a6N.js} +1 -1
- package/dist/assets/{flowDiagram-NV44I4VS-DhEi8qtz.js → flowDiagram-NV44I4VS-D662eMD1.js} +1 -1
- package/dist/assets/{ganttDiagram-JELNMOA3-BtpUvOuc.js → ganttDiagram-JELNMOA3-c2mXK47M.js} +1 -1
- package/dist/assets/{gitGraphDiagram-V2S2FVAM-MBES60cx.js → gitGraphDiagram-V2S2FVAM-DUMhfhsS.js} +1 -1
- package/dist/assets/{graph-BcwvIN-m.js → graph-DkWlmVun.js} +1 -1
- package/dist/assets/index-BVY_PYJU.css +1 -0
- package/dist/assets/index-kH6EkBsZ.js +790 -0
- package/dist/assets/{infoDiagram-HS3SLOUP-vaIFYyC9.js → infoDiagram-HS3SLOUP-OTL7qvkP.js} +1 -1
- package/dist/assets/{isUndefined-DVO6xakl.js → isUndefined-BmZvH1PX.js} +1 -1
- package/dist/assets/{journeyDiagram-XKPGCS4Q-D-_q99If.js → journeyDiagram-XKPGCS4Q-BYzqE5D8.js} +1 -1
- package/dist/assets/{kanban-definition-3W4ZIXB7-i_AZcs_J.js → kanban-definition-3W4ZIXB7-NtzQ4y9p.js} +1 -1
- package/dist/assets/{layout-CPJ9nR_z.js → layout-CwhyLMkP.js} +1 -1
- package/dist/assets/{linear-KBGpPBiH.js → linear-Cirf2V1x.js} +1 -1
- package/dist/assets/{min-By5f7YdD.js → min-C81vLJYh.js} +1 -1
- package/dist/assets/{mindmap-definition-VGOIOE7T-DRjtRxFS.js → mindmap-definition-VGOIOE7T-TDnLQIKZ.js} +1 -1
- package/dist/assets/{pieDiagram-ADFJNKIX-BkkSglWM.js → pieDiagram-ADFJNKIX-d0-JhjNZ.js} +1 -1
- package/dist/assets/{quadrantDiagram-AYHSOK5B-DY-Ng2qt.js → quadrantDiagram-AYHSOK5B-wCoihp8a.js} +1 -1
- package/dist/assets/{requirementDiagram-UZGBJVZJ-B5w0etXG.js → requirementDiagram-UZGBJVZJ-BqiCIjcI.js} +1 -1
- package/dist/assets/{sankeyDiagram-TZEHDZUN-BWntUp8b.js → sankeyDiagram-TZEHDZUN-BPH0Pd1D.js} +1 -1
- package/dist/assets/{sequenceDiagram-WL72ISMW-Cl77fd7y.js → sequenceDiagram-WL72ISMW-B8FfJraH.js} +1 -1
- package/dist/assets/{stateDiagram-FKZM4ZOC-B8_DoPW6.js → stateDiagram-FKZM4ZOC-ZQR3Q9_8.js} +1 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-uDYn1wPF.js +1 -0
- package/dist/assets/{timeline-definition-IT6M3QCI-udvNLTe_.js → timeline-definition-IT6M3QCI-D-arYxX5.js} +1 -1
- package/dist/assets/{treemap-GDKQZRPO-BSvoMl74.js → treemap-GDKQZRPO-Dw5BHp_B.js} +1 -1
- package/dist/assets/{xychartDiagram-PRI3JC2R-CTdy-FCX.js → xychartDiagram-PRI3JC2R-Dt1zH6V2.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/App.tsx +7 -2
- package/src/features/account/pages/invitation-accept.tsx +92 -0
- package/src/features/account/pages/workspace-members.tsx +153 -42
- package/src/features/auth/components/require-auth.tsx +13 -18
- package/src/features/auth/lib/auth-api.ts +196 -0
- package/src/features/auth/lib/auth-session.ts +0 -1
- package/src/features/auth/pages/login.tsx +169 -16
- package/src/features/auth/pages/verify.tsx +89 -0
- package/src/features/chatkit/components/public-chat-widget.tsx +89 -8
- package/src/features/chatkit/lib/chatkit-client.ts +66 -0
- package/src/features/chatkit/pages/public-chat.tsx +17 -30
- package/src/features/shared/components/top-navigation/account-menu.tsx +20 -7
- package/src/features/shared/components/top-navigation/studio-brand.tsx +14 -3
- package/src/features/workflow/components/dialogs/publish-workflow-dialog.tsx +121 -0
- package/src/features/workflow/data/templates/candidate-badges.ts +28 -0
- package/src/features/workflow/data/workflow-types.ts +6 -0
- package/src/features/workflow/lib/workflow-storage-helpers.ts +5 -3
- package/src/features/workflow/lib/workflow-storage-versioning.ts +4 -0
- package/src/features/workflow/lib/workflow-storage.types.ts +1 -0
- package/src/features/workflow/pages/workflow/components/workflow-tab-content.tsx +122 -37
- package/src/features/workflow/pages/workflow/hooks/controller/build-layout-props.ts +2 -0
- package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-lifecycle.ts +1 -0
- package/src/features/workflow/pages/workflow/hooks/use-workflow-loader.ts +5 -0
- package/src/features/workflow/pages/workflow/hooks/use-workflow-metadata-state.ts +3 -0
- package/src/features/workflow/pages/workflow/hooks/use-workflow-saver.ts +11 -2
- package/src/features/workflow/pages/workflow-gallery/workflow-gallery-tabs.tsx +47 -4
- package/src/lib/api.ts +78 -1
- package/src/vite-env.d.ts +1 -0
- package/dist/assets/channel-CzCzGn0f.js +0 -1
- package/dist/assets/classDiagram-2ON5EDUG-ekNI5d_4.js +0 -1
- package/dist/assets/classDiagram-v2-WZHVMYZB-ekNI5d_4.js +0 -1
- package/dist/assets/clone-DowBu1Kh.js +0 -1
- package/dist/assets/index-DLYzf0K-.js +0 -775
- package/dist/assets/index-DebsXgfj.css +0 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-DHL7p-md.js +0 -1
- package/src/features/MODULES.md +0 -199
- package/src/features/auth/components/auto-login.tsx +0 -51
- package/src/features/auth/lib/oidc-client.ts +0 -565
- package/src/features/auth/pages/oauth-callback.tsx +0 -63
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { Check, Globe, Lock } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "@/design-system/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
DialogFooter,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
} from "@/design-system/ui/dialog";
|
|
13
|
+
import { cn } from "@/lib/utils";
|
|
14
|
+
|
|
15
|
+
export type PublishVisibility = "public" | "workspace";
|
|
16
|
+
|
|
17
|
+
interface PublishOption {
|
|
18
|
+
value: PublishVisibility;
|
|
19
|
+
title: string;
|
|
20
|
+
description: string;
|
|
21
|
+
icon: typeof Globe;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const PUBLISH_OPTIONS: PublishOption[] = [
|
|
25
|
+
{
|
|
26
|
+
value: "public",
|
|
27
|
+
title: "Public",
|
|
28
|
+
description:
|
|
29
|
+
"Anyone with the link can use the workflow. No sign-in required.",
|
|
30
|
+
icon: Globe,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: "workspace",
|
|
34
|
+
title: "Workspace only",
|
|
35
|
+
description:
|
|
36
|
+
"Only signed-in members of this workspace can use the workflow.",
|
|
37
|
+
icon: Lock,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
interface PublishWorkflowDialogProps {
|
|
42
|
+
open: boolean;
|
|
43
|
+
isPending?: boolean;
|
|
44
|
+
onOpenChange: (open: boolean) => void;
|
|
45
|
+
onConfirm: (requireLogin: boolean) => Promise<void> | void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function PublishWorkflowDialog({
|
|
49
|
+
open,
|
|
50
|
+
isPending = false,
|
|
51
|
+
onOpenChange,
|
|
52
|
+
onConfirm,
|
|
53
|
+
}: PublishWorkflowDialogProps) {
|
|
54
|
+
const [selected, setSelected] = useState<PublishVisibility>("public");
|
|
55
|
+
const handleOpenChange = (nextOpen: boolean) => {
|
|
56
|
+
if (!nextOpen) {
|
|
57
|
+
setSelected("public");
|
|
58
|
+
}
|
|
59
|
+
onOpenChange(nextOpen);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
64
|
+
<DialogContent>
|
|
65
|
+
<DialogHeader>
|
|
66
|
+
<DialogTitle>Publish workflow</DialogTitle>
|
|
67
|
+
<DialogDescription>
|
|
68
|
+
Choose who can access the published chat.
|
|
69
|
+
</DialogDescription>
|
|
70
|
+
</DialogHeader>
|
|
71
|
+
<div className="grid gap-3 py-2">
|
|
72
|
+
{PUBLISH_OPTIONS.map((option) => {
|
|
73
|
+
const Icon = option.icon;
|
|
74
|
+
const isSelected = selected === option.value;
|
|
75
|
+
return (
|
|
76
|
+
<button
|
|
77
|
+
key={option.value}
|
|
78
|
+
type="button"
|
|
79
|
+
aria-pressed={isSelected}
|
|
80
|
+
onClick={() => setSelected(option.value)}
|
|
81
|
+
disabled={isPending}
|
|
82
|
+
className={cn(
|
|
83
|
+
"flex items-start gap-3 rounded-lg border p-3 text-left transition-colors",
|
|
84
|
+
isSelected
|
|
85
|
+
? "border-primary bg-primary/5"
|
|
86
|
+
: "border-border hover:bg-muted/40",
|
|
87
|
+
)}
|
|
88
|
+
>
|
|
89
|
+
<Icon className="mt-0.5 h-5 w-5 shrink-0 text-muted-foreground" />
|
|
90
|
+
<div className="min-w-0 flex-1">
|
|
91
|
+
<p className="text-sm font-medium">{option.title}</p>
|
|
92
|
+
<p className="text-xs text-muted-foreground">
|
|
93
|
+
{option.description}
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
{isSelected ? (
|
|
97
|
+
<Check className="mt-0.5 h-4 w-4 shrink-0 text-primary" />
|
|
98
|
+
) : null}
|
|
99
|
+
</button>
|
|
100
|
+
);
|
|
101
|
+
})}
|
|
102
|
+
</div>
|
|
103
|
+
<DialogFooter>
|
|
104
|
+
<Button
|
|
105
|
+
variant="outline"
|
|
106
|
+
onClick={() => handleOpenChange(false)}
|
|
107
|
+
disabled={isPending}
|
|
108
|
+
>
|
|
109
|
+
Cancel
|
|
110
|
+
</Button>
|
|
111
|
+
<Button
|
|
112
|
+
onClick={() => void onConfirm(selected === "workspace")}
|
|
113
|
+
disabled={isPending}
|
|
114
|
+
>
|
|
115
|
+
{isPending ? "Publishing..." : "Publish"}
|
|
116
|
+
</Button>
|
|
117
|
+
</DialogFooter>
|
|
118
|
+
</DialogContent>
|
|
119
|
+
</Dialog>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -27,6 +27,33 @@ export interface CandidateBadgeDefinition extends CandidateBadgeSpec {
|
|
|
27
27
|
templateDefinition: WorkflowTemplateDefinition;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Derive a candidate's group slug from its colleague-candidates directory path.
|
|
32
|
+
*
|
|
33
|
+
* Candidates are grouped one level under `colleagues/`: a directory that holds a
|
|
34
|
+
* `workflow.py` directly (no slash in the id) is independent and returns null,
|
|
35
|
+
* while a nested path such as `news_desk/feed_curator` belongs to the
|
|
36
|
+
* `news_desk` group.
|
|
37
|
+
*/
|
|
38
|
+
export const candidateGroupSlug = (
|
|
39
|
+
candidateId: string | null | undefined,
|
|
40
|
+
): string | null => {
|
|
41
|
+
if (!candidateId) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const separator = candidateId.indexOf("/");
|
|
45
|
+
return separator === -1 ? null : candidateId.slice(0, separator);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Turn an underscored group slug into a display label, e.g.
|
|
50
|
+
* `news_desk` → `News desk`.
|
|
51
|
+
*/
|
|
52
|
+
export const formatCandidateGroupName = (slug: string): string => {
|
|
53
|
+
const spaced = slug.replace(/_/g, " ");
|
|
54
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
55
|
+
};
|
|
56
|
+
|
|
30
57
|
const toStringArray = (value: unknown): string[] | undefined => {
|
|
31
58
|
if (!Array.isArray(value)) {
|
|
32
59
|
return undefined;
|
|
@@ -70,6 +97,7 @@ const buildCandidateWorkflow = (
|
|
|
70
97
|
handle: preserveHandle,
|
|
71
98
|
name: spec.name,
|
|
72
99
|
description: spec.description,
|
|
100
|
+
candidateGroup: candidateGroupSlug(spec.candidateId),
|
|
73
101
|
avatarEmoji: avatarId,
|
|
74
102
|
createdAt: "2026-01-01T00:00:00Z",
|
|
75
103
|
updatedAt: "2026-01-01T00:00:00Z",
|
|
@@ -34,6 +34,12 @@ export interface Workflow {
|
|
|
34
34
|
id: string;
|
|
35
35
|
handle?: string;
|
|
36
36
|
teamId?: string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Group slug for candidate colleagues, derived from the first path segment
|
|
39
|
+
* of the colleague-candidates directory (e.g. "news_desk"). Null when the
|
|
40
|
+
* candidate lives directly under `colleagues/` and is therefore independent.
|
|
41
|
+
*/
|
|
42
|
+
candidateGroup?: string | null;
|
|
37
43
|
name: string;
|
|
38
44
|
description?: string;
|
|
39
45
|
avatarEmoji?: string | null;
|
|
@@ -218,8 +218,7 @@ const parseWorkflowMetadata = (
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
const metadataRecord = metadata as Record<string, unknown>;
|
|
221
|
-
const workflowMetadata =
|
|
222
|
-
metadataRecord.workflow ?? metadataRecord.canvas;
|
|
221
|
+
const workflowMetadata = metadataRecord.workflow ?? metadataRecord.canvas;
|
|
223
222
|
if (!workflowMetadata || typeof workflowMetadata !== "object") {
|
|
224
223
|
return (
|
|
225
224
|
resolveTemplateFallback() ?? {
|
|
@@ -232,7 +231,9 @@ const parseWorkflowMetadata = (
|
|
|
232
231
|
}
|
|
233
232
|
|
|
234
233
|
const workflowRecord = workflowMetadata as Record<string, unknown>;
|
|
235
|
-
const snapshotPayload = workflowRecord.snapshot as
|
|
234
|
+
const snapshotPayload = workflowRecord.snapshot as
|
|
235
|
+
| WorkflowSnapshot
|
|
236
|
+
| undefined;
|
|
236
237
|
const summaryPayload = workflowRecord.summary as
|
|
237
238
|
| WorkflowDiffResult["summary"]
|
|
238
239
|
| undefined;
|
|
@@ -373,6 +374,7 @@ export const toStoredWorkflow = (
|
|
|
373
374
|
lastRun: undefined,
|
|
374
375
|
isArchived: workflow.is_archived,
|
|
375
376
|
isPublic: workflow.is_public,
|
|
377
|
+
requireLogin: workflow.require_login,
|
|
376
378
|
shareUrl: workflow.share_url ?? null,
|
|
377
379
|
chatkitStartScreenPrompts: workflow.chatkit?.start_screen_prompts ?? null,
|
|
378
380
|
chatkitSupportedModels: workflow.chatkit?.supported_models ?? null,
|
|
@@ -124,4 +124,8 @@ export const persistRunnableConfig = async (
|
|
|
124
124
|
}),
|
|
125
125
|
},
|
|
126
126
|
);
|
|
127
|
+
// The cached workflow still holds the previous runnable config; drop it so the
|
|
128
|
+
// next fetch reflects the value we just persisted.
|
|
129
|
+
invalidateWorkflowCache(workflowId);
|
|
130
|
+
return targetVersion;
|
|
127
131
|
};
|
|
@@ -249,6 +249,7 @@ export interface StoredWorkflow extends Workflow {
|
|
|
249
249
|
draftAccess?: "personal" | "authenticated" | "workspace";
|
|
250
250
|
isArchived?: boolean;
|
|
251
251
|
isPublic?: boolean;
|
|
252
|
+
requireLogin?: boolean;
|
|
252
253
|
shareUrl?: string | null;
|
|
253
254
|
chatkitStartScreenPrompts?: ChatKitStartScreenPrompt[] | null;
|
|
254
255
|
chatkitSupportedModels?: ChatKitSupportedModel[] | null;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
AlertTriangle,
|
|
5
5
|
Copy,
|
|
6
6
|
ExternalLink,
|
|
7
|
+
LoaderCircle,
|
|
7
8
|
Play,
|
|
8
9
|
RefreshCw,
|
|
9
10
|
UserMinus,
|
|
@@ -25,8 +26,15 @@ import {
|
|
|
25
26
|
} from "@/design-system/ui/alert-dialog";
|
|
26
27
|
import { Button } from "@/design-system/ui/button";
|
|
27
28
|
import { Switch } from "@/design-system/ui/switch";
|
|
29
|
+
import {
|
|
30
|
+
Tooltip,
|
|
31
|
+
TooltipContent,
|
|
32
|
+
TooltipProvider,
|
|
33
|
+
TooltipTrigger,
|
|
34
|
+
} from "@/design-system/ui/tooltip";
|
|
28
35
|
import { toast } from "@/hooks/use-toast";
|
|
29
36
|
import { ConfirmDeleteWorkflowDialog } from "@features/workflow/components/dialogs/confirm-delete-workflow-dialog";
|
|
37
|
+
import { PublishWorkflowDialog } from "@features/workflow/components/dialogs/publish-workflow-dialog";
|
|
30
38
|
import { UpdateWorkflowDialog } from "@features/workflow/components/dialogs/update-workflow-dialog";
|
|
31
39
|
import { deleteWorkflow } from "@features/workflow/lib/workflow-storage";
|
|
32
40
|
import {
|
|
@@ -57,10 +65,12 @@ export interface WorkflowTabContentProps {
|
|
|
57
65
|
isLoading: boolean;
|
|
58
66
|
loadError: string | null;
|
|
59
67
|
isRunPending: boolean;
|
|
68
|
+
isRunning: boolean;
|
|
60
69
|
onRunWorkflow: () => Promise<void>;
|
|
61
70
|
onSaveConfig: (nextConfig: WorkflowRunnableConfig | null) => Promise<void>;
|
|
62
71
|
hasCronTriggerNode: boolean;
|
|
63
72
|
initialIsPublished: boolean;
|
|
73
|
+
initialRequireLogin: boolean;
|
|
64
74
|
initialShareUrl: string | null;
|
|
65
75
|
missingCredentials?: string[];
|
|
66
76
|
}
|
|
@@ -186,10 +196,12 @@ export function WorkflowTabContent({
|
|
|
186
196
|
isLoading,
|
|
187
197
|
loadError,
|
|
188
198
|
isRunPending,
|
|
199
|
+
isRunning,
|
|
189
200
|
onRunWorkflow,
|
|
190
201
|
onSaveConfig,
|
|
191
202
|
hasCronTriggerNode,
|
|
192
203
|
initialIsPublished,
|
|
204
|
+
initialRequireLogin,
|
|
193
205
|
initialShareUrl,
|
|
194
206
|
missingCredentials = [],
|
|
195
207
|
}: WorkflowTabContentProps) {
|
|
@@ -198,9 +210,11 @@ export function WorkflowTabContent({
|
|
|
198
210
|
const latestVersion = versions.at(-1);
|
|
199
211
|
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
|
200
212
|
const [isPublished, setIsPublished] = useState(initialIsPublished);
|
|
213
|
+
const [requireLogin, setRequireLogin] = useState(initialRequireLogin);
|
|
201
214
|
const [isScheduled, setIsScheduled] = useState(false);
|
|
202
215
|
const [shareUrl, setShareUrl] = useState<string | null>(initialShareUrl);
|
|
203
216
|
const [isPublishPending, setIsPublishPending] = useState(false);
|
|
217
|
+
const [isPublishDialogOpen, setIsPublishDialogOpen] = useState(false);
|
|
204
218
|
const [isSchedulePending, setIsSchedulePending] = useState(false);
|
|
205
219
|
const [diagramSvg, setDiagramSvg] = useState<string | null>(null);
|
|
206
220
|
const [diagramError, setDiagramError] = useState<string | null>(null);
|
|
@@ -216,12 +230,14 @@ export function WorkflowTabContent({
|
|
|
216
230
|
if (!workflowId) {
|
|
217
231
|
setIsPublished(false);
|
|
218
232
|
setIsScheduled(false);
|
|
233
|
+
setRequireLogin(false);
|
|
219
234
|
setShareUrl(null);
|
|
220
235
|
return;
|
|
221
236
|
}
|
|
222
237
|
setIsPublished(initialIsPublished);
|
|
238
|
+
setRequireLogin(initialRequireLogin);
|
|
223
239
|
setShareUrl(initialShareUrl);
|
|
224
|
-
}, [initialIsPublished, initialShareUrl, workflowId]);
|
|
240
|
+
}, [initialIsPublished, initialRequireLogin, initialShareUrl, workflowId]);
|
|
225
241
|
|
|
226
242
|
useEffect(() => {
|
|
227
243
|
if (!workflowId) {
|
|
@@ -375,6 +391,8 @@ export function WorkflowTabContent({
|
|
|
375
391
|
|
|
376
392
|
const canConfigure = Boolean(workflowId);
|
|
377
393
|
const canRun = Boolean(workflowId && latestVersion);
|
|
394
|
+
const runButtonDisabled = !canRun || isRunPending || isRunning;
|
|
395
|
+
const runButtonLabel = isRunPending || isRunning ? "Running..." : "Run";
|
|
378
396
|
const latestConfig = latestVersion?.runnableConfig ?? null;
|
|
379
397
|
const canToggleSchedule = hasCronTriggerNode || isScheduled;
|
|
380
398
|
|
|
@@ -409,33 +427,60 @@ export function WorkflowTabContent({
|
|
|
409
427
|
return;
|
|
410
428
|
}
|
|
411
429
|
|
|
430
|
+
if (nextValue) {
|
|
431
|
+
setIsPublishDialogOpen(true);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
412
435
|
setIsPublishPending(true);
|
|
413
436
|
try {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
"Workflow is now public and available via its chat URL.",
|
|
423
|
-
});
|
|
424
|
-
} else {
|
|
425
|
-
await unpublishWorkflow(workflowId, "studio");
|
|
426
|
-
setIsPublished(false);
|
|
427
|
-
setShareUrl(null);
|
|
428
|
-
toast({
|
|
429
|
-
title: "Workflow unpublished",
|
|
430
|
-
description: "Workflow is now private.",
|
|
431
|
-
});
|
|
432
|
-
}
|
|
437
|
+
await unpublishWorkflow(workflowId, "studio");
|
|
438
|
+
setIsPublished(false);
|
|
439
|
+
setRequireLogin(false);
|
|
440
|
+
setShareUrl(null);
|
|
441
|
+
toast({
|
|
442
|
+
title: "Workflow unpublished",
|
|
443
|
+
description: "Workflow is now private.",
|
|
444
|
+
});
|
|
433
445
|
} catch (error) {
|
|
434
|
-
setIsPublished(
|
|
446
|
+
setIsPublished(true);
|
|
435
447
|
toast({
|
|
436
|
-
title:
|
|
437
|
-
|
|
438
|
-
|
|
448
|
+
title: "Failed to unpublish workflow",
|
|
449
|
+
description: getErrorMessage(error, "Unable to update publish status."),
|
|
450
|
+
variant: "destructive",
|
|
451
|
+
});
|
|
452
|
+
} finally {
|
|
453
|
+
setIsPublishPending(false);
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const handleConfirmPublish = async (requireLogin: boolean) => {
|
|
458
|
+
if (!workflowId) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
setIsPublishPending(true);
|
|
463
|
+
try {
|
|
464
|
+
const result = await publishWorkflow(workflowId, {
|
|
465
|
+
actor: "studio",
|
|
466
|
+
requireLogin,
|
|
467
|
+
});
|
|
468
|
+
setIsPublished(true);
|
|
469
|
+
setRequireLogin(result.workflow.require_login);
|
|
470
|
+
setShareUrl(result.shareUrl);
|
|
471
|
+
setIsPublishDialogOpen(false);
|
|
472
|
+
toast({
|
|
473
|
+
title: "Workflow published",
|
|
474
|
+
description:
|
|
475
|
+
result.message ??
|
|
476
|
+
(requireLogin
|
|
477
|
+
? "Workflow is now available to signed-in workspace members."
|
|
478
|
+
: "Workflow is now public and available via its chat URL."),
|
|
479
|
+
});
|
|
480
|
+
} catch (error) {
|
|
481
|
+
setIsPublished(false);
|
|
482
|
+
toast({
|
|
483
|
+
title: "Failed to publish workflow",
|
|
439
484
|
description: getErrorMessage(error, "Unable to update publish status."),
|
|
440
485
|
variant: "destructive",
|
|
441
486
|
});
|
|
@@ -537,15 +582,31 @@ export function WorkflowTabContent({
|
|
|
537
582
|
</p>
|
|
538
583
|
</div>
|
|
539
584
|
<div className="flex items-center gap-4">
|
|
540
|
-
<
|
|
541
|
-
<
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
585
|
+
<TooltipProvider>
|
|
586
|
+
<Tooltip>
|
|
587
|
+
<TooltipTrigger asChild>
|
|
588
|
+
<div className="flex items-center gap-2">
|
|
589
|
+
<span className="text-sm text-muted-foreground">
|
|
590
|
+
Publish
|
|
591
|
+
</span>
|
|
592
|
+
<Switch
|
|
593
|
+
aria-label="Publish workflow"
|
|
594
|
+
checked={isPublished}
|
|
595
|
+
onCheckedChange={(checked) =>
|
|
596
|
+
void handlePublishToggle(checked)
|
|
597
|
+
}
|
|
598
|
+
disabled={isPublishPending}
|
|
599
|
+
/>
|
|
600
|
+
</div>
|
|
601
|
+
</TooltipTrigger>
|
|
602
|
+
<TooltipContent className="max-w-xs">
|
|
603
|
+
Expose this workflow through a shareable chat URL. When
|
|
604
|
+
publishing, choose <strong>Public</strong> (anyone with the
|
|
605
|
+
link) or <strong>Workspace only</strong> (sign-in required for
|
|
606
|
+
members of this workspace).
|
|
607
|
+
</TooltipContent>
|
|
608
|
+
</Tooltip>
|
|
609
|
+
</TooltipProvider>
|
|
549
610
|
<div className="flex items-center gap-2">
|
|
550
611
|
<span className="text-sm text-muted-foreground">Schedule</span>
|
|
551
612
|
<Switch
|
|
@@ -578,16 +639,23 @@ export function WorkflowTabContent({
|
|
|
578
639
|
) : null}
|
|
579
640
|
<Button
|
|
580
641
|
onClick={() => {
|
|
642
|
+
if (isRunPending || isRunning) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
581
645
|
if (hasMissingCredentials) {
|
|
582
646
|
setIsMissingCredentialsDialogOpen(true);
|
|
583
647
|
return;
|
|
584
648
|
}
|
|
585
649
|
void onRunWorkflow();
|
|
586
650
|
}}
|
|
587
|
-
disabled={
|
|
651
|
+
disabled={runButtonDisabled}
|
|
588
652
|
>
|
|
589
|
-
|
|
590
|
-
|
|
653
|
+
{isRunPending || isRunning ? (
|
|
654
|
+
<LoaderCircle className="mr-1.5 h-4 w-4 animate-spin" />
|
|
655
|
+
) : (
|
|
656
|
+
<Play className="mr-1.5 h-4 w-4" />
|
|
657
|
+
)}
|
|
658
|
+
{runButtonLabel}
|
|
591
659
|
</Button>
|
|
592
660
|
<Button
|
|
593
661
|
variant="outline"
|
|
@@ -599,11 +667,21 @@ export function WorkflowTabContent({
|
|
|
599
667
|
</div>
|
|
600
668
|
</div>
|
|
601
669
|
|
|
670
|
+
{(isRunPending || isRunning) && (
|
|
671
|
+
<div
|
|
672
|
+
role="status"
|
|
673
|
+
className="rounded-md border border-primary/30 bg-primary/5 px-3 py-2 text-sm text-primary"
|
|
674
|
+
>
|
|
675
|
+
Workflow run in progress. Check the latest record on the Trace tab
|
|
676
|
+
for live status.
|
|
677
|
+
</div>
|
|
678
|
+
)}
|
|
679
|
+
|
|
602
680
|
{isPublished && shareUrl && (
|
|
603
681
|
<div className="flex items-center justify-between rounded-md border border-border/60 bg-muted/20 px-3 py-2">
|
|
604
682
|
<div className="min-w-0">
|
|
605
683
|
<p className="text-xs uppercase tracking-wide text-muted-foreground">
|
|
606
|
-
Public URL
|
|
684
|
+
{requireLogin ? "Members-only URL" : "Public URL"}
|
|
607
685
|
</p>
|
|
608
686
|
<a
|
|
609
687
|
href={shareUrl}
|
|
@@ -728,6 +806,13 @@ export function WorkflowTabContent({
|
|
|
728
806
|
/>
|
|
729
807
|
) : null}
|
|
730
808
|
|
|
809
|
+
<PublishWorkflowDialog
|
|
810
|
+
open={isPublishDialogOpen}
|
|
811
|
+
isPending={isPublishPending}
|
|
812
|
+
onOpenChange={setIsPublishDialogOpen}
|
|
813
|
+
onConfirm={handleConfirmPublish}
|
|
814
|
+
/>
|
|
815
|
+
|
|
731
816
|
<AlertDialog
|
|
732
817
|
open={isMissingCredentialsDialogOpen}
|
|
733
818
|
onOpenChange={setIsMissingCredentialsDialogOpen}
|
|
@@ -83,12 +83,14 @@ export function buildWorkflowLayoutProps(
|
|
|
83
83
|
isLoading: core.metadata.isWorkflowLoading,
|
|
84
84
|
loadError: core.metadata.workflowLoadError,
|
|
85
85
|
isRunPending: execution.isRunPending,
|
|
86
|
+
isRunning: core.execution.isRunning,
|
|
86
87
|
onRunWorkflow: execution.handleRunPersistedWorkflow,
|
|
87
88
|
onSaveConfig: resources.saver.handleSaveWorkflowConfig,
|
|
88
89
|
hasCronTriggerNode: hasSchedulableCronTrigger(
|
|
89
90
|
core.metadata.workflowVersions ?? [],
|
|
90
91
|
),
|
|
91
92
|
initialIsPublished: core.metadata.isWorkflowPublic,
|
|
93
|
+
initialRequireLogin: core.metadata.workflowRequireLogin,
|
|
92
94
|
initialShareUrl: core.metadata.workflowShareUrl,
|
|
93
95
|
missingCredentials: resources.credentialReadiness.missingCredentials,
|
|
94
96
|
};
|
|
@@ -18,6 +18,7 @@ export function useWorkflowLifecycle(
|
|
|
18
18
|
setChatkitStartScreenPrompts: core.metadata.setChatkitStartScreenPrompts,
|
|
19
19
|
setChatkitSupportedModels: core.metadata.setChatkitSupportedModels,
|
|
20
20
|
setIsWorkflowPublic: core.metadata.setIsWorkflowPublic,
|
|
21
|
+
setWorkflowRequireLogin: core.metadata.setWorkflowRequireLogin,
|
|
21
22
|
setWorkflowShareUrl: core.metadata.setWorkflowShareUrl,
|
|
22
23
|
setIsWorkflowLoading: core.metadata.setIsWorkflowLoading,
|
|
23
24
|
setWorkflowLoadError: core.metadata.setWorkflowLoadError,
|
|
@@ -28,6 +28,7 @@ interface UseWorkflowLoaderParams {
|
|
|
28
28
|
SetStateAction<ChatKitSupportedModel[] | null>
|
|
29
29
|
>;
|
|
30
30
|
setIsWorkflowPublic: Dispatch<SetStateAction<boolean>>;
|
|
31
|
+
setWorkflowRequireLogin: Dispatch<SetStateAction<boolean>>;
|
|
31
32
|
setWorkflowShareUrl: Dispatch<SetStateAction<string | null>>;
|
|
32
33
|
setIsWorkflowLoading: Dispatch<SetStateAction<boolean>>;
|
|
33
34
|
setWorkflowLoadError: Dispatch<SetStateAction<string | null>>;
|
|
@@ -46,6 +47,7 @@ export function useWorkflowLoader({
|
|
|
46
47
|
setChatkitStartScreenPrompts,
|
|
47
48
|
setChatkitSupportedModels,
|
|
48
49
|
setIsWorkflowPublic,
|
|
50
|
+
setWorkflowRequireLogin,
|
|
49
51
|
setWorkflowShareUrl,
|
|
50
52
|
setIsWorkflowLoading,
|
|
51
53
|
setWorkflowLoadError,
|
|
@@ -67,6 +69,7 @@ export function useWorkflowLoader({
|
|
|
67
69
|
setChatkitStartScreenPrompts(null);
|
|
68
70
|
setChatkitSupportedModels(null);
|
|
69
71
|
setIsWorkflowPublic(false);
|
|
72
|
+
setWorkflowRequireLogin(false);
|
|
70
73
|
setWorkflowShareUrl(null);
|
|
71
74
|
setExecutions([]);
|
|
72
75
|
setActiveExecutionId(null);
|
|
@@ -102,6 +105,7 @@ export function useWorkflowLoader({
|
|
|
102
105
|
);
|
|
103
106
|
setChatkitSupportedModels(persisted.chatkitSupportedModels ?? null);
|
|
104
107
|
setIsWorkflowPublic(persisted.isPublic ?? false);
|
|
108
|
+
setWorkflowRequireLogin(persisted.requireLogin ?? false);
|
|
105
109
|
setWorkflowShareUrl(persisted.shareUrl ?? null);
|
|
106
110
|
setExecutions([]);
|
|
107
111
|
setActiveExecutionId(null);
|
|
@@ -124,6 +128,7 @@ export function useWorkflowLoader({
|
|
|
124
128
|
setChatkitStartScreenPrompts(null);
|
|
125
129
|
setChatkitSupportedModels(null);
|
|
126
130
|
setIsWorkflowPublic(false);
|
|
131
|
+
setWorkflowRequireLogin(false);
|
|
127
132
|
setWorkflowShareUrl(null);
|
|
128
133
|
setExecutions([]);
|
|
129
134
|
setActiveExecutionId(null);
|
|
@@ -21,6 +21,7 @@ export function useWorkflowMetadataState() {
|
|
|
21
21
|
null,
|
|
22
22
|
);
|
|
23
23
|
const [isWorkflowPublic, setIsWorkflowPublic] = useState(false);
|
|
24
|
+
const [workflowRequireLogin, setWorkflowRequireLogin] = useState(false);
|
|
24
25
|
const [workflowShareUrl, setWorkflowShareUrl] = useState<string | null>(null);
|
|
25
26
|
const [chatkitStartScreenPrompts, setChatkitStartScreenPrompts] = useState<
|
|
26
27
|
ChatKitStartScreenPrompt[] | null
|
|
@@ -46,6 +47,8 @@ export function useWorkflowMetadataState() {
|
|
|
46
47
|
setWorkflowLoadError,
|
|
47
48
|
isWorkflowPublic,
|
|
48
49
|
setIsWorkflowPublic,
|
|
50
|
+
workflowRequireLogin,
|
|
51
|
+
setWorkflowRequireLogin,
|
|
49
52
|
workflowShareUrl,
|
|
50
53
|
setWorkflowShareUrl,
|
|
51
54
|
chatkitStartScreenPrompts,
|
|
@@ -58,11 +58,20 @@ export function useWorkflowSaver(
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
|
-
await persistRunnableConfig(
|
|
61
|
+
const targetVersion = await persistRunnableConfig(
|
|
62
62
|
currentWorkflowId,
|
|
63
63
|
"studio",
|
|
64
64
|
runnableConfig,
|
|
65
65
|
);
|
|
66
|
+
// Keep the in-memory versions in sync so reopening the config sheet
|
|
67
|
+
// shows the saved values without a page refresh.
|
|
68
|
+
setWorkflowVersions((versions) =>
|
|
69
|
+
versions.map((version) =>
|
|
70
|
+
version.versionNumber === targetVersion
|
|
71
|
+
? { ...version, runnableConfig }
|
|
72
|
+
: version,
|
|
73
|
+
),
|
|
74
|
+
);
|
|
66
75
|
toast({
|
|
67
76
|
title: "Workflow config saved",
|
|
68
77
|
description: `Saved config for "${workflowName}".`,
|
|
@@ -76,7 +85,7 @@ export function useWorkflowSaver(
|
|
|
76
85
|
});
|
|
77
86
|
}
|
|
78
87
|
},
|
|
79
|
-
[currentWorkflowId, workflowName],
|
|
88
|
+
[currentWorkflowId, setWorkflowVersions, workflowName],
|
|
80
89
|
);
|
|
81
90
|
|
|
82
91
|
const handleSaveWorkflowDetails = useCallback(async () => {
|