orcheo-studio 0.21.3 → 0.23.0

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.
Files changed (99) hide show
  1. package/.bumpversion.cfg +1 -1
  2. package/dist/assets/{arc-CI8iBP22.js → arc-4lqv6Kjm.js} +1 -1
  3. package/dist/assets/{architectureDiagram-VXUJARFQ-9lccnHEL.js → architectureDiagram-VXUJARFQ-DjhiFMDA.js} +1 -1
  4. package/dist/assets/{blockDiagram-VD42YOAC-CJPKWPSp.js → blockDiagram-VD42YOAC-AZ1Rspxy.js} +1 -1
  5. package/dist/assets/{c4Diagram-YG6GDRKO-CuW7nex5.js → c4Diagram-YG6GDRKO-eH7LJ-pB.js} +1 -1
  6. package/dist/assets/channel-BbjHmdR-.js +1 -0
  7. package/dist/assets/{chunk-4BX2VUAB-BVQ3rR7o.js → chunk-4BX2VUAB-DJPBOwLj.js} +1 -1
  8. package/dist/assets/{chunk-55IACEB6-BTpqJ_MA.js → chunk-55IACEB6-BtkroFP3.js} +1 -1
  9. package/dist/assets/{chunk-B4BG7PRW-Duatgdyr.js → chunk-B4BG7PRW-9UuoCVI1.js} +1 -1
  10. package/dist/assets/{chunk-DI55MBZ5-CxEvlbgl.js → chunk-DI55MBZ5-DFossSVg.js} +1 -1
  11. package/dist/assets/{chunk-FMBD7UC4-C07lS0oH.js → chunk-FMBD7UC4-DZoCKsqW.js} +1 -1
  12. package/dist/assets/{chunk-QN33PNHL-C09PhK0w.js → chunk-QN33PNHL-CGFY-glY.js} +1 -1
  13. package/dist/assets/{chunk-QZHKN3VN-Dy3GgrIN.js → chunk-QZHKN3VN-CBx9MjYx.js} +1 -1
  14. package/dist/assets/{chunk-TZMSLE5B-9HPWR_AG.js → chunk-TZMSLE5B-BQ1P9b2p.js} +1 -1
  15. package/dist/assets/classDiagram-2ON5EDUG-B3gA5-gj.js +1 -0
  16. package/dist/assets/classDiagram-v2-WZHVMYZB-B3gA5-gj.js +1 -0
  17. package/dist/assets/clone-Ctw1pu1z.js +1 -0
  18. package/dist/assets/{cose-bilkent-S5V4N54A-B9TWyynz.js → cose-bilkent-S5V4N54A-Dy5ctOeT.js} +1 -1
  19. package/dist/assets/{dagre-6UL2VRFP-DFuJOuNQ.js → dagre-6UL2VRFP-CFPE5-yw.js} +1 -1
  20. package/dist/assets/{diagram-PSM6KHXK-3Jj-OXdd.js → diagram-PSM6KHXK-DmESDfR2.js} +1 -1
  21. package/dist/assets/{diagram-QEK2KX5R-DtAwZnbJ.js → diagram-QEK2KX5R-CumSxfGH.js} +1 -1
  22. package/dist/assets/{diagram-S2PKOQOG-atZg3Snz.js → diagram-S2PKOQOG-C1AaIowU.js} +1 -1
  23. package/dist/assets/{erDiagram-Q2GNP2WA-BoqqRkgy.js → erDiagram-Q2GNP2WA-BKk1ijaf.js} +1 -1
  24. package/dist/assets/{flowDiagram-NV44I4VS-Bmg68IjU.js → flowDiagram-NV44I4VS-BFMJTeTZ.js} +1 -1
  25. package/dist/assets/{ganttDiagram-JELNMOA3-53TZ4oeR.js → ganttDiagram-JELNMOA3-CYobSBkw.js} +1 -1
  26. package/dist/assets/{gitGraphDiagram-V2S2FVAM-D1DddraJ.js → gitGraphDiagram-V2S2FVAM-DYNLBSGu.js} +1 -1
  27. package/dist/assets/{graph-Cb10Gpkt.js → graph-CCH_5x2X.js} +1 -1
  28. package/dist/assets/index-BVY_PYJU.css +1 -0
  29. package/dist/assets/index-CD3NusgQ.js +790 -0
  30. package/dist/assets/{infoDiagram-HS3SLOUP-DxsHJs_0.js → infoDiagram-HS3SLOUP-C8K2nOgc.js} +1 -1
  31. package/dist/assets/{isUndefined-D1qU3_L5.js → isUndefined-Cg-i6rYz.js} +1 -1
  32. package/dist/assets/{journeyDiagram-XKPGCS4Q-CZDE4S5c.js → journeyDiagram-XKPGCS4Q-UjysRuQi.js} +1 -1
  33. package/dist/assets/{kanban-definition-3W4ZIXB7-pRhzMZ6H.js → kanban-definition-3W4ZIXB7-C0mLJbkK.js} +1 -1
  34. package/dist/assets/{layout-C0AW3yEL.js → layout-Ctq7ed-p.js} +1 -1
  35. package/dist/assets/{linear-DobjN4tM.js → linear-MdTfjVMd.js} +1 -1
  36. package/dist/assets/{min-B3Oh8jmi.js → min-CkeT6BIA.js} +1 -1
  37. package/dist/assets/{mindmap-definition-VGOIOE7T-g-SEeftZ.js → mindmap-definition-VGOIOE7T-B1KVkl4d.js} +1 -1
  38. package/dist/assets/{pieDiagram-ADFJNKIX-DiwhMRkx.js → pieDiagram-ADFJNKIX-Cw6TMSJj.js} +1 -1
  39. package/dist/assets/{quadrantDiagram-AYHSOK5B-BIZMdGTC.js → quadrantDiagram-AYHSOK5B-8Lq_anf6.js} +1 -1
  40. package/dist/assets/{requirementDiagram-UZGBJVZJ-D5nzjn-8.js → requirementDiagram-UZGBJVZJ-CXBuMo24.js} +1 -1
  41. package/dist/assets/{sankeyDiagram-TZEHDZUN-CnCESEvf.js → sankeyDiagram-TZEHDZUN-DggxeLrr.js} +1 -1
  42. package/dist/assets/{sequenceDiagram-WL72ISMW-DKBzac_O.js → sequenceDiagram-WL72ISMW-DZqrjzbw.js} +1 -1
  43. package/dist/assets/{stateDiagram-FKZM4ZOC-CcT1-5cM.js → stateDiagram-FKZM4ZOC-D4rhKl_S.js} +1 -1
  44. package/dist/assets/stateDiagram-v2-4FDKWEC3-C81OVjQ3.js +1 -0
  45. package/dist/assets/{timeline-definition-IT6M3QCI-DhFmWcr3.js → timeline-definition-IT6M3QCI-vp3knils.js} +1 -1
  46. package/dist/assets/{treemap-GDKQZRPO-CtMKB2mb.js → treemap-GDKQZRPO-RByzG6o8.js} +1 -1
  47. package/dist/assets/{xychartDiagram-PRI3JC2R-OhMt1B4k.js → xychartDiagram-PRI3JC2R-DOLBdPza.js} +1 -1
  48. package/dist/index.html +2 -7
  49. package/index.html +0 -5
  50. package/package.json +1 -1
  51. package/src/App.tsx +27 -2
  52. package/src/features/account/pages/invitation-accept.tsx +92 -0
  53. package/src/features/account/pages/workspace-members.tsx +153 -42
  54. package/src/features/auth/components/require-auth.tsx +13 -18
  55. package/src/features/auth/lib/auth-api.ts +196 -0
  56. package/src/features/auth/lib/auth-session.ts +0 -1
  57. package/src/features/auth/pages/login.tsx +169 -16
  58. package/src/features/auth/pages/verify.tsx +89 -0
  59. package/src/features/chatkit/components/public-chat-widget.tsx +89 -8
  60. package/src/features/chatkit/lib/chatkit-client.ts +66 -0
  61. package/src/features/chatkit/pages/public-chat.tsx +24 -33
  62. package/src/features/shared/components/top-navigation/account-menu.tsx +20 -7
  63. package/src/features/shared/components/top-navigation/studio-brand.tsx +14 -3
  64. package/src/features/workflow/components/dialogs/publish-workflow-dialog.tsx +121 -0
  65. package/src/features/workflow/data/templates/candidate-badges.ts +5 -0
  66. package/src/features/workflow/data/workflow-types.ts +1 -0
  67. package/src/features/workflow/lib/workflow-storage-api.ts +35 -4
  68. package/src/features/workflow/lib/workflow-storage-helpers.ts +15 -6
  69. package/src/features/workflow/lib/workflow-storage.ts +2 -1
  70. package/src/features/workflow/lib/workflow-storage.types.ts +10 -0
  71. package/src/features/workflow/pages/workflow/components/workflow-tab-content.tsx +122 -37
  72. package/src/features/workflow/pages/workflow/hooks/controller/build-layout-props.ts +2 -0
  73. package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-lifecycle.ts +1 -0
  74. package/src/features/workflow/pages/workflow/hooks/use-workflow-loader.ts +5 -0
  75. package/src/features/workflow/pages/workflow/hooks/use-workflow-metadata-state.ts +3 -0
  76. package/src/features/workflow/pages/workflow-gallery/create-team-dialog.tsx +137 -0
  77. package/src/features/workflow/pages/workflow-gallery/onboard-team-dialog.tsx +57 -0
  78. package/src/features/workflow/pages/workflow-gallery/team-section.tsx +58 -0
  79. package/src/features/workflow/pages/workflow-gallery/use-workflow-gallery-actions.ts +124 -21
  80. package/src/features/workflow/pages/workflow-gallery/use-workflow-gallery-state.ts +22 -2
  81. package/src/features/workflow/pages/workflow-gallery/use-workflow-gallery.ts +2 -0
  82. package/src/features/workflow/pages/workflow-gallery/workflow-card.tsx +26 -5
  83. package/src/features/workflow/pages/workflow-gallery/workflow-gallery-tabs.tsx +92 -17
  84. package/src/features/workflow/pages/workflow-gallery.tsx +45 -1
  85. package/src/lib/api.ts +91 -2
  86. package/src/lib/workspace-routing.ts +15 -0
  87. package/src/main.tsx +7 -0
  88. package/src/vite-env.d.ts +1 -0
  89. package/dist/assets/channel-C-DaAO-p.js +0 -1
  90. package/dist/assets/classDiagram-2ON5EDUG-B2LNkTFb.js +0 -1
  91. package/dist/assets/classDiagram-v2-WZHVMYZB-B2LNkTFb.js +0 -1
  92. package/dist/assets/clone-DcGVOYZT.js +0 -1
  93. package/dist/assets/index-B0_h2wCA.css +0 -1
  94. package/dist/assets/index-DmIcay1T.js +0 -775
  95. package/dist/assets/stateDiagram-v2-4FDKWEC3-Cle1H2JX.js +0 -1
  96. package/src/features/MODULES.md +0 -199
  97. package/src/features/auth/components/auto-login.tsx +0 -51
  98. package/src/features/auth/lib/oidc-client.ts +0 -565
  99. package/src/features/auth/pages/oauth-callback.tsx +0 -63
