orcheo-studio 0.24.5 → 0.24.7
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-C-7b0zH4.js → arc-D2-yXHYr.js} +1 -1
- package/dist/assets/{architectureDiagram-VXUJARFQ-Ciy8lV6J.js → architectureDiagram-VXUJARFQ-T90TGA7h.js} +1 -1
- package/dist/assets/{blockDiagram-VD42YOAC-dC88adQ_.js → blockDiagram-VD42YOAC-DD-sctie.js} +1 -1
- package/dist/assets/{c4Diagram-YG6GDRKO-BiHVvXEu.js → c4Diagram-YG6GDRKO-CMFh9ow5.js} +1 -1
- package/dist/assets/channel-BHDMf1UK.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-C6V7Huup.js → chunk-4BX2VUAB-CxEXc54f.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DRmZnlbz.js → chunk-55IACEB6-Cu79bwG9.js} +1 -1
- package/dist/assets/{chunk-B4BG7PRW-DwV5xRJr.js → chunk-B4BG7PRW-B9kTs9bC.js} +1 -1
- package/dist/assets/{chunk-DI55MBZ5-BHsUZ0IJ.js → chunk-DI55MBZ5-C4vlj7YJ.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-6Q-Lz0AM.js → chunk-FMBD7UC4-BAbI4xA2.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-BFy_ml8O.js → chunk-QN33PNHL-Byawnjwi.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-C1fN91FN.js → chunk-QZHKN3VN-X9swQQ1H.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-FLRSQ_Fi.js → chunk-TZMSLE5B-qdHYq1mZ.js} +1 -1
- package/dist/assets/classDiagram-2ON5EDUG-CmzZyWnG.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-CmzZyWnG.js +1 -0
- package/dist/assets/clone-BCqfTdPh.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-BSWP1X1D.js → cose-bilkent-S5V4N54A-DxP3F1r-.js} +1 -1
- package/dist/assets/{dagre-6UL2VRFP-CYoZFlcz.js → dagre-6UL2VRFP-JZzCnaiy.js} +1 -1
- package/dist/assets/{diagram-PSM6KHXK-B8RGX0YL.js → diagram-PSM6KHXK-xDDV3S5Y.js} +1 -1
- package/dist/assets/{diagram-QEK2KX5R-ChWXJ8p_.js → diagram-QEK2KX5R-GQ6mbBr_.js} +1 -1
- package/dist/assets/{diagram-S2PKOQOG-Dwic0Fjn.js → diagram-S2PKOQOG-CuoOnnN6.js} +1 -1
- package/dist/assets/{erDiagram-Q2GNP2WA-0NSDB0DX.js → erDiagram-Q2GNP2WA-CFMLhasi.js} +1 -1
- package/dist/assets/{flowDiagram-NV44I4VS-DEoGWs9L.js → flowDiagram-NV44I4VS-Dfz2XZo7.js} +1 -1
- package/dist/assets/{ganttDiagram-JELNMOA3-DqzV4S6l.js → ganttDiagram-JELNMOA3-Bv566-v-.js} +1 -1
- package/dist/assets/{gitGraphDiagram-V2S2FVAM-B-EIQM_H.js → gitGraphDiagram-V2S2FVAM-Bp0dgH1E.js} +1 -1
- package/dist/assets/{graph-Ha1DTJNG.js → graph-Dkgmy-o1.js} +1 -1
- package/dist/assets/index-BoHYiVT1.js +810 -0
- package/dist/assets/index-CES1mKJf.css +1 -0
- package/dist/assets/{infoDiagram-HS3SLOUP-C4_vpEkn.js → infoDiagram-HS3SLOUP-CoZX6U-w.js} +1 -1
- package/dist/assets/{isUndefined-DkuoqItu.js → isUndefined-DFmulv-o.js} +1 -1
- package/dist/assets/{journeyDiagram-XKPGCS4Q-BxY4e6OZ.js → journeyDiagram-XKPGCS4Q-CI7nMgKu.js} +1 -1
- package/dist/assets/{kanban-definition-3W4ZIXB7-BTX3_Q7O.js → kanban-definition-3W4ZIXB7-CyVNs44-.js} +1 -1
- package/dist/assets/{layout-Dt1W0fl0.js → layout-DZqvH5JV.js} +1 -1
- package/dist/assets/{linear-CjywSMUX.js → linear-CiLQWbi5.js} +1 -1
- package/dist/assets/{min-Dblq6bty.js → min-CmXn2PCy.js} +1 -1
- package/dist/assets/{mindmap-definition-VGOIOE7T-C5P7bAFm.js → mindmap-definition-VGOIOE7T-B_F2Nu7b.js} +1 -1
- package/dist/assets/{pieDiagram-ADFJNKIX-BSMylwok.js → pieDiagram-ADFJNKIX-BJvzYaii.js} +1 -1
- package/dist/assets/{quadrantDiagram-AYHSOK5B-C74B7bqa.js → quadrantDiagram-AYHSOK5B-Cqk76fvz.js} +1 -1
- package/dist/assets/{requirementDiagram-UZGBJVZJ-CvPzL8Le.js → requirementDiagram-UZGBJVZJ-vGfLRbYk.js} +1 -1
- package/dist/assets/{sankeyDiagram-TZEHDZUN-BFHm6g6t.js → sankeyDiagram-TZEHDZUN-CregXq64.js} +1 -1
- package/dist/assets/{sequenceDiagram-WL72ISMW-e74mJ0xk.js → sequenceDiagram-WL72ISMW-DNrzmFke.js} +1 -1
- package/dist/assets/{stateDiagram-FKZM4ZOC-VLQxNVA7.js → stateDiagram-FKZM4ZOC-gdr8ZncR.js} +1 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-BbOMPcrN.js +1 -0
- package/dist/assets/{timeline-definition-IT6M3QCI-oveNP_QD.js → timeline-definition-IT6M3QCI-CdtbG32P.js} +1 -1
- package/dist/assets/{treemap-GDKQZRPO-Cowimd29.js → treemap-GDKQZRPO-uMGJkzE9.js} +1 -1
- package/dist/assets/{xychartDiagram-PRI3JC2R-CP9Fm_aO.js → xychartDiagram-PRI3JC2R-BsBFf9PQ.js} +1 -1
- package/dist/favicon.ico +0 -0
- package/dist/index.html +8 -2
- package/dist/orcheo-mark.png +0 -0
- package/index.html +6 -0
- package/package.json +1 -1
- package/public/favicon.ico +0 -0
- package/public/orcheo-mark.png +0 -0
- package/src/design-system/ui/button.tsx +2 -2
- 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/auth/pages/login.tsx +1 -1
- 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/pages/public-chat.tsx +17 -15
- package/src/features/shared/components/top-navigation/studio-brand.tsx +1 -1
- 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/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/theme/theme.css +74 -74
- package/src/features/workflow/lib/mermaid-renderer.ts +86 -2
- package/src/features/workflow/lib/workflow-folder-selection.ts +239 -0
- package/src/features/workflow/pages/workflow/components/settings-tab-content.tsx +24 -9
- package/src/features/workflow/pages/workflow/components/workflow-tab-content.tsx +8 -5
- package/src/features/workflow/pages/workflow-gallery/workflow-card.tsx +16 -16
- package/src/index.css +126 -59
- package/tailwind.config.js +50 -2
- package/dist/assets/channel-pJdT5i-M.js +0 -1
- package/dist/assets/classDiagram-2ON5EDUG-CNT-EGNU.js +0 -1
- package/dist/assets/classDiagram-v2-WZHVMYZB-CNT-EGNU.js +0 -1
- package/dist/assets/clone-BCi0j082.js +0 -1
- package/dist/assets/index-DTxvVcXp.js +0 -795
- package/dist/assets/index-Df4jFRhd.css +0 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-Bbpq2t6B.js +0 -1
- package/dist/orcheo.png +0 -0
- package/public/orcheo.png +0 -0
|
@@ -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>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { FileJson, FileText, FolderUp, Loader2 } from "lucide-react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import {
|
|
5
|
+
collectDroppedFolderFiles,
|
|
6
|
+
collectFolderInputFiles,
|
|
7
|
+
loadWorkflowFolderSelection,
|
|
8
|
+
WorkflowFolderSelectionError,
|
|
9
|
+
type WorkflowFolderSelection,
|
|
10
|
+
} from "@features/workflow/lib/workflow-folder-selection";
|
|
11
|
+
|
|
12
|
+
interface WorkflowFolderPickerProps {
|
|
13
|
+
/** Unique prefix for the input id so labels stay associated per dialog. */
|
|
14
|
+
idPrefix: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** Name of the currently selected script, or "" when none is selected. */
|
|
17
|
+
scriptName: string;
|
|
18
|
+
/** Name of the currently selected config, or null when none is selected. */
|
|
19
|
+
configName: string | null;
|
|
20
|
+
onSelect: (selection: WorkflowFolderSelection) => void;
|
|
21
|
+
onError: (message: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const READ_FOLDER_FALLBACK_ERROR =
|
|
25
|
+
"Could not read the selected folder. Please try again.";
|
|
26
|
+
const EMPTY_FOLDER_ERROR =
|
|
27
|
+
"No files were found in the selected folder. Add a folder containing a .py workflow script and try again.";
|
|
28
|
+
|
|
29
|
+
export function WorkflowFolderPicker({
|
|
30
|
+
idPrefix,
|
|
31
|
+
disabled = false,
|
|
32
|
+
scriptName,
|
|
33
|
+
configName,
|
|
34
|
+
onSelect,
|
|
35
|
+
onError,
|
|
36
|
+
}: WorkflowFolderPickerProps) {
|
|
37
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
38
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
39
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
40
|
+
const inputId = `${idPrefix}-folder`;
|
|
41
|
+
|
|
42
|
+
// `webkitdirectory` is not part of the standard input typings, so set it
|
|
43
|
+
// imperatively to opt the file picker into folder selection.
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const input = inputRef.current;
|
|
46
|
+
if (input) {
|
|
47
|
+
input.setAttribute("webkitdirectory", "");
|
|
48
|
+
input.setAttribute("directory", "");
|
|
49
|
+
}
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
const processFiles = useCallback(
|
|
53
|
+
async (files: File[]) => {
|
|
54
|
+
if (files.length === 0) {
|
|
55
|
+
onError(EMPTY_FOLDER_ERROR);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
setIsProcessing(true);
|
|
59
|
+
try {
|
|
60
|
+
const selection = await loadWorkflowFolderSelection(files);
|
|
61
|
+
onSelect(selection);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const message =
|
|
64
|
+
err instanceof WorkflowFolderSelectionError
|
|
65
|
+
? err.message
|
|
66
|
+
: READ_FOLDER_FALLBACK_ERROR;
|
|
67
|
+
onError(message);
|
|
68
|
+
} finally {
|
|
69
|
+
setIsProcessing(false);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
[onSelect, onError],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const handleInputChange = useCallback(
|
|
76
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
77
|
+
const files = collectFolderInputFiles(event.target.files);
|
|
78
|
+
// Allow re-selecting the same folder later.
|
|
79
|
+
event.target.value = "";
|
|
80
|
+
void processFiles(files);
|
|
81
|
+
},
|
|
82
|
+
[processFiles],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const handleDrop = useCallback(
|
|
86
|
+
(event: React.DragEvent<HTMLLabelElement>) => {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
setIsDragging(false);
|
|
89
|
+
if (disabled) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
void (async () => {
|
|
93
|
+
try {
|
|
94
|
+
const files = await collectDroppedFolderFiles(event.dataTransfer);
|
|
95
|
+
await processFiles(files);
|
|
96
|
+
} catch {
|
|
97
|
+
onError(READ_FOLDER_FALLBACK_ERROR);
|
|
98
|
+
}
|
|
99
|
+
})();
|
|
100
|
+
},
|
|
101
|
+
[disabled, processFiles, onError],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const handleDragOver = useCallback(
|
|
105
|
+
(event: React.DragEvent<HTMLLabelElement>) => {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
if (!disabled) {
|
|
108
|
+
setIsDragging(true);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
[disabled],
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const handleDragLeave = useCallback(
|
|
115
|
+
(event: React.DragEvent<HTMLLabelElement>) => {
|
|
116
|
+
// Ignore drag-leave events bubbling up from child elements.
|
|
117
|
+
if (event.currentTarget.contains(event.relatedTarget as Node | null)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
setIsDragging(false);
|
|
121
|
+
},
|
|
122
|
+
[],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const hasSelection = scriptName.length > 0;
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="space-y-1.5">
|
|
129
|
+
<label
|
|
130
|
+
htmlFor={inputId}
|
|
131
|
+
onDrop={handleDrop}
|
|
132
|
+
onDragOver={handleDragOver}
|
|
133
|
+
onDragEnter={handleDragOver}
|
|
134
|
+
onDragLeave={handleDragLeave}
|
|
135
|
+
className={cn(
|
|
136
|
+
"flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-md border border-dashed border-input bg-transparent px-4 py-6 text-center transition-colors",
|
|
137
|
+
"hover:border-ring focus-within:outline-none focus-within:ring-1 focus-within:ring-ring",
|
|
138
|
+
disabled && "pointer-events-none opacity-50",
|
|
139
|
+
isDragging && "border-ring bg-accent",
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
{isProcessing ? (
|
|
143
|
+
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
144
|
+
) : (
|
|
145
|
+
<FolderUp className="h-6 w-6 text-muted-foreground" />
|
|
146
|
+
)}
|
|
147
|
+
<span className="text-sm font-medium">
|
|
148
|
+
{isDragging ? "Drop folder to load" : "Drag a folder here or browse"}
|
|
149
|
+
</span>
|
|
150
|
+
<span className="text-xs text-muted-foreground">
|
|
151
|
+
The folder should contain one <code>.py</code> script and an optional{" "}
|
|
152
|
+
<code>.json</code> config.
|
|
153
|
+
</span>
|
|
154
|
+
</label>
|
|
155
|
+
<input
|
|
156
|
+
ref={inputRef}
|
|
157
|
+
id={inputId}
|
|
158
|
+
type="file"
|
|
159
|
+
multiple
|
|
160
|
+
className="sr-only"
|
|
161
|
+
onChange={handleInputChange}
|
|
162
|
+
disabled={disabled}
|
|
163
|
+
/>
|
|
164
|
+
{hasSelection && (
|
|
165
|
+
<div className="space-y-1 rounded-md bg-muted/50 px-3 py-2 text-xs">
|
|
166
|
+
<p className="flex items-center gap-1.5 text-foreground">
|
|
167
|
+
<FileText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
|
168
|
+
<span className="truncate">{scriptName}</span>
|
|
169
|
+
</p>
|
|
170
|
+
<p className="flex items-center gap-1.5 text-muted-foreground">
|
|
171
|
+
<FileJson className="h-3.5 w-3.5 shrink-0" />
|
|
172
|
+
<span className="truncate">{configName ?? "No config file"}</span>
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -28,9 +28,9 @@ interface WorkflowDiffDialogProps {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const summaryBadgeClass = {
|
|
31
|
-
added: "bg-
|
|
32
|
-
removed: "bg-
|
|
33
|
-
modified: "bg-
|
|
31
|
+
added: "bg-success-muted text-success-muted-foreground",
|
|
32
|
+
removed: "bg-destructive-muted text-destructive-muted-foreground",
|
|
33
|
+
modified: "bg-info-muted text-info-muted-foreground",
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
const formatValue = (value: unknown) =>
|
|
@@ -103,12 +103,12 @@ const Differences = ({
|
|
|
103
103
|
<p className="text-muted-foreground">{entry.detail}</p>
|
|
104
104
|
)}
|
|
105
105
|
{entry.type !== "added" && entry.before !== undefined && (
|
|
106
|
-
<pre className="bg-
|
|
106
|
+
<pre className="bg-destructive-muted/60 rounded-md p-2 font-mono text-xs whitespace-pre-wrap">
|
|
107
107
|
- {formatValue(entry.before)}
|
|
108
108
|
</pre>
|
|
109
109
|
)}
|
|
110
110
|
{entry.type !== "removed" && entry.after !== undefined && (
|
|
111
|
-
<pre className="bg-
|
|
111
|
+
<pre className="bg-success-muted/60 rounded-md p-2 font-mono text-xs whitespace-pre-wrap">
|
|
112
112
|
+ {formatValue(entry.after)}
|
|
113
113
|
</pre>
|
|
114
114
|
)}
|
|
@@ -22,13 +22,12 @@ interface WorkflowHistoryTableProps {
|
|
|
22
22
|
onSelectVersion: (versionId: string) => void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const statusBadgeClass =
|
|
26
|
-
"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400";
|
|
25
|
+
const statusBadgeClass = "bg-success-muted text-success-muted-foreground";
|
|
27
26
|
|
|
28
27
|
const summaryBadgeClasses: Record<"added" | "removed" | "modified", string> = {
|
|
29
28
|
added: statusBadgeClass,
|
|
30
|
-
removed: "bg-
|
|
31
|
-
modified: "bg-
|
|
29
|
+
removed: "bg-destructive-muted text-destructive-muted-foreground",
|
|
30
|
+
modified: "bg-info-muted text-info-muted-foreground",
|
|
32
31
|
};
|
|
33
32
|
|
|
34
33
|
const SummaryBadge = ({
|