@ryanfw/prompt-orchestration-pipeline 0.4.0 → 0.6.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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/src/components/JobCard.jsx +1 -1
  3. package/src/components/JobDetail.jsx +45 -12
  4. package/src/components/JobTable.jsx +40 -1
  5. package/src/components/Layout.jsx +146 -22
  6. package/src/components/PageSubheader.jsx +75 -0
  7. package/src/components/UploadSeed.jsx +0 -70
  8. package/src/components/ui/Logo.jsx +16 -0
  9. package/src/core/config.js +145 -13
  10. package/src/core/file-io.js +12 -27
  11. package/src/core/orchestrator.js +92 -78
  12. package/src/core/pipeline-runner.js +13 -6
  13. package/src/core/status-writer.js +63 -52
  14. package/src/core/task-runner.js +61 -1
  15. package/src/llm/index.js +97 -40
  16. package/src/pages/Code.jsx +297 -0
  17. package/src/pages/PipelineDetail.jsx +47 -8
  18. package/src/pages/PromptPipelineDashboard.jsx +6 -53
  19. package/src/providers/deepseek.js +17 -1
  20. package/src/providers/openai.js +1 -1
  21. package/src/ui/client/adapters/job-adapter.js +26 -2
  22. package/src/ui/client/hooks/useJobDetailWithUpdates.js +0 -1
  23. package/src/ui/client/index.css +6 -0
  24. package/src/ui/client/index.html +1 -1
  25. package/src/ui/client/main.jsx +2 -0
  26. package/src/ui/dist/assets/{index-CxcrauYR.js → index-WgJUlSmE.js} +716 -307
  27. package/src/ui/dist/assets/style-x0V-5m8e.css +62 -0
  28. package/src/ui/dist/index.html +3 -3
  29. package/src/ui/job-reader.js +0 -108
  30. package/src/ui/server.js +54 -0
  31. package/src/ui/sse-enhancer.js +0 -1
  32. package/src/ui/transformers/list-transformer.js +32 -12
  33. package/src/ui/transformers/status-transformer.js +11 -11
  34. package/src/utils/token-cost-calculator.js +297 -0
  35. package/src/utils/ui.jsx +4 -4
  36. package/src/ui/dist/assets/style-D6K_oQ12.css +0 -62
@@ -4,6 +4,7 @@ import { Box, Flex, Text } from "@radix-ui/themes";
4
4
  import JobDetail from "../components/JobDetail.jsx";
5
5
  import { useJobDetailWithUpdates } from "../ui/client/hooks/useJobDetailWithUpdates.js";
6
6
  import Layout from "../components/Layout.jsx";
7
+ import PageSubheader from "../components/PageSubheader.jsx";
7
8
  import { statusBadge } from "../utils/ui.jsx";
8
9
 
