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.
Files changed (90) hide show
  1. package/.bumpversion.cfg +1 -1
  2. package/dist/assets/{arc-C-7b0zH4.js → arc-D2-yXHYr.js} +1 -1
  3. package/dist/assets/{architectureDiagram-VXUJARFQ-Ciy8lV6J.js → architectureDiagram-VXUJARFQ-T90TGA7h.js} +1 -1
  4. package/dist/assets/{blockDiagram-VD42YOAC-dC88adQ_.js → blockDiagram-VD42YOAC-DD-sctie.js} +1 -1
  5. package/dist/assets/{c4Diagram-YG6GDRKO-BiHVvXEu.js → c4Diagram-YG6GDRKO-CMFh9ow5.js} +1 -1
  6. package/dist/assets/channel-BHDMf1UK.js +1 -0
  7. package/dist/assets/{chunk-4BX2VUAB-C6V7Huup.js → chunk-4BX2VUAB-CxEXc54f.js} +1 -1
  8. package/dist/assets/{chunk-55IACEB6-DRmZnlbz.js → chunk-55IACEB6-Cu79bwG9.js} +1 -1
  9. package/dist/assets/{chunk-B4BG7PRW-DwV5xRJr.js → chunk-B4BG7PRW-B9kTs9bC.js} +1 -1
  10. package/dist/assets/{chunk-DI55MBZ5-BHsUZ0IJ.js → chunk-DI55MBZ5-C4vlj7YJ.js} +1 -1
  11. package/dist/assets/{chunk-FMBD7UC4-6Q-Lz0AM.js → chunk-FMBD7UC4-BAbI4xA2.js} +1 -1
  12. package/dist/assets/{chunk-QN33PNHL-BFy_ml8O.js → chunk-QN33PNHL-Byawnjwi.js} +1 -1
  13. package/dist/assets/{chunk-QZHKN3VN-C1fN91FN.js → chunk-QZHKN3VN-X9swQQ1H.js} +1 -1
  14. package/dist/assets/{chunk-TZMSLE5B-FLRSQ_Fi.js → chunk-TZMSLE5B-qdHYq1mZ.js} +1 -1
  15. package/dist/assets/classDiagram-2ON5EDUG-CmzZyWnG.js +1 -0
  16. package/dist/assets/classDiagram-v2-WZHVMYZB-CmzZyWnG.js +1 -0
  17. package/dist/assets/clone-BCqfTdPh.js +1 -0
  18. package/dist/assets/{cose-bilkent-S5V4N54A-BSWP1X1D.js → cose-bilkent-S5V4N54A-DxP3F1r-.js} +1 -1
  19. package/dist/assets/{dagre-6UL2VRFP-CYoZFlcz.js → dagre-6UL2VRFP-JZzCnaiy.js} +1 -1
  20. package/dist/assets/{diagram-PSM6KHXK-B8RGX0YL.js → diagram-PSM6KHXK-xDDV3S5Y.js} +1 -1
  21. package/dist/assets/{diagram-QEK2KX5R-ChWXJ8p_.js → diagram-QEK2KX5R-GQ6mbBr_.js} +1 -1
  22. package/dist/assets/{diagram-S2PKOQOG-Dwic0Fjn.js → diagram-S2PKOQOG-CuoOnnN6.js} +1 -1
  23. package/dist/assets/{erDiagram-Q2GNP2WA-0NSDB0DX.js → erDiagram-Q2GNP2WA-CFMLhasi.js} +1 -1
  24. package/dist/assets/{flowDiagram-NV44I4VS-DEoGWs9L.js → flowDiagram-NV44I4VS-Dfz2XZo7.js} +1 -1
  25. package/dist/assets/{ganttDiagram-JELNMOA3-DqzV4S6l.js → ganttDiagram-JELNMOA3-Bv566-v-.js} +1 -1
  26. package/dist/assets/{gitGraphDiagram-V2S2FVAM-B-EIQM_H.js → gitGraphDiagram-V2S2FVAM-Bp0dgH1E.js} +1 -1
  27. package/dist/assets/{graph-Ha1DTJNG.js → graph-Dkgmy-o1.js} +1 -1
  28. package/dist/assets/index-BoHYiVT1.js +810 -0
  29. package/dist/assets/index-CES1mKJf.css +1 -0
  30. package/dist/assets/{infoDiagram-HS3SLOUP-C4_vpEkn.js → infoDiagram-HS3SLOUP-CoZX6U-w.js} +1 -1
  31. package/dist/assets/{isUndefined-DkuoqItu.js → isUndefined-DFmulv-o.js} +1 -1
  32. package/dist/assets/{journeyDiagram-XKPGCS4Q-BxY4e6OZ.js → journeyDiagram-XKPGCS4Q-CI7nMgKu.js} +1 -1
  33. package/dist/assets/{kanban-definition-3W4ZIXB7-BTX3_Q7O.js → kanban-definition-3W4ZIXB7-CyVNs44-.js} +1 -1
  34. package/dist/assets/{layout-Dt1W0fl0.js → layout-DZqvH5JV.js} +1 -1
  35. package/dist/assets/{linear-CjywSMUX.js → linear-CiLQWbi5.js} +1 -1
  36. package/dist/assets/{min-Dblq6bty.js → min-CmXn2PCy.js} +1 -1
  37. package/dist/assets/{mindmap-definition-VGOIOE7T-C5P7bAFm.js → mindmap-definition-VGOIOE7T-B_F2Nu7b.js} +1 -1
  38. package/dist/assets/{pieDiagram-ADFJNKIX-BSMylwok.js → pieDiagram-ADFJNKIX-BJvzYaii.js} +1 -1
  39. package/dist/assets/{quadrantDiagram-AYHSOK5B-C74B7bqa.js → quadrantDiagram-AYHSOK5B-Cqk76fvz.js} +1 -1
  40. package/dist/assets/{requirementDiagram-UZGBJVZJ-CvPzL8Le.js → requirementDiagram-UZGBJVZJ-vGfLRbYk.js} +1 -1
  41. package/dist/assets/{sankeyDiagram-TZEHDZUN-BFHm6g6t.js → sankeyDiagram-TZEHDZUN-CregXq64.js} +1 -1
  42. package/dist/assets/{sequenceDiagram-WL72ISMW-e74mJ0xk.js → sequenceDiagram-WL72ISMW-DNrzmFke.js} +1 -1
  43. package/dist/assets/{stateDiagram-FKZM4ZOC-VLQxNVA7.js → stateDiagram-FKZM4ZOC-gdr8ZncR.js} +1 -1
  44. package/dist/assets/stateDiagram-v2-4FDKWEC3-BbOMPcrN.js +1 -0
  45. package/dist/assets/{timeline-definition-IT6M3QCI-oveNP_QD.js → timeline-definition-IT6M3QCI-CdtbG32P.js} +1 -1
  46. package/dist/assets/{treemap-GDKQZRPO-Cowimd29.js → treemap-GDKQZRPO-uMGJkzE9.js} +1 -1
  47. package/dist/assets/{xychartDiagram-PRI3JC2R-CP9Fm_aO.js → xychartDiagram-PRI3JC2R-BsBFf9PQ.js} +1 -1
  48. package/dist/favicon.ico +0 -0
  49. package/dist/index.html +8 -2
  50. package/dist/orcheo-mark.png +0 -0
  51. package/index.html +6 -0
  52. package/package.json +1 -1
  53. package/public/favicon.ico +0 -0
  54. package/public/orcheo-mark.png +0 -0
  55. package/src/design-system/ui/button.tsx +2 -2
  56. package/src/features/account/components/settings/appearance-settings-tab.tsx +1 -1
  57. package/src/features/account/pages/profile/components/profile-general-tab.tsx +1 -1
  58. package/src/features/auth/pages/login.tsx +1 -1
  59. package/src/features/auth/pages/verify.tsx +1 -1
  60. package/src/features/chatkit/components/public-chat-error-boundary.tsx +1 -1
  61. package/src/features/chatkit/components/public-chat-widget.tsx +2 -3
  62. package/src/features/chatkit/lib/chatkit-theme.ts +3 -2
  63. package/src/features/chatkit/pages/public-chat.tsx +17 -15
  64. package/src/features/shared/components/top-navigation/studio-brand.tsx +1 -1
  65. package/src/features/shared/components/workspace-bootstrap-gate.tsx +4 -4
  66. package/src/features/workflow/components/dialogs/credential-access-badge.tsx +2 -2
  67. package/src/features/workflow/components/dialogs/credential-status-badge.tsx +2 -2
  68. package/src/features/workflow/components/dialogs/credentials-table.tsx +1 -1
  69. package/src/features/workflow/components/dialogs/update-workflow-dialog.tsx +34 -78
  70. package/src/features/workflow/components/dialogs/upload-workflow-dialog.tsx +40 -136
  71. package/src/features/workflow/components/dialogs/workflow-folder-picker.tsx +178 -0
  72. package/src/features/workflow/components/panels/workflow-diff-dialog.tsx +5 -5
  73. package/src/features/workflow/components/panels/workflow-history-table.tsx +3 -4
  74. package/src/features/workflow/components/trace/agent-prism/theme/theme.css +74 -74
  75. package/src/features/workflow/lib/mermaid-renderer.ts +86 -2
  76. package/src/features/workflow/lib/workflow-folder-selection.ts +239 -0
  77. package/src/features/workflow/pages/workflow/components/settings-tab-content.tsx +24 -9
  78. package/src/features/workflow/pages/workflow/components/workflow-tab-content.tsx +8 -5
  79. package/src/features/workflow/pages/workflow-gallery/workflow-card.tsx +16 -16
  80. package/src/index.css +126 -59
  81. package/tailwind.config.js +50 -2
  82. package/dist/assets/channel-pJdT5i-M.js +0 -1
  83. package/dist/assets/classDiagram-2ON5EDUG-CNT-EGNU.js +0 -1
  84. package/dist/assets/classDiagram-v2-WZHVMYZB-CNT-EGNU.js +0 -1
  85. package/dist/assets/clone-BCi0j082.js +0 -1
  86. package/dist/assets/index-DTxvVcXp.js +0 -795
  87. package/dist/assets/index-Df4jFRhd.css +0 -1
  88. package/dist/assets/stateDiagram-v2-4FDKWEC3-Bbpq2t6B.js +0 -1
  89. package/dist/orcheo.png +0 -0
  90. package/public/orcheo.png +0 -0
