@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,97 @@
1
+ import React, { useRef, useEffect } from "react";
2
+ import { X, Copy, Check } from "lucide-react";
3
+ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
4
+ import { oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
5
+
6
+ export const SchemaPreviewPanel = ({
7
+ fileName,
8
+ type,
9
+ content,
10
+ loading,
11
+ error,
12
+ onClose,
13
+ }) => {
14
+ const panelRef = useRef(null);
15
+ const [copied, setCopied] = React.useState(false);
16
+
17
+ const handleCopy = async () => {
18
+ if (content) {
19
+ try {
20
+ await navigator.clipboard.writeText(content);
21
+ setCopied(true);
22
+ setTimeout(() => setCopied(false), 2000);
23
+ } catch (error) {
24
+ console.error("Failed to copy content to clipboard:", error);
25
+ }
26
+ }
27
+ };
28
+
29
+ useEffect(() => {
30
+ if (panelRef.current) {
31
+ panelRef.current.focus();
32
+ }
33
+ }, []);
34
+
35
+ const handleKeyDown = (e) => {
36
+ if (e.key === "Escape") {
37
+ onClose();
38
+ }
39
+ };
40
+
41
+ return (
42
+ <div
43
+ ref={panelRef}
44
+ tabIndex={-1}
45
+ onKeyDown={handleKeyDown}
46
+ role="region"
47
+ aria-label={`${type} preview for ${fileName}`}
48
+ className="fixed bottom-0 left-0 right-0 h-[50%] bg-white border-t shadow-lg flex flex-col z-10"
49
+ >
50
+ <div className="flex items-center justify-between px-4 py-2 border-b bg-slate-50">
51
+ <span className="font-medium text-sm">
52
+ {fileName} ({type})
53
+ </span>
54
+ <div className="flex items-center gap-1">
55
+ <button
56
+ onClick={handleCopy}
57
+ aria-label="Copy content"
58
+ className="hover:bg-slate-200 rounded p-1"
59
+ disabled={!content}
60
+ >
61
+ {copied ? (
62
+ <Check className="h-4 w-4 text-green-600" />
63
+ ) : (
64
+ <Copy className="h-4 w-4" />
65
+ )}
66
+ </button>
67
+ <button
68
+ onClick={onClose}
69
+ aria-label="Close preview"
70
+ className="hover:bg-slate-200 rounded p-1"
71
+ >
72
+ <X className="h-4 w-4" />
73
+ </button>
74
+ </div>
75
+ </div>
76
+ <div className="flex-1 overflow-auto p-4">
77
+ {loading ? (
78
+ <div className="text-sm text-muted-foreground" aria-live="polite">Loading...</div>
79
+ ) : error ? (
80
+ <div className="text-sm text-red-600" role="alert">{error}</div>
81
+ ) : content ? (
82
+ <SyntaxHighlighter
83
+ language="json"
84
+ style={oneLight}
85
+ customStyle={{
86
+ margin: 0,
87
+ background: "transparent",
88
+ fontSize: "12px",
89
+ }}
90
+ >
91
+ {content}
92
+ </SyntaxHighlighter>
93
+ ) : null}
94
+ </div>
95
+ </div>
96
+ );
97
+ };
@@ -0,0 +1,36 @@
1
+ import React from "react";
2
+ import { Badge } from "./ui/badge.jsx";
3
+
4
+ export const StageTimeline = React.memo(({ stages }) => {
5
+ // Filter out stages without a name property (required for React key and display)
6
+ const validStages = stages?.filter((stage) => stage?.name) || [];
7
+
8
+ // Sort by order, with defensive handling for missing order property
9
+ const sortedStages = [...validStages].sort((a, b) => {
10
+ const orderA = typeof a.order === 'number' ? a.order : Number.MAX_SAFE_INTEGER;
11
+ const orderB = typeof b.order === 'number' ? b.order : Number.MAX_SAFE_INTEGER;
12
+ return orderA - orderB;
13
+ });
14
+
15
+ return (
16
+ <ol
17
+ role="list"
18
+ aria-label="Task execution stages"
19
+ className="relative border-l border-slate-300 ml-1 space-y-4"
20
+ >
21
+ {sortedStages.map((stage) => (
22
+ <li key={stage.name} className="flex items-center gap-3 pl-4 relative">
23
+ <div className="absolute left-[-5px] w-2 h-2 rounded-full bg-blue-500" />
24
+ <span className="text-sm">{stage.name}</span>
25
+ {stage.isAsync && (
26
+ <Badge intent="amber" className="ml-auto">
27
+ async
28
+ </Badge>
29
+ )}
30
+ </li>
31
+ ))}
32
+ </ol>
33
+ );
34
+ });
35
+
36
+ StageTimeline.displayName = "StageTimeline";
@@ -0,0 +1,227 @@
1
+ import React, { useState } from "react";
2
+ import { Table } from "@radix-ui/themes";
3
+ import { SidebarSection } from "./ui/sidebar.jsx";
4
+ import { Badge } from "./ui/badge.jsx";
5
+ import { Button } from "./ui/button.jsx";
6
+ import { StageTimeline } from "./StageTimeline.jsx";
7
+ import { SchemaPreviewPanel } from "./SchemaPreviewPanel.jsx";
8
+
9
+ const formatDate = (isoString) => {
10
+ if (
11
+ !isoString ||
12
+ (typeof isoString !== "string" && !(isoString instanceof Date))
13
+ ) {
14
+ return "Unknown";
15
+ }
16
+
17
+ const date = isoString instanceof Date ? isoString : new Date(isoString);
18
+
19
+ if (Number.isNaN(date.getTime())) {
20
+ return "Unknown";
21
+ }
22
+
23
+ try {
24
+ return new Intl.DateTimeFormat("en-US", {
25
+ dateStyle: "medium",
26
+ timeStyle: "short",
27
+ }).format(date);
28
+ } catch {
29
+ return "Unknown";
30
+ }
31
+ };
32
+
33
+ const ArtifactTable = ({
34
+ artifacts,
35
+ showRequired,
36
+ emptyMessage,
37
+ onViewSchema,
38
+ }) => {
39
+ if (artifacts.length === 0) {
40
+ return (
41
+ <div className="text-sm text-muted-foreground italic">{emptyMessage}</div>
42
+ );
43
+ }
44
+
45
+ return (
46
+ <Table.Root size="1">
47
+ <Table.Header>
48
+ <Table.Row>
49
+ <Table.ColumnHeaderCell>File</Table.ColumnHeaderCell>
50
+ <Table.ColumnHeaderCell>Stage</Table.ColumnHeaderCell>
51
+ {showRequired && (
52
+ <Table.ColumnHeaderCell>Required</Table.ColumnHeaderCell>
53
+ )}
54
+ <Table.ColumnHeaderCell>Actions</Table.ColumnHeaderCell>
55
+ </Table.Row>
56
+ </Table.Header>
57
+ <Table.Body>
58
+ {artifacts.map((artifact, idx) => (
59
+ <Table.Row key={idx}>
60
+ <Table.Cell>
61
+ <code className="text-sm">{artifact.fileName}</code>
62
+ </Table.Cell>
63
+ <Table.Cell>
64
+ <Badge intent="blue">{artifact.stage}</Badge>
65
+ </Table.Cell>
66
+ {showRequired && (
67
+ <Table.Cell>
68
+ {artifact.required && <Badge intent="red">required</Badge>}
69
+ </Table.Cell>
70
+ )}
71
+ <Table.Cell>
72
+ {artifact.fileName.endsWith(".json") && (
73
+ <div className="flex gap-1">
74
+ <Button
75
+ variant="ghost"
76
+ size="sm"
77
+ onClick={() => onViewSchema(artifact.fileName, "schema")}
78
+ >
79
+ Schema
80
+ </Button>
81
+ <Button
82
+ variant="ghost"
83
+ size="sm"
84
+ onClick={() => onViewSchema(artifact.fileName, "sample")}
85
+ >
86
+ Sample
87
+ </Button>
88
+ </div>
89
+ )}
90
+ </Table.Cell>
91
+ </Table.Row>
92
+ ))}
93
+ </Table.Body>
94
+ </Table.Root>
95
+ );
96
+ };
97
+
98
+ const ModelList = ({ models }) => {
99
+ if (models.length === 0) {
100
+ return <div className="text-sm text-muted-foreground">No models used</div>;
101
+ }
102
+
103
+ return (
104
+ <ul className="space-y-1 text-sm">
105
+ {models.map((model, idx) => (
106
+ <li key={idx} className="text-slate-700">
107
+ {model.provider}.{model.method} @ {model.stage}
108
+ </li>
109
+ ))}
110
+ </ul>
111
+ );
112
+ };
113
+
114
+ export const TaskAnalysisDisplay = React.memo(
115
+ ({ analysis, loading, error, pipelineSlug }) => {
116
+ const [previewFile, setPreviewFile] = useState(null);
117
+ const [previewContent, setPreviewContent] = useState(null);
118
+ const [previewLoading, setPreviewLoading] = useState(false);
119
+ const [previewError, setPreviewError] = useState(null);
120
+
121
+ const handleViewSchema = async (fileName, type) => {
122
+ setPreviewFile({ fileName, type });
123
+ setPreviewLoading(true);
124
+ setPreviewError(null);
125
+ setPreviewContent(null);
126
+
127
+ try {
128
+ const res = await fetch(
129
+ `/api/pipelines/${encodeURIComponent(
130
+ pipelineSlug
131
+ )}/schemas/${encodeURIComponent(fileName)}?type=${type}`
132
+ );
133
+ const data = await res.json();
134
+ if (!res.ok) throw new Error(data.message || "Failed to load");
135
+ setPreviewContent(data.data);
136
+ } catch (err) {
137
+ setPreviewError(err.message);
138
+ } finally {
139
+ setPreviewLoading(false);
140
+ }
141
+ };
142
+
143
+ const handleClosePreview = () => {
144
+ setPreviewFile(null);
145
+ setPreviewContent(null);
146
+ setPreviewError(null);
147
+ };
148
+
149
+ if (loading) {
150
+ return (
151
+ <div className="p-6 text-sm text-muted-foreground" aria-busy="true">
152
+ Loading analysis...
153
+ </div>
154
+ );
155
+ }
156
+
157
+ if (error) {
158
+ return (
159
+ <div className="p-6 text-sm text-red-600" role="alert">
160
+ {error}
161
+ </div>
162
+ );
163
+ }
164
+
165
+ if (analysis === null) {
166
+ return (
167
+ <div className="p-6 text-sm text-muted-foreground">
168
+ No analysis available
169
+ </div>
170
+ );
171
+ }
172
+
173
+ return (
174
+ <div>
175
+ <SidebarSection title="Artifacts">
176
+ <div className="space-y-4">
177
+ <div>
178
+ <h4 className="text-sm font-medium text-slate-700 mb-2">Reads</h4>
179
+ <ArtifactTable
180
+ artifacts={analysis.artifacts.reads}
181
+ showRequired
182
+ emptyMessage="No reads"
183
+ onViewSchema={handleViewSchema}
184
+ />
185
+ </div>
186
+ <div>
187
+ <h4 className="text-sm font-medium text-slate-700 mb-2">
188
+ Writes
189
+ </h4>
190
+ <ArtifactTable
191
+ artifacts={analysis.artifacts.writes}
192
+ showRequired={false}
193
+ emptyMessage="No writes"
194
+ onViewSchema={handleViewSchema}
195
+ />
196
+ </div>
197
+ </div>
198
+ </SidebarSection>
199
+
200
+ <SidebarSection title="Stages">
201
+ <StageTimeline stages={analysis.stages} />
202
+ </SidebarSection>
203
+
204
+ <SidebarSection title="Models">
205
+ <ModelList models={analysis.models} />
206
+ </SidebarSection>
207
+
208
+ <div className="px-6 pb-6 text-xs text-muted-foreground">
209
+ Analyzed at: {formatDate(analysis.analyzedAt)}
210
+ </div>
211
+
212
+ {previewFile && (
213
+ <SchemaPreviewPanel
214
+ fileName={previewFile.fileName}
215
+ type={previewFile.type}
216
+ content={previewContent}
217
+ loading={previewLoading}
218
+ error={previewError}
219
+ onClose={handleClosePreview}
220
+ />
221
+ )}
222
+ </div>
223
+ );
224
+ }
225
+ );
226
+
227
+ TaskAnalysisDisplay.displayName = "TaskAnalysisDisplay";