9
10
  export default function PipelineDetail() {
@@ -12,7 +13,14 @@ export default function PipelineDetail() {
12
13
  // Handle missing job ID (undefined/null)
13
14
  if (jobId === undefined || jobId === null) {
14
15
  return (
15
- <Layout title="Pipeline Details" showBackButton={true}>
16
+ <Layout
17
+ pageTitle="Pipeline Details"
18
+ breadcrumbs={[
19
+ { label: "Home", href: "/" },
20
+ { label: "Pipeline Details" },
21
+ ]}
22
+ showBackButton={true}
23
+ >
16
24
  <Flex align="center" justify="center" className="min-h-64">
17
25
  <Box className="text-center">
18
26
  <Text size="5" weight="medium" color="red" className="mb-2">
@@ -28,7 +36,14 @@ export default function PipelineDetail() {
28
36
 
29
37
  if (loading) {
30
38
  return (
31
- <Layout title="Pipeline Details" showBackButton={true}>
39
+ <Layout
40
+ pageTitle="Pipeline Details"
41
+ breadcrumbs={[
42
+ { label: "Home", href: "/" },
43
+ { label: "Pipeline Details" },
44
+ ]}
45
+ showBackButton={true}
46
+ >
32
47
  <Flex align="center" justify="center" className="min-h-64">
33
48
  <Box className="text-center">
34
49
  <Text size="5" weight="medium" className="mb-2">
@@ -42,7 +57,14 @@ export default function PipelineDetail() {
42
57
 
43
58
  if (error) {
44
59
  return (
45
- <Layout title="Pipeline Details" showBackButton={true}>
60
+ <Layout
61
+ pageTitle="Pipeline Details"
62
+ breadcrumbs={[
63
+ { label: "Home", href: "/" },
64
+ { label: "Pipeline Details" },
65
+ ]}
66
+ showBackButton={true}
67
+ >
46
68
  <Flex align="center" justify="center" className="min-h-64">
47
69
  <Box className="text-center">
48
70
  <Text size="5" weight="medium" color="red" className="mb-2">
@@ -59,7 +81,14 @@ export default function PipelineDetail() {
59
81
 
60
82
  if (!job) {
61
83
  return (
62
- <Layout title="Pipeline Details" showBackButton={true}>
84
+ <Layout
85
+ pageTitle="Pipeline Details"
86
+ breadcrumbs={[
87
+ { label: "Home", href: "/" },
88
+ { label: "Pipeline Details" },
89
+ ]}
90
+ showBackButton={true}
91
+ >
63
92
  <Flex align="center" justify="center" className="min-h-64">
64
93
  <Box className="text-center">
65
94
  <Text size="5" weight="medium" className="mb-2">
@@ -89,8 +118,15 @@ export default function PipelineDetail() {
89
118
  return { tasks: pipelineTasks };
90
119
  })();
91
120
 
92
- // Header actions: job ID and status badge
93
- const headerActions = (
121
+ const pageTitle = job.name || "Pipeline Details";
122
+ const breadcrumbs = [
123
+ { label: "Home", href: "/" },
124
+ { label: "Pipeline Details" },
125
+ ...(job.name ? [{ label: job.name }] : []),
126
+ ];
127
+
128
+ // Right side content for PageSubheader: job ID and status badge
129
+ const subheaderRightContent = (
94
130
  <Flex align="center" gap="3" className="shrink-0">
95
131
  <Text size="2" color="gray">
96
132
  ID: {job.id || jobId}
@@ -101,10 +137,13 @@ export default function PipelineDetail() {
101
137
 
102
138
  return (
103
139
  <Layout
104
- title={job.name || "Pipeline Details"}
140
+ pageTitle={pageTitle}
141
+ breadcrumbs={breadcrumbs}
105
142
  showBackButton={true}
106
- actions={headerActions}
107
143
  >
144
+ <PageSubheader breadcrumbs={breadcrumbs} maxWidth="max-w-7xl">
145
+ {subheaderRightContent}
146
+ </PageSubheader>
108
147
  <JobDetail job={job} pipeline={pipeline} />
109
148
  </Layout>
110
149
  );
@@ -2,7 +2,7 @@
2
2
  import React, { useEffect, useMemo, useState } from "react";
3
3
  import { useNavigate } from "react-router-dom";
4
4
 
5
- import { Box, Flex, Text, Heading, Tabs, Card } from "@radix-ui/themes";
5
+ import { Box, Flex, Text, Heading, Tabs } from "@radix-ui/themes";
6
6
 
7
7
  import { Progress } from "../components/ui/progress";
8
8
  import { useJobListWithUpdates } from "../ui/client/hooks/useJobListWithUpdates";
@@ -12,7 +12,6 @@ import { useTicker } from "../ui/client/hooks/useTicker";
12
12
 
13
13
  // Referenced components — leave these alone
14
14
  import JobTable from "../components/JobTable";
15
- import UploadSeed from "../components/UploadSeed";
16
15
  import Layout from "../components/Layout.jsx";
17
16
 
18
17
  export default function PromptPipelineDashboard({ isConnected }) {
@@ -55,8 +54,6 @@ export default function PromptPipelineDashboard({ isConnected }) {
55
54
  return src.map(adaptJobSummary);
56
55
  }, [apiJobs, error]);
57
56
  const [activeTab, setActiveTab] = useState("current");
58
- const [seedUploadSuccess, setSeedUploadSuccess] = useState(null);
59
- const [seedUploadTimer, setSeedUploadTimer] = useState(null);
60
57
 
61
58
  // Shared ticker for live duration updates
62
59
  const now = useTicker(10000);
@@ -111,34 +108,6 @@ export default function PromptPipelineDashboard({ isConnected }) {
111
108
  }
112
109
  };
113
110
 
114
- // Handle seed upload success
115
- const handleSeedUploadSuccess = ({ jobName }) => {
116
- // Clear any existing timer
117
- if (seedUploadTimer) {
118
- clearTimeout(seedUploadTimer);
119
- }
120
-
121
- // Set success message
122
- setSeedUploadSuccess(jobName);
123
-
124
- // Auto-clear after exactly 5000 ms
125
- const timer = setTimeout(() => {
126
- setSeedUploadSuccess(null);
127
- setSeedUploadTimer(null);
128
- }, 5000);
129
-
130
- setSeedUploadTimer(timer);
131
- };
132
-
133
- // Cleanup timer on unmount
134
- useEffect(() => {
135
- return () => {
136
- if (seedUploadTimer) {
137
- clearTimeout(seedUploadTimer);
138
- }
139
- };
140
- }, [seedUploadTimer]);
141
-
142
111
  // Header actions for Layout
143
112
  const headerActions = runningJobs.length > 0 && (
144
113
  <Flex align="center" gap="2" className="text-gray-11">
@@ -154,26 +123,6 @@ export default function PromptPipelineDashboard({ isConnected }) {
154
123
 
155
124
  return (
156
125
  <Layout title="Prompt Pipeline" actions={headerActions}>
157
- {/* Upload Seed File Section */}
158
- <Card className="mb-6">
159
- <Flex direction="column" gap="3">
160
- <Heading size="4" weight="medium" className="text-gray-12">
161
- Upload Seed File
162
- </Heading>
163
-
164
- {/* Success Message */}
165
- {seedUploadSuccess && (
166
- <Box className="rounded-md bg-green-50 p-3 border border-green-200">
167
- <Text size="2" className="text-green-800">
168
- Job <strong>{seedUploadSuccess}</strong> created successfully
169
- </Text>
170
- </Box>
171
- )}
172
-
173
- <UploadSeed onUploadSuccess={handleSeedUploadSuccess} />
174
- </Flex>
175
- </Card>
176
-
177
126
  {error && (
178
127
  <Box className="mb-4 rounded-md bg-yellow-50 p-3 border border-yellow-200">
179
128
  <Text size="2" className="text-yellow-800">
@@ -181,7 +130,11 @@ export default function PromptPipelineDashboard({ isConnected }) {
181
130
  </Text>
182
131
  </Box>
183
132
  )}
184
- <Tabs.Root value={activeTab} onValueChange={setActiveTab}>
133
+ <Tabs.Root
134
+ value={activeTab}
135
+ onValueChange={setActiveTab}
136
+ className="mt-4"
137
+ >
185
138
  <Tabs.List aria-label="Job filters">
186
139
  <Tabs.Trigger value="current">Current ({currentCount})</Tabs.Trigger>
187
140
  <Tabs.Trigger value="errors">Errors ({errorCount})</Tabs.Trigger>
@@ -71,9 +71,25 @@ export async function deepseekChat({
71
71
  const data = await response.json();
72
72
  const content = data.choices[0].message.content;
73
73
 
74
+ // Only try JSON parsing if responseFormat indicates JSON output
75
+ if (responseFormat?.type === "json_object" || responseFormat === "json") {
76
+ const parsed = tryParseJSON(content);
77
+ if (parsed === null && attempt < maxRetries) {
78
+ // JSON parsing failed, retry
79
+ lastError = new Error("Failed to parse JSON response");
80
+ continue;
81
+ }
82
+ return {
83
+ content: parsed,
84
+ usage: data.usage,
85
+ raw: data,
86
+ };
87
+ }
88
+
74
89
  return {
75
- content: tryParseJSON(content),
90
+ content,
76
91
  usage: data.usage,
92
+ raw: data,
77
93
  };
78
94
  } catch (error) {
79
95
  lastError = error;
@@ -31,7 +31,7 @@ export async function openaiChat({
31
31
  temperature,
32
32
  maxTokens,
33
33
  max_tokens, // Explicitly destructure to prevent it from being in ...rest
34
- responseFormat, // { type: 'json_object' } | { json_schema, name } | 'json'
34
+ responseFormat = "json", // Default to JSON for legacy compatibility
35
35
  tools,
36
36
  toolChoice,
37
37
  seed,
@@ -66,6 +66,8 @@ function normalizeTasks(rawTasks) {
66
66
  artifacts: Array.isArray(t && t.artifacts)
67
67
  ? t.artifacts.slice()
68
68
  : undefined,
69
+ // Preserve tokenUsage if present
70
+ ...(t && t.tokenUsage ? { tokenUsage: t.tokenUsage } : {}),
69
71
  };
70
72
  tasks[name] = taskObj;
71
73
  });
@@ -100,6 +102,8 @@ function normalizeTasks(rawTasks) {
100
102
  artifacts: Array.isArray(t && t.artifacts)
101
103
  ? t.artifacts.slice()
102
104
  : undefined,
105
+ // Preserve tokenUsage if present
106
+ ...(t && t.tokenUsage ? { tokenUsage: t.tokenUsage } : {}),
103
107
  };
104
108
  });
105
109
  return { tasks, warnings };
@@ -158,7 +162,7 @@ export function adaptJobSummary(apiJob) {
158
162
  // Demo-only: read canonical fields strictly
159
163
  const id = apiJob.jobId;
160
164
  const name = apiJob.title || "";
161
- const rawTasks = apiJob.tasksStatus;
165
+ const rawTasks = apiJob.tasks;
162
166
  const location = apiJob.location;
163
167
 
164
168
  // Job-level stage metadata
@@ -197,6 +201,21 @@ export function adaptJobSummary(apiJob) {
197
201
  if (pipeline != null) job.pipeline = pipeline;
198
202
  if (pipelineLabel != null) job.pipelineLabel = pipelineLabel;
199
203
 
204
+ // Costs summary from API
205
+ if (apiJob.costsSummary) {
206
+ job.costsSummary = {
207
+ totalTokens: apiJob.costsSummary.totalTokens || 0,
208
+ totalInputTokens: apiJob.costsSummary.totalInputTokens || 0,
209
+ totalOutputTokens: apiJob.costsSummary.totalOutputTokens || 0,
210
+ totalCost: apiJob.costsSummary.totalCost || 0,
211
+ totalInputCost: apiJob.costsSummary.totalInputCost || 0,
212
+ totalOutputCost: apiJob.costsSummary.totalOutputCost || 0,
213
+ };
214
+ // Add top-level numeric mirrors for convenience
215
+ job.totalCost = job.costsSummary.totalCost;
216
+ job.totalTokens = job.costsSummary.totalTokens;
217
+ }
218
+
200
219
  // Include warnings for debugging
201
220
  if (warnings.length > 0) job.__warnings = warnings;
202
221
 
@@ -212,7 +231,7 @@ export function adaptJobDetail(apiDetail) {
212
231
  // Demo-only: read canonical fields strictly
213
232
  const id = apiDetail.jobId;
214
233
  const name = apiDetail.title || "";
215
- const rawTasks = apiDetail.tasksStatus;
234
+ const rawTasks = apiDetail.tasks;
216
235
  const location = apiDetail.location;
217
236
 
218
237
  // Job-level stage metadata
@@ -251,6 +270,11 @@ export function adaptJobDetail(apiDetail) {
251
270
  if (pipeline != null) detail.pipeline = pipeline;
252
271
  if (pipelineLabel != null) detail.pipelineLabel = pipelineLabel;
253
272
 
273
+ // Preserve job detail costs
274
+ if (apiDetail.costs) {
275
+ detail.costs = apiDetail.costs;
276
+ }
277
+
254
278
  // Include warnings for debugging
255
279
  if (warnings.length > 0) detail.__warnings = warnings;
256
280
 
@@ -242,7 +242,6 @@ export function useJobDetailWithUpdates(jobId) {
242
242
  status: jobData?.status,
243
243
  hasTasks: !!jobData?.tasks,
244
244
  taskKeys: jobData?.tasks ? Object.keys(jobData.tasks) : [],
245
- hasTasksStatus: !!jobData?.tasksStatus,
246
245
  });
247
246
  if (mountedRef.current) {
248
247
  logger.log("Refetch successful, updating data");
@@ -2,6 +2,11 @@
2
2
  @import "@radix-ui/themes/styles.css" layer(radix);
3
3
  @import "tailwindcss";
4
4
 
5
+ .radix-themes {
6
+ --heading-font-family: "Source Sans 3", sans-serif;
7
+ --body-font-family: "Source Sans 3", sans-serif;
8
+ }
9
+
5
10
  /* Reset and base styles */
6
11
  @layer base {
7
12
  * {
@@ -22,6 +27,7 @@ body {
22
27
  font-family: "Inter", sans-serif;
23
28
  line-height: 1.6;
24
29
  min-height: 100vh;
30
+ background-color: #dff2fe;
25
31
  }
26
32
 
27
33
  .container {
@@ -6,7 +6,7 @@
6
6
  <link rel="preconnect" href="https://fonts.googleapis.com" />
7
7
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
8
8
  <link
9
- href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"
9
+ href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400..700;1,400..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
10
10
  rel="stylesheet"
11
11
  />
12
12
  <title>Prompt Pipeline Dashboard</title>
@@ -16,6 +16,7 @@ import ReactDOM from "react-dom/client";
16
16
  import { BrowserRouter, Routes, Route } from "react-router-dom";
17
17
  import PromptPipelineDashboard from "@/pages/PromptPipelineDashboard.jsx";
18
18
  import PipelineDetail from "@/pages/PipelineDetail.jsx";
19
+ import Code from "@/pages/Code.jsx";
19
20
  import { Theme } from "@radix-ui/themes";
20
21
 
21
22
  ReactDOM.createRoot(document.getElementById("root")).render(
@@ -31,6 +32,7 @@ ReactDOM.createRoot(document.getElementById("root")).render(
31
32
  <Routes>
32
33
  <Route path="/" element={<PromptPipelineDashboard />} />
33
34
  <Route path="/pipeline/:jobId" element={<PipelineDetail />} />
35
+ <Route path="/code" element={<Code />} />
34
36
  </Routes>
35
37
  </BrowserRouter>
36
38
  </Theme>