orcheo-studio 0.21.3 → 0.22.2
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-CI8iBP22.js → arc-De9dLqyl.js} +1 -1
- package/dist/assets/{architectureDiagram-VXUJARFQ-9lccnHEL.js → architectureDiagram-VXUJARFQ-hEslfmYm.js} +1 -1
- package/dist/assets/{blockDiagram-VD42YOAC-CJPKWPSp.js → blockDiagram-VD42YOAC-CyWSMWJa.js} +1 -1
- package/dist/assets/{c4Diagram-YG6GDRKO-CuW7nex5.js → c4Diagram-YG6GDRKO-CxoTVJ47.js} +1 -1
- package/dist/assets/channel-CzCzGn0f.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-BVQ3rR7o.js → chunk-4BX2VUAB-twftUw9o.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-BTpqJ_MA.js → chunk-55IACEB6-DQZDNQUo.js} +1 -1
- package/dist/assets/{chunk-B4BG7PRW-Duatgdyr.js → chunk-B4BG7PRW-BYGOoNB5.js} +1 -1
- package/dist/assets/{chunk-DI55MBZ5-CxEvlbgl.js → chunk-DI55MBZ5-qyPhcZF3.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-C07lS0oH.js → chunk-FMBD7UC4-BgUDHGEG.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-C09PhK0w.js → chunk-QN33PNHL-DHlMh8NC.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Dy3GgrIN.js → chunk-QZHKN3VN-Bfc8IV0O.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-9HPWR_AG.js → chunk-TZMSLE5B-BqwdftTm.js} +1 -1
- package/dist/assets/classDiagram-2ON5EDUG-ekNI5d_4.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-ekNI5d_4.js +1 -0
- package/dist/assets/clone-DowBu1Kh.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-B9TWyynz.js → cose-bilkent-S5V4N54A-g5agH7fS.js} +1 -1
- package/dist/assets/{dagre-6UL2VRFP-DFuJOuNQ.js → dagre-6UL2VRFP-ajLMaXPP.js} +1 -1
- package/dist/assets/{diagram-PSM6KHXK-3Jj-OXdd.js → diagram-PSM6KHXK-BndVcBhB.js} +1 -1
- package/dist/assets/{diagram-QEK2KX5R-DtAwZnbJ.js → diagram-QEK2KX5R-BFiIYeu7.js} +1 -1
- package/dist/assets/{diagram-S2PKOQOG-atZg3Snz.js → diagram-S2PKOQOG-DXy-A9pn.js} +1 -1
- package/dist/assets/{erDiagram-Q2GNP2WA-BoqqRkgy.js → erDiagram-Q2GNP2WA-D_Bac_58.js} +1 -1
- package/dist/assets/{flowDiagram-NV44I4VS-Bmg68IjU.js → flowDiagram-NV44I4VS-DhEi8qtz.js} +1 -1
- package/dist/assets/{ganttDiagram-JELNMOA3-53TZ4oeR.js → ganttDiagram-JELNMOA3-BtpUvOuc.js} +1 -1
- package/dist/assets/{gitGraphDiagram-V2S2FVAM-D1DddraJ.js → gitGraphDiagram-V2S2FVAM-MBES60cx.js} +1 -1
- package/dist/assets/{graph-Cb10Gpkt.js → graph-BcwvIN-m.js} +1 -1
- package/dist/assets/index-DLYzf0K-.js +775 -0
- package/dist/assets/index-DebsXgfj.css +1 -0
- package/dist/assets/{infoDiagram-HS3SLOUP-DxsHJs_0.js → infoDiagram-HS3SLOUP-vaIFYyC9.js} +1 -1
- package/dist/assets/{isUndefined-D1qU3_L5.js → isUndefined-DVO6xakl.js} +1 -1
- package/dist/assets/{journeyDiagram-XKPGCS4Q-CZDE4S5c.js → journeyDiagram-XKPGCS4Q-D-_q99If.js} +1 -1
- package/dist/assets/{kanban-definition-3W4ZIXB7-pRhzMZ6H.js → kanban-definition-3W4ZIXB7-i_AZcs_J.js} +1 -1
- package/dist/assets/{layout-C0AW3yEL.js → layout-CPJ9nR_z.js} +1 -1
- package/dist/assets/{linear-DobjN4tM.js → linear-KBGpPBiH.js} +1 -1
- package/dist/assets/{min-B3Oh8jmi.js → min-By5f7YdD.js} +1 -1
- package/dist/assets/{mindmap-definition-VGOIOE7T-g-SEeftZ.js → mindmap-definition-VGOIOE7T-DRjtRxFS.js} +1 -1
- package/dist/assets/{pieDiagram-ADFJNKIX-DiwhMRkx.js → pieDiagram-ADFJNKIX-BkkSglWM.js} +1 -1
- package/dist/assets/{quadrantDiagram-AYHSOK5B-BIZMdGTC.js → quadrantDiagram-AYHSOK5B-DY-Ng2qt.js} +1 -1
- package/dist/assets/{requirementDiagram-UZGBJVZJ-D5nzjn-8.js → requirementDiagram-UZGBJVZJ-B5w0etXG.js} +1 -1
- package/dist/assets/{sankeyDiagram-TZEHDZUN-CnCESEvf.js → sankeyDiagram-TZEHDZUN-BWntUp8b.js} +1 -1
- package/dist/assets/{sequenceDiagram-WL72ISMW-DKBzac_O.js → sequenceDiagram-WL72ISMW-Cl77fd7y.js} +1 -1
- package/dist/assets/{stateDiagram-FKZM4ZOC-CcT1-5cM.js → stateDiagram-FKZM4ZOC-B8_DoPW6.js} +1 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-DHL7p-md.js +1 -0
- package/dist/assets/{timeline-definition-IT6M3QCI-DhFmWcr3.js → timeline-definition-IT6M3QCI-udvNLTe_.js} +1 -1
- package/dist/assets/{treemap-GDKQZRPO-CtMKB2mb.js → treemap-GDKQZRPO-BSvoMl74.js} +1 -1
- package/dist/assets/{xychartDiagram-PRI3JC2R-OhMt1B4k.js → xychartDiagram-PRI3JC2R-CTdy-FCX.js} +1 -1
- package/dist/index.html +2 -7
- package/index.html +0 -5
- package/package.json +1 -1
- package/src/App.tsx +20 -0
- package/src/features/chatkit/pages/public-chat.tsx +7 -3
- package/src/features/workflow/data/templates/candidate-badges.ts +5 -0
- package/src/features/workflow/data/workflow-types.ts +1 -0
- package/src/features/workflow/lib/workflow-storage-api.ts +35 -4
- package/src/features/workflow/lib/workflow-storage-helpers.ts +10 -3
- package/src/features/workflow/lib/workflow-storage.ts +2 -1
- package/src/features/workflow/lib/workflow-storage.types.ts +9 -0
- package/src/features/workflow/pages/workflow-gallery/create-team-dialog.tsx +137 -0
- package/src/features/workflow/pages/workflow-gallery/onboard-team-dialog.tsx +57 -0
- package/src/features/workflow/pages/workflow-gallery/team-section.tsx +58 -0
- package/src/features/workflow/pages/workflow-gallery/use-workflow-gallery-actions.ts +124 -21
- package/src/features/workflow/pages/workflow-gallery/use-workflow-gallery-state.ts +22 -2
- package/src/features/workflow/pages/workflow-gallery/use-workflow-gallery.ts +2 -0
- package/src/features/workflow/pages/workflow-gallery/workflow-card.tsx +26 -5
- package/src/features/workflow/pages/workflow-gallery/workflow-gallery-tabs.tsx +91 -16
- package/src/features/workflow/pages/workflow-gallery.tsx +45 -1
- package/src/lib/api.ts +13 -1
- package/src/lib/workspace-routing.ts +15 -0
- package/src/main.tsx +7 -0
- package/dist/assets/channel-C-DaAO-p.js +0 -1
- package/dist/assets/classDiagram-2ON5EDUG-B2LNkTFb.js +0 -1
- package/dist/assets/classDiagram-v2-WZHVMYZB-B2LNkTFb.js +0 -1
- package/dist/assets/clone-DcGVOYZT.js +0 -1
- package/dist/assets/index-B0_h2wCA.css +0 -1
- package/dist/assets/index-DmIcay1T.js +0 -775
- package/dist/assets/stateDiagram-v2-4FDKWEC3-Cle1H2JX.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
2
|
import { useNavigate } from "react-router-dom";
|
|
3
3
|
import { toast } from "@/hooks/use-toast";
|
|
4
4
|
import {
|
|
@@ -10,14 +10,29 @@ import {
|
|
|
10
10
|
deleteWorkflow,
|
|
11
11
|
onboardCandidateAsWorkflow,
|
|
12
12
|
} from "@features/workflow/lib/workflow-storage";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
type ApiTeam,
|
|
15
|
+
createTeam,
|
|
16
|
+
deleteTeam,
|
|
17
|
+
fetchWorkflowVersions,
|
|
18
|
+
} from "@features/workflow/lib/workflow-storage-api";
|
|
14
19
|
import { getWorkflowRouteRef } from "@features/workflow/lib/workflow-storage-helpers";
|
|
15
20
|
import { getSelectedWorkspaceSlug } from "@/lib/workspace-session";
|
|
16
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
getWorkspaceTeamWorkflowPath,
|
|
23
|
+
getWorkspaceWorkflowPath,
|
|
24
|
+
} from "@/lib/workspace-routing";
|
|
17
25
|
import { type WorkflowGalleryTab } from "./types";
|
|
18
26
|
|
|
19
27
|
interface WorkflowGalleryActionsArgs {
|
|
20
28
|
setSelectedTab: (value: WorkflowGalleryTab) => void;
|
|
29
|
+
teams: ApiTeam[];
|
|
30
|
+
refreshTeams: () => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface OnboardTarget {
|
|
34
|
+
candidateId: string;
|
|
35
|
+
candidateName: string;
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
const STARTER_TEMPLATE_IDS = ["template-python-agent"];
|
|
@@ -81,30 +96,27 @@ export const useWorkflowGalleryActions = (
|
|
|
81
96
|
state: WorkflowGalleryActionsArgs,
|
|
82
97
|
) => {
|
|
83
98
|
const navigate = useNavigate();
|
|
99
|
+
const [onboardTarget, setOnboardTarget] = useState<OnboardTarget | null>(
|
|
100
|
+
null,
|
|
101
|
+
);
|
|
102
|
+
const [isCreateTeamOpen, setIsCreateTeamOpen] = useState(false);
|
|
84
103
|
|
|
85
104
|
const handleOpenWorkflow = useCallback(
|
|
86
|
-
(workflowId: string) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
105
|
+
(workflowId: string, teamSlug?: string) => {
|
|
106
|
+
const slug = getSelectedWorkspaceSlug();
|
|
107
|
+
const path =
|
|
108
|
+
teamSlug
|
|
109
|
+
? getWorkspaceTeamWorkflowPath(slug, teamSlug, workflowId)
|
|
110
|
+
: getWorkspaceWorkflowPath(slug, workflowId);
|
|
111
|
+
navigate(path);
|
|
90
112
|
},
|
|
91
113
|
[navigate],
|
|
92
114
|
);
|
|
93
115
|
|
|
94
|
-
const
|
|
95
|
-
async (
|
|
116
|
+
const performOnboard = useCallback(
|
|
117
|
+
async (candidateId: string, teamId?: string) => {
|
|
96
118
|
try {
|
|
97
|
-
const
|
|
98
|
-
if (!badge?.candidateId) {
|
|
99
|
-
toast({
|
|
100
|
-
title: "Candidate unavailable",
|
|
101
|
-
description: "We couldn't find that candidate. Please try another.",
|
|
102
|
-
variant: "destructive",
|
|
103
|
-
});
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const workflow = await onboardCandidateAsWorkflow(badge.candidateId);
|
|
119
|
+
const workflow = await onboardCandidateAsWorkflow(candidateId, teamId);
|
|
108
120
|
|
|
109
121
|
state.setSelectedTab("all");
|
|
110
122
|
|
|
@@ -113,7 +125,8 @@ export const useWorkflowGalleryActions = (
|
|
|
113
125
|
description: `"${workflow.name}" has been onboarded to your workspace.`,
|
|
114
126
|
});
|
|
115
127
|
|
|
116
|
-
|
|
128
|
+
const team = teamId ? state.teams.find((t) => t.id === teamId) : null;
|
|
129
|
+
handleOpenWorkflow(getWorkflowRouteRef(workflow), team?.slug);
|
|
117
130
|
} catch (error) {
|
|
118
131
|
toast({
|
|
119
132
|
title: "Failed to onboard candidate",
|
|
@@ -126,6 +139,48 @@ export const useWorkflowGalleryActions = (
|
|
|
126
139
|
[handleOpenWorkflow, state],
|
|
127
140
|
);
|
|
128
141
|
|
|
142
|
+
const handleUseTemplate = useCallback(
|
|
143
|
+
async (templateId: string) => {
|
|
144
|
+
const badge = getCandidateBadgeDefinition(templateId);
|
|
145
|
+
if (!badge?.candidateId) {
|
|
146
|
+
toast({
|
|
147
|
+
title: "Candidate unavailable",
|
|
148
|
+
description: "We couldn't find that candidate. Please try another.",
|
|
149
|
+
variant: "destructive",
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// With more than one team, ask which team to onboard into. Otherwise the
|
|
155
|
+
// server places the colleague in the workspace's default team.
|
|
156
|
+
if (state.teams.length > 1) {
|
|
157
|
+
setOnboardTarget({
|
|
158
|
+
candidateId: badge.candidateId,
|
|
159
|
+
candidateName: badge.name ?? badge.candidateId,
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
await performOnboard(badge.candidateId);
|
|
165
|
+
},
|
|
166
|
+
[performOnboard, state.teams],
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const confirmOnboardTeam = useCallback(
|
|
170
|
+
async (teamId: string) => {
|
|
171
|
+
const target = onboardTarget;
|
|
172
|
+
setOnboardTarget(null);
|
|
173
|
+
if (target) {
|
|
174
|
+
await performOnboard(target.candidateId, teamId);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
[onboardTarget, performOnboard],
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const cancelOnboardTeam = useCallback(() => {
|
|
181
|
+
setOnboardTarget(null);
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
129
184
|
const handleImportStarterPack = useCallback(async () => {
|
|
130
185
|
try {
|
|
131
186
|
const results = await Promise.allSettled(
|
|
@@ -193,6 +248,40 @@ export const useWorkflowGalleryActions = (
|
|
|
193
248
|
}
|
|
194
249
|
}, []);
|
|
195
250
|
|
|
251
|
+
const handleCreateTeam = useCallback(
|
|
252
|
+
async (name: string, slug: string) => {
|
|
253
|
+
await createTeam({ name, slug });
|
|
254
|
+
await state.refreshTeams();
|
|
255
|
+
toast({
|
|
256
|
+
title: "Team created",
|
|
257
|
+
description: `"${name}" is ready to group colleagues.`,
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
[state],
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const handleDeleteTeam = useCallback(
|
|
264
|
+
async (teamId: string) => {
|
|
265
|
+
const team = state.teams.find((t) => t.id === teamId);
|
|
266
|
+
try {
|
|
267
|
+
await deleteTeam(teamId);
|
|
268
|
+
await state.refreshTeams();
|
|
269
|
+
toast({
|
|
270
|
+
title: "Team removed",
|
|
271
|
+
description: team ? `"${team.name}" has been removed.` : undefined,
|
|
272
|
+
});
|
|
273
|
+
} catch (error) {
|
|
274
|
+
toast({
|
|
275
|
+
title: "Cannot remove team",
|
|
276
|
+
description:
|
|
277
|
+
error instanceof Error ? error.message : "Unknown error occurred",
|
|
278
|
+
variant: "destructive",
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
[state],
|
|
283
|
+
);
|
|
284
|
+
|
|
196
285
|
const handleDeleteWorkflow = useCallback(
|
|
197
286
|
async (workflowId: string, workflowName: string) => {
|
|
198
287
|
try {
|
|
@@ -213,11 +302,25 @@ export const useWorkflowGalleryActions = (
|
|
|
213
302
|
[],
|
|
214
303
|
);
|
|
215
304
|
|
|
305
|
+
const openCreateTeamDialog = useCallback(() => setIsCreateTeamOpen(true), []);
|
|
306
|
+
const closeCreateTeamDialog = useCallback(
|
|
307
|
+
() => setIsCreateTeamOpen(false),
|
|
308
|
+
[],
|
|
309
|
+
);
|
|
310
|
+
|
|
216
311
|
return {
|
|
217
312
|
handleOpenWorkflow,
|
|
218
313
|
handleUseTemplate,
|
|
219
314
|
handleImportStarterPack,
|
|
220
315
|
handleExportWorkflow,
|
|
221
316
|
handleDeleteWorkflow,
|
|
317
|
+
onboardTarget,
|
|
318
|
+
confirmOnboardTeam,
|
|
319
|
+
cancelOnboardTeam,
|
|
320
|
+
isCreateTeamOpen,
|
|
321
|
+
openCreateTeamDialog,
|
|
322
|
+
closeCreateTeamDialog,
|
|
323
|
+
handleCreateTeam,
|
|
324
|
+
handleDeleteTeam,
|
|
222
325
|
};
|
|
223
326
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { toast } from "@/hooks/use-toast";
|
|
3
3
|
import {
|
|
4
4
|
GALLERY_TEMPLATE_WORKFLOWS,
|
|
@@ -16,7 +16,9 @@ import {
|
|
|
16
16
|
} from "@features/workflow/lib/workflow-storage";
|
|
17
17
|
import {
|
|
18
18
|
type ApiCandidate,
|
|
19
|
+
type ApiTeam,
|
|
19
20
|
fetchCandidates,
|
|
21
|
+
fetchTeams,
|
|
20
22
|
} from "@features/workflow/lib/workflow-storage-api";
|
|
21
23
|
import {
|
|
22
24
|
type WorkflowGalleryTab,
|
|
@@ -33,6 +35,8 @@ interface WorkflowGalleryStateSlice {
|
|
|
33
35
|
tabCounts: WorkflowGalleryTabCounts;
|
|
34
36
|
isTemplateView: boolean;
|
|
35
37
|
templates: Workflow[];
|
|
38
|
+
teams: ApiTeam[];
|
|
39
|
+
refreshTeams: () => Promise<void>;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
const toCandidateSpec = (candidate: ApiCandidate): CandidateBadgeSpec => ({
|
|
@@ -63,6 +67,7 @@ const matchesWorkflowSearch = (
|
|
|
63
67
|
export const useWorkflowGalleryState = (): WorkflowGalleryStateSlice => {
|
|
64
68
|
const [workflows, setWorkflows] = useState<StoredWorkflow[]>([]);
|
|
65
69
|
const [candidateWorkflows, setCandidateWorkflows] = useState<Workflow[]>([]);
|
|
70
|
+
const [teams, setTeams] = useState<ApiTeam[]>([]);
|
|
66
71
|
const [isLoadingWorkflows, setIsLoadingWorkflows] = useState(true);
|
|
67
72
|
const [searchQuery, setSearchQuery] = useState("");
|
|
68
73
|
const [selectedTab, setSelectedTab] = useState<WorkflowGalleryTab>("all");
|
|
@@ -96,6 +101,15 @@ export const useWorkflowGalleryState = (): WorkflowGalleryStateSlice => {
|
|
|
96
101
|
};
|
|
97
102
|
}, []);
|
|
98
103
|
|
|
104
|
+
const refreshTeams = useCallback(async () => {
|
|
105
|
+
try {
|
|
106
|
+
const teamList = await fetchTeams();
|
|
107
|
+
setTeams(teamList);
|
|
108
|
+
} catch {
|
|
109
|
+
// non-fatal — teams list will stay stale
|
|
110
|
+
}
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
99
113
|
useEffect(() => {
|
|
100
114
|
let isMounted = true;
|
|
101
115
|
|
|
@@ -104,9 +118,13 @@ export const useWorkflowGalleryState = (): WorkflowGalleryStateSlice => {
|
|
|
104
118
|
setIsLoadingWorkflows(true);
|
|
105
119
|
}
|
|
106
120
|
try {
|
|
107
|
-
const items = await
|
|
121
|
+
const [items, teamList] = await Promise.all([
|
|
122
|
+
listWorkflows({ forceRefresh }),
|
|
123
|
+
fetchTeams().catch(() => [] as ApiTeam[]),
|
|
124
|
+
]);
|
|
108
125
|
if (isMounted) {
|
|
109
126
|
setWorkflows(items);
|
|
127
|
+
setTeams(teamList);
|
|
110
128
|
}
|
|
111
129
|
} catch (error) {
|
|
112
130
|
if (!isMounted) {
|
|
@@ -202,5 +220,7 @@ export const useWorkflowGalleryState = (): WorkflowGalleryStateSlice => {
|
|
|
202
220
|
tabCounts,
|
|
203
221
|
isTemplateView,
|
|
204
222
|
templates,
|
|
223
|
+
teams,
|
|
224
|
+
refreshTeams,
|
|
205
225
|
};
|
|
206
226
|
};
|
|
@@ -19,7 +19,10 @@ import {
|
|
|
19
19
|
import { cn } from "@/lib/utils";
|
|
20
20
|
import { resolveAvatarUrl } from "@/assets/avatars";
|
|
21
21
|
import { ConfirmDeleteWorkflowDialog } from "@features/workflow/components/dialogs/confirm-delete-workflow-dialog";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
getCandidateBadgeByHandle,
|
|
24
|
+
getCandidateBadgeDefinition,
|
|
25
|
+
} from "@features/workflow/data/templates/candidate-badges";
|
|
23
26
|
import { type Workflow } from "@features/workflow/data/workflow-data";
|
|
24
27
|
import { getWorkflowRouteRef } from "@features/workflow/lib/workflow-storage-helpers";
|
|
25
28
|
import { WORKFLOW_GALLERY_CARD_ASPECT_CLASSNAME } from "./workflow-card-size";
|
|
@@ -37,6 +40,7 @@ const getWorkflowTemplateAvatar = (workflow: Workflow): string | undefined => {
|
|
|
37
40
|
interface WorkflowCardProps {
|
|
38
41
|
workflow: Workflow;
|
|
39
42
|
isTemplate: boolean;
|
|
43
|
+
teamSlug?: string;
|
|
40
44
|
workspaceLabel: string;
|
|
41
45
|
onOpenWorkflow: (workflowId: string) => void;
|
|
42
46
|
onUseTemplate: (workflowId: string) => void;
|
|
@@ -50,6 +54,7 @@ interface WorkflowCardProps {
|
|
|
50
54
|
export const WorkflowCard = ({
|
|
51
55
|
workflow,
|
|
52
56
|
isTemplate,
|
|
57
|
+
teamSlug,
|
|
53
58
|
workspaceLabel,
|
|
54
59
|
onOpenWorkflow,
|
|
55
60
|
onUseTemplate,
|
|
@@ -62,8 +67,24 @@ export const WorkflowCard = ({
|
|
|
62
67
|
const candidateBadge = isTemplate
|
|
63
68
|
? getCandidateBadgeDefinition(workflow.id)
|
|
64
69
|
: undefined;
|
|
70
|
+
const templateId = !isTemplate
|
|
71
|
+
? workflow.versions?.at(-1)?.templateId
|
|
72
|
+
: undefined;
|
|
73
|
+
const templateBadge = !isTemplate
|
|
74
|
+
? (templateId
|
|
75
|
+
? getCandidateBadgeDefinition(templateId)
|
|
76
|
+
: workflow.handle
|
|
77
|
+
? getCandidateBadgeByHandle(workflow.handle)
|
|
78
|
+
: undefined)
|
|
79
|
+
: undefined;
|
|
80
|
+
const displayBadge = candidateBadge ?? templateBadge;
|
|
65
81
|
const headerLabel = isTemplate ? "Candidate" : workspaceLabel;
|
|
66
|
-
const
|
|
82
|
+
const rawHandle = workflow.handle ?? workflow.id;
|
|
83
|
+
const workflowSlug = isTemplate
|
|
84
|
+
? rawHandle.replace(/^template-/, "").replace(/_/g, "-")
|
|
85
|
+
: rawHandle;
|
|
86
|
+
const displayHandle =
|
|
87
|
+
!isTemplate && teamSlug ? `${teamSlug}/${workflowSlug}` : workflowSlug;
|
|
67
88
|
const avatarUrl = resolveAvatarUrl(
|
|
68
89
|
workflow.avatarEmoji ?? getWorkflowTemplateAvatar(workflow),
|
|
69
90
|
workflow.id,
|
|
@@ -271,15 +292,15 @@ export const WorkflowCard = ({
|
|
|
271
292
|
</div>
|
|
272
293
|
|
|
273
294
|
<div className="mt-1 shrink-0 font-mono text-[10px] tracking-[0.04em] text-muted-foreground">
|
|
274
|
-
@{
|
|
295
|
+
@{displayHandle}
|
|
275
296
|
</div>
|
|
276
297
|
|
|
277
298
|
<div className="mt-3 flex min-h-0 w-full flex-1 flex-col items-center justify-start">
|
|
278
299
|
<div className="h-px w-8 rounded-full bg-border/80" />
|
|
279
300
|
|
|
280
|
-
{
|
|
301
|
+
{displayBadge?.subtitle ? (
|
|
281
302
|
<div className="mt-1 shrink-0 font-mono text-[10px] font-semibold uppercase tracking-[0.12em] text-muted-foreground">
|
|
282
|
-
{
|
|
303
|
+
{displayBadge.subtitle}
|
|
283
304
|
</div>
|
|
284
305
|
) : null}
|
|
285
306
|
|
|
@@ -9,9 +9,11 @@ import {
|
|
|
9
9
|
} from "@/design-system/ui/tabs";
|
|
10
10
|
import { Loader2, Search, Upload, Zap } from "lucide-react";
|
|
11
11
|
import { type Workflow } from "@features/workflow/data/workflow-data";
|
|
12
|
+
import { type ApiTeam } from "@features/workflow/lib/workflow-storage-api";
|
|
12
13
|
import { UploadWorkflowDialog } from "@features/workflow/components/dialogs/upload-workflow-dialog";
|
|
13
14
|
import { useUploadsAllowed } from "@/hooks/use-uploads-allowed";
|
|
14
15
|
import { WorkflowCard } from "./workflow-card";
|
|
16
|
+
import { TeamSection } from "./team-section";
|
|
15
17
|
import {
|
|
16
18
|
type WorkflowGalleryTab,
|
|
17
19
|
type WorkflowGalleryTabCounts,
|
|
@@ -24,17 +26,19 @@ interface WorkflowGalleryTabsProps {
|
|
|
24
26
|
sortedWorkflows: Workflow[];
|
|
25
27
|
tabCounts: WorkflowGalleryTabCounts;
|
|
26
28
|
isTemplateView: boolean;
|
|
29
|
+
teams?: ApiTeam[];
|
|
27
30
|
workspaceLabel: string;
|
|
28
31
|
searchQuery: string;
|
|
29
32
|
onSearchQueryChange: (value: string) => void;
|
|
30
33
|
onImportStarterPack: () => void;
|
|
31
|
-
onOpenWorkflow: (workflowId: string) => void;
|
|
34
|
+
onOpenWorkflow: (workflowId: string, teamSlug?: string) => void;
|
|
32
35
|
onUseTemplate: (workflowId: string) => void;
|
|
33
36
|
onExportWorkflow: (workflow: Workflow) => void;
|
|
34
37
|
onDeleteWorkflow: (
|
|
35
38
|
workflowId: string,
|
|
36
39
|
workflowName: string,
|
|
37
40
|
) => Promise<void> | void;
|
|
41
|
+
onDeleteTeam?: (teamId: string) => void;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
export const WorkflowGalleryTabs = ({
|
|
@@ -44,6 +48,7 @@ export const WorkflowGalleryTabs = ({
|
|
|
44
48
|
sortedWorkflows,
|
|
45
49
|
tabCounts,
|
|
46
50
|
isTemplateView,
|
|
51
|
+
teams = [],
|
|
47
52
|
workspaceLabel,
|
|
48
53
|
searchQuery,
|
|
49
54
|
onSearchQueryChange,
|
|
@@ -52,6 +57,7 @@ export const WorkflowGalleryTabs = ({
|
|
|
52
57
|
onUseTemplate,
|
|
53
58
|
onExportWorkflow,
|
|
54
59
|
onDeleteWorkflow,
|
|
60
|
+
onDeleteTeam,
|
|
55
61
|
}: WorkflowGalleryTabsProps) => {
|
|
56
62
|
const [isUploadOpen, setIsUploadOpen] = useState(false);
|
|
57
63
|
const uploadsAllowed = useUploadsAllowed();
|
|
@@ -60,6 +66,87 @@ export const WorkflowGalleryTabs = ({
|
|
|
60
66
|
onSearchQueryChange(event.target.value);
|
|
61
67
|
};
|
|
62
68
|
|
|
69
|
+
const renderGrid = (items: Workflow[], teamSlug?: string) => (
|
|
70
|
+
<div className="grid grid-cols-1 gap-3 pb-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6">
|
|
71
|
+
{items.map((workflow) => (
|
|
72
|
+
<WorkflowCard
|
|
73
|
+
key={workflow.id}
|
|
74
|
+
workflow={workflow}
|
|
75
|
+
isTemplate={isTemplateView}
|
|
76
|
+
teamSlug={teamSlug}
|
|
77
|
+
workspaceLabel={workspaceLabel}
|
|
78
|
+
onOpenWorkflow={(id) => onOpenWorkflow(id, teamSlug)}
|
|
79
|
+
onUseTemplate={onUseTemplate}
|
|
80
|
+
onExportWorkflow={onExportWorkflow}
|
|
81
|
+
onDeleteWorkflow={onDeleteWorkflow}
|
|
82
|
+
/>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Group colleagues into vertical, collapsible team sections. Candidate
|
|
88
|
+
// (template) view stays flat since candidates are not yet assigned a team.
|
|
89
|
+
const renderColleagues = () => {
|
|
90
|
+
if (isTemplateView) {
|
|
91
|
+
return renderGrid(sortedWorkflows);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// No teams yet — fall back to flat grid (e.g. during first load).
|
|
95
|
+
if (teams.length === 0) {
|
|
96
|
+
return renderGrid(sortedWorkflows);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const byTeam = new Map<string, Workflow[]>();
|
|
100
|
+
for (const workflow of sortedWorkflows) {
|
|
101
|
+
const key = workflow.teamId ?? "__none__";
|
|
102
|
+
const bucket = byTeam.get(key);
|
|
103
|
+
if (bucket) {
|
|
104
|
+
bucket.push(workflow);
|
|
105
|
+
} else {
|
|
106
|
+
byTeam.set(key, [workflow]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// All known teams, in default-first order from the server, then any
|
|
111
|
+
// orphaned workflows whose team no longer appears in the list.
|
|
112
|
+
const leftovers = [...byTeam.keys()].filter(
|
|
113
|
+
(key) => key !== "__none__" && !teams.some((t) => t.id === key),
|
|
114
|
+
);
|
|
115
|
+
const sections = [
|
|
116
|
+
...teams.map((team) => ({
|
|
117
|
+
key: team.id,
|
|
118
|
+
name: team.name,
|
|
119
|
+
slug: team.slug,
|
|
120
|
+
})),
|
|
121
|
+
...leftovers.map((key) => ({ key, name: "Other", slug: undefined })),
|
|
122
|
+
...(byTeam.has("__none__")
|
|
123
|
+
? [{ key: "__none__", name: "Ungrouped", slug: undefined }]
|
|
124
|
+
: []),
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="flex flex-col gap-1 pb-6">
|
|
129
|
+
{sections.map((section) => {
|
|
130
|
+
const items = byTeam.get(section.key) ?? [];
|
|
131
|
+
return (
|
|
132
|
+
<TeamSection
|
|
133
|
+
key={section.key}
|
|
134
|
+
name={section.name}
|
|
135
|
+
count={items.length}
|
|
136
|
+
onRemove={
|
|
137
|
+
onDeleteTeam && section.slug !== undefined
|
|
138
|
+
? () => onDeleteTeam(section.key)
|
|
139
|
+
: undefined
|
|
140
|
+
}
|
|
141
|
+
>
|
|
142
|
+
{items.length > 0 ? renderGrid(items, section.slug) : null}
|
|
143
|
+
</TeamSection>
|
|
144
|
+
);
|
|
145
|
+
})}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
63
150
|
return (
|
|
64
151
|
<Tabs
|
|
65
152
|
value={selectedTab}
|
|
@@ -132,7 +219,8 @@ export const WorkflowGalleryTabs = ({
|
|
|
132
219
|
</p>
|
|
133
220
|
</div>
|
|
134
221
|
</div>
|
|
135
|
-
) : sortedWorkflows.length === 0
|
|
222
|
+
) : sortedWorkflows.length === 0 &&
|
|
223
|
+
(isTemplateView || teams.length === 0) ? (
|
|
136
224
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
137
225
|
<div className="mb-4 rounded-full bg-muted p-4">
|
|
138
226
|
<Zap className="h-8 w-8 text-muted-foreground" />
|
|
@@ -152,20 +240,7 @@ export const WorkflowGalleryTabs = ({
|
|
|
152
240
|
</div>
|
|
153
241
|
</div>
|
|
154
242
|
) : (
|
|
155
|
-
|
|
156
|
-
{sortedWorkflows.map((workflow) => (
|
|
157
|
-
<WorkflowCard
|
|
158
|
-
key={workflow.id}
|
|
159
|
-
workflow={workflow}
|
|
160
|
-
isTemplate={isTemplateView}
|
|
161
|
-
workspaceLabel={workspaceLabel}
|
|
162
|
-
onOpenWorkflow={onOpenWorkflow}
|
|
163
|
-
onUseTemplate={onUseTemplate}
|
|
164
|
-
onExportWorkflow={onExportWorkflow}
|
|
165
|
-
onDeleteWorkflow={onDeleteWorkflow}
|
|
166
|
-
/>
|
|
167
|
-
))}
|
|
168
|
-
</div>
|
|
243
|
+
renderColleagues()
|
|
169
244
|
)}
|
|
170
245
|
</TabsContent>
|
|
171
246
|
</Tabs>
|
|
@@ -3,7 +3,11 @@ import TopNavigation from "@features/shared/components/top-navigation";
|
|
|
3
3
|
import { getActiveWorkspace } from "@/lib/api";
|
|
4
4
|
import useCredentialVault from "@/hooks/use-credential-vault";
|
|
5
5
|
import { usePageContext } from "@/hooks/use-page-context";
|
|
6
|
+
import { Button } from "@/design-system/ui/button";
|
|
7
|
+
import { Plus } from "lucide-react";
|
|
6
8
|
import { WorkflowGalleryTabs } from "@/features/workflow/pages/workflow-gallery/workflow-gallery-tabs";
|
|
9
|
+
import { OnboardTeamDialog } from "@/features/workflow/pages/workflow-gallery/onboard-team-dialog";
|
|
10
|
+
import { CreateTeamDialog } from "@/features/workflow/pages/workflow-gallery/create-team-dialog";
|
|
7
11
|
import { useWorkflowGallery } from "@/features/workflow/pages/workflow-gallery/use-workflow-gallery";
|
|
8
12
|
|
|
9
13
|
export default function WorkflowGallery() {
|
|
@@ -53,11 +57,20 @@ export default function WorkflowGallery() {
|
|
|
53
57
|
sortedWorkflows,
|
|
54
58
|
tabCounts,
|
|
55
59
|
isTemplateView,
|
|
60
|
+
teams,
|
|
56
61
|
handleUseTemplate,
|
|
57
62
|
handleImportStarterPack,
|
|
58
63
|
handleExportWorkflow,
|
|
59
64
|
handleDeleteWorkflow,
|
|
60
65
|
handleOpenWorkflow,
|
|
66
|
+
onboardTarget,
|
|
67
|
+
confirmOnboardTeam,
|
|
68
|
+
cancelOnboardTeam,
|
|
69
|
+
isCreateTeamOpen,
|
|
70
|
+
openCreateTeamDialog,
|
|
71
|
+
closeCreateTeamDialog,
|
|
72
|
+
handleCreateTeam,
|
|
73
|
+
handleDeleteTeam,
|
|
61
74
|
} = useWorkflowGallery();
|
|
62
75
|
|
|
63
76
|
return (
|
|
@@ -71,7 +84,7 @@ export default function WorkflowGallery() {
|
|
|
71
84
|
onRevealCredentialSecret={onRevealCredentialSecret}
|
|
72
85
|
/>
|
|
73
86
|
|
|
74
|
-
<main className="flex flex-1 min-h-0 flex-col overflow-hidden">
|
|
87
|
+
<main className="relative flex flex-1 min-h-0 flex-col overflow-hidden">
|
|
75
88
|
<div className="flex-1 overflow-auto">
|
|
76
89
|
<WorkflowGalleryTabs
|
|
77
90
|
selectedTab={selectedTab}
|
|
@@ -80,6 +93,7 @@ export default function WorkflowGallery() {
|
|
|
80
93
|
sortedWorkflows={sortedWorkflows}
|
|
81
94
|
tabCounts={tabCounts}
|
|
82
95
|
isTemplateView={isTemplateView}
|
|
96
|
+
teams={teams}
|
|
83
97
|
workspaceLabel={workspaceLabel}
|
|
84
98
|
searchQuery={searchQuery}
|
|
85
99
|
onSearchQueryChange={setSearchQuery}
|
|
@@ -88,9 +102,39 @@ export default function WorkflowGallery() {
|
|
|
88
102
|
onUseTemplate={handleUseTemplate}
|
|
89
103
|
onExportWorkflow={handleExportWorkflow}
|
|
90
104
|
onDeleteWorkflow={handleDeleteWorkflow}
|
|
105
|
+
onDeleteTeam={handleDeleteTeam}
|
|
91
106
|
/>
|
|
92
107
|
</div>
|
|
108
|
+
|
|
109
|
+
{!isTemplateView && (
|
|
110
|
+
<div className="absolute bottom-4 left-4">
|
|
111
|
+
<Button variant="outline" size="sm" onClick={openCreateTeamDialog}>
|
|
112
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
113
|
+
New team
|
|
114
|
+
</Button>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
93
117
|
</main>
|
|
118
|
+
|
|
119
|
+
<CreateTeamDialog
|
|
120
|
+
open={isCreateTeamOpen}
|
|
121
|
+
onOpenChange={(open) => {
|
|
122
|
+
if (!open) closeCreateTeamDialog();
|
|
123
|
+
}}
|
|
124
|
+
onCreate={handleCreateTeam}
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
<OnboardTeamDialog
|
|
128
|
+
open={onboardTarget !== null}
|
|
129
|
+
candidateName={onboardTarget?.candidateName ?? ""}
|
|
130
|
+
teams={teams}
|
|
131
|
+
onSelect={confirmOnboardTeam}
|
|
132
|
+
onOpenChange={(open) => {
|
|
133
|
+
if (!open) {
|
|
134
|
+
cancelOnboardTeam();
|
|
135
|
+
}
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
94
138
|
</div>
|
|
95
139
|
);
|
|
96
140
|
}
|
package/src/lib/api.ts
CHANGED
|
@@ -244,7 +244,19 @@ async function requestSystemJson<T>(
|
|
|
244
244
|
const errorData = await response.json().catch(() => ({
|
|
245
245
|
detail: "Failed to complete request",
|
|
246
246
|
}));
|
|
247
|
-
|
|
247
|
+
const detail = errorData.detail;
|
|
248
|
+
let message: string;
|
|
249
|
+
if (typeof detail === "string") {
|
|
250
|
+
message = detail;
|
|
251
|
+
} else if (detail && typeof detail === "object" && "error" in detail) {
|
|
252
|
+
const err = detail.error as Record<string, unknown>;
|
|
253
|
+
message = typeof err.message === "string" ? err.message : `HTTP ${response.status}`;
|
|
254
|
+
} else if (Array.isArray(detail)) {
|
|
255
|
+
message = detail.map((e: Record<string, unknown>) => e.msg ?? JSON.stringify(e)).join("; ");
|
|
256
|
+
} else {
|
|
257
|
+
message = `HTTP ${response.status}`;
|
|
258
|
+
}
|
|
259
|
+
throw new Error(message);
|
|
248
260
|
}
|
|
249
261
|
|
|
250
262
|
return response.json();
|
|
@@ -53,6 +53,21 @@ export const getWorkspaceWorkflowPath = (
|
|
|
53
53
|
return slug ? `/${slug}/${ref}` : `/${ref}`;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
+
export const getWorkspaceTeamWorkflowPath = (
|
|
57
|
+
workspaceSlug: string | null | undefined,
|
|
58
|
+
teamSlug: string,
|
|
59
|
+
workflowRef: string,
|
|
60
|
+
): string => {
|
|
61
|
+
const slug = trimPathSegment(workspaceSlug);
|
|
62
|
+
const ref = workflowRef.trim();
|
|
63
|
+
if (!ref) {
|
|
64
|
+
return getWorkspaceGalleryPath(slug);
|
|
65
|
+
}
|
|
66
|
+
return slug
|
|
67
|
+
? `/${slug}/team/${teamSlug}/${ref}`
|
|
68
|
+
: `/team/${teamSlug}/${ref}`;
|
|
69
|
+
};
|
|
70
|
+
|
|
56
71
|
export const getWorkspaceSlugFromPathname = (
|
|
57
72
|
pathname: string,
|
|
58
73
|
): string | null => {
|
package/src/main.tsx
CHANGED
|
@@ -3,8 +3,15 @@ import * as ReactDOM from "react-dom";
|
|
|
3
3
|
import { createRoot } from "react-dom/client";
|
|
4
4
|
import App from "./App";
|
|
5
5
|
import "./index.css";
|
|
6
|
+
import { buildBackendHttpUrl } from "./lib/config";
|
|
6
7
|
import { initializeTheme, watchSystemTheme } from "./lib/theme";
|
|
7
8
|
|
|
9
|
+
const chatkitScript = document.createElement("script");
|
|
10
|
+
chatkitScript.src = buildBackendHttpUrl("/api/chatkit/assets/chatkit.js");
|
|
11
|
+
chatkitScript.async = true;
|
|
12
|
+
chatkitScript.crossOrigin = "anonymous";
|
|
13
|
+
document.head.appendChild(chatkitScript);
|
|
14
|
+
|
|
8
15
|
// Initialize theme before React renders to prevent flash of wrong theme
|
|
9
16
|
initializeTheme();
|
|
10
17
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{ap as o,aq as n}from"./index-DmIcay1T.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-Duatgdyr.js";import{_ as i}from"./index-DmIcay1T.js";import"./chunk-FMBD7UC4-C07lS0oH.js";import"./chunk-55IACEB6-BTpqJ_MA.js";import"./chunk-QN33PNHL-C09PhK0w.js";var p={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{p as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-Duatgdyr.js";import{_ as i}from"./index-DmIcay1T.js";import"./chunk-FMBD7UC4-C07lS0oH.js";import"./chunk-55IACEB6-BTpqJ_MA.js";import"./chunk-QN33PNHL-C09PhK0w.js";var p={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{p as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{ay as r}from"./index-DmIcay1T.js";var a=4;function n(o){return r(o,a)}export{n as c};
|