@ryanfw/prompt-orchestration-pipeline 0.12.0 → 0.13.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 (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
@@ -0,0 +1,28 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ const parser = require("@babel/parser");
4
+
5
+ /**
6
+ * Parse task source code into a Babel AST.
7
+ *
8
+ * @param {string} code - The source code to parse
9
+ * @returns {import("@babel/types").File} The parsed AST
10
+ * @throws {Error} If parsing fails, includes syntax error location and message
11
+ */
12
+ export function parseTaskSource(code) {
13
+ try {
14
+ const ast = parser.parse(code, {
15
+ sourceType: "module",
16
+ plugins: ["jsx"],
17
+ });
18
+ return ast;
19
+ } catch (error) {
20
+ const loc = error.loc
21
+ ? `line ${error.loc.line}, column ${error.loc.column}`
22
+ : "unknown location";
23
+ throw new Error(
24
+ `Failed to parse task source code at ${loc}: ${error.message}`,
25
+ { cause: error }
26
+ );
27
+ }
28
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Check if a path is inside a try/catch block.
3
+ *
4
+ * @param {import("@babel/traverse").NodePath} path - The node path to check
5
+ * @returns {boolean} True if inside try/catch, false otherwise
6
+ */
7
+ export function isInsideTryCatch(path) {
8
+ return path.findParent((p) => p.isTryStatement()) !== null;
9
+ }
10
+
11
+ /**
12
+ * Get the stage name by finding the nearest exported function.
13
+ *
14
+ * Walks up the AST to find the parent ExportNamedDeclaration and returns
15
+ * the exported identifier name.
16
+ *
17
+ * @param {import("@babel/traverse").NodePath} path - The node path to start from
18
+ * @returns {string | null} The stage name or null if not in exported function
19
+ */
20
+ export function getStageName(path) {
21
+ const exportPath = path.findParent((p) => p.isExportNamedDeclaration());
22
+
23
+ if (!exportPath) {
24
+ return null;
25
+ }
26
+
27
+ const declaration = exportPath.node.declaration;
28
+
29
+ // Handle: export function name() {}
30
+ if (declaration?.type === "FunctionDeclaration") {
31
+ return declaration.id?.name ?? null;
32
+ }
33
+
34
+ // Handle: export const name = () => {}
35
+ if (declaration?.type === "VariableDeclaration") {
36
+ const declarator = declaration.declarations[0];
37
+ if (declarator?.id?.type === "Identifier") {
38
+ return declarator.id.name;
39
+ }
40
+ }
41
+
42
+ return null;
43
+ }
@@ -0,0 +1,145 @@
1
+ import { useState, useRef, useCallback } from "react";
2
+ import { fetchSSE } from "../sse-fetch.js";
3
+
4
+ const initialState = {
5
+ status: "idle",
6
+ pipelineSlug: null,
7
+ totalTasks: 0,
8
+ completedTasks: 0,
9
+ totalArtifacts: 0,
10
+ completedArtifacts: 0,
11
+ currentTask: null,
12
+ currentArtifact: null,
13
+ error: null,
14
+ };
15
+
16
+ /**
17
+ * useAnalysisProgress - Hook for managing pipeline analysis progress via SSE
18
+ *
19
+ * Provides state and controls for triggering pipeline analysis with real-time
20
+ * progress updates via Server-Sent Events.
21
+ *
22
+ * @returns {Object} { ...state, startAnalysis, reset }
23
+ */
24
+ export function useAnalysisProgress() {
25
+ const [state, setState] = useState(initialState);
26
+ const cancelRef = useRef(null);
27
+
28
+ const reset = useCallback(() => {
29
+ if (cancelRef.current) {
30
+ cancelRef.current();
31
+ cancelRef.current = null;
32
+ }
33
+ setState(initialState);
34
+ }, []);
35
+
36
+ const handleEvent = useCallback((event, data) => {
37
+ switch (event) {
38
+ case "started":
39
+ setState((prev) => ({
40
+ ...prev,
41
+ status: "running",
42
+ pipelineSlug: data.pipelineSlug || prev.pipelineSlug,
43
+ totalTasks: data.totalTasks || 0,
44
+ totalArtifacts: data.totalArtifacts || 0,
45
+ }));
46
+ break;
47
+
48
+ case "task:start":
49
+ setState((prev) => ({
50
+ ...prev,
51
+ currentTask: data.taskId || null,
52
+ }));
53
+ break;
54
+
55
+ case "artifact:start":
56
+ setState((prev) => ({
57
+ ...prev,
58
+ currentArtifact: data.artifactName || null,
59
+ }));
60
+ break;
61
+
62
+ case "artifact:complete":
63
+ setState((prev) => ({
64
+ ...prev,
65
+ completedArtifacts: prev.completedArtifacts + 1,
66
+ }));
67
+ break;
68
+
69
+ case "task:complete":
70
+ setState((prev) => ({
71
+ ...prev,
72
+ completedTasks: prev.completedTasks + 1,
73
+ currentArtifact: null,
74
+ }));
75
+ break;
76
+
77
+ case "complete":
78
+ setState((prev) => ({
79
+ ...prev,
80
+ status: "complete",
81
+ currentTask: null,
82
+ currentArtifact: null,
83
+ }));
84
+ if (cancelRef.current) {
85
+ cancelRef.current = null;
86
+ }
87
+ break;
88
+
89
+ case "error":
90
+ setState((prev) => ({
91
+ ...prev,
92
+ status: "error",
93
+ error: data.message || "Unknown error",
94
+ }));
95
+ if (cancelRef.current) {
96
+ cancelRef.current = null;
97
+ }
98
+ break;
99
+
100
+ default:
101
+ console.warn("Unknown SSE event:", event);
102
+ }
103
+ }, []);
104
+
105
+ const startAnalysis = useCallback(
106
+ async (pipelineSlug) => {
107
+ // Reset previous state
108
+ if (cancelRef.current) {
109
+ cancelRef.current();
110
+ }
111
+
112
+ setState({
113
+ ...initialState,
114
+ status: "connecting",
115
+ pipelineSlug,
116
+ });
117
+
118
+ const url = `/api/pipelines/${encodeURIComponent(pipelineSlug)}/analyze`;
119
+
120
+ const sse = fetchSSE(
121
+ url,
122
+ {},
123
+ handleEvent,
124
+ // Error handler for HTTP errors
125
+ (errorData) => {
126
+ setState((prev) => ({
127
+ ...prev,
128
+ status: "error",
129
+ error:
130
+ errorData.message ||
131
+ `HTTP ${errorData.status || "error"}: ${errorData.statusText || "Unknown error"}`,
132
+ }));
133
+ }
134
+ );
135
+ cancelRef.current = sse.cancel;
136
+ },
137
+ [handleEvent]
138
+ );
139
+
140
+ return {
141
+ ...state,
142
+ startAnalysis,
143
+ reset,
144
+ };
145
+ }
@@ -2,6 +2,55 @@
2
2
  @import "@radix-ui/themes/styles.css" layer(radix);
3
3
  @import "tailwindcss";
4
4
 
5
+ /* Steel Terminal Theme - CSS Variables */
6
+ :root {
7
+ /* Base colors */
8
+ --background: 30 5% 97%; /* Soft warm gray */
9
+ --foreground: 210 15% 15%; /* Deep slate */
10
+ --card: 0 0% 100%; /* Pure white */
11
+ --card-foreground: 210 15% 15%; /* Deep slate */
12
+ --popover: 0 0% 100%; /* Pure white */
13
+ --popover-foreground: 210 15% 15%; /* Deep slate */
14
+
15
+ /* Primary colors - Indigo for primary actions with clear active state */
16
+ --primary: 243deg 75% 59%; /* #4f46e5 - indigo-600 */
17
+ --primary-foreground: 0deg 0% 100%; /* #ffffff */
18
+ --primary-hover: 243deg 77% 49%; /* #4338ca - indigo-700 */
19
+
20
+ /* Secondary colors - For outline buttons */
21
+ --secondary: 210deg 20% 98%; /* #f8fafc - slate-50 */
22
+ --secondary-foreground: 243deg 75% 30%; /* #312e81 - indigo-950 */
23
+
24
+ /* Destructive colors - For danger actions */
25
+ --destructive: 0deg 84% 60%; /* #dc2626 - red-600 */
26
+ --destructive-foreground: 0deg 0% 100%; /* #ffffff */
27
+
28
+ /* Muted colors - For disabled/subtle states */
29
+ --muted: 210deg 40% 96%; /* #f1f5f9 - slate-100 */
30
+ --muted-foreground: 243deg 50% 45%; /* #6366f1 - indigo-500 */
31
+
32
+ /* Accent colors - For highlights */
33
+ --accent: 210deg 40% 96%; /* #f1f5f9 - slate-100 */
34
+ --accent-foreground: 243deg 75% 59%; /* #4f46e5 - indigo-600 */
35
+
36
+ /* Border and input colors */
37
+ --border: 214deg 32% 91%; /* #e2e8f0 - slate-200 */
38
+ --input: 214deg 32% 91%; /* #e2e8f0 - slate-200 */
39
+
40
+ /* Ring - For focus states */
41
+ --ring: 243deg 75% 59%; /* #4f46e5 - indigo-600 */
42
+
43
+ /* Status colors */
44
+ --success: 142deg 76% 36%; /* #16a34a - green-600 */
45
+ --success-foreground: 0deg 0% 100%;
46
+ --warning: 38deg 92% 50%; /* #ca8a04 - yellow-600 */
47
+ --warning-foreground: 0deg 0% 100%;
48
+ --error: 0deg 84% 60%; /* #dc2626 - red-600 */
49
+ --error-foreground: 0deg 0% 100%;
50
+ --info: 215deg 90% 52%; /* #0284c7 - sky-600 */
51
+ --info-foreground: 0deg 0% 100%;
52
+ }
53
+
5
54
  .radix-themes {
6
55
  --heading-font-family: "Source Sans 3", sans-serif;
7
56
  --body-font-family: "Source Sans 3", sans-serif;
@@ -44,3 +93,18 @@ body {
44
93
  margin: 0 auto;
45
94
  padding: 2rem;
46
95
  }
96
+
97
+ /* Bouncing wave animation for typing indicators */
98
+ @keyframes bounce-wave {
99
+ 0%,
100
+ 100% {
101
+ transform: translateY(0);
102
+ }
103
+ 50% {
104
+ transform: translateY(-8px);
105
+ }
106
+ }
107
+
108
+ .animate-bounce-wave {
109
+ animation: bounce-wave 0.6s ease-in-out infinite;
110
+ }
@@ -17,6 +17,8 @@ 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
19
  import Code from "@/pages/Code.jsx";
20
+ import PipelineList from "@/pages/PipelineList.jsx";
21
+ import PipelineTypeDetail from "@/pages/PipelineTypeDetail.jsx";
20
22
  import { Theme } from "@radix-ui/themes";
21
23
  import { ToastProvider } from "@/components/ui/toast.jsx";
22
24
 
@@ -34,6 +36,8 @@ ReactDOM.createRoot(document.getElementById("root")).render(
34
36
  <Routes>
35
37
  <Route path="/" element={<PromptPipelineDashboard />} />
36
38
  <Route path="/pipeline/:jobId" element={<PipelineDetail />} />
39
+ <Route path="/pipelines" element={<PipelineList />} />
40
+ <Route path="/pipelines/:slug" element={<PipelineTypeDetail />} />
37
41
  <Route path="/code" element={<Code />} />
38
42
  </Routes>
39
43
  </BrowserRouter>
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Parse Server-Sent Events from a fetch response stream.
3
+ * Supports POST requests (unlike native EventSource).
4
+ *
5
+ * @param {string} url - The URL to fetch
6
+ * @param {RequestInit} options - Fetch options (method defaults to POST)
7
+ * @param {function} onEvent - Callback for each SSE event: (eventName, parsedData) => void
8
+ * @param {function} onError - Callback for HTTP errors: (errorData) => void
9
+ * @returns {{ cancel: () => void }} Object with cancel method to abort the fetch
10
+ */
11
+ export function fetchSSE(url, options = {}, onEvent, onError) {
12
+ if (typeof onEvent !== "function") {
13
+ throw new Error("onEvent callback is required");
14
+ }
15
+
16
+ const controller = new AbortController();
17
+ const requestOptions = {
18
+ method: "POST",
19
+ ...options,
20
+ signal: controller.signal,
21
+ };
22
+
23
+ fetch(url, requestOptions)
24
+ .then(async (response) => {
25
+ // Handle HTTP errors before attempting to read stream
26
+ if (!response.ok) {
27
+ let errorData = {};
28
+ try {
29
+ errorData = await response.json();
30
+ } catch {
31
+ // If response isn't JSON, just use status text
32
+ errorData = {
33
+ ok: false,
34
+ code: "http_error",
35
+ message: response.statusText,
36
+ status: response.status,
37
+ };
38
+ }
39
+ if (typeof onError === "function") {
40
+ onError(errorData);
41
+ } else {
42
+ console.error(`[sse-fetch] HTTP ${response.status}:`, errorData);
43
+ }
44
+ return;
45
+ }
46
+
47
+ const reader = response.body.getReader();
48
+ const decoder = new TextDecoder();
49
+ let buffer = "";
50
+
51
+ while (true) {
52
+ const { done, value } = await reader.read();
53
+ if (done) break;
54
+
55
+ buffer += decoder.decode(value, { stream: true });
56
+ const events = buffer.split("\n\n");
57
+
58
+ // Keep the last partial event in the buffer
59
+ buffer = events.pop();
60
+
61
+ for (const eventText of events) {
62
+ if (!eventText.trim()) continue;
63
+
64
+ const event = parseSSEEvent(eventText);
65
+ if (event) {
66
+ onEvent(event.type, event.data);
67
+ }
68
+ }
69
+ }
70
+
71
+ // Process any remaining content in buffer
72
+ if (buffer.trim()) {
73
+ const event = parseSSEEvent(buffer);
74
+ if (event) {
75
+ onEvent(event.type, event.data);
76
+ }
77
+ }
78
+ })
79
+ .catch((error) => {
80
+ if (error.name === "AbortError") {
81
+ return; // Expected when cancel() is called
82
+ }
83
+ console.error("[sse-fetch] Error:", error);
84
+ });
85
+
86
+ return {
87
+ cancel: () => controller.abort(),
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Parse a single SSE event string.
93
+ *
94
+ * @param {string} eventText - The raw SSE event text
95
+ * @returns {{ type: string, data: object }|null} Parsed event or null if invalid
96
+ */
97
+ function parseSSEEvent(eventText) {
98
+ let type = null;
99
+ let data = null;
100
+
101
+ for (const line of eventText.split("\n")) {
102
+ const trimmedLine = line.trim();
103
+ if (trimmedLine.startsWith("event:")) {
104
+ type = trimmedLine.slice(6).trim();
105
+ } else if (trimmedLine.startsWith("data:")) {
106
+ const dataStr = trimmedLine.slice(5).trim();
107
+ try {
108
+ data = JSON.parse(dataStr);
109
+ } catch {
110
+ console.error("[sse-fetch] Failed to parse data:", dataStr);
111
+ }
112
+ }
113
+ }
114
+
115
+ if (!type || data === null) {
116
+ return null;
117
+ }
118
+
119
+ return { type, data };
120
+ }