@@ -1,4 +1,4 @@
1
- import { useCallback, useRef, useState } from "react";
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 handleScriptFileChange = useCallback(
102
- (event: React.ChangeEvent<HTMLInputElement>) => {
103
- const file = event.target.files?.[0];
104
- if (!file) {
105
- return;
106
- }
107
- const validationError = validateUploadFile(file, {
108
- label: "Workflow script",
109
- extension: ".py",
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 reader = new FileReader();
126
- reader.onload = (e) => {
127
- const text = e.target?.result;
128
- if (typeof text === "string") {
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 handleConfigFileChange = useCallback(
142
- (event: React.ChangeEvent<HTMLInputElement>) => {
143
- const file = event.target.files?.[0];
144
- if (!file) {
145
- return;
146
- }
147
- const validationError = validateUploadFile(file, {
148
- label: "Config file",
149
- extension: ".json",
150
- maxBytes: MAX_CONFIG_UPLOAD_BYTES,
151
- acceptedMimeTypes: JSON_CONFIG_MIME_TYPES,
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
- Upload a Python workflow script and an optional JSON config to
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-script">
240
- Workflow Script <span className="text-destructive">*</span>
157
+ <Label htmlFor="upload-folder">
158
+ Workflow Folder <span className="text-destructive">*</span>
241
159
  </Label>
242
- <Input
243
- id="upload-script"
244
- ref={scriptInputRef}
245
- type="file"
246
- accept=".py"
247
- onChange={handleScriptFileChange}
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-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
32
- removed: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
33
- modified: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
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-red-50 dark:bg-red-900/20 rounded-md p-2 font-mono text-xs whitespace-pre-wrap">
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-green-50 dark:bg-green-900/20 rounded-md p-2 font-mono text-xs whitespace-pre-wrap">
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-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
31
- modified: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
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 = ({