@ryanfw/prompt-orchestration-pipeline 0.11.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 +11 -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/DAGGrid.jsx +157 -47
- 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/RestartJobModal.jsx +26 -6
- package/src/components/ui/StopJobModal.jsx +183 -0
- 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 +11 -4
- package/src/core/lifecycle-policy.js +62 -0
- package/src/core/pipeline-runner.js +312 -217
- package/src/core/status-writer.js +84 -0
- package/src/llm/index.js +129 -9
- package/src/pages/Code.jsx +8 -1
- package/src/pages/PipelineDetail.jsx +84 -2
- package/src/pages/PipelineList.jsx +214 -0
- package/src/pages/PipelineTypeDetail.jsx +234 -0
- package/src/pages/PromptPipelineDashboard.jsx +10 -11
- 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/adapters/job-adapter.js +60 -0
- package/src/ui/client/api.js +233 -8
- package/src/ui/client/hooks/useAnalysisProgress.js +145 -0
- package/src/ui/client/hooks/useJobList.js +14 -1
- 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/app.js +262 -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/favicon.svg +12 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/endpoints/create-pipeline-endpoint.js +194 -0
- package/src/ui/endpoints/file-endpoints.js +330 -0
- package/src/ui/endpoints/job-control-endpoints.js +1001 -0
- package/src/ui/endpoints/job-endpoints.js +62 -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/sse-endpoints.js +223 -0
- package/src/ui/endpoints/state-endpoint.js +85 -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/endpoints/upload-endpoints.js +406 -0
- package/src/ui/express-app.js +227 -0
- package/src/ui/lib/analysis-lock.js +67 -0
- package/src/ui/lib/sse.js +30 -0
- package/src/ui/server.js +42 -1880
- package/src/ui/sse-broadcast.js +93 -0
- package/src/ui/utils/http-utils.js +139 -0
- package/src/ui/utils/mime-types.js +196 -0
- package/src/ui/utils/slug.js +31 -0
- package/src/ui/vite.config.js +22 -0
- package/src/ui/watcher.js +28 -2
- package/src/utils/jobs.js +39 -0
- package/src/ui/dist/assets/index-DeDzq-Kk.js +0 -23863
- package/src/ui/dist/assets/style-aBtD_Yrs.css +0 -62
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import { Callout } from "@radix-ui/themes";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Callout, Table, Text, Box } from "@radix-ui/themes";
|
|
3
3
|
import { TaskFilePane } from "./TaskFilePane.jsx";
|
|
4
4
|
import { TaskState } from "../config/statuses.js";
|
|
5
|
+
import { Sidebar, SidebarSection } from "./ui/sidebar.jsx";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* TaskDetailSidebar component for displaying task details in a slide-over panel
|
|
@@ -12,9 +13,11 @@ import { TaskState } from "../config/statuses.js";
|
|
|
12
13
|
* @param {string} props.jobId - Job ID for file operations
|
|
13
14
|
* @param {string} props.taskId - Task ID for file operations
|
|
14
15
|
* @param {string|null} props.taskBody - Task body for error callout when status is FAILED
|
|
16
|
+
* @param {Object} props.taskError - Error object with message and stack
|
|
15
17
|
* @param {Function} props.filesByTypeForItem - Selector returning { artifacts, logs, tmp }
|
|
16
18
|
* @param {Object} props.task - Original task item, passed for filesByTypeForItem
|
|
17
19
|
* @param {Function} props.onClose - Close handler
|
|
20
|
+
* @param {number} props.taskIndex - Task index for ID compatibility
|
|
18
21
|
*/
|
|
19
22
|
export function TaskDetailSidebar({
|
|
20
23
|
open,
|
|
@@ -27,36 +30,28 @@ export function TaskDetailSidebar({
|
|
|
27
30
|
filesByTypeForItem = () => ({ artifacts: [], logs: [], tmp: [] }),
|
|
28
31
|
task,
|
|
29
32
|
onClose,
|
|
30
|
-
taskIndex, //
|
|
33
|
+
taskIndex: _taskIndex, // eslint-disable-line no-unused-vars
|
|
31
34
|
}) {
|
|
32
35
|
// Internal state
|
|
33
36
|
const [filePaneType, setFilePaneType] = useState("artifacts");
|
|
34
37
|
const [filePaneOpen, setFilePaneOpen] = useState(false);
|
|
35
38
|
const [filePaneFilename, setFilePaneFilename] = useState(null);
|
|
36
39
|
const [showStack, setShowStack] = useState(false);
|
|
37
|
-
const closeButtonRef = useRef(null);
|
|
38
40
|
|
|
39
41
|
// Get CSS classes for card header based on status (mirrored from DAGGrid)
|
|
40
42
|
const getHeaderClasses = (status) => {
|
|
41
43
|
switch (status) {
|
|
42
44
|
case TaskState.DONE:
|
|
43
|
-
return "bg-
|
|
45
|
+
return "bg-success/10 border-success/30 text-success-foreground";
|
|
44
46
|
case TaskState.RUNNING:
|
|
45
|
-
return "bg-
|
|
47
|
+
return "bg-warning/10 border-warning/30 text-warning-foreground";
|
|
46
48
|
case TaskState.FAILED:
|
|
47
|
-
return "bg-
|
|
49
|
+
return "bg-error/10 border-error/30 text-error-foreground";
|
|
48
50
|
default:
|
|
49
|
-
return "bg-
|
|
51
|
+
return "bg-muted/50 border-input text-foreground";
|
|
50
52
|
}
|
|
51
53
|
};
|
|
52
54
|
|
|
53
|
-
// Focus close button when sidebar opens
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
if (open && closeButtonRef.current) {
|
|
56
|
-
closeButtonRef.current.focus();
|
|
57
|
-
}
|
|
58
|
-
}, [open]);
|
|
59
|
-
|
|
60
55
|
// Reset internal state when open changes
|
|
61
56
|
useEffect(() => {
|
|
62
57
|
if (open) {
|
|
@@ -93,100 +88,80 @@ export function TaskDetailSidebar({
|
|
|
93
88
|
const filesForTab = filesForStep[filePaneType] ?? [];
|
|
94
89
|
|
|
95
90
|
return (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
>
|
|
103
|
-
{/* Header */}
|
|
104
|
-
<div
|
|
105
|
-
className={`px-6 py-4 border-b flex items-center justify-between ${getHeaderClasses(status)}`}
|
|
91
|
+
<>
|
|
92
|
+
<Sidebar
|
|
93
|
+
open={open}
|
|
94
|
+
onOpenChange={(isOpen) => !isOpen && onClose()}
|
|
95
|
+
title={title}
|
|
96
|
+
headerClassName={getHeaderClasses(status)}
|
|
106
97
|
>
|
|
107
|
-
<div
|
|
108
|
-
id={`slide-over-title-${taskIndex}`}
|
|
109
|
-
className="text-lg font-semibold truncate"
|
|
110
|
-
>
|
|
111
|
-
{title}
|
|
112
|
-
</div>
|
|
113
|
-
<button
|
|
114
|
-
ref={closeButtonRef}
|
|
115
|
-
type="button"
|
|
116
|
-
aria-label="Close details"
|
|
117
|
-
onClick={onClose}
|
|
118
|
-
className="rounded-md border border-gray-300 text-gray-700 hover:bg-gray-50 px-3 py-1.5 text-base"
|
|
119
|
-
>
|
|
120
|
-
×
|
|
121
|
-
</button>
|
|
122
|
-
</div>
|
|
123
|
-
|
|
124
|
-
<div className="p-6 space-y-8 overflow-y-auto h-full">
|
|
125
98
|
{/* Error Callout - shown when task has error status */}
|
|
126
99
|
{status === TaskState.FAILED && (taskError?.message || taskBody) && (
|
|
127
|
-
<
|
|
128
|
-
<
|
|
129
|
-
<Callout.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
{showStack ? "Hide stack" : "Show stack"}
|
|
144
|
-
</button>
|
|
145
|
-
{showStack && (
|
|
146
|
-
<pre
|
|
147
|
-
id="error-stack"
|
|
148
|
-
className="mt-2 p-2 bg-gray-50 border rounded text-xs font-mono max-h-64 overflow-auto whitespace-pre-wrap"
|
|
100
|
+
<SidebarSection className="bg-destructive/5 border-b">
|
|
101
|
+
<section aria-label="Error">
|
|
102
|
+
<Callout.Root role="alert" aria-live="assertive">
|
|
103
|
+
<Callout.Text className="whitespace-pre-wrap break-words">
|
|
104
|
+
{taskError?.message || taskBody}
|
|
105
|
+
</Callout.Text>
|
|
106
|
+
</Callout.Root>
|
|
107
|
+
|
|
108
|
+
{/* Stack trace toggle */}
|
|
109
|
+
{taskError?.stack && (
|
|
110
|
+
<div className="mt-3">
|
|
111
|
+
<button
|
|
112
|
+
onClick={() => setShowStack(!showStack)}
|
|
113
|
+
className="text-sm text-primary hover:text-primary/80 underline"
|
|
114
|
+
aria-expanded={showStack}
|
|
115
|
+
aria-controls="error-stack"
|
|
149
116
|
>
|
|
150
|
-
{
|
|
151
|
-
</
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
117
|
+
{showStack ? "Hide stack" : "Show stack"}
|
|
118
|
+
</button>
|
|
119
|
+
{showStack && (
|
|
120
|
+
<pre
|
|
121
|
+
id="error-stack"
|
|
122
|
+
className="mt-2 p-2 bg-muted border rounded text-xs font-mono max-h-64 overflow-auto whitespace-pre-wrap"
|
|
123
|
+
>
|
|
124
|
+
{taskError.stack}
|
|
125
|
+
</pre>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
</section>
|
|
130
|
+
</SidebarSection>
|
|
156
131
|
)}
|
|
157
132
|
|
|
158
133
|
{/* File Display Area with Type Tabs */}
|
|
159
|
-
<
|
|
134
|
+
<SidebarSection>
|
|
160
135
|
<div className="flex items-center justify-between mb-4">
|
|
161
|
-
<h3 className="text-base font-semibold text-
|
|
136
|
+
<h3 className="text-base font-semibold text-foreground">Files</h3>
|
|
162
137
|
<div className="flex items-center space-x-2">
|
|
163
|
-
<div className="flex rounded-lg border border-
|
|
138
|
+
<div className="flex rounded-lg border border-input bg-muted p-1">
|
|
164
139
|
<button
|
|
165
140
|
onClick={() => setFilePaneType("artifacts")}
|
|
166
|
-
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
|
|
141
|
+
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors border-l-2 ${
|
|
167
142
|
filePaneType === "artifacts"
|
|
168
|
-
? "bg-
|
|
169
|
-
: "text-
|
|
143
|
+
? "bg-background text-foreground shadow-sm border-indigo-400"
|
|
144
|
+
: "text-muted-foreground hover:text-foreground border-transparent"
|
|
170
145
|
}`}
|
|
171
146
|
>
|
|
172
147
|
Artifacts
|
|
173
148
|
</button>
|
|
174
149
|
<button
|
|
175
150
|
onClick={() => setFilePaneType("logs")}
|
|
176
|
-
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
|
|
151
|
+
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors border-l-2 ${
|
|
177
152
|
filePaneType === "logs"
|
|
178
|
-
? "bg-
|
|
179
|
-
: "text-
|
|
153
|
+
? "bg-background text-foreground shadow-sm border-indigo-400"
|
|
154
|
+
: "text-muted-foreground hover:text-foreground border-transparent"
|
|
180
155
|
}`}
|
|
181
156
|
>
|
|
182
157
|
Logs
|
|
183
158
|
</button>
|
|
184
159
|
<button
|
|
185
160
|
onClick={() => setFilePaneType("tmp")}
|
|
186
|
-
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
|
|
161
|
+
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors border-l-2 ${
|
|
187
162
|
filePaneType === "tmp"
|
|
188
|
-
? "bg-
|
|
189
|
-
: "text-
|
|
163
|
+
? "bg-background text-foreground shadow-sm border-indigo-400"
|
|
164
|
+
: "text-muted-foreground hover:text-foreground border-transparent"
|
|
190
165
|
}`}
|
|
191
166
|
>
|
|
192
167
|
Temp
|
|
@@ -194,46 +169,73 @@ export function TaskDetailSidebar({
|
|
|
194
169
|
</div>
|
|
195
170
|
</div>
|
|
196
171
|
</div>
|
|
197
|
-
</section>
|
|
198
172
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
<div className="space-y-1">
|
|
173
|
+
{/* File List Table */}
|
|
174
|
+
<div className="space-y-2">
|
|
175
|
+
<Text size="2" className="text-muted-foreground">
|
|
176
|
+
{filePaneType.charAt(0).toUpperCase() + filePaneType.slice(1)}{" "}
|
|
177
|
+
files for {taskId}
|
|
178
|
+
</Text>
|
|
206
179
|
{filesForTab.length === 0 ? (
|
|
207
|
-
<
|
|
208
|
-
|
|
209
|
-
|
|
180
|
+
<Box className="py-4 text-center">
|
|
181
|
+
<Text size="2" className="text-muted-foreground italic">
|
|
182
|
+
No {filePaneType} files available for this task
|
|
183
|
+
</Text>
|
|
184
|
+
</Box>
|
|
210
185
|
) : (
|
|
211
|
-
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
186
|
+
<Table.Root radius="none">
|
|
187
|
+
<Table.Header>
|
|
188
|
+
<Table.Row>
|
|
189
|
+
<Table.ColumnHeaderCell>File Name</Table.ColumnHeaderCell>
|
|
190
|
+
</Table.Row>
|
|
191
|
+
</Table.Header>
|
|
192
|
+
<Table.Body>
|
|
193
|
+
{filesForTab.map((name) => (
|
|
194
|
+
<Table.Row
|
|
195
|
+
key={`${filePaneType}-${name}`}
|
|
196
|
+
className="cursor-pointer hover:bg-slate-50/50"
|
|
197
|
+
onClick={() => handleFileClick(name)}
|
|
198
|
+
>
|
|
199
|
+
<Table.Cell>
|
|
200
|
+
<Text size="2">{name}</Text>
|
|
201
|
+
</Table.Cell>
|
|
202
|
+
</Table.Row>
|
|
203
|
+
))}
|
|
204
|
+
</Table.Body>
|
|
205
|
+
</Table.Root>
|
|
222
206
|
)}
|
|
223
207
|
</div>
|
|
224
|
-
</div>
|
|
225
208
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
209
|
+
{/* Inline File Preview */}
|
|
210
|
+
{filePaneOpen && (
|
|
211
|
+
<div className="mt-4 border-t pt-4">
|
|
212
|
+
<div className="flex items-center justify-between mb-2">
|
|
213
|
+
<Text size="2" weight="medium" className="text-foreground">
|
|
214
|
+
File Preview
|
|
215
|
+
</Text>
|
|
216
|
+
<button
|
|
217
|
+
onClick={handleFilePaneClose}
|
|
218
|
+
className="text-sm text-muted-foreground hover:text-foreground"
|
|
219
|
+
>
|
|
220
|
+
Close Preview
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
<div className="h-96 overflow-auto">
|
|
224
|
+
<TaskFilePane
|
|
225
|
+
isOpen={filePaneOpen}
|
|
226
|
+
jobId={jobId}
|
|
227
|
+
taskId={taskId}
|
|
228
|
+
type={filePaneType}
|
|
229
|
+
filename={filePaneFilename}
|
|
230
|
+
onClose={handleFilePaneClose}
|
|
231
|
+
inline={true}
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
)}
|
|
236
|
+
</SidebarSection>
|
|
237
|
+
</Sidebar>
|
|
238
|
+
</>
|
|
237
239
|
);
|
|
238
240
|
}
|
|
239
241
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from "react";
|
|
2
|
+
import { Button } from "./ui/button.jsx";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* TaskFilePane component for displaying a single task file with preview
|
|
@@ -9,6 +10,7 @@ import React, { useState, useEffect, useRef } from "react";
|
|
|
9
10
|
* @param {string} props.type - File type (artifacts|logs|tmp)
|
|
10
11
|
* @param {string} props.filename - File name to display
|
|
11
12
|
* @param {Function} props.onClose - Close handler
|
|
13
|
+
* @param {boolean} props.inline - Whether to render inline (vs modal)
|
|
12
14
|
*/
|
|
13
15
|
export function TaskFilePane({
|
|
14
16
|
isOpen,
|
|
@@ -17,6 +19,7 @@ export function TaskFilePane({
|
|
|
17
19
|
type,
|
|
18
20
|
filename,
|
|
19
21
|
onClose,
|
|
22
|
+
inline = false,
|
|
20
23
|
}) {
|
|
21
24
|
const [copyNotice, setCopyNotice] = useState(null);
|
|
22
25
|
const [loading, setLoading] = useState(false);
|
|
@@ -364,12 +367,14 @@ export function TaskFilePane({
|
|
|
364
367
|
<p className="mt-1 text-sm text-red-700">
|
|
365
368
|
{error.error?.message || "Unknown error"}
|
|
366
369
|
</p>
|
|
367
|
-
<
|
|
370
|
+
<Button
|
|
371
|
+
variant="ghost"
|
|
372
|
+
size="sm"
|
|
368
373
|
onClick={handleRetry}
|
|
369
|
-
className="mt-2 text-
|
|
374
|
+
className="mt-2 text-red-600 hover:text-red-800"
|
|
370
375
|
>
|
|
371
376
|
Retry
|
|
372
|
-
</
|
|
377
|
+
</Button>
|
|
373
378
|
</div>
|
|
374
379
|
</div>
|
|
375
380
|
</div>
|
|
@@ -493,12 +498,18 @@ export function TaskFilePane({
|
|
|
493
498
|
return null;
|
|
494
499
|
}
|
|
495
500
|
|
|
501
|
+
const containerClassName = inline
|
|
502
|
+
? "flex flex-col h-full"
|
|
503
|
+
: "bg-white rounded-lg shadow-xl w-full max-w-6xl max-h-[90vh] flex flex-col";
|
|
504
|
+
|
|
496
505
|
return (
|
|
497
|
-
<div className=
|
|
506
|
+
<div className={containerClassName}>
|
|
498
507
|
{/* Header */}
|
|
499
|
-
<div className="flex items-center justify-between p-4 border-b">
|
|
508
|
+
<div className="flex items-center justify-between p-4 border-b bg-white">
|
|
500
509
|
<div>
|
|
501
|
-
<h2 className="text-lg font-semibold">
|
|
510
|
+
<h2 className="text-lg font-semibold">
|
|
511
|
+
{inline ? "File Preview" : filename}
|
|
512
|
+
</h2>
|
|
502
513
|
</div>
|
|
503
514
|
<button
|
|
504
515
|
ref={closeButtonRef}
|
|
@@ -523,44 +534,88 @@ export function TaskFilePane({
|
|
|
523
534
|
</div>
|
|
524
535
|
|
|
525
536
|
{/* Preview */}
|
|
526
|
-
<div className="flex-1 flex flex-col bg-gray-50">
|
|
527
|
-
{/* Preview Header */}
|
|
528
|
-
|
|
529
|
-
<div className="
|
|
530
|
-
<div>
|
|
531
|
-
<
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
537
|
+
<div className="flex-1 flex flex-col bg-gray-50 overflow-hidden">
|
|
538
|
+
{/* Preview Header - only show filename in non-inline mode */}
|
|
539
|
+
{!inline && (
|
|
540
|
+
<div className="bg-white border-b p-4">
|
|
541
|
+
<div className="flex items-center justify-between">
|
|
542
|
+
<div>
|
|
543
|
+
<h3 className="font-medium">{filename}</h3>
|
|
544
|
+
<div className="flex items-center text-sm text-gray-500 mt-1">
|
|
545
|
+
{size && <span>{formatSize(size)}</span>}
|
|
546
|
+
{size && mtime && <span className="mx-1">•</span>}
|
|
547
|
+
{mtime && <span>{formatDate(mtime)}</span>}
|
|
548
|
+
{mime && (size || mtime) && <span className="mx-1">•</span>}
|
|
549
|
+
{mime && <span>{mime}</span>}
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
<div className="flex items-center space-x-2">
|
|
553
|
+
{copyNotice && (
|
|
554
|
+
<div
|
|
555
|
+
className={`text-sm ${
|
|
556
|
+
copyNotice.type === "success"
|
|
557
|
+
? "text-green-600"
|
|
558
|
+
: "text-red-600"
|
|
559
|
+
}`}
|
|
560
|
+
>
|
|
561
|
+
{copyNotice.message}
|
|
562
|
+
</div>
|
|
563
|
+
)}
|
|
564
|
+
{content && encoding === "utf8" && (
|
|
565
|
+
<Button
|
|
566
|
+
variant="solid"
|
|
567
|
+
size="sm"
|
|
568
|
+
onClick={handleCopy}
|
|
569
|
+
aria-label="Copy content to clipboard"
|
|
570
|
+
>
|
|
571
|
+
Copy
|
|
572
|
+
</Button>
|
|
573
|
+
)}
|
|
538
574
|
</div>
|
|
539
575
|
</div>
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
>
|
|
549
|
-
|
|
576
|
+
</div>
|
|
577
|
+
)}
|
|
578
|
+
|
|
579
|
+
{/* Inline preview header with filename and actions */}
|
|
580
|
+
{inline && (
|
|
581
|
+
<div className="bg-white border-b p-3">
|
|
582
|
+
<div className="flex items-center justify-between">
|
|
583
|
+
<div>
|
|
584
|
+
<h3 className="font-medium text-sm">{filename}</h3>
|
|
585
|
+
<div className="flex items-center text-xs text-gray-500 mt-1">
|
|
586
|
+
{size && <span>{formatSize(size)}</span>}
|
|
587
|
+
{size && mtime && <span className="mx-1">•</span>}
|
|
588
|
+
{mtime && <span>{formatDate(mtime)}</span>}
|
|
589
|
+
{mime && (size || mtime) && <span className="mx-1">•</span>}
|
|
590
|
+
{mime && <span>{mime}</span>}
|
|
550
591
|
</div>
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
592
|
+
</div>
|
|
593
|
+
<div className="flex items-center space-x-2">
|
|
594
|
+
{copyNotice && (
|
|
595
|
+
<div
|
|
596
|
+
className={`text-xs ${
|
|
597
|
+
copyNotice.type === "success"
|
|
598
|
+
? "text-green-600"
|
|
599
|
+
: "text-red-600"
|
|
600
|
+
}`}
|
|
601
|
+
>
|
|
602
|
+
{copyNotice.message}
|
|
603
|
+
</div>
|
|
604
|
+
)}
|
|
605
|
+
{content && encoding === "utf8" && (
|
|
606
|
+
<Button
|
|
607
|
+
variant="solid"
|
|
608
|
+
size="sm"
|
|
609
|
+
onClick={handleCopy}
|
|
610
|
+
aria-label="Copy content to clipboard"
|
|
611
|
+
>
|
|
612
|
+
Copy
|
|
613
|
+
</Button>
|
|
614
|
+
)}
|
|
615
|
+
</div>
|
|
561
616
|
</div>
|
|
562
617
|
</div>
|
|
563
|
-
|
|
618
|
+
)}
|
|
564
619
|
|
|
565
620
|
{/* Preview Content */}
|
|
566
621
|
<div className="flex-1 overflow-auto">{renderContent()}</div>
|
|
@@ -7,7 +7,7 @@ import { Button } from "./button.jsx";
|
|
|
7
7
|
* @param {Object} props
|
|
8
8
|
* @param {boolean} props.open - Whether the modal is open
|
|
9
9
|
* @param {Function} props.onClose - Function to call when modal is closed
|
|
10
|
-
* @param {Function} props.onConfirm - Function to call when restart is confirmed
|
|
10
|
+
* @param {Function} props.onConfirm - Function to call when restart is confirmed (receives options object)
|
|
11
11
|
* @param {string} props.jobId - The ID of the job to restart
|
|
12
12
|
* @param {string} props.taskId - The ID of the task that triggered the restart (optional)
|
|
13
13
|
* @param {boolean} props.isSubmitting - Whether the restart action is in progress
|
|
@@ -47,7 +47,10 @@ export function RestartJobModal({
|
|
|
47
47
|
const handleKeyDown = (e) => {
|
|
48
48
|
if (e.key === "Enter" && !isSubmitting && open) {
|
|
49
49
|
e.preventDefault();
|
|
50
|
-
|
|
50
|
+
// Do not confirm via Enter when a task is set; let the user click explicitly
|
|
51
|
+
if (!taskId) {
|
|
52
|
+
onConfirm({ singleTask: false });
|
|
53
|
+
}
|
|
51
54
|
}
|
|
52
55
|
};
|
|
53
56
|
|
|
@@ -100,9 +103,15 @@ export function RestartJobModal({
|
|
|
100
103
|
</Text>
|
|
101
104
|
|
|
102
105
|
{taskId && (
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
+
<>
|
|
107
|
+
<Text as="p" className="text-sm text-gray-600 mb-3">
|
|
108
|
+
<strong>Triggered from task:</strong> {taskId}
|
|
109
|
+
</Text>
|
|
110
|
+
<Text as="p" className="text-sm text-blue-600 mb-3">
|
|
111
|
+
<strong>Just this task:</strong> Only the selected task will
|
|
112
|
+
be reset and re-run. Other tasks remain unchanged.
|
|
113
|
+
</Text>
|
|
114
|
+
</>
|
|
106
115
|
)}
|
|
107
116
|
|
|
108
117
|
<Text as="p" className="text-sm text-gray-500 italic">
|
|
@@ -121,9 +130,20 @@ export function RestartJobModal({
|
|
|
121
130
|
Cancel
|
|
122
131
|
</Button>
|
|
123
132
|
|
|
133
|
+
{taskId && (
|
|
134
|
+
<Button
|
|
135
|
+
variant="outline"
|
|
136
|
+
onClick={() => onConfirm({ singleTask: true })}
|
|
137
|
+
disabled={isSubmitting}
|
|
138
|
+
className="min-w-[120px]"
|
|
139
|
+
>
|
|
140
|
+
{isSubmitting ? "Running..." : "Just this task"}
|
|
141
|
+
</Button>
|
|
142
|
+
)}
|
|
143
|
+
|
|
124
144
|
<Button
|
|
125
145
|
variant="destructive"
|
|
126
|
-
onClick={onConfirm}
|
|
146
|
+
onClick={() => onConfirm({ singleTask: false })}
|
|
127
147
|
disabled={isSubmitting}
|
|
128
148
|
className="min-w-[80px]"
|
|
129
149
|
>
|