@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.
- 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
|
@@ -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";
|