@@ -48,9 +48,20 @@ export default function StudioBrand() {
48
48
  </Badge>
49
49
  </button>
50
50
  </TooltipTrigger>
51
- <TooltipContent side="bottom" className="max-w-xs">
52
- This environment is experimental. Data created here may be reset,
53
- deleted, or not migrated to the launched version.
51
+ <TooltipContent side="bottom" className="max-w-sm">
52
+ Beta Disclaimer & Terms: Orcheo Cloud is a free beta for evaluation
53
+ and testing, provided as-is. Data, workflows, and credentials may be
54
+ reset, deleted, or not migrated, so do not use it as your only
55
+ storage for critical production data; read the{" "}
56
+ <a
57
+ href="https://ai-colleagues.com/terms"
58
+ target="_blank"
59
+ rel="noreferrer"
60
+ className="font-medium underline underline-offset-2"
61
+ >
62
+ full terms
63
+ </a>
64
+ .
54
65
  </TooltipContent>
55
66
  </Tooltip>
56
67
  </TooltipProvider>
@@ -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
+ }
@@ -132,6 +132,11 @@ export const getCandidateBadgeDefinition = (
132
132
  ): CandidateBadgeDefinition | undefined =>
133
133
  candidateBadges.find((badge) => badge.workflow.id === workflowId);
