@ryanfw/prompt-orchestration-pipeline 0.12.0 → 0.13.1
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/package.json +10 -1
- package/src/cli/analyze-task.js +51 -0
- package/src/cli/index.js +8 -0
- package/src/components/AddPipelineSidebar.jsx +144 -0
- package/src/components/AnalysisProgressTray.jsx +87 -0
- package/src/components/JobTable.jsx +4 -3
- package/src/components/Layout.jsx +142 -139
- package/src/components/MarkdownRenderer.jsx +149 -0
- package/src/components/PipelineDAGGrid.jsx +404 -0
- package/src/components/PipelineTypeTaskSidebar.jsx +96 -0
- package/src/components/SchemaPreviewPanel.jsx +97 -0
- package/src/components/StageTimeline.jsx +36 -0
- package/src/components/TaskAnalysisDisplay.jsx +227 -0
- package/src/components/TaskCreationSidebar.jsx +447 -0
- package/src/components/TaskDetailSidebar.jsx +119 -117
- package/src/components/TaskFilePane.jsx +94 -39
- package/src/components/ui/button.jsx +59 -27
- package/src/components/ui/sidebar.jsx +118 -0
- package/src/config/models.js +99 -67
- package/src/core/config.js +4 -1
- package/src/llm/index.js +129 -9
- package/src/pages/PipelineDetail.jsx +6 -6
- package/src/pages/PipelineList.jsx +214 -0
- package/src/pages/PipelineTypeDetail.jsx +234 -0
- package/src/providers/deepseek.js +76 -16
- package/src/providers/openai.js +61 -34
- package/src/task-analysis/enrichers/analysis-writer.js +62 -0
- package/src/task-analysis/enrichers/schema-deducer.js +145 -0
- package/src/task-analysis/enrichers/schema-writer.js +74 -0
- package/src/task-analysis/extractors/artifacts.js +137 -0
- package/src/task-analysis/extractors/llm-calls.js +176 -0
- package/src/task-analysis/extractors/stages.js +51 -0
- package/src/task-analysis/index.js +103 -0
- package/src/task-analysis/parser.js +28 -0
- package/src/task-analysis/utils/ast.js +43 -0
- package/src/ui/client/hooks/useAnalysisProgress.js +145 -0
- package/src/ui/client/index.css +64 -0
- package/src/ui/client/main.jsx +4 -0
- package/src/ui/client/sse-fetch.js +120 -0
- package/src/ui/dist/assets/index-cjHV9mYW.js +82578 -0
- package/src/ui/dist/assets/index-cjHV9mYW.js.map +1 -0
- package/src/ui/dist/assets/style-CoM9SoQF.css +180 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/endpoints/create-pipeline-endpoint.js +194 -0
- package/src/ui/endpoints/pipeline-analysis-endpoint.js +246 -0
- package/src/ui/endpoints/pipeline-type-detail-endpoint.js +181 -0
- package/src/ui/endpoints/pipelines-endpoint.js +133 -0
- package/src/ui/endpoints/schema-file-endpoint.js +105 -0
- package/src/ui/endpoints/task-analysis-endpoint.js +104 -0
- package/src/ui/endpoints/task-creation-endpoint.js +114 -0
- package/src/ui/endpoints/task-save-endpoint.js +101 -0
- package/src/ui/express-app.js +45 -0
- package/src/ui/lib/analysis-lock.js +67 -0
- package/src/ui/lib/sse.js +30 -0
- package/src/ui/server.js +4 -0
- package/src/ui/utils/slug.js +31 -0
- package/src/ui/watcher.js +28 -2
- package/src/ui/dist/assets/index-B320avRx.js +0 -26613
- package/src/ui/dist/assets/index-B320avRx.js.map +0 -1
- package/src/ui/dist/assets/style-BYCoLBnK.css +0 -62
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryanfw/prompt-orchestration-pipeline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "A Prompt-orchestration pipeline (POP) is a framework for building, running, and experimenting with complex chains of LLM tasks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/ui/server.js",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
|
+
"analyze": "node src/cli/index.js analyze",
|
|
24
|
+
"deduce-schemas": "node scripts/deduce-schemas.js",
|
|
23
25
|
"test": "vitest run --config ./vite.config.js --root .",
|
|
24
26
|
"lint": "eslint . --ext .js,.jsx",
|
|
25
27
|
"backend": "NODE_ENV=development nodemon src/ui/server.js",
|
|
@@ -34,6 +36,9 @@
|
|
|
34
36
|
"demo:prod": "npm run ui:build && NODE_ENV=production PO_ROOT=demo node src/ui/server.js"
|
|
35
37
|
},
|
|
36
38
|
"dependencies": {
|
|
39
|
+
"@babel/parser": "^7.28.5",
|
|
40
|
+
"@babel/traverse": "^7.28.5",
|
|
41
|
+
"@babel/types": "^7.28.5",
|
|
37
42
|
"@radix-ui/react-progress": "^1.1.7",
|
|
38
43
|
"@radix-ui/react-tabs": "^1.1.13",
|
|
39
44
|
"@radix-ui/react-toast": "^1.2.15",
|
|
@@ -49,7 +54,11 @@
|
|
|
49
54
|
"openai": "^5.23.1",
|
|
50
55
|
"react": "^19.2.0",
|
|
51
56
|
"react-dom": "^19.2.0",
|
|
57
|
+
"react-markdown": "^10.1.0",
|
|
52
58
|
"react-router-dom": "^7.9.4",
|
|
59
|
+
"react-syntax-highlighter": "^15.6.1",
|
|
60
|
+
"rehype-highlight": "^7.0.2",
|
|
61
|
+
"remark-gfm": "^4.0.1",
|
|
53
62
|
"tslib": "^2.8.1"
|
|
54
63
|
},
|
|
55
64
|
"devDependencies": {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Analyze a task file and output the analysis as JSON.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} taskPath - Path to the task file
|
|
8
|
+
* @returns {Promise<void>}
|
|
9
|
+
*/
|
|
10
|
+
export async function analyzeTaskFile(taskPath) {
|
|
11
|
+
try {
|
|
12
|
+
// Use dynamic import to handle ESM/CommonJS interop for @babel/traverse
|
|
13
|
+
const { analyzeTask } = await import("../task-analysis/index.js");
|
|
14
|
+
|
|
15
|
+
// Resolve the task path (handle both relative and absolute paths)
|
|
16
|
+
const absolutePath = path.isAbsolute(taskPath)
|
|
17
|
+
? taskPath
|
|
18
|
+
: path.resolve(process.cwd(), taskPath);
|
|
19
|
+
|
|
20
|
+
// Read the task file
|
|
21
|
+
const code = await fs.readFile(absolutePath, "utf8");
|
|
22
|
+
|
|
23
|
+
// Run analysis
|
|
24
|
+
const analysis = analyzeTask(code, absolutePath);
|
|
25
|
+
|
|
26
|
+
// Output as JSON
|
|
27
|
+
console.log(JSON.stringify(analysis, null, 2));
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error && error.code === "ENOENT") {
|
|
30
|
+
console.error(`Error: Task file not found: ${taskPath}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const isDev =
|
|
35
|
+
process.env.NODE_ENV === "development" ||
|
|
36
|
+
process.env.DEBUG_TASK_ANALYSIS === "1";
|
|
37
|
+
|
|
38
|
+
console.error("Error analyzing task:");
|
|
39
|
+
|
|
40
|
+
if (isDev) {
|
|
41
|
+
// In development/debug mode, preserve full error context (including stack trace)
|
|
42
|
+
console.error(error && error.stack ? error.stack : error);
|
|
43
|
+
} else if (error && typeof error.message === "string") {
|
|
44
|
+
// In normal mode, show a concise message while keeping the library's formatting
|
|
45
|
+
console.error(error.message);
|
|
46
|
+
} else {
|
|
47
|
+
console.error(String(error));
|
|
48
|
+
}
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { updatePipelineJson } from "./update-pipeline-json.js";
|
|
10
|
+
import { analyzeTaskFile } from "./analyze-task.js";
|
|
10
11
|
|
|
11
12
|
// Derive package root for resolving internal paths regardless of host CWD
|
|
12
13
|
const currentFile = fileURLToPath(import.meta.url);
|
|
@@ -368,6 +369,13 @@ program
|
|
|
368
369
|
}
|
|
369
370
|
});
|
|
370
371
|
|
|
372
|
+
program
|
|
373
|
+
.command("analyze <task-path>")
|
|
374
|
+
.description("Analyze a task file and output metadata")
|
|
375
|
+
.action(async (taskPath) => {
|
|
376
|
+
await analyzeTaskFile(taskPath);
|
|
377
|
+
});
|
|
378
|
+
|
|
371
379
|
program
|
|
372
380
|
.command("add-pipeline-task <pipeline-slug> <task-slug>")
|
|
373
381
|
.description("Add a new task to a pipeline")
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { useNavigate } from "react-router-dom";
|
|
3
|
+
import { Button } from "./ui/button.jsx";
|
|
4
|
+
import { Sidebar, SidebarFooter } from "./ui/sidebar.jsx";
|
|
5
|
+
|
|
6
|
+
export function AddPipelineSidebar({ open, onOpenChange }) {
|
|
7
|
+
const [name, setName] = useState("");
|
|
8
|
+
const [description, setDescription] = useState("");
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
const [submitting, setSubmitting] = useState(false);
|
|
11
|
+
const navigate = useNavigate();
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!open) {
|
|
15
|
+
setName("");
|
|
16
|
+
setDescription("");
|
|
17
|
+
setError(null);
|
|
18
|
+
}
|
|
19
|
+
}, [open]);
|
|
20
|
+
|
|
21
|
+
const handleSubmit = async (e) => {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
setError(null);
|
|
24
|
+
setSubmitting(true);
|
|
25
|
+
|
|
26
|
+
if (!name.trim() || !description.trim()) {
|
|
27
|
+
setError("Name and description are required");
|
|
28
|
+
setSubmitting(false);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch("/api/pipelines", {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
name: name.trim(),
|
|
40
|
+
description: description.trim(),
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const result = await response.json();
|
|
46
|
+
throw new Error(result.error || "Failed to create pipeline");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { slug } = await response.json();
|
|
50
|
+
onOpenChange(false);
|
|
51
|
+
|
|
52
|
+
// Wait for watcher to detect registry change and reload config
|
|
53
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
54
|
+
|
|
55
|
+
navigate(`/pipelines/${slug}`);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
setError(err.message || "Failed to create pipeline");
|
|
58
|
+
} finally {
|
|
59
|
+
setSubmitting(false);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Sidebar
|
|
65
|
+
open={open}
|
|
66
|
+
onOpenChange={onOpenChange}
|
|
67
|
+
title="Add Pipeline Type"
|
|
68
|
+
description="Create a new pipeline type for your workflow"
|
|
69
|
+
>
|
|
70
|
+
<form onSubmit={handleSubmit}>
|
|
71
|
+
<div className="p-6 space-y-4">
|
|
72
|
+
<label className="block">
|
|
73
|
+
<span className="block text-sm font-medium text-foreground mb-1">
|
|
74
|
+
Name
|
|
75
|
+
</span>
|
|
76
|
+
<input
|
|
77
|
+
type="text"
|
|
78
|
+
value={name}
|
|
79
|
+
onChange={(e) => setName(e.target.value)}
|
|
80
|
+
className="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring bg-background"
|
|
81
|
+
placeholder="My Pipeline"
|
|
82
|
+
aria-describedby="name-description"
|
|
83
|
+
/>
|
|
84
|
+
<span
|
|
85
|
+
id="name-description"
|
|
86
|
+
className="text-xs text-muted-foreground"
|
|
87
|
+
>
|
|
88
|
+
A unique identifier for this pipeline type
|
|
89
|
+
</span>
|
|
90
|
+
</label>
|
|
91
|
+
|
|
92
|
+
<label className="block">
|
|
93
|
+
<span className="block text-sm font-medium text-foreground mb-1">
|
|
94
|
+
Description
|
|
95
|
+
</span>
|
|
96
|
+
<textarea
|
|
97
|
+
value={description}
|
|
98
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
99
|
+
rows={3}
|
|
100
|
+
className="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring bg-background resize-none"
|
|
101
|
+
placeholder="Describe what this pipeline does"
|
|
102
|
+
aria-describedby="description-description"
|
|
103
|
+
/>
|
|
104
|
+
<span
|
|
105
|
+
id="description-description"
|
|
106
|
+
className="text-xs text-muted-foreground"
|
|
107
|
+
>
|
|
108
|
+
Explain the purpose and expected outcomes
|
|
109
|
+
</span>
|
|
110
|
+
</label>
|
|
111
|
+
|
|
112
|
+
{error && (
|
|
113
|
+
<div className="p-3 bg-destructive/10 border border-destructive/20 rounded-md">
|
|
114
|
+
<p className="text-sm text-destructive">{error}</p>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<SidebarFooter>
|
|
120
|
+
<Button
|
|
121
|
+
variant="outline"
|
|
122
|
+
size="md"
|
|
123
|
+
type="button"
|
|
124
|
+
onClick={() => onOpenChange(false)}
|
|
125
|
+
className="flex-1"
|
|
126
|
+
>
|
|
127
|
+
Cancel
|
|
128
|
+
</Button>
|
|
129
|
+
<Button
|
|
130
|
+
variant="solid"
|
|
131
|
+
size="md"
|
|
132
|
+
type="submit"
|
|
133
|
+
loading={submitting}
|
|
134
|
+
className="flex-1"
|
|
135
|
+
>
|
|
136
|
+
Create
|
|
137
|
+
</Button>
|
|
138
|
+
</SidebarFooter>
|
|
139
|
+
</form>
|
|
140
|
+
</Sidebar>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default AddPipelineSidebar;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Progress } from "./ui/progress.jsx";
|
|
3
|
+
import { Button } from "./ui/button.jsx";
|
|
4
|
+
|
|
5
|
+
export function AnalysisProgressTray({
|
|
6
|
+
status,
|
|
7
|
+
pipelineSlug,
|
|
8
|
+
completedTasks = 0,
|
|
9
|
+
totalTasks = 0,
|
|
10
|
+
completedArtifacts = 0,
|
|
11
|
+
totalArtifacts = 0,
|
|
12
|
+
currentTask,
|
|
13
|
+
currentArtifact,
|
|
14
|
+
error,
|
|
15
|
+
onDismiss,
|
|
16
|
+
}) {
|
|
17
|
+
if (status === "idle") return null;
|
|
18
|
+
|
|
19
|
+
const progressPct = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
|
|
20
|
+
const progressVariant =
|
|
21
|
+
status === "error"
|
|
22
|
+
? "error"
|
|
23
|
+
: status === "complete"
|
|
24
|
+
? "completed"
|
|
25
|
+
: "running";
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="fixed bottom-4 right-4 z-50 w-80 rounded-lg border bg-white shadow-lg dark:bg-gray-800 dark:border-gray-700">
|
|
29
|
+
<div className="flex items-center justify-between border-b p-3 dark:border-gray-700">
|
|
30
|
+
<h3 className="font-semibold text-sm">Analyzing {pipelineSlug}</h3>
|
|
31
|
+
<Button
|
|
32
|
+
variant="ghost"
|
|
33
|
+
size="sm"
|
|
34
|
+
onClick={onDismiss}
|
|
35
|
+
className="h-6 w-6 p-0"
|
|
36
|
+
>
|
|
37
|
+
×
|
|
38
|
+
</Button>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="p-3">
|
|
42
|
+
{status === "running" && (
|
|
43
|
+
<>
|
|
44
|
+
<div className="mb-2 flex items-center justify-between text-xs">
|
|
45
|
+
<span className="text-muted-foreground">
|
|
46
|
+
{completedTasks} of {totalTasks} tasks
|
|
47
|
+
</span>
|
|
48
|
+
</div>
|
|
49
|
+
<Progress
|
|
50
|
+
value={progressPct}
|
|
51
|
+
variant={progressVariant}
|
|
52
|
+
className="mb-3"
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
{currentArtifact && (
|
|
56
|
+
<p className="text-xs text-muted-foreground">
|
|
57
|
+
Deducing schema for {currentArtifact}...
|
|
58
|
+
</p>
|
|
59
|
+
)}
|
|
60
|
+
{currentTask && !currentArtifact && (
|
|
61
|
+
<p className="text-xs text-muted-foreground">
|
|
62
|
+
Analyzing {currentTask}...
|
|
63
|
+
</p>
|
|
64
|
+
)}
|
|
65
|
+
</>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{status === "complete" && (
|
|
69
|
+
<div className="flex items-center gap-2 text-sm">
|
|
70
|
+
<span className="text-green-600 dark:text-green-400">✓</span>
|
|
71
|
+
<span>Analysis complete</span>
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{status === "error" && (
|
|
76
|
+
<div className="text-sm text-red-600 dark:text-red-400">
|
|
77
|
+
{error || "Analysis failed"}
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
{status === "connecting" && (
|
|
82
|
+
<div className="text-sm text-muted-foreground">Connecting...</div>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Box, Flex, Table, Text
|
|
2
|
+
import { Box, Flex, Table, Text } from "@radix-ui/themes";
|
|
3
3
|
import { Progress } from "./ui/progress";
|
|
4
|
+
import { Button } from "./ui/button.jsx";
|
|
4
5
|
import { TimerReset, ChevronRight } from "lucide-react";
|
|
5
6
|
import { fmtDuration, jobCumulativeDurationMs } from "../utils/duration.js";
|
|
6
7
|
import { countCompleted } from "../utils/jobs";
|
|
@@ -234,8 +235,8 @@ export default function JobTable({ jobs, pipeline, onOpenJob }) {
|
|
|
234
235
|
<Table.Cell>
|
|
235
236
|
<Button
|
|
236
237
|
variant="ghost"
|
|
237
|
-
size="
|
|
238
|
-
className="opacity-0 group-hover:opacity-100 transition-opacity
|
|
238
|
+
size="sm"
|
|
239
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
|
239
240
|
aria-label={`View details for ${jobTitle}`}
|
|
240
241
|
>
|
|
241
242
|
<ChevronRight className="h-4 w-4" />
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from "react";
|
|
2
2
|
import { useNavigate, useLocation, Link } from "react-router-dom";
|
|
3
|
-
import * as Tooltip from "@radix-ui/react-tooltip";
|
|
4
3
|
import { Box, Flex, Text, Heading, Link as RadixLink } from "@radix-ui/themes";
|
|
5
4
|
import { Button } from "./ui/button.jsx";
|
|
6
5
|
import Logo from "./ui/Logo.jsx";
|
|
7
6
|
import PageSubheader from "./PageSubheader.jsx";
|
|
8
7
|
import UploadSeed from "./UploadSeed.jsx";
|
|
9
|
-
import {
|
|
8
|
+
import { Code2, Upload, List } from "lucide-react";
|
|
10
9
|
import "./ui/focus-styles.css";
|
|
11
10
|
|
|
12
11
|
/**
|
|
@@ -85,153 +84,157 @@ export default function Layout({
|
|
|
85
84
|
}, [isUploadOpen]);
|
|
86
85
|
|
|
87
86
|
return (
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
<Box className="min-h-screen bg-gray-1">
|
|
88
|
+
{/* Skip to main content link for accessibility */}
|
|
89
|
+
<Box
|
|
90
|
+
as="a"
|
|
91
|
+
href="#main-content"
|
|
92
|
+
className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 z-50 bg-primary text-primary-foreground px-3 py-2 rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 transition-colors"
|
|
93
|
+
>
|
|
94
|
+
Skip to main content
|
|
95
|
+
</Box>
|
|
96
|
+
|
|
97
|
+
{/* Header */}
|
|
98
|
+
<Box
|
|
99
|
+
role="banner"
|
|
100
|
+
className="sticky top-0 z-20 border-b border-gray-300 bg-gray-1/80 backdrop-blur supports-[backdrop-filter]:bg-gray-1/60"
|
|
101
|
+
>
|
|
102
|
+
<Flex
|
|
103
|
+
align="center"
|
|
104
|
+
justify="between"
|
|
105
|
+
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8 py-4`}
|
|
106
|
+
gap="4"
|
|
95
107
|
>
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
{/* Left side: Navigation and title */}
|
|
109
|
+
<Flex align="center" className="min-w-0 flex-1">
|
|
110
|
+
{/* Logo */}
|
|
111
|
+
<Box
|
|
112
|
+
asChild
|
|
113
|
+
className="shrink-0"
|
|
114
|
+
style={{ width: "80px", height: "60px" }}
|
|
115
|
+
>
|
|
116
|
+
<Link
|
|
117
|
+
to="/"
|
|
118
|
+
aria-label="Go to homepage"
|
|
119
|
+
className="rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
|
120
|
+
>
|
|
121
|
+
<Logo />
|
|
122
|
+
</Link>
|
|
123
|
+
</Box>
|
|
124
|
+
|
|
125
|
+
{/* App title - clickable to navigate to dashboard */}
|
|
126
|
+
<Box
|
|
127
|
+
asChild
|
|
128
|
+
className="shrink-0 cursor-pointer hover:bg-gray-3 rounded p-1 -m-1 transition-colors"
|
|
129
|
+
onClick={() => navigate("/")}
|
|
130
|
+
>
|
|
131
|
+
<Heading
|
|
132
|
+
size="6"
|
|
133
|
+
weight="medium"
|
|
134
|
+
className="text-gray-12 truncate"
|
|
135
|
+
>
|
|
136
|
+
<>
|
|
137
|
+
Prompt
|
|
138
|
+
<br />
|
|
139
|
+
Pipeline
|
|
140
|
+
</>
|
|
141
|
+
</Heading>
|
|
142
|
+
</Box>
|
|
143
|
+
</Flex>
|
|
144
|
+
|
|
145
|
+
{/* Center: Navigation */}
|
|
146
|
+
<nav
|
|
147
|
+
role="navigation"
|
|
148
|
+
aria-label="Main navigation"
|
|
149
|
+
className="hidden md:flex"
|
|
150
|
+
>
|
|
151
|
+
<Flex align="center" gap="6">
|
|
152
|
+
<RadixLink
|
|
153
|
+
href="/pipelines"
|
|
154
|
+
className={`text-sm font-medium transition-colors hover:text-blue-600 ${
|
|
155
|
+
isActivePath("/pipelines")
|
|
156
|
+
? "text-blue-600"
|
|
157
|
+
: "text-gray-11 hover:text-gray-12"
|
|
158
|
+
}`}
|
|
159
|
+
aria-current={isActivePath("/pipelines") ? "page" : undefined}
|
|
160
|
+
>
|
|
161
|
+
<Flex align="center" gap="2">
|
|
162
|
+
<List className="h-4 w-4" />
|
|
163
|
+
Pipelines
|
|
164
|
+
</Flex>
|
|
165
|
+
</RadixLink>
|
|
166
|
+
<RadixLink
|
|
167
|
+
href="/code"
|
|
168
|
+
className={`text-sm font-medium transition-colors hover:text-blue-600 ${
|
|
169
|
+
isActivePath("/code")
|
|
170
|
+
? "text-blue-600"
|
|
171
|
+
: "text-gray-11 hover:text-gray-12"
|
|
172
|
+
}`}
|
|
173
|
+
aria-current={isActivePath("/code") ? "page" : undefined}
|
|
174
|
+
>
|
|
175
|
+
<Flex align="center" gap="2">
|
|
176
|
+
<Code2 className="h-4 w-4" />
|
|
177
|
+
Help
|
|
178
|
+
</Flex>
|
|
179
|
+
</RadixLink>
|
|
180
|
+
</Flex>
|
|
181
|
+
</nav>
|
|
182
|
+
|
|
183
|
+
{/* Right side: Actions */}
|
|
184
|
+
<Flex align="center" gap="3" className="shrink-0">
|
|
185
|
+
{actions}
|
|
186
|
+
<Button
|
|
187
|
+
size="md"
|
|
188
|
+
variant="solid"
|
|
189
|
+
onClick={toggleUploadPanel}
|
|
190
|
+
aria-controls="layout-upload-panel"
|
|
191
|
+
aria-expanded={isUploadOpen}
|
|
192
|
+
>
|
|
193
|
+
<Upload className="h-4 w-4" />
|
|
194
|
+
<Text size="2" className="ml-2">
|
|
195
|
+
Upload Seed
|
|
196
|
+
</Text>
|
|
197
|
+
</Button>
|
|
198
|
+
</Flex>
|
|
199
|
+
</Flex>
|
|
200
|
+
</Box>
|
|
98
201
|
|
|
99
|
-
|
|
202
|
+
{/* Upload Panel */}
|
|
203
|
+
{isUploadOpen && (
|
|
100
204
|
<Box
|
|
101
|
-
|
|
102
|
-
|
|
205
|
+
id="layout-upload-panel"
|
|
206
|
+
ref={uploadPanelRef}
|
|
207
|
+
role="region"
|
|
208
|
+
aria-label="Upload seed file"
|
|
209
|
+
className="bg-blue-50"
|
|
103
210
|
>
|
|
104
211
|
<Flex
|
|
105
|
-
|
|
106
|
-
|
|
212
|
+
direction="column"
|
|
213
|
+
gap="3"
|
|
107
214
|
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8 py-4`}
|
|
108
|
-
gap="4"
|
|
109
215
|
>
|
|
110
|
-
{/*
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
style={{ width: "80px", height: "60px" }}
|
|
117
|
-
>
|
|
118
|
-
<Link
|
|
119
|
-
to="/"
|
|
120
|
-
aria-label="Go to homepage"
|
|
121
|
-
className="rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
|
122
|
-
>
|
|
123
|
-
<Logo />
|
|
124
|
-
</Link>
|
|
125
|
-
</Box>
|
|
126
|
-
|
|
127
|
-
{/* App title - clickable to navigate to dashboard */}
|
|
128
|
-
<Box
|
|
129
|
-
asChild
|
|
130
|
-
className="shrink-0 cursor-pointer hover:bg-gray-3 rounded p-1 -m-1 transition-colors"
|
|
131
|
-
onClick={() => navigate("/")}
|
|
132
|
-
>
|
|
133
|
-
<Heading
|
|
134
|
-
size="6"
|
|
135
|
-
weight="medium"
|
|
136
|
-
className="text-gray-12 truncate"
|
|
137
|
-
>
|
|
138
|
-
<>
|
|
139
|
-
Prompt
|
|
140
|
-
<br />
|
|
141
|
-
Pipeline
|
|
142
|
-
</>
|
|
143
|
-
</Heading>
|
|
216
|
+
{/* Success Message */}
|
|
217
|
+
{seedUploadSuccess && (
|
|
218
|
+
<Box className="rounded-md bg-green-50 p-3 border border-green-200">
|
|
219
|
+
<Text size="2" className="text-green-800">
|
|
220
|
+
Job <strong>{seedUploadSuccess}</strong> created successfully
|
|
221
|
+
</Text>
|
|
144
222
|
</Box>
|
|
145
|
-
|
|
223
|
+
)}
|
|
146
224
|
|
|
147
|
-
{
|
|
148
|
-
<nav
|
|
149
|
-
role="navigation"
|
|
150
|
-
aria-label="Main navigation"
|
|
151
|
-
className="hidden md:flex"
|
|
152
|
-
>
|
|
153
|
-
<Flex align="center" gap="6">
|
|
154
|
-
<RadixLink
|
|
155
|
-
href="/code"
|
|
156
|
-
className={`text-sm font-medium transition-colors hover:text-blue-600 ${
|
|
157
|
-
isActivePath("/code")
|
|
158
|
-
? "text-blue-600"
|
|
159
|
-
: "text-gray-11 hover:text-gray-12"
|
|
160
|
-
}`}
|
|
161
|
-
aria-current={isActivePath("/code") ? "page" : undefined}
|
|
162
|
-
>
|
|
163
|
-
<Flex align="center" gap="2">
|
|
164
|
-
<Code2 className="h-4 w-4" />
|
|
165
|
-
Help
|
|
166
|
-
</Flex>
|
|
167
|
-
</RadixLink>
|
|
168
|
-
</Flex>
|
|
169
|
-
</nav>
|
|
170
|
-
|
|
171
|
-
{/* Right side: Actions */}
|
|
172
|
-
<Flex align="center" gap="3" className="shrink-0">
|
|
173
|
-
{actions}
|
|
174
|
-
<Tooltip.Root delayDuration={200}>
|
|
175
|
-
<Tooltip.Trigger asChild>
|
|
176
|
-
<Button
|
|
177
|
-
size="sm"
|
|
178
|
-
variant="default"
|
|
179
|
-
onClick={toggleUploadPanel}
|
|
180
|
-
aria-controls="layout-upload-panel"
|
|
181
|
-
aria-expanded={isUploadOpen}
|
|
182
|
-
>
|
|
183
|
-
<Upload className="h-4 w-4" />
|
|
184
|
-
<Text size="2" className="ml-2">
|
|
185
|
-
Upload Seed
|
|
186
|
-
</Text>
|
|
187
|
-
</Button>
|
|
188
|
-
</Tooltip.Trigger>
|
|
189
|
-
<Tooltip.Content side="bottom" sideOffset={5}>
|
|
190
|
-
<Text size="2">Upload seed file</Text>
|
|
191
|
-
</Tooltip.Content>
|
|
192
|
-
</Tooltip.Root>
|
|
193
|
-
</Flex>
|
|
225
|
+
<UploadSeed onUploadSuccess={handleSeedUploadSuccess} />
|
|
194
226
|
</Flex>
|
|
195
227
|
</Box>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
direction="column"
|
|
208
|
-
gap="3"
|
|
209
|
-
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8 py-4`}
|
|
210
|
-
>
|
|
211
|
-
{/* Success Message */}
|
|
212
|
-
{seedUploadSuccess && (
|
|
213
|
-
<Box className="rounded-md bg-green-50 p-3 border border-green-200">
|
|
214
|
-
<Text size="2" className="text-green-800">
|
|
215
|
-
Job <strong>{seedUploadSuccess}</strong> created
|
|
216
|
-
successfully
|
|
217
|
-
</Text>
|
|
218
|
-
</Box>
|
|
219
|
-
)}
|
|
220
|
-
|
|
221
|
-
<UploadSeed onUploadSuccess={handleSeedUploadSuccess} />
|
|
222
|
-
</Flex>
|
|
223
|
-
</Box>
|
|
224
|
-
)}
|
|
225
|
-
|
|
226
|
-
{/* Main content */}
|
|
227
|
-
<main
|
|
228
|
-
id="main-content"
|
|
229
|
-
role="main"
|
|
230
|
-
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8`}
|
|
231
|
-
>
|
|
232
|
-
{children}
|
|
233
|
-
</main>
|
|
234
|
-
</Box>
|
|
235
|
-
</Tooltip.Provider>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{/* Main content */}
|
|
231
|
+
<main
|
|
232
|
+
id="main-content"
|
|
233
|
+
role="main"
|
|
234
|
+
className={`mx-auto w-full ${maxWidth} px-4 sm:px-6 lg:px-8`}
|
|
235
|
+
>
|
|
236
|
+
{children}
|
|
237
|
+
</main>
|
|
238
|
+
</Box>
|
|
236
239
|
);
|
|
237
240
|
}
|