@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.
Files changed (60) hide show
  1. package/package.json +10 -1
  2. package/src/cli/analyze-task.js +51 -0
  3. package/src/cli/index.js +8 -0
  4. package/src/components/AddPipelineSidebar.jsx +144 -0
  5. package/src/components/AnalysisProgressTray.jsx +87 -0
  6. package/src/components/JobTable.jsx +4 -3
  7. package/src/components/Layout.jsx +142 -139
  8. package/src/components/MarkdownRenderer.jsx +149 -0
  9. package/src/components/PipelineDAGGrid.jsx +404 -0
  10. package/src/components/PipelineTypeTaskSidebar.jsx +96 -0
  11. package/src/components/SchemaPreviewPanel.jsx +97 -0
  12. package/src/components/StageTimeline.jsx +36 -0
  13. package/src/components/TaskAnalysisDisplay.jsx +227 -0
  14. package/src/components/TaskCreationSidebar.jsx +447 -0
  15. package/src/components/TaskDetailSidebar.jsx +119 -117
  16. package/src/components/TaskFilePane.jsx +94 -39
  17. package/src/components/ui/button.jsx +59 -27
  18. package/src/components/ui/sidebar.jsx +118 -0
  19. package/src/config/models.js +99 -67
  20. package/src/core/config.js +4 -1
  21. package/src/llm/index.js +129 -9
  22. package/src/pages/PipelineDetail.jsx +6 -6
  23. package/src/pages/PipelineList.jsx +214 -0
  24. package/src/pages/PipelineTypeDetail.jsx +234 -0
  25. package/src/providers/deepseek.js +76 -16
  26. package/src/providers/openai.js +61 -34
  27. package/src/task-analysis/enrichers/analysis-writer.js +62 -0
  28. package/src/task-analysis/enrichers/schema-deducer.js +145 -0
  29. package/src/task-analysis/enrichers/schema-writer.js +74 -0
  30. package/src/task-analysis/extractors/artifacts.js +137 -0
  31. package/src/task-analysis/extractors/llm-calls.js +176 -0
  32. package/src/task-analysis/extractors/stages.js +51 -0
  33. package/src/task-analysis/index.js +103 -0
  34. package/src/task-analysis/parser.js +28 -0
  35. package/src/task-analysis/utils/ast.js +43 -0
  36. package/src/ui/client/hooks/useAnalysisProgress.js +145 -0
  37. package/src/ui/client/index.css +64 -0
  38. package/src/ui/client/main.jsx +4 -0
  39. package/src/ui/client/sse-fetch.js +120 -0
  40. package/src/ui/dist/assets/index-cjHV9mYW.js +82578 -0
  41. package/src/ui/dist/assets/index-cjHV9mYW.js.map +1 -0
  42. package/src/ui/dist/assets/style-CoM9SoQF.css +180 -0
  43. package/src/ui/dist/index.html +2 -2
  44. package/src/ui/endpoints/create-pipeline-endpoint.js +194 -0
  45. package/src/ui/endpoints/pipeline-analysis-endpoint.js +246 -0
  46. package/src/ui/endpoints/pipeline-type-detail-endpoint.js +181 -0
  47. package/src/ui/endpoints/pipelines-endpoint.js +133 -0
  48. package/src/ui/endpoints/schema-file-endpoint.js +105 -0
  49. package/src/ui/endpoints/task-analysis-endpoint.js +104 -0
  50. package/src/ui/endpoints/task-creation-endpoint.js +114 -0
  51. package/src/ui/endpoints/task-save-endpoint.js +101 -0
  52. package/src/ui/express-app.js +45 -0
  53. package/src/ui/lib/analysis-lock.js +67 -0
  54. package/src/ui/lib/sse.js +30 -0
  55. package/src/ui/server.js +4 -0
  56. package/src/ui/utils/slug.js +31 -0
  57. package/src/ui/watcher.js +28 -2
  58. package/src/ui/dist/assets/index-B320avRx.js +0 -26613
  59. package/src/ui/dist/assets/index-B320avRx.js.map +0 -1
  60. 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.12.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, Button } from "@radix-ui/themes";
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="1"
238
- className="opacity-0 group-hover:opacity-100 transition-opacity text-slate-500 hover:text-slate-700"
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 { ArrowLeft, Code2, Upload } from "lucide-react";
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
- <Tooltip.Provider delayDuration={200}>
89
- <Box className="min-h-screen bg-gray-1">
90
- {/* Skip to main content link for accessibility */}
91
- <Box
92
- as="a"
93
- href="#main-content"
94
- className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 z-50 bg-blue-600 text-white px-3 py-2 rounded-md text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
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
- Skip to main content
97
- </Box>
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
- {/* Header */}
202
+ {/* Upload Panel */}
203
+ {isUploadOpen && (
100
204
  <Box
101
- role="banner"
102
- className="sticky top-0 z-20 border-b border-gray-300 bg-gray-1/80 backdrop-blur supports-[backdrop-filter]:bg-gray-1/60"
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
- align="center"
106
- justify="between"
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
- {/* Left side: Navigation and title */}
111
- <Flex align="center" className="min-w-0 flex-1">
112
- {/* Logo */}
113
- <Box
114
- asChild
115
- className="shrink-0"
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
- </Flex>
223
+ )}
146
224
 
147
- {/* Center: Navigation */}
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
- {/* Upload Panel */}
198
- {isUploadOpen && (
199
- <Box
200
- id="layout-upload-panel"
201
- ref={uploadPanelRef}
202
- role="region"
203
- aria-label="Upload seed file"
204
- className="bg-blue-50"
205
- >
206
- <Flex
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
  }