orcheo-studio 0.24.4 → 0.24.6
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-99TH-Koz.js → arc-CJePtrV4.js} +1 -1
- package/dist/assets/{architectureDiagram-VXUJARFQ-DugaTSY2.js → architectureDiagram-VXUJARFQ-BavAz0jp.js} +1 -1
- package/dist/assets/{blockDiagram-VD42YOAC-BH68wVtD.js → blockDiagram-VD42YOAC-Bx1XAydH.js} +1 -1
- package/dist/assets/{c4Diagram-YG6GDRKO-V6Vqtp7B.js → c4Diagram-YG6GDRKO-B2LhByin.js} +1 -1
- package/dist/assets/channel-BuDj-lWJ.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-CvbCAMNv.js → chunk-4BX2VUAB-UqiH5dLp.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-h_T3kQh7.js → chunk-55IACEB6-ChxTr_6v.js} +1 -1
- package/dist/assets/{chunk-B4BG7PRW-Dm6dgbGh.js → chunk-B4BG7PRW-2ETw-eA1.js} +1 -1
- package/dist/assets/{chunk-DI55MBZ5-DSXFA4Fi.js → chunk-DI55MBZ5-BdxVSKej.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DJDlfjt1.js → chunk-FMBD7UC4-BJF2M5iQ.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-BnjUGeBL.js → chunk-QN33PNHL-DI9t3nKL.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BRudOghZ.js → chunk-QZHKN3VN-DsbUIBgB.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-CpxSpvqK.js → chunk-TZMSLE5B-DzMhWfBV.js} +1 -1
- package/dist/assets/classDiagram-2ON5EDUG-Bc5AzWzU.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-Bc5AzWzU.js +1 -0
- package/dist/assets/clone-CtxIENQC.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-Dl80WZ_a.js → cose-bilkent-S5V4N54A-BcKKbLWz.js} +1 -1
- package/dist/assets/{dagre-6UL2VRFP-vw-vkw78.js → dagre-6UL2VRFP-CERJ9KSL.js} +1 -1
- package/dist/assets/{diagram-PSM6KHXK-B4MT5Nvf.js → diagram-PSM6KHXK-DGl--Cyq.js} +1 -1
- package/dist/assets/{diagram-QEK2KX5R-CMioZnRI.js → diagram-QEK2KX5R-DbfdELCd.js} +1 -1
- package/dist/assets/{diagram-S2PKOQOG-PJAE8YS0.js → diagram-S2PKOQOG-CsR4YAm5.js} +1 -1
- package/dist/assets/{erDiagram-Q2GNP2WA-DCd-WouG.js → erDiagram-Q2GNP2WA-BlOr_gpI.js} +1 -1
- package/dist/assets/{flowDiagram-NV44I4VS-DS__-WT4.js → flowDiagram-NV44I4VS-BAyDXjxF.js} +1 -1
- package/dist/assets/{ganttDiagram-JELNMOA3-Btifh-31.js → ganttDiagram-JELNMOA3-b2ntam6s.js} +1 -1
- package/dist/assets/{gitGraphDiagram-V2S2FVAM-BCyjopey.js → gitGraphDiagram-V2S2FVAM-BvdeZrga.js} +1 -1
- package/dist/assets/{graph-6YBx8iQ0.js → graph-DMrtDwYQ.js} +1 -1
- package/dist/assets/{index-lkycEDHM.js → index-CaWVBOSf.js} +204 -189
- package/dist/assets/index-CbLxL6va.css +1 -0
- package/dist/assets/{infoDiagram-HS3SLOUP-CAsJ2eU1.js → infoDiagram-HS3SLOUP-CFO6LsEb.js} +1 -1
- package/dist/assets/{isUndefined-CcAlyXn3.js → isUndefined-DE2VOYrW.js} +1 -1
- package/dist/assets/{journeyDiagram-XKPGCS4Q-Bi8XsX15.js → journeyDiagram-XKPGCS4Q-Svi8cltR.js} +1 -1
- package/dist/assets/{kanban-definition-3W4ZIXB7-PTAFVsDn.js → kanban-definition-3W4ZIXB7-C7uds03_.js} +1 -1
- package/dist/assets/{layout-Do-GOFdW.js → layout-D5N_DnHw.js} +1 -1
- package/dist/assets/{linear-CZlgolw0.js → linear-h9-K1RBq.js} +1 -1
- package/dist/assets/{min-N15IYW1Y.js → min-CjtZYucR.js} +1 -1
- package/dist/assets/{mindmap-definition-VGOIOE7T-Dlgxv6Jo.js → mindmap-definition-VGOIOE7T-Bw80_6TW.js} +1 -1
- package/dist/assets/{pieDiagram-ADFJNKIX-Dr2AfIw5.js → pieDiagram-ADFJNKIX-DGukFJSP.js} +1 -1
- package/dist/assets/{quadrantDiagram-AYHSOK5B-B8TPuvW6.js → quadrantDiagram-AYHSOK5B-CU7GPi0P.js} +1 -1
- package/dist/assets/{requirementDiagram-UZGBJVZJ-CdD2oP1N.js → requirementDiagram-UZGBJVZJ-D_aCZrji.js} +1 -1
- package/dist/assets/{sankeyDiagram-TZEHDZUN-XEMX6l5k.js → sankeyDiagram-TZEHDZUN-Wgtprjih.js} +1 -1
- package/dist/assets/{sequenceDiagram-WL72ISMW-BlIKpBev.js → sequenceDiagram-WL72ISMW-BCOCCVxS.js} +1 -1
- package/dist/assets/{stateDiagram-FKZM4ZOC-D3KGQY6v.js → stateDiagram-FKZM4ZOC-DHVe22f9.js} +1 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-Bq9eQr8D.js +1 -0
- package/dist/assets/{timeline-definition-IT6M3QCI-TkL4mz_f.js → timeline-definition-IT6M3QCI-Cm4CAEQk.js} +1 -1
- package/dist/assets/{treemap-GDKQZRPO-BDp_c1Vc.js → treemap-GDKQZRPO-DvLhDp9r.js} +1 -1
- package/dist/assets/{xychartDiagram-PRI3JC2R-D0dNQImL.js → xychartDiagram-PRI3JC2R-BtcJYIPe.js} +1 -1
- package/dist/index.html +8 -2
- package/index.html +6 -0
- package/package.json +1 -1
- package/src/App.tsx +48 -44
- package/src/design-system/ui/button.tsx +1 -1
- package/src/features/account/components/settings/appearance-settings-tab.tsx +1 -1
- package/src/features/account/pages/profile/components/profile-general-tab.tsx +1 -1
- package/src/features/account/pages/workspace-members.tsx +58 -62
- package/src/features/auth/pages/login.tsx +4 -2
- package/src/features/auth/pages/verify.tsx +1 -1
- package/src/features/chatkit/components/public-chat-error-boundary.tsx +1 -1
- package/src/features/chatkit/components/public-chat-widget.tsx +2 -3
- package/src/features/chatkit/lib/chatkit-theme.ts +3 -2
- package/src/features/chatkit/lib/public-chat-access.ts +13 -0
- package/src/features/chatkit/pages/public-chat.tsx +18 -27
- package/src/features/shared/components/chat-interface.types.ts +1 -5
- package/src/features/shared/components/top-navigation/studio-brand.tsx +1 -1
- package/src/features/shared/components/top-navigation/version-status.tsx +27 -31
- package/src/features/shared/components/workspace-bootstrap-gate.tsx +4 -4
- package/src/features/workflow/components/dialogs/credential-access-badge.tsx +2 -2
- package/src/features/workflow/components/dialogs/credential-status-badge.tsx +2 -2
- package/src/features/workflow/components/dialogs/credentials-table.tsx +1 -1
- package/src/features/workflow/components/dialogs/update-workflow-dialog.tsx +34 -78
- package/src/features/workflow/components/dialogs/upload-workflow-dialog.tsx +40 -136
- package/src/features/workflow/components/dialogs/workflow-folder-picker.tsx +178 -0
- package/src/features/workflow/components/panels/rjsf-cron-widget.tsx +35 -10
- package/src/features/workflow/components/panels/workflow-diff-dialog.tsx +5 -5
- package/src/features/workflow/components/panels/workflow-history-table.tsx +3 -4
- package/src/features/workflow/components/trace/agent-prism/SpanCard/SpanCardConnector.tsx +1 -5
- package/src/features/workflow/components/trace/agent-prism/shared.ts +1 -10
- package/src/features/workflow/components/trace/agent-prism/theme/theme.css +74 -74
- package/src/features/workflow/lib/workflow-execution-builders.ts +4 -4
- package/src/features/workflow/lib/workflow-execution.types.ts +2 -9
- package/src/features/workflow/lib/workflow-folder-selection.ts +239 -0
- package/src/features/workflow/lib/workflow-frontmatter.ts +3 -1
- package/src/features/workflow/lib/workflow-storage-api.ts +5 -9
- package/src/features/workflow/lib/workflow-storage-helpers.ts +2 -4
- package/src/features/workflow/lib/workflow-storage.ts +6 -2
- package/src/features/workflow/lib/workflow-storage.types.ts +1 -6
- package/src/features/workflow/lib/workflow-upload-errors.ts +6 -2
- package/src/features/workflow/pages/workflow/components/settings-tab-content.tsx +24 -9
- package/src/features/workflow/pages/workflow/components/workflow-tab-content.tsx +6 -4
- package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-execution-controller.ts +4 -1
- package/src/features/workflow/pages/workflow/hooks/use-execution-updates.ts +4 -2
- package/src/features/workflow/pages/workflow-gallery/create-team-dialog.tsx +2 -6
- package/src/features/workflow/pages/workflow-gallery/workflow-card.tsx +16 -16
- package/src/index.css +101 -59
- package/src/lib/api.ts +7 -2
- package/src/lib/workspace-routing.ts +1 -3
- package/tailwind.config.js +47 -2
- package/dist/assets/channel-D562bZxL.js +0 -1
- package/dist/assets/classDiagram-2ON5EDUG-Dix_KnuM.js +0 -1
- package/dist/assets/classDiagram-v2-WZHVMYZB-Dix_KnuM.js +0 -1
- package/dist/assets/clone-0c2Tu-SH.js +0 -1
- package/dist/assets/index-Df4jFRhd.css +0 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-Di3Nkv2n.js +0 -1
|
@@ -44,17 +44,6 @@ const parseCache = (raw: string | null): SystemInfoCachePayload | null => {
|
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
const shouldRefresh = (cache: SystemInfoCachePayload | null): boolean => {
|
|
48
|
-
if (!cache) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
const lastCheckedAt = Date.parse(cache.checkedAt);
|
|
52
|
-
if (Number.isNaN(lastCheckedAt)) {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
return Date.now() - lastCheckedAt >= UPDATE_CHECK_TTL_MS;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
47
|
const parseSemver = (value: string | null | undefined): ParsedSemver | null => {
|
|
59
48
|
if (!value) {
|
|
60
49
|
return null;
|
|
@@ -152,7 +141,10 @@ const isDismissed = (): boolean => {
|
|
|
152
141
|
};
|
|
153
142
|
|
|
154
143
|
export default function VersionStatus() {
|
|
155
|
-
const [
|
|
144
|
+
const [cachedInfo, setCachedInfo] = useState<SystemInfoResponse | null>(null);
|
|
145
|
+
const [liveBackendVersion, setLiveBackendVersion] = useState<string | null>(
|
|
146
|
+
null,
|
|
147
|
+
);
|
|
156
148
|
const [dismissedReminder, setDismissedReminder] = useState<boolean>(() =>
|
|
157
149
|
isDismissed(),
|
|
158
150
|
);
|
|
@@ -163,11 +155,12 @@ export default function VersionStatus() {
|
|
|
163
155
|
window.localStorage.getItem(UPDATE_CHECK_CACHE_KEY),
|
|
164
156
|
);
|
|
165
157
|
if (cache) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
158
|
+
setCachedInfo(cache.payload);
|
|
159
|
+
setLiveBackendVersion(cache.payload.backend.current_version);
|
|
160
|
+
const cacheAge = Date.now() - Date.parse(cache.checkedAt);
|
|
161
|
+
if (cacheAge < UPDATE_CHECK_TTL_MS) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
171
164
|
}
|
|
172
165
|
|
|
173
166
|
let active = true;
|
|
@@ -176,7 +169,8 @@ export default function VersionStatus() {
|
|
|
176
169
|
if (!active) {
|
|
177
170
|
return;
|
|
178
171
|
}
|
|
179
|
-
|
|
172
|
+
setLiveBackendVersion(payload.backend.current_version);
|
|
173
|
+
setCachedInfo(payload);
|
|
180
174
|
const cachePayload: SystemInfoCachePayload = {
|
|
181
175
|
checkedAt: new Date().toISOString(),
|
|
182
176
|
payload,
|
|
@@ -196,39 +190,41 @@ export default function VersionStatus() {
|
|
|
196
190
|
}, []);
|
|
197
191
|
|
|
198
192
|
const studioUpdateAvailable = useMemo(() => {
|
|
199
|
-
if (!
|
|
193
|
+
if (!cachedInfo) {
|
|
200
194
|
return false;
|
|
201
195
|
}
|
|
202
|
-
if (
|
|
196
|
+
if (cachedInfo.studio.update_available) {
|
|
203
197
|
return true;
|
|
204
198
|
}
|
|
205
|
-
return compareSemver(studioVersion,
|
|
206
|
-
}, [studioVersion,
|
|
199
|
+
return compareSemver(studioVersion, cachedInfo.studio.latest_version) < 0;
|
|
200
|
+
}, [studioVersion, cachedInfo]);
|
|
207
201
|
|
|
208
202
|
const showReminder =
|
|
209
|
-
((
|
|
203
|
+
((cachedInfo?.backend.update_available ?? false) ||
|
|
210
204
|
studioUpdateAvailable) &&
|
|
211
205
|
!dismissedReminder;
|
|
212
206
|
|
|
213
207
|
const versionSummary = useMemo(() => {
|
|
214
|
-
const backend =
|
|
208
|
+
const backend = liveBackendVersion ?? "unknown";
|
|
215
209
|
return `Orcheo ${studioVersion} · Backend ${backend}`;
|
|
216
|
-
}, [studioVersion,
|
|
210
|
+
}, [studioVersion, liveBackendVersion]);
|
|
217
211
|
|
|
218
212
|
const updateLines = useMemo(() => {
|
|
219
|
-
if (!
|
|
213
|
+
if (!cachedInfo) return [];
|
|
220
214
|
const lines: string[] = [];
|
|
221
|
-
if (
|
|
215
|
+
if (cachedInfo.backend.update_available) {
|
|
222
216
|
lines.push(
|
|
223
|
-
`Backend: ${
|
|
217
|
+
`Backend: ${cachedInfo.backend.current_version} → ${cachedInfo.backend.latest_version}`,
|
|
224
218
|
);
|
|
225
219
|
}
|
|
226
220
|
if (studioUpdateAvailable) {
|
|
227
|
-
const studioCurrent =
|
|
228
|
-
lines.push(
|
|
221
|
+
const studioCurrent = cachedInfo.studio.current_version ?? studioVersion;
|
|
222
|
+
lines.push(
|
|
223
|
+
`Orcheo: ${studioCurrent} → ${cachedInfo.studio.latest_version}`,
|
|
224
|
+
);
|
|
229
225
|
}
|
|
230
226
|
return lines;
|
|
231
|
-
}, [studioUpdateAvailable, studioVersion,
|
|
227
|
+
}, [studioUpdateAvailable, studioVersion, cachedInfo]);
|
|
232
228
|
|
|
233
229
|
const dismissUpdateReminder = () => {
|
|
234
230
|
if (typeof window === "undefined") {
|
|
@@ -155,7 +155,7 @@ export function WorkspaceBootstrapGate({
|
|
|
155
155
|
|
|
156
156
|
if (isLoading) {
|
|
157
157
|
return (
|
|
158
|
-
<div className="flex min-h-screen items-center justify-center bg-
|
|
158
|
+
<div className="flex min-h-screen items-center justify-center bg-background text-foreground">
|
|
159
159
|
<Card className="w-full max-w-md border-border bg-card/80 backdrop-blur-xl dark:border-primary/25 dark:bg-primary/5">
|
|
160
160
|
<CardContent className="flex items-center gap-3 p-6 text-sm text-muted-foreground">
|
|
161
161
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
@@ -171,16 +171,16 @@ export function WorkspaceBootstrapGate({
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
return (
|
|
174
|
-
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-
|
|
174
|
+
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-background text-foreground">
|
|
175
175
|
<div
|
|
176
|
-
className="absolute inset-0 bg-gradient-to-br from-
|
|
176
|
+
className="absolute inset-0 bg-gradient-to-br from-background via-secondary to-background dark:via-card/80"
|
|
177
177
|
aria-hidden="true"
|
|
178
178
|
/>
|
|
179
179
|
<div
|
|
180
180
|
className="absolute inset-0 opacity-40 mix-blend-soft-light dark:opacity-60"
|
|
181
181
|
style={{
|
|
182
182
|
backgroundImage:
|
|
183
|
-
"radial-gradient(circle at 20% 20%, rgba(
|
|
183
|
+
"radial-gradient(circle at 20% 20%, rgba(224, 192, 144, 0.25), transparent 45%), radial-gradient(circle at 80% 30%, rgba(248, 120, 37, 0.2), transparent 50%), radial-gradient(circle at 50% 80%, rgba(246, 245, 212, 0.35), transparent 55%)",
|
|
184
184
|
}}
|
|
185
185
|
aria-hidden="true"
|
|
186
186
|
/>
|
|
@@ -12,7 +12,7 @@ export function CredentialAccessBadge({ access }: CredentialAccessBadgeProps) {
|
|
|
12
12
|
return (
|
|
13
13
|
<Badge
|
|
14
14
|
variant="outline"
|
|
15
|
-
className="
|
|
15
|
+
className="border-info/30 bg-info-muted text-info-muted-foreground"
|
|
16
16
|
>
|
|
17
17
|
<Lock className="h-3 w-3 mr-1" />
|
|
18
18
|
Scoped
|
|
@@ -22,7 +22,7 @@ export function CredentialAccessBadge({ access }: CredentialAccessBadgeProps) {
|
|
|
22
22
|
return (
|
|
23
23
|
<Badge
|
|
24
24
|
variant="outline"
|
|
25
|
-
className="
|
|
25
|
+
className="border-primary/30 bg-primary/10 text-primary-active"
|
|
26
26
|
>
|
|
27
27
|
<Users className="h-3 w-3 mr-1" />
|
|
28
28
|
Shared
|
|
@@ -15,7 +15,7 @@ export function CredentialStatusBadge({ status }: CredentialStatusBadgeProps) {
|
|
|
15
15
|
return (
|
|
16
16
|
<Badge
|
|
17
17
|
variant="outline"
|
|
18
|
-
className="flex items-center gap-1
|
|
18
|
+
className="flex items-center gap-1 border-success/30 bg-success-muted text-success-muted-foreground"
|
|
19
19
|
>
|
|
20
20
|
<CheckCircle2 className="h-3 w-3" />
|
|
21
21
|
Healthy
|
|
@@ -25,7 +25,7 @@ export function CredentialStatusBadge({ status }: CredentialStatusBadgeProps) {
|
|
|
25
25
|
return (
|
|
26
26
|
<Badge
|
|
27
27
|
variant="outline"
|
|
28
|
-
className="flex items-center gap-1
|
|
28
|
+
className="flex items-center gap-1 border-destructive/30 bg-destructive-muted text-destructive-muted-foreground"
|
|
29
29
|
>
|
|
30
30
|
<AlertTriangle className="h-3 w-3" />
|
|
31
31
|
Unhealthy
|
|
@@ -353,7 +353,7 @@ export function CredentialsTable({
|
|
|
353
353
|
className={cn(
|
|
354
354
|
"h-6 w-6 transition-colors",
|
|
355
355
|
currentCopyState === "success" &&
|
|
356
|
-
"text-
|
|
356
|
+
"text-success hover:text-success/80",
|
|
357
357
|
currentCopyState === "error" &&
|
|
358
358
|
"text-destructive hover:text-destructive",
|
|
359
359
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback,
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
2
|
import { Loader2, RefreshCw } from "lucide-react";
|
|
3
3
|
import { Button } from "@/design-system/ui/button";
|
|
4
4
|
import {
|
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
DialogHeader,
|
|
10
10
|
DialogTitle,
|
|
11
11
|
} from "@/design-system/ui/dialog";
|
|
12
|
-
import { Input } from "@/design-system/ui/input";
|
|
13
12
|
import { Label } from "@/design-system/ui/label";
|
|
14
13
|
import { toast } from "@/hooks/use-toast";
|
|
15
14
|
import { updateWorkflowFromFiles } from "@features/workflow/lib/workflow-storage";
|
|
15
|
+
import { WorkflowFolderPicker } from "./workflow-folder-picker";
|
|
16
|
+
import type { WorkflowFolderSelection } from "@features/workflow/lib/workflow-folder-selection";
|
|
16
17
|
|
|
17
18
|
interface UpdateWorkflowDialogProps {
|
|
18
19
|
open: boolean;
|
|
@@ -27,11 +28,9 @@ export function UpdateWorkflowDialog({
|
|
|
27
28
|
workflowId,
|
|
28
29
|
workflowName,
|
|
29
30
|
}: UpdateWorkflowDialogProps) {
|
|
30
|
-
const scriptInputRef = useRef<HTMLInputElement>(null);
|
|
31
|
-
const configInputRef = useRef<HTMLInputElement>(null);
|
|
32
|
-
|
|
33
31
|
const [scriptContent, setScriptContent] = useState<string | null>(null);
|
|
34
32
|
const [scriptFileName, setScriptFileName] = useState("");
|
|
33
|
+
const [configFileName, setConfigFileName] = useState<string | null>(null);
|
|
35
34
|
const [configContent, setConfigContent] = useState<Record<
|
|
36
35
|
string,
|
|
37
36
|
unknown
|
|
@@ -42,15 +41,10 @@ export function UpdateWorkflowDialog({
|
|
|
42
41
|
const reset = useCallback(() => {
|
|
43
42
|
setScriptContent(null);
|
|
44
43
|
setScriptFileName("");
|
|
44
|
+
setConfigFileName(null);
|
|
45
45
|
setConfigContent(null);
|
|
46
46
|
setIsUpdating(false);
|
|
47
47
|
setError(null);
|
|
48
|
-
if (scriptInputRef.current) {
|
|
49
|
-
scriptInputRef.current.value = "";
|
|
50
|
-
}
|
|
51
|
-
if (configInputRef.current) {
|
|
52
|
-
configInputRef.current.value = "";
|
|
53
|
-
}
|
|
54
48
|
}, []);
|
|
55
49
|
|
|
56
50
|
const handleOpenChange = useCallback(
|
|
@@ -63,50 +57,26 @@ export function UpdateWorkflowDialog({
|
|
|
63
57
|
[onOpenChange, reset],
|
|
64
58
|
);
|
|
65
59
|
|
|
66
|
-
const
|
|
67
|
-
(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const reader = new FileReader();
|
|
74
|
-
reader.onload = (e) => {
|
|
75
|
-
const text = e.target?.result;
|
|
76
|
-
if (typeof text === "string") {
|
|
77
|
-
setScriptContent(text);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
reader.readAsText(file);
|
|
81
|
-
},
|
|
82
|
-
[],
|
|
83
|
-
);
|
|
60
|
+
const handleSelect = useCallback((selection: WorkflowFolderSelection) => {
|
|
61
|
+
setError(null);
|
|
62
|
+
setScriptContent(selection.scriptContent);
|
|
63
|
+
setScriptFileName(selection.scriptName);
|
|
64
|
+
setConfigFileName(selection.configName);
|
|
65
|
+
setConfigContent(selection.configContent);
|
|
66
|
+
}, []);
|
|
84
67
|
|
|
85
|
-
const
|
|
86
|
-
(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const parsed = JSON.parse(text) as Record<string, unknown>;
|
|
99
|
-
setConfigContent(parsed);
|
|
100
|
-
setError(null);
|
|
101
|
-
} catch {
|
|
102
|
-
setError("config.json is not valid JSON.");
|
|
103
|
-
setConfigContent(null);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
reader.readAsText(file);
|
|
107
|
-
},
|
|
108
|
-
[],
|
|
109
|
-
);
|
|
68
|
+
const handleSelectionError = useCallback((message: string) => {
|
|
69
|
+
setScriptContent(null);
|
|
70
|
+
setScriptFileName("");
|
|
71
|
+
setConfigFileName(null);
|
|
72
|
+
setConfigContent(null);
|
|
73
|
+
setError(message);
|
|
74
|
+
toast({
|
|
75
|
+
title: "Couldn't use that folder",
|
|
76
|
+
description: message,
|
|
77
|
+
variant: "destructive",
|
|
78
|
+
});
|
|
79
|
+
}, []);
|
|
110
80
|
|
|
111
81
|
const handleUpdate = useCallback(async () => {
|
|
112
82
|
if (!scriptContent) {
|
|
@@ -149,35 +119,21 @@ export function UpdateWorkflowDialog({
|
|
|
149
119
|
<DialogHeader>
|
|
150
120
|
<DialogTitle>Update Workflow</DialogTitle>
|
|
151
121
|
<DialogDescription>
|
|
152
|
-
|
|
153
|
-
version of “{workflowName}”.
|
|
122
|
+
Select a folder with a new Python script and optional JSON config to
|
|
123
|
+
create a new version of “{workflowName}”.
|
|
154
124
|
</DialogDescription>
|
|
155
125
|
</DialogHeader>
|
|
156
126
|
<div className="grid gap-4 py-4">
|
|
157
127
|
<div className="space-y-1.5">
|
|
158
|
-
<Label htmlFor="update-
|
|
159
|
-
Workflow
|
|
128
|
+
<Label htmlFor="update-folder">
|
|
129
|
+
Workflow Folder <span className="text-destructive">*</span>
|
|
160
130
|
</Label>
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
disabled={isUpdating}
|
|
168
|
-
/>
|
|
169
|
-
{scriptFileName && (
|
|
170
|
-
<p className="text-xs text-muted-foreground">{scriptFileName}</p>
|
|
171
|
-
)}
|
|
172
|
-
</div>
|
|
173
|
-
<div className="space-y-1.5">
|
|
174
|
-
<Label htmlFor="update-config">Config (optional)</Label>
|
|
175
|
-
<Input
|
|
176
|
-
id="update-config"
|
|
177
|
-
ref={configInputRef}
|
|
178
|
-
type="file"
|
|
179
|
-
accept=".json"
|
|
180
|
-
onChange={handleConfigFileChange}
|
|
131
|
+
<WorkflowFolderPicker
|
|
132
|
+
idPrefix="update"
|
|
133
|
+
scriptName={scriptFileName}
|
|
134
|
+
configName={configFileName}
|
|
135
|
+
onSelect={handleSelect}
|
|
136
|
+
onError={handleSelectionError}
|
|
181
137
|
disabled={isUpdating}
|
|
182
138
|
/>
|
|
183
139
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback,
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
2
|
import { useNavigate } from "react-router-dom";
|
|
3
3
|
import { Loader2, Upload } from "lucide-react";
|
|
4
4
|
import { Button } from "@/design-system/ui/button";
|
|
@@ -21,51 +21,24 @@ import { parseWorkflowFrontmatter } from "@features/workflow/lib/workflow-frontm
|
|
|
21
21
|
import { getWorkflowRouteRef } from "@features/workflow/lib/workflow-storage-helpers";
|
|
22
22
|
import { getSelectedWorkspaceSlug } from "@/lib/workspace-session";
|
|
23
23
|
import { getWorkspaceWorkflowPath } from "@/lib/workspace-routing";
|
|
24
|
+
import { WorkflowFolderPicker } from "./workflow-folder-picker";
|
|
25
|
+
import type { WorkflowFolderSelection } from "@features/workflow/lib/workflow-folder-selection";
|
|
24
26
|
|
|
25
27
|
interface UploadWorkflowDialogProps {
|
|
26
28
|
open: boolean;
|
|
27
29
|
onOpenChange: (open: boolean) => void;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
const MAX_SCRIPT_UPLOAD_BYTES = 1024 * 1024;
|
|
31
|
-
const MAX_CONFIG_UPLOAD_BYTES = 256 * 1024;
|
|
32
|
-
const JSON_CONFIG_MIME_TYPES = new Set(["", "application/json", "text/json"]);
|
|
33
|
-
|
|
34
|
-
const formatUploadLimit = (bytes: number): string => {
|
|
35
|
-
if (bytes >= 1024 * 1024) {
|
|
36
|
-
return `${bytes / (1024 * 1024)} MB`;
|
|
37
|
-
}
|
|
38
|
-
return `${bytes / 1024} KB`;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const validateUploadFile = (
|
|
42
|
-
file: File,
|
|
43
|
-
options: {
|
|
44
|
-
label: string;
|
|
45
|
-
extension: string;
|
|
46
|
-
maxBytes: number;
|
|
47
|
-
},
|
|
48
|
-
): string | null => {
|
|
49
|
-
if (!file.name.toLowerCase().endsWith(options.extension)) {
|
|
50
|
-
return `${options.label} must use the ${options.extension} extension.`;
|
|
51
|
-
}
|
|
52
|
-
if (file.size > options.maxBytes) {
|
|
53
|
-
return `${options.label} must be ${formatUploadLimit(options.maxBytes)} or smaller.`;
|
|
54
|
-
}
|
|
55
|
-
return null;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
32
|
export function UploadWorkflowDialog({
|
|
59
33
|
open,
|
|
60
34
|
onOpenChange,
|
|
61
35
|
}: UploadWorkflowDialogProps) {
|
|
62
36
|
const navigate = useNavigate();
|
|
63
|
-
const scriptInputRef = useRef<HTMLInputElement>(null);
|
|
64
|
-
const configInputRef = useRef<HTMLInputElement>(null);
|
|
65
37
|
|
|
66
38
|
const [workflowName, setWorkflowName] = useState("");
|
|
67
39
|
const [scriptContent, setScriptContent] = useState<string | null>(null);
|
|
68
40
|
const [scriptFileName, setScriptFileName] = useState("");
|
|
41
|
+
const [configFileName, setConfigFileName] = useState<string | null>(null);
|
|
69
42
|
const [configContent, setConfigContent] = useState<Record<
|
|
70
43
|
string,
|
|
71
44
|
unknown
|
|
@@ -77,15 +50,10 @@ export function UploadWorkflowDialog({
|
|
|
77
50
|
setWorkflowName("");
|
|
78
51
|
setScriptContent(null);
|
|
79
52
|
setScriptFileName("");
|
|
53
|
+
setConfigFileName(null);
|
|
80
54
|
setConfigContent(null);
|
|
81
55
|
setIsUploading(false);
|
|
82
56
|
setError(null);
|
|
83
|
-
if (scriptInputRef.current) {
|
|
84
|
-
scriptInputRef.current.value = "";
|
|
85
|
-
}
|
|
86
|
-
if (configInputRef.current) {
|
|
87
|
-
configInputRef.current.value = "";
|
|
88
|
-
}
|
|
89
57
|
}, []);
|
|
90
58
|
|
|
91
59
|
const handleOpenChange = useCallback(
|
|
@@ -98,83 +66,33 @@ export function UploadWorkflowDialog({
|
|
|
98
66
|
[onOpenChange, reset],
|
|
99
67
|
);
|
|
100
68
|
|
|
101
|
-
const
|
|
102
|
-
(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
maxBytes: MAX_SCRIPT_UPLOAD_BYTES,
|
|
111
|
-
});
|
|
112
|
-
if (validationError) {
|
|
113
|
-
setError(validationError);
|
|
114
|
-
setScriptContent(null);
|
|
115
|
-
setScriptFileName("");
|
|
116
|
-
event.target.value = "";
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
setError(null);
|
|
120
|
-
setScriptFileName(file.name);
|
|
121
|
-
if (!workflowName) {
|
|
122
|
-
const nameWithoutExt = file.name.replace(/\.py$/i, "");
|
|
123
|
-
setWorkflowName(nameWithoutExt);
|
|
69
|
+
const handleSelect = useCallback((selection: WorkflowFolderSelection) => {
|
|
70
|
+
setError(null);
|
|
71
|
+
setScriptContent(selection.scriptContent);
|
|
72
|
+
setScriptFileName(selection.scriptName);
|
|
73
|
+
setConfigFileName(selection.configName);
|
|
74
|
+
setConfigContent(selection.configContent);
|
|
75
|
+
setWorkflowName((current) => {
|
|
76
|
+
if (current) {
|
|
77
|
+
return current;
|
|
124
78
|
}
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
setScriptContent(text);
|
|
130
|
-
const frontmatter = parseWorkflowFrontmatter(text);
|
|
131
|
-
if (!workflowName && frontmatter.name) {
|
|
132
|
-
setWorkflowName(frontmatter.name);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
reader.readAsText(file);
|
|
137
|
-
},
|
|
138
|
-
[workflowName],
|
|
139
|
-
);
|
|
79
|
+
const frontmatter = parseWorkflowFrontmatter(selection.scriptContent);
|
|
80
|
+
return frontmatter.name ?? selection.scriptName.replace(/\.py$/i, "");
|
|
81
|
+
});
|
|
82
|
+
}, []);
|
|
140
83
|
|
|
141
|
-
const
|
|
142
|
-
(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (validationError) {
|
|
154
|
-
setError(validationError);
|
|
155
|
-
setConfigContent(null);
|
|
156
|
-
event.target.value = "";
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
const reader = new FileReader();
|
|
160
|
-
reader.onload = (e) => {
|
|
161
|
-
const text = e.target?.result;
|
|
162
|
-
if (typeof text !== "string") {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
const parsed = JSON.parse(text) as Record<string, unknown>;
|
|
167
|
-
setConfigContent(parsed);
|
|
168
|
-
setError(null);
|
|
169
|
-
} catch {
|
|
170
|
-
setError("config.json is not valid JSON.");
|
|
171
|
-
setConfigContent(null);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
reader.readAsText(file);
|
|
175
|
-
},
|
|
176
|
-
[],
|
|
177
|
-
);
|
|
84
|
+
const handleSelectionError = useCallback((message: string) => {
|
|
85
|
+
setScriptContent(null);
|
|
86
|
+
setScriptFileName("");
|
|
87
|
+
setConfigFileName(null);
|
|
88
|
+
setConfigContent(null);
|
|
89
|
+
setError(message);
|
|
90
|
+
toast({
|
|
91
|
+
title: "Couldn't use that folder",
|
|
92
|
+
description: message,
|
|
93
|
+
variant: "destructive",
|
|
94
|
+
});
|
|
95
|
+
}, []);
|
|
178
96
|
|
|
179
97
|
const handleUpload = useCallback(async () => {
|
|
180
98
|
if (!scriptContent || !workflowName.trim()) {
|
|
@@ -230,35 +148,21 @@ export function UploadWorkflowDialog({
|
|
|
230
148
|
<DialogHeader>
|
|
231
149
|
<DialogTitle>Upload Workflow</DialogTitle>
|
|
232
150
|
<DialogDescription>
|
|
233
|
-
|
|
234
|
-
create a new workflow.
|
|
151
|
+
Select a folder containing a Python workflow script and an optional
|
|
152
|
+
JSON config to create a new workflow.
|
|
235
153
|
</DialogDescription>
|
|
236
154
|
</DialogHeader>
|
|
237
155
|
<div className="grid gap-4 py-4">
|
|
238
156
|
<div className="space-y-1.5">
|
|
239
|
-
<Label htmlFor="upload-
|
|
240
|
-
Workflow
|
|
157
|
+
<Label htmlFor="upload-folder">
|
|
158
|
+
Workflow Folder <span className="text-destructive">*</span>
|
|
241
159
|
</Label>
|
|
242
|
-
<
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
disabled={isUploading}
|
|
249
|
-
/>
|
|
250
|
-
{scriptFileName && (
|
|
251
|
-
<p className="text-xs text-muted-foreground">{scriptFileName}</p>
|
|
252
|
-
)}
|
|
253
|
-
</div>
|
|
254
|
-
<div className="space-y-1.5">
|
|
255
|
-
<Label htmlFor="upload-config">Config (optional)</Label>
|
|
256
|
-
<Input
|
|
257
|
-
id="upload-config"
|
|
258
|
-
ref={configInputRef}
|
|
259
|
-
type="file"
|
|
260
|
-
accept=".json"
|
|
261
|
-
onChange={handleConfigFileChange}
|
|
160
|
+
<WorkflowFolderPicker
|
|
161
|
+
idPrefix="upload"
|
|
162
|
+
scriptName={scriptFileName}
|
|
163
|
+
configName={configFileName}
|
|
164
|
+
onSelect={handleSelect}
|
|
165
|
+
onError={handleSelectionError}
|
|
262
166
|
disabled={isUploading}
|
|
263
167
|
/>
|
|
264
168
|
</div>
|