134
134
 
135
+ export const getCandidateBadgeByHandle = (
136
+ handle: string,
137
+ ): CandidateBadgeDefinition | undefined =>
138
+ candidateBadges.find((badge) => badge.handle === handle);
139
+
135
140
  export const getCandidateTemplateDefinition = (
136
141
  templateId: string,
137
142
  ): WorkflowTemplateDefinition | undefined =>
@@ -33,6 +33,7 @@ export interface WorkflowMermaidPreviewVersion {
33
33
  export interface Workflow {
34
34
  id: string;
35
35
  handle?: string;
36
+ teamId?: string | null;
36
37
  name: string;
37
38
  description?: string;
38
39
  avatarEmoji?: string | null;
@@ -1,6 +1,7 @@
1
1
  import { authFetch } from "@/lib/auth-fetch";
2
2
  import { buildBackendHttpUrl, getBackendBaseUrl } from "@/lib/config";
3
3
  import type {
4
+ ApiTeam,
4
5
  ApiWorkflow,
5
6
  ApiWorkflowPagePayload,
6
7
  ApiWorkflowRun,
@@ -105,11 +106,19 @@ export const fetchWorkflowPageData = async (
105
106
 
106
107
  export const fetchPublicWorkflow = async (
107
108
  workflowId: string,
109
+ context?: { workspaceSlug?: string; teamSlug?: string },
108
110
  ): Promise<PublicWorkflowMetadata | undefined> => {
109
111
  try {
110
- return await request<PublicWorkflowMetadata>(
111
- `${PUBLIC_WORKFLOW_PATH}/${workflowId}/public`,
112
- );
112
+ const params = new URLSearchParams();
113
+ if (context?.workspaceSlug) {
114
+ params.set("workspace_slug", context.workspaceSlug);
115
+ }
116
+ if (context?.teamSlug) {
117
+ params.set("team_slug", context.teamSlug);
118
+ }
119
+ const queryString = params.toString();
120
+ const url = `${PUBLIC_WORKFLOW_PATH}/${workflowId}/public${queryString ? `?${queryString}` : ""}`;
121
+ return await request<PublicWorkflowMetadata>(url);
113
122
  } catch (error) {
114
123
  if (
115
124
  error instanceof ApiRequestError &&
@@ -175,12 +184,34 @@ export interface ApiCandidate {
175
184
  export const fetchCandidates = async (): Promise<ApiCandidate[]> =>
176
185
  request<ApiCandidate[]>("/api/candidates");
177
186
 
187
+ export const fetchTeams = async (): Promise<ApiTeam[]> =>
188
+ request<ApiTeam[]>("/api/teams");
189
+
190
+ export const createTeam = async (input: {
191
+ name: string;
192
+ slug: string;
193
+ }): Promise<ApiTeam> =>
194
+ request<ApiTeam>("/api/teams", {
195
+ method: "POST",
196
+ body: JSON.stringify(input),
197
+ });
198
+
199
+ export const deleteTeam = async (teamId: string): Promise<void> =>
200
+ request<void>(`/api/teams/${teamId}`, {
201
+ method: "DELETE",
202
+ expectJson: false,
203
+ });
204
+
178
205
  export const onboardCandidate = async (
179
206
  candidateId: string,
207
+ teamId?: string,
180
208
  ): Promise<ApiWorkflow> =>
181
209
  request<ApiWorkflow>("/api/candidates/onboard", {
182
210
  method: "POST",
183
- body: JSON.stringify({ id: candidateId }),
211
+ body: JSON.stringify({
212
+ id: candidateId,
213
+ ...(teamId ? { team_id: teamId } : {}),
214
+ }),
184
215
  });
185
216
 
186
217
  export const fetchWorkflowListeners = async (
@@ -135,9 +135,15 @@ export const toFlowEdges = (edges: WorkflowEdge[]): FlowEdge[] =>
135
135
 
136
136
  export const getWorkflowRouteRef = (
137
137
  workflow:
138
- | Pick<ApiWorkflow, "id" | "handle">
139
- | Pick<Workflow, "id" | "handle">,
140
- ): string => workflow.handle ?? workflow.id;
138
+ | Pick<ApiWorkflow, "id" | "handle" | "team_id">
139
+ | Pick<Workflow, "id" | "handle" | "teamId">,
140
+ ): string => {
141
+ const teamId = "team_id" in workflow ? workflow.team_id : workflow.teamId;
142
+ if (teamId) {
143
+ return workflow.id;
144
+ }
145
+ return workflow.handle ?? workflow.id;
146
+ };
141
147
 
142
148
  export const resolveWorkflowVersionMermaidSource = (
143
149
  version:
@@ -212,8 +218,7 @@ const parseWorkflowMetadata = (
212
218
  }
213
219
 
214
220
  const metadataRecord = metadata as Record<string, unknown>;
215
- const workflowMetadata =
216
- metadataRecord.workflow ?? metadataRecord.canvas;
221
+ const workflowMetadata = metadataRecord.workflow ?? metadataRecord.canvas;
217
222
  if (!workflowMetadata || typeof workflowMetadata !== "object") {
218
223
  return (
219
224
  resolveTemplateFallback() ?? {
@@ -226,7 +231,9 @@ const parseWorkflowMetadata = (
226
231
  }
227
232
 
228
233
  const workflowRecord = workflowMetadata as Record<string, unknown>;
229
- const snapshotPayload = workflowRecord.snapshot as WorkflowSnapshot | undefined;
234
+ const snapshotPayload = workflowRecord.snapshot as
235
+ | WorkflowSnapshot
236
+ | undefined;
230
237
  const summaryPayload = workflowRecord.summary as
231
238
  | WorkflowDiffResult["summary"]
232
239
  | undefined;
@@ -351,6 +358,7 @@ export const toStoredWorkflow = (
351
358
  return {
352
359
  id: workflow.id,
353
360
  handle: workflow.handle ?? undefined,
361
+ teamId: workflow.team_id ?? undefined,
354
362
  name: workflow.name,
355
363
  description: workflow.description ?? undefined,
356
364
  avatarEmoji,
@@ -366,6 +374,7 @@ export const toStoredWorkflow = (
366
374
  lastRun: undefined,
367
375
  isArchived: workflow.is_archived,
368
376
  isPublic: workflow.is_public,
377
+ requireLogin: workflow.require_login,
369
378
  shareUrl: workflow.share_url ?? null,
370
379
  chatkitStartScreenPrompts: workflow.chatkit?.start_screen_prompts ?? null,
371
380
  chatkitSupportedModels: workflow.chatkit?.supported_models ?? null,
@@ -458,8 +458,9 @@ export const deleteWorkflow = async (
458
458
 
459
459
  export const onboardCandidateAsWorkflow = async (
460
460
  candidateId: string,
461
+ teamId?: string,
461
462
  ): Promise<StoredWorkflow> => {
462
- const apiWorkflow = await onboardCandidate(candidateId);
463
+ const apiWorkflow = await onboardCandidate(candidateId, teamId);
463
464
  invalidateWorkflowCache(apiWorkflow.id);
464
465
  const stored = await ensureWorkflow(apiWorkflow.id);
465
466
  if (!stored) {
@@ -6,9 +6,17 @@ import type {
6
6
  import type { WorkflowDiffResult, WorkflowSnapshot } from "./workflow-diff";
7
7
  import type { RJSFSchema } from "@rjsf/utils";
8
8
 
9
+ export interface ApiTeam {
10
+ id: string;
11
+ slug: string;
12
+ name: string;
13
+ is_default: boolean;
14
+ }
15
+
9
16
  export interface ApiWorkflow {
10
17
  id: string;
11
18
  handle?: string | null;
19
+ team_id?: string | null;
12
20
  name: string;
13
21
  slug: string;
14
22
  description: string | null;
@@ -237,9 +245,11 @@ export interface WorkflowVersionRecord {
237
245
 
238
246
  export interface StoredWorkflow extends Workflow {
239
247
  versions: WorkflowVersionRecord[];
248
+ teamId?: string | null;
240
249
  draftAccess?: "personal" | "authenticated" | "workspace";
241
250
  isArchived?: boolean;
242
251
  isPublic?: boolean;
252
+ requireLogin?: boolean;
243
253
  shareUrl?: string | null;
244
254
  chatkitStartScreenPrompts?: ChatKitStartScreenPrompt[] | null;
245
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
- if (nextValue) {
415
- const result = await publishWorkflow(workflowId, { actor: "studio" });
416
- setIsPublished(true);
417
- setShareUrl(result.shareUrl);
418
- toast({
419
- title: "Workflow published",
420
- description:
421
- result.message ??
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(!nextValue);
446
+ setIsPublished(true);
435
447
  toast({
436
- title: nextValue
437
- ? "Failed to publish workflow"
438
- : "Failed to unpublish workflow",
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
- <div className="flex items-center gap-2">
541
- <span className="text-sm text-muted-foreground">Publish</span>
542
- <Switch
543
- aria-label="Publish workflow"
544
- checked={isPublished}
545
- onCheckedChange={(checked) => void handlePublishToggle(checked)}
546
- disabled={isPublishPending}
547
- />
548
- </div>
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={!canRun || isRunPending}
651
+ disabled={runButtonDisabled}
588
652
  >
589
- <Play className="mr-1.5 h-4 w-4" />
590
- {isRunPending ? "Running..." : "Run"}
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);