@ryanfw/prompt-orchestration-pipeline 0.0.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +415 -24
- package/package.json +45 -8
- package/src/api/files.js +48 -0
- package/src/api/index.js +149 -53
- package/src/api/validators/seed.js +141 -0
- package/src/cli/index.js +456 -29
- package/src/cli/run-orchestrator.js +39 -0
- package/src/cli/update-pipeline-json.js +47 -0
- package/src/components/DAGGrid.jsx +649 -0
- package/src/components/JobCard.jsx +96 -0
- package/src/components/JobDetail.jsx +159 -0
- package/src/components/JobTable.jsx +202 -0
- package/src/components/Layout.jsx +134 -0
- package/src/components/TaskFilePane.jsx +570 -0
- package/src/components/UploadSeed.jsx +239 -0
- package/src/components/ui/badge.jsx +20 -0
- package/src/components/ui/button.jsx +43 -0
- package/src/components/ui/card.jsx +20 -0
- package/src/components/ui/focus-styles.css +60 -0
- package/src/components/ui/progress.jsx +26 -0
- package/src/components/ui/select.jsx +27 -0
- package/src/components/ui/separator.jsx +6 -0
- package/src/config/paths.js +99 -0
- package/src/core/config.js +270 -9
- package/src/core/file-io.js +202 -0
- package/src/core/module-loader.js +157 -0
- package/src/core/orchestrator.js +275 -294
- package/src/core/pipeline-runner.js +95 -41
- package/src/core/progress.js +66 -0
- package/src/core/status-writer.js +331 -0
- package/src/core/task-runner.js +719 -73
- package/src/core/validation.js +120 -1
- package/src/lib/utils.js +6 -0
- package/src/llm/README.md +139 -30
- package/src/llm/index.js +222 -72
- package/src/pages/PipelineDetail.jsx +111 -0
- package/src/pages/PromptPipelineDashboard.jsx +223 -0
- package/src/providers/deepseek.js +3 -15
- package/src/ui/client/adapters/job-adapter.js +258 -0
- package/src/ui/client/bootstrap.js +120 -0
- package/src/ui/client/hooks/useJobDetailWithUpdates.js +619 -0
- package/src/ui/client/hooks/useJobList.js +50 -0
- package/src/ui/client/hooks/useJobListWithUpdates.js +335 -0
- package/src/ui/client/hooks/useTicker.js +26 -0
- package/src/ui/client/index.css +31 -0
- package/src/ui/client/index.html +18 -0
- package/src/ui/client/main.jsx +38 -0
- package/src/ui/config-bridge.browser.js +149 -0
- package/src/ui/config-bridge.js +149 -0
- package/src/ui/config-bridge.node.js +310 -0
- package/src/ui/dist/assets/index-BDABnI-4.js +33399 -0
- package/src/ui/dist/assets/style-Ks8LY8gB.css +28496 -0
- package/src/ui/dist/index.html +19 -0
- package/src/ui/endpoints/job-endpoints.js +300 -0
- package/src/ui/file-reader.js +216 -0
- package/src/ui/job-change-detector.js +83 -0
- package/src/ui/job-index.js +231 -0
- package/src/ui/job-reader.js +274 -0
- package/src/ui/job-scanner.js +188 -0
- package/src/ui/public/app.js +3 -1
- package/src/ui/server.js +1636 -59
- package/src/ui/sse-enhancer.js +149 -0
- package/src/ui/sse.js +204 -0
- package/src/ui/state-snapshot.js +252 -0
- package/src/ui/transformers/list-transformer.js +347 -0
- package/src/ui/transformers/status-transformer.js +307 -0
- package/src/ui/watcher.js +61 -7
- package/src/utils/dag.js +101 -0
- package/src/utils/duration.js +126 -0
- package/src/utils/id-generator.js +30 -0
- package/src/utils/jobs.js +7 -0
- package/src/utils/pipelines.js +44 -0
- package/src/utils/task-files.js +271 -0
- package/src/utils/ui.jsx +76 -0
- package/src/ui/public/index.html +0 -53
- package/src/ui/public/style.css +0 -341
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box } from "@radix-ui/themes";
|
|
3
|
+
import { Button } from "./ui/button.jsx";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Normalize upload errors to a user-facing string
|
|
7
|
+
*/
|
|
8
|
+
export const normalizeUploadError = (err) => {
|
|
9
|
+
if (!err) return "Upload failed";
|
|
10
|
+
if (typeof err === "string") return err;
|
|
11
|
+
if (err instanceof Error) return err.message;
|
|
12
|
+
if (typeof err === "object") {
|
|
13
|
+
if ("message" in err && err.message) return String(err.message);
|
|
14
|
+
if ("error" in err && err.error) return String(err.error);
|
|
15
|
+
}
|
|
16
|
+
return "Upload failed";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* UploadSeed component for uploading seed files
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} props
|
|
23
|
+
* @param {function} props.onUploadSuccess - Callback called on successful upload with { jobName }
|
|
24
|
+
*/
|
|
25
|
+
export default function UploadSeed({ onUploadSuccess }) {
|
|
26
|
+
const fileInputRef = React.useRef(null);
|
|
27
|
+
const [showSample, setShowSample] = useState(false);
|
|
28
|
+
const [error, setError] = useState(null);
|
|
29
|
+
|
|
30
|
+
// Sample seed file structure for reference
|
|
31
|
+
const sampleSeed = {
|
|
32
|
+
name: "some-name",
|
|
33
|
+
pipeline: "content-generation",
|
|
34
|
+
data: {
|
|
35
|
+
type: "some-type",
|
|
36
|
+
contentType: "blog-post",
|
|
37
|
+
targetAudience: "software-developers",
|
|
38
|
+
tone: "professional-yet-accessible",
|
|
39
|
+
length: "1500-2000 words",
|
|
40
|
+
outputFormat: "blog-post",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const handleFileChange = async (event) => {
|
|
45
|
+
const files = event.target.files;
|
|
46
|
+
if (!files || files.length === 0) return;
|
|
47
|
+
|
|
48
|
+
const file = files[0];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const formData = new FormData();
|
|
52
|
+
formData.append("file", file);
|
|
53
|
+
|
|
54
|
+
const response = await fetch("/api/upload/seed", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: formData,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const result = await response.json();
|
|
60
|
+
|
|
61
|
+
if (result.success) {
|
|
62
|
+
// Emit console log as required
|
|
63
|
+
console.log("Seed uploaded:", result.jobName);
|
|
64
|
+
|
|
65
|
+
// Clear any prior error and call success callback
|
|
66
|
+
setError(null);
|
|
67
|
+
if (onUploadSuccess) {
|
|
68
|
+
onUploadSuccess({ jobName: result.jobName });
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
console.error("Upload failed:", result.message);
|
|
72
|
+
setError(normalizeUploadError(result));
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error("Upload error:", error);
|
|
76
|
+
setError(normalizeUploadError(error));
|
|
77
|
+
} finally {
|
|
78
|
+
// Reset file input
|
|
79
|
+
if (fileInputRef.current) {
|
|
80
|
+
fileInputRef.current.value = "";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleDropAreaClick = () => {
|
|
86
|
+
if (fileInputRef.current) {
|
|
87
|
+
fileInputRef.current.click();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleDragOver = (event) => {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
event.currentTarget.classList.add("border-blue-500", "bg-blue-50");
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleDragLeave = (event) => {
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
event.currentTarget.classList.remove("border-blue-500", "bg-blue-50");
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleDrop = (event) => {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
event.currentTarget.classList.remove("border-blue-500", "bg-blue-50");
|
|
104
|
+
|
|
105
|
+
if (event.dataTransfer.files.length > 0) {
|
|
106
|
+
const files = event.dataTransfer.files;
|
|
107
|
+
const fileInput = fileInputRef.current;
|
|
108
|
+
if (fileInput) {
|
|
109
|
+
const dataTransfer = new DataTransfer();
|
|
110
|
+
dataTransfer.items.add(files[0]);
|
|
111
|
+
fileInput.files = dataTransfer.files;
|
|
112
|
+
handleFileChange({ target: fileInput });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div data-testid="upload-seed" className="space-y-3">
|
|
119
|
+
{error && (
|
|
120
|
+
<Box
|
|
121
|
+
role="alert"
|
|
122
|
+
data-testid="upload-error"
|
|
123
|
+
className="rounded-md bg-red-50 p-3 border border-red-200"
|
|
124
|
+
>
|
|
125
|
+
<div className="flex items-start justify-between gap-3">
|
|
126
|
+
<div className="text-sm text-red-800">{error}</div>
|
|
127
|
+
<Button
|
|
128
|
+
size="1"
|
|
129
|
+
variant="ghost"
|
|
130
|
+
onClick={() => setError(null)}
|
|
131
|
+
data-testid="dismiss-error"
|
|
132
|
+
>
|
|
133
|
+
Dismiss
|
|
134
|
+
</Button>
|
|
135
|
+
</div>
|
|
136
|
+
</Box>
|
|
137
|
+
)}
|
|
138
|
+
<div
|
|
139
|
+
data-testid="upload-area"
|
|
140
|
+
className={`
|
|
141
|
+
border-2 border-dashed rounded-lg p-6 text-center cursor-pointer transition-colors
|
|
142
|
+
border-gray-400 bg-white text-gray-600 hover:border-blue-500 hover:bg-blue-50
|
|
143
|
+
`}
|
|
144
|
+
onClick={handleDropAreaClick}
|
|
145
|
+
onDragOver={handleDragOver}
|
|
146
|
+
onDragLeave={handleDragLeave}
|
|
147
|
+
onDrop={handleDrop}
|
|
148
|
+
role="button"
|
|
149
|
+
tabIndex={0}
|
|
150
|
+
>
|
|
151
|
+
<div className="space-y-2">
|
|
152
|
+
<svg
|
|
153
|
+
className="mx-auto h-8 w-8 text-gray-400"
|
|
154
|
+
fill="none"
|
|
155
|
+
viewBox="0 0 24 24"
|
|
156
|
+
stroke="currentColor"
|
|
157
|
+
aria-hidden="true"
|
|
158
|
+
>
|
|
159
|
+
<path
|
|
160
|
+
strokeLinecap="round"
|
|
161
|
+
strokeLinejoin="round"
|
|
162
|
+
strokeWidth={2}
|
|
163
|
+
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
|
164
|
+
/>
|
|
165
|
+
</svg>
|
|
166
|
+
<div className="text-sm">
|
|
167
|
+
<span className="font-medium text-gray-900">Click to upload</span>{" "}
|
|
168
|
+
or drag and drop
|
|
169
|
+
</div>
|
|
170
|
+
<p className="text-xs text-gray-500">JSON files only</p>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<input
|
|
175
|
+
ref={fileInputRef}
|
|
176
|
+
type="file"
|
|
177
|
+
accept=".json"
|
|
178
|
+
className="hidden"
|
|
179
|
+
onChange={handleFileChange}
|
|
180
|
+
data-testid="file-input"
|
|
181
|
+
/>
|
|
182
|
+
|
|
183
|
+
{/* Sample seed file section */}
|
|
184
|
+
<div className="border border-gray-200 rounded-lg overflow-hidden">
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
onClick={() => setShowSample(!showSample)}
|
|
188
|
+
className="w-full px-4 py-3 text-left bg-gray-50 hover:bg-gray-100 transition-colors flex items-center justify-between"
|
|
189
|
+
aria-expanded={showSample}
|
|
190
|
+
data-testid="sample-toggle"
|
|
191
|
+
>
|
|
192
|
+
<span className="text-sm font-medium text-gray-700">
|
|
193
|
+
Need help? View sample seed file structure
|
|
194
|
+
</span>
|
|
195
|
+
<svg
|
|
196
|
+
className={`w-4 h-4 text-gray-500 transition-transform ${
|
|
197
|
+
showSample ? "rotate-180" : ""
|
|
198
|
+
}`}
|
|
199
|
+
fill="none"
|
|
200
|
+
viewBox="0 0 24 24"
|
|
201
|
+
stroke="currentColor"
|
|
202
|
+
>
|
|
203
|
+
<path
|
|
204
|
+
strokeLinecap="round"
|
|
205
|
+
strokeLinejoin="round"
|
|
206
|
+
strokeWidth={2}
|
|
207
|
+
d="M19 9l-7 7-7-7"
|
|
208
|
+
/>
|
|
209
|
+
</svg>
|
|
210
|
+
</button>
|
|
211
|
+
|
|
212
|
+
{showSample && (
|
|
213
|
+
<div className="p-4 bg-white border-t border-gray-200">
|
|
214
|
+
<div className="flex items-center justify-between mb-2">
|
|
215
|
+
<p className="text-xs text-gray-600">
|
|
216
|
+
Use this structure as a reference for your seed file:
|
|
217
|
+
</p>
|
|
218
|
+
<button
|
|
219
|
+
type="button"
|
|
220
|
+
onClick={() =>
|
|
221
|
+
navigator.clipboard.writeText(
|
|
222
|
+
JSON.stringify(sampleSeed, null, 2)
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
className="text-xs bg-blue-100 text-blue-700 px-2 py-1 rounded hover:bg-blue-200 transition-colors"
|
|
226
|
+
data-testid="copy-sample"
|
|
227
|
+
>
|
|
228
|
+
Copy
|
|
229
|
+
</button>
|
|
230
|
+
</div>
|
|
231
|
+
<pre className="text-xs bg-gray-50 p-3 rounded overflow-auto max-h-60">
|
|
232
|
+
{JSON.stringify(sampleSeed, null, 2)}
|
|
233
|
+
</pre>
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export function Badge({ children, intent = "gray", className = "", ...props }) {
|
|
3
|
+
const intents = {
|
|
4
|
+
gray: "border-slate-300 text-slate-700 bg-slate-100",
|
|
5
|
+
blue: "border-blue-300 text-blue-800 bg-blue-100",
|
|
6
|
+
green: "border-green-300 text-green-800 bg-green-100",
|
|
7
|
+
red: "border-red-300 text-red-800 bg-red-100",
|
|
8
|
+
amber: "border-amber-300 text-amber-900 bg-amber-100",
|
|
9
|
+
};
|
|
10
|
+
const cls = [
|
|
11
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium",
|
|
12
|
+
intents[intent] || intents.gray,
|
|
13
|
+
className,
|
|
14
|
+
].join(" ");
|
|
15
|
+
return (
|
|
16
|
+
<span className={cls} {...props}>
|
|
17
|
+
{children}
|
|
18
|
+
</span>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button as RadixButton } from "@radix-ui/themes";
|
|
3
|
+
|
|
4
|
+
export function Button({
|
|
5
|
+
variant = "solid",
|
|
6
|
+
size = "2",
|
|
7
|
+
className = "",
|
|
8
|
+
...props
|
|
9
|
+
}) {
|
|
10
|
+
// Map custom variant names to Radix UI variants
|
|
11
|
+
const radixVariant =
|
|
12
|
+
variant === "solid"
|
|
13
|
+
? "solid"
|
|
14
|
+
: variant === "outline"
|
|
15
|
+
? "outline"
|
|
16
|
+
: variant === "ghost"
|
|
17
|
+
? "ghost"
|
|
18
|
+
: variant === "secondary"
|
|
19
|
+
? "soft"
|
|
20
|
+
: variant === "destructive"
|
|
21
|
+
? "solid"
|
|
22
|
+
: "solid";
|
|
23
|
+
|
|
24
|
+
// Map custom size names to Radix UI sizes
|
|
25
|
+
const radixSize =
|
|
26
|
+
size === "sm" ? "1" : size === "md" ? "2" : size === "lg" ? "3" : "2";
|
|
27
|
+
|
|
28
|
+
// Map destructive variant to appropriate color
|
|
29
|
+
const color = variant === "destructive" ? "red" : undefined;
|
|
30
|
+
|
|
31
|
+
// Combine base classes with any additional className
|
|
32
|
+
const combinedClassName = `transition-colors duration-200 ${className}`;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<RadixButton
|
|
36
|
+
variant={radixVariant}
|
|
37
|
+
size={radixSize}
|
|
38
|
+
color={color}
|
|
39
|
+
className={combinedClassName}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export function Card({ className = "", ...p }) {
|
|
3
|
+
return (
|
|
4
|
+
<div
|
|
5
|
+
className={["rounded-xl border bg-white shadow-sm", className].join(" ")}
|
|
6
|
+
{...p}
|
|
7
|
+
/>
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
export function CardHeader({ className = "", ...p }) {
|
|
11
|
+
return <div className={["p-4 border-b", className].join(" ")} {...p} />;
|
|
12
|
+
}
|
|
13
|
+
export function CardTitle({ className = "", ...p }) {
|
|
14
|
+
return (
|
|
15
|
+
<h3 className={["text-base font-semibold", className].join(" ")} {...p} />
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
export function CardContent({ className = "", ...p }) {
|
|
19
|
+
return <div className={["p-4", className].join(" ")} {...p} />;
|
|
20
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* Focus styles for accessibility */
|
|
2
|
+
.focus-visible:focus {
|
|
3
|
+
outline: 2px solid #2563eb;
|
|
4
|
+
outline-offset: 2px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* High contrast focus for better visibility */
|
|
8
|
+
.focus-visible:focus-visible {
|
|
9
|
+
outline: 2px solid #1d4ed8;
|
|
10
|
+
outline-offset: 2px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Skip link styles */
|
|
14
|
+
.skip-link {
|
|
15
|
+
position: absolute;
|
|
16
|
+
top: -40px;
|
|
17
|
+
left: 6px;
|
|
18
|
+
background: #2563eb;
|
|
19
|
+
color: white;
|
|
20
|
+
padding: 8px;
|
|
21
|
+
text-decoration: none;
|
|
22
|
+
border-radius: 4px;
|
|
23
|
+
z-index: 1000;
|
|
24
|
+
transition: top 0.3s;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.skip-link:focus {
|
|
28
|
+
top: 6px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Ensure good contrast for interactive elements */
|
|
32
|
+
.interactive-element {
|
|
33
|
+
min-height: 44px;
|
|
34
|
+
min-width: 44px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Header navigation focus styles */
|
|
38
|
+
.nav-link:focus {
|
|
39
|
+
background-color: #f3f4f6;
|
|
40
|
+
border-radius: 4px;
|
|
41
|
+
outline: 2px solid #2563eb;
|
|
42
|
+
outline-offset: -2px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Button focus styles */
|
|
46
|
+
button:focus-visible {
|
|
47
|
+
outline: 2px solid #2563eb;
|
|
48
|
+
outline-offset: 2px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Link hover and focus states */
|
|
52
|
+
a:hover {
|
|
53
|
+
text-decoration: underline;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
a:focus-visible {
|
|
57
|
+
outline: 2px solid #2563eb;
|
|
58
|
+
outline-offset: 2px;
|
|
59
|
+
border-radius: 2px;
|
|
60
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export function Progress({ value = 0, variant = "default", className = "" }) {
|
|
3
|
+
const pct = Math.max(0, Math.min(100, Number(value)));
|
|
4
|
+
|
|
5
|
+
const variantClasses = {
|
|
6
|
+
default: "bg-blue-600",
|
|
7
|
+
running: "bg-blue-600",
|
|
8
|
+
error: "bg-red-600",
|
|
9
|
+
completed: "bg-green-600",
|
|
10
|
+
pending: "bg-slate-400",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className={[
|
|
16
|
+
"h-2 w-full overflow-hidden rounded bg-slate-200",
|
|
17
|
+
className,
|
|
18
|
+
].join(" ")}
|
|
19
|
+
>
|
|
20
|
+
<div
|
|
21
|
+
className={`h-full transition-all duration-300 ${variantClasses[variant] || variantClasses.default}`}
|
|
22
|
+
style={{ width: `${pct}%` }}
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export function Select({ value, onValueChange, children, className = "" }) {
|
|
3
|
+
return (
|
|
4
|
+
<select
|
|
5
|
+
className={[
|
|
6
|
+
"h-9 rounded-md border px-3 text-sm bg-white",
|
|
7
|
+
className,
|
|
8
|
+
].join(" ")}
|
|
9
|
+
value={value}
|
|
10
|
+
onChange={(e) => onValueChange?.(e.target.value)}
|
|
11
|
+
>
|
|
12
|
+
{children}
|
|
13
|
+
</select>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
export function SelectItem({ value, children }) {
|
|
17
|
+
return <option value={value}>{children}</option>;
|
|
18
|
+
}
|
|
19
|
+
export function SelectTrigger({ children, ...p }) {
|
|
20
|
+
return <>{children}</>;
|
|
21
|
+
} // keep API compatible
|
|
22
|
+
export function SelectContent({ children }) {
|
|
23
|
+
return <>{children}</>;
|
|
24
|
+
}
|
|
25
|
+
export function SelectValue({ placeholder }) {
|
|
26
|
+
return <>{placeholder}</>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path resolution utilities for pipeline data directories
|
|
3
|
+
* @module config/paths
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from "path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve pipeline data directory paths from a base directory
|
|
10
|
+
* @param {string} baseDir - Base data directory
|
|
11
|
+
* @returns {Object} Object containing resolved paths
|
|
12
|
+
*/
|
|
13
|
+
function resolvePipelinePaths(baseDir) {
|
|
14
|
+
return {
|
|
15
|
+
pending: path.join(baseDir, "pipeline-data", "pending"),
|
|
16
|
+
current: path.join(baseDir, "pipeline-data", "current"),
|
|
17
|
+
complete: path.join(baseDir, "pipeline-data", "complete"),
|
|
18
|
+
rejected: path.join(baseDir, "pipeline-data", "rejected"),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the exact pending filename for a given job ID
|
|
24
|
+
* @param {string} baseDir - Base data directory
|
|
25
|
+
* @param {string} jobId - Job ID
|
|
26
|
+
* @returns {string} Full path to pending seed file
|
|
27
|
+
*/
|
|
28
|
+
function getPendingSeedPath(baseDir, jobId) {
|
|
29
|
+
const paths = resolvePipelinePaths(baseDir);
|
|
30
|
+
return path.join(paths.pending, `${jobId}-seed.json`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the current seed file path for a given job ID
|
|
35
|
+
* @param {string} baseDir - Base data directory
|
|
36
|
+
* @param {string} jobId - Job ID
|
|
37
|
+
* @returns {string} Full path to current seed file
|
|
38
|
+
*/
|
|
39
|
+
function getCurrentSeedPath(baseDir, jobId) {
|
|
40
|
+
const paths = resolvePipelinePaths(baseDir);
|
|
41
|
+
return path.join(paths.current, jobId, "seed.json");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the complete seed file path for a given job ID
|
|
46
|
+
* @param {string} baseDir - Base data directory
|
|
47
|
+
* @param {string} jobId - Job ID
|
|
48
|
+
* @returns {string} Full path to complete seed file
|
|
49
|
+
*/
|
|
50
|
+
function getCompleteSeedPath(baseDir, jobId) {
|
|
51
|
+
const paths = resolvePipelinePaths(baseDir);
|
|
52
|
+
return path.join(paths.complete, jobId, "seed.json");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the job directory path for a given job ID and location
|
|
57
|
+
* @param {string} baseDir - Base data directory
|
|
58
|
+
* @param {string} jobId - Job ID
|
|
59
|
+
* @param {string} location - Job location ('current', 'complete', 'pending')
|
|
60
|
+
* @returns {string} Full path to job directory
|
|
61
|
+
*/
|
|
62
|
+
function getJobDirectoryPath(baseDir, jobId, location) {
|
|
63
|
+
const paths = resolvePipelinePaths(baseDir);
|
|
64
|
+
return path.join(paths[location], jobId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the job metadata file path for a given job ID
|
|
69
|
+
* @param {string} baseDir - Base data directory
|
|
70
|
+
* @param {string} jobId - Job ID
|
|
71
|
+
* @param {string} location - Job location ('current', 'complete')
|
|
72
|
+
* @returns {string} Full path to job metadata file
|
|
73
|
+
*/
|
|
74
|
+
function getJobMetadataPath(baseDir, jobId, location = "current") {
|
|
75
|
+
const jobDir = getJobDirectoryPath(baseDir, jobId, location);
|
|
76
|
+
return path.join(jobDir, "job.json");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the pipeline snapshot file path for a given job ID
|
|
81
|
+
* @param {string} baseDir - Base data directory
|
|
82
|
+
* @param {string} jobId - Job ID
|
|
83
|
+
* @param {string} location - Job location ('current', 'complete')
|
|
84
|
+
* @returns {string} Full path to pipeline snapshot file
|
|
85
|
+
*/
|
|
86
|
+
function getJobPipelinePath(baseDir, jobId, location = "current") {
|
|
87
|
+
const jobDir = getJobDirectoryPath(baseDir, jobId, location);
|
|
88
|
+
return path.join(jobDir, "pipeline.json");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export {
|
|
92
|
+
resolvePipelinePaths,
|
|
93
|
+
getPendingSeedPath,
|
|
94
|
+
getCurrentSeedPath,
|
|
95
|
+
getCompleteSeedPath,
|
|
96
|
+
getJobDirectoryPath,
|
|
97
|
+
getJobMetadataPath,
|
|
98
|
+
getJobPipelinePath,
|
|
99
|
+
};
|