stagent 0.6.2 → 0.6.3
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/dist/cli.js +47 -1
- package/package.json +1 -2
- package/src/app/api/documents/[id]/route.ts +5 -1
- package/src/app/api/documents/[id]/versions/route.ts +53 -0
- package/src/app/api/documents/route.ts +5 -1
- package/src/app/api/projects/[id]/documents/route.ts +124 -0
- package/src/app/api/projects/[id]/route.ts +35 -3
- package/src/app/api/projects/__tests__/delete-project.test.ts +1 -0
- package/src/app/api/schedules/route.ts +19 -1
- package/src/app/api/tasks/[id]/route.ts +37 -2
- package/src/app/api/tasks/[id]/siblings/route.ts +48 -0
- package/src/app/api/tasks/route.ts +8 -9
- package/src/app/api/workflows/[id]/documents/route.ts +209 -0
- package/src/app/api/workflows/[id]/execute/route.ts +6 -2
- package/src/app/api/workflows/[id]/route.ts +16 -3
- package/src/app/api/workflows/[id]/status/route.ts +18 -2
- package/src/app/api/workflows/route.ts +13 -2
- package/src/app/documents/page.tsx +5 -1
- package/src/app/layout.tsx +0 -1
- package/src/app/manifest.ts +3 -3
- package/src/app/projects/[id]/page.tsx +62 -2
- package/src/components/documents/document-chip-bar.tsx +17 -1
- package/src/components/documents/document-detail-view.tsx +51 -0
- package/src/components/documents/document-grid.tsx +5 -0
- package/src/components/documents/document-table.tsx +4 -0
- package/src/components/documents/types.ts +3 -0
- package/src/components/projects/project-form-sheet.tsx +133 -2
- package/src/components/schedules/schedule-form.tsx +113 -1
- package/src/components/shared/document-picker-sheet.tsx +283 -0
- package/src/components/tasks/task-card.tsx +8 -1
- package/src/components/tasks/task-create-panel.tsx +137 -14
- package/src/components/tasks/task-detail-view.tsx +47 -0
- package/src/components/tasks/task-edit-dialog.tsx +125 -2
- package/src/components/workflows/workflow-form-view.tsx +231 -7
- package/src/components/workflows/workflow-kanban-card.tsx +8 -1
- package/src/components/workflows/workflow-list.tsx +90 -45
- package/src/components/workflows/workflow-status-view.tsx +167 -22
- package/src/lib/__tests__/npx-process-cwd.test.ts +17 -2
- package/src/lib/agents/profiles/registry.ts +6 -3
- package/src/lib/book/__tests__/chapter-slugs.test.ts +80 -0
- package/src/lib/book/chapter-generator.ts +4 -19
- package/src/lib/book/chapter-mapping.ts +17 -0
- package/src/lib/book/content.ts +5 -16
- package/src/lib/book/update-detector.ts +3 -16
- package/src/lib/chat/engine.ts +1 -0
- package/src/lib/chat/system-prompt.ts +9 -1
- package/src/lib/chat/tool-catalog.ts +1 -0
- package/src/lib/chat/tools/settings-tools.ts +109 -0
- package/src/lib/chat/tools/workflow-tools.ts +145 -2
- package/src/lib/data/clear.ts +12 -0
- package/src/lib/db/bootstrap.ts +48 -0
- package/src/lib/db/migrations/0016_add_workflow_document_inputs.sql +13 -0
- package/src/lib/db/migrations/0017_add_document_picker_tables.sql +25 -0
- package/src/lib/db/migrations/0018_add_workflow_run_number.sql +2 -0
- package/src/lib/db/schema.ts +77 -0
- package/src/lib/docs/reader.ts +2 -3
- package/src/lib/documents/context-builder.ts +75 -2
- package/src/lib/documents/document-resolver.ts +119 -0
- package/src/lib/documents/processors/spreadsheet.ts +2 -1
- package/src/lib/schedules/scheduler.ts +31 -1
- package/src/lib/utils/app-root.ts +20 -0
- package/src/lib/validators/__tests__/task.test.ts +43 -10
- package/src/lib/validators/task.ts +7 -1
- package/src/lib/workflows/blueprints/registry.ts +3 -3
- package/src/lib/workflows/engine.ts +24 -8
- package/src/lib/workflows/types.ts +14 -0
- package/public/icon.svg +0 -13
- package/src/components/tasks/file-upload.tsx +0 -120
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect } from "react";
|
|
4
|
-
import { useRouter } from "next/navigation";
|
|
3
|
+
import { useState, useEffect, useCallback } from "react";
|
|
4
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
6
|
import { Input } from "@/components/ui/input";
|
|
7
7
|
import { Textarea } from "@/components/ui/textarea";
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
ArrowDown,
|
|
28
28
|
Brain,
|
|
29
29
|
ShieldCheck,
|
|
30
|
+
FileText,
|
|
31
|
+
X,
|
|
30
32
|
} from "lucide-react";
|
|
31
33
|
import { toast } from "sonner";
|
|
32
34
|
import { FormSectionCard } from "@/components/shared/form-section-card";
|
|
@@ -47,6 +49,8 @@ import {
|
|
|
47
49
|
MAX_PARALLEL_BRANCHES,
|
|
48
50
|
MIN_PARALLEL_BRANCHES,
|
|
49
51
|
} from "@/lib/workflows/parallel";
|
|
52
|
+
import { DocumentPickerSheet } from "@/components/shared/document-picker-sheet";
|
|
53
|
+
import { getFileIcon, formatSize } from "@/components/documents/utils";
|
|
50
54
|
import {
|
|
51
55
|
DEFAULT_SWARM_CONCURRENCY_LIMIT,
|
|
52
56
|
MAX_SWARM_WORKERS,
|
|
@@ -284,6 +288,15 @@ function normalizeSwarmSteps(
|
|
|
284
288
|
);
|
|
285
289
|
}
|
|
286
290
|
|
|
291
|
+
const PATTERN_LABELS: Record<string, string> = {
|
|
292
|
+
sequence: "Sequence",
|
|
293
|
+
"planner-executor": "Planner → Executor",
|
|
294
|
+
checkpoint: "Checkpoint",
|
|
295
|
+
loop: "Autonomous Loop",
|
|
296
|
+
parallel: "Parallel Research",
|
|
297
|
+
swarm: "Multi-Agent Swarm",
|
|
298
|
+
};
|
|
299
|
+
|
|
287
300
|
const PATTERN_ICONS: Record<string, React.ReactNode> = {
|
|
288
301
|
sequence: <ArrowDown className="h-3.5 w-3.5 text-muted-foreground" />,
|
|
289
302
|
"planner-executor": <Brain className="h-3.5 w-3.5 text-muted-foreground" />,
|
|
@@ -304,6 +317,7 @@ export function WorkflowFormView({
|
|
|
304
317
|
runtimeOptions.map((runtime) => [runtime.id, runtime.label])
|
|
305
318
|
);
|
|
306
319
|
const router = useRouter();
|
|
320
|
+
const searchParams = useSearchParams();
|
|
307
321
|
const mode = workflow ? (clone ? "clone" : "edit") : "create";
|
|
308
322
|
|
|
309
323
|
const [name, setName] = useState("");
|
|
@@ -313,6 +327,13 @@ export function WorkflowFormView({
|
|
|
313
327
|
const [loading, setLoading] = useState(false);
|
|
314
328
|
const [error, setError] = useState<string | null>(null);
|
|
315
329
|
|
|
330
|
+
// Document pool state
|
|
331
|
+
const [selectedDocIds, setSelectedDocIds] = useState<Set<string>>(new Set());
|
|
332
|
+
const [selectedDocs, setSelectedDocs] = useState<
|
|
333
|
+
Array<{ id: string; originalName: string; mimeType: string; size: number }>
|
|
334
|
+
>([]);
|
|
335
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
336
|
+
|
|
316
337
|
// Loop-specific state
|
|
317
338
|
const [loopPrompt, setLoopPrompt] = useState("");
|
|
318
339
|
const [maxIterations, setMaxIterations] = useState(5);
|
|
@@ -325,6 +346,119 @@ export function WorkflowFormView({
|
|
|
325
346
|
DEFAULT_SWARM_CONCURRENCY_LIMIT
|
|
326
347
|
);
|
|
327
348
|
|
|
349
|
+
// Pre-populate documents from URL params (e.g., from Output Dock chain)
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
const inputDocsParam = searchParams.get("inputDocs");
|
|
352
|
+
if (inputDocsParam) {
|
|
353
|
+
const docIds = inputDocsParam.split(",").filter(Boolean);
|
|
354
|
+
if (docIds.length > 0) {
|
|
355
|
+
setSelectedDocIds(new Set(docIds));
|
|
356
|
+
// Fetch document metadata for display
|
|
357
|
+
Promise.all(
|
|
358
|
+
docIds.map((id) =>
|
|
359
|
+
fetch(`/api/documents?id=${id}`)
|
|
360
|
+
.then((r) => r.json())
|
|
361
|
+
.then((docs) =>
|
|
362
|
+
Array.isArray(docs) && docs.length > 0 ? docs[0] : null
|
|
363
|
+
)
|
|
364
|
+
.catch(() => null)
|
|
365
|
+
)
|
|
366
|
+
).then((results) => {
|
|
367
|
+
setSelectedDocs(
|
|
368
|
+
results.filter(Boolean).map((d: Record<string, unknown>) => ({
|
|
369
|
+
id: d.id as string,
|
|
370
|
+
originalName: d.originalName as string,
|
|
371
|
+
mimeType: d.mimeType as string,
|
|
372
|
+
size: d.size as number,
|
|
373
|
+
}))
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}, [searchParams]);
|
|
379
|
+
|
|
380
|
+
// Handle document picker confirmation
|
|
381
|
+
const handleDocPickerConfirm = useCallback(
|
|
382
|
+
(ids: string[]) => {
|
|
383
|
+
setSelectedDocIds(new Set(ids));
|
|
384
|
+
// Fetch metadata for newly selected docs
|
|
385
|
+
const newIds = ids.filter(
|
|
386
|
+
(id) => !selectedDocs.some((d) => d.id === id)
|
|
387
|
+
);
|
|
388
|
+
if (newIds.length > 0) {
|
|
389
|
+
fetch(`/api/documents?projectId=${projectId}&status=ready`)
|
|
390
|
+
.then((r) => r.json())
|
|
391
|
+
.then((allDocs: Array<Record<string, unknown>>) => {
|
|
392
|
+
const idSet = new Set(ids);
|
|
393
|
+
setSelectedDocs(
|
|
394
|
+
allDocs
|
|
395
|
+
.filter((d) => idSet.has(d.id as string))
|
|
396
|
+
.map((d) => ({
|
|
397
|
+
id: d.id as string,
|
|
398
|
+
originalName: d.originalName as string,
|
|
399
|
+
mimeType: d.mimeType as string,
|
|
400
|
+
size: d.size as number,
|
|
401
|
+
}))
|
|
402
|
+
);
|
|
403
|
+
})
|
|
404
|
+
.catch(() => {});
|
|
405
|
+
} else {
|
|
406
|
+
// Remove deselected docs
|
|
407
|
+
setSelectedDocs((prev) =>
|
|
408
|
+
prev.filter((d) => ids.includes(d.id))
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
[projectId, selectedDocs]
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
function removeDocument(id: string) {
|
|
416
|
+
setSelectedDocIds((prev) => {
|
|
417
|
+
const next = new Set(prev);
|
|
418
|
+
next.delete(id);
|
|
419
|
+
return next;
|
|
420
|
+
});
|
|
421
|
+
setSelectedDocs((prev) => prev.filter((d) => d.id !== id));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Load existing document bindings when editing
|
|
425
|
+
useEffect(() => {
|
|
426
|
+
if (!workflow || clone) return;
|
|
427
|
+
fetch(`/api/workflows/${workflow.id}/documents`)
|
|
428
|
+
.then((r) => r.json())
|
|
429
|
+
.then((bindings: Array<{ documentId: string; document: { id: string; originalName: string; mimeType: string; size: number } | null }>) => {
|
|
430
|
+
const docs = bindings
|
|
431
|
+
.filter((b) => b.document)
|
|
432
|
+
.map((b) => b.document!);
|
|
433
|
+
if (docs.length > 0) {
|
|
434
|
+
setSelectedDocIds(new Set(docs.map((d) => d.id)));
|
|
435
|
+
setSelectedDocs(docs);
|
|
436
|
+
}
|
|
437
|
+
})
|
|
438
|
+
.catch(() => {});
|
|
439
|
+
}, [workflow, clone]);
|
|
440
|
+
|
|
441
|
+
// Auto-populate project default documents for new workflows
|
|
442
|
+
useEffect(() => {
|
|
443
|
+
if (workflow || !projectId) return; // Only for create mode with a project selected
|
|
444
|
+
fetch(`/api/projects/${projectId}/documents`)
|
|
445
|
+
.then((r) => r.json())
|
|
446
|
+
.then((docs: Array<Record<string, unknown>>) => {
|
|
447
|
+
if (Array.isArray(docs) && docs.length > 0) {
|
|
448
|
+
setSelectedDocIds(new Set(docs.map((d) => d.id as string)));
|
|
449
|
+
setSelectedDocs(
|
|
450
|
+
docs.map((d) => ({
|
|
451
|
+
id: d.id as string,
|
|
452
|
+
originalName: d.originalName as string,
|
|
453
|
+
mimeType: d.mimeType as string,
|
|
454
|
+
size: d.size as number,
|
|
455
|
+
}))
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
.catch(() => {});
|
|
460
|
+
}, [projectId, workflow]);
|
|
461
|
+
|
|
328
462
|
// Pre-populate form for edit/clone
|
|
329
463
|
useEffect(() => {
|
|
330
464
|
if (!workflow) return;
|
|
@@ -723,6 +857,23 @@ export function WorkflowFormView({
|
|
|
723
857
|
});
|
|
724
858
|
|
|
725
859
|
if (res.ok) {
|
|
860
|
+
const data = await res.json().catch(() => null);
|
|
861
|
+
const workflowId = isEdit ? workflow.id : data?.id;
|
|
862
|
+
|
|
863
|
+
// Attach pool documents to the workflow via junction table
|
|
864
|
+
if (workflowId && selectedDocIds.size > 0) {
|
|
865
|
+
await fetch(`/api/workflows/${workflowId}/documents`, {
|
|
866
|
+
method: "POST",
|
|
867
|
+
headers: { "Content-Type": "application/json" },
|
|
868
|
+
body: JSON.stringify({
|
|
869
|
+
documentIds: [...selectedDocIds],
|
|
870
|
+
}),
|
|
871
|
+
}).catch(() => {
|
|
872
|
+
// Non-blocking — workflow was created, docs attachment is best-effort
|
|
873
|
+
console.warn("[workflow-form] Failed to attach pool documents");
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
726
877
|
toast.success(
|
|
727
878
|
mode === "edit"
|
|
728
879
|
? "Workflow updated"
|
|
@@ -734,9 +885,8 @@ export function WorkflowFormView({
|
|
|
734
885
|
if (isEdit) {
|
|
735
886
|
router.push(`/workflows/${workflow.id}`);
|
|
736
887
|
} else {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
router.push(`/workflows/${data.id}`);
|
|
888
|
+
if (workflowId) {
|
|
889
|
+
router.push(`/workflows/${workflowId}`);
|
|
740
890
|
} else {
|
|
741
891
|
router.push("/workflows");
|
|
742
892
|
}
|
|
@@ -963,15 +1113,20 @@ export function WorkflowFormView({
|
|
|
963
1113
|
</div>
|
|
964
1114
|
<div className="space-y-1.5">
|
|
965
1115
|
<Label>Pattern</Label>
|
|
1116
|
+
{mode === "edit" ? (
|
|
1117
|
+
<div className="flex h-9 w-fit items-center gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm opacity-50 cursor-not-allowed">
|
|
1118
|
+
{PATTERN_ICONS[pattern]}
|
|
1119
|
+
{PATTERN_LABELS[pattern] ?? pattern}
|
|
1120
|
+
</div>
|
|
1121
|
+
) : (
|
|
966
1122
|
<Select
|
|
967
1123
|
value={pattern}
|
|
968
1124
|
onValueChange={(value) =>
|
|
969
1125
|
setPattern(value as WorkflowPattern)
|
|
970
1126
|
}
|
|
971
|
-
disabled={mode === "edit"}
|
|
972
1127
|
>
|
|
973
1128
|
<SelectTrigger>
|
|
974
|
-
<SelectValue />
|
|
1129
|
+
<SelectValue placeholder="Select pattern" />
|
|
975
1130
|
</SelectTrigger>
|
|
976
1131
|
<SelectContent>
|
|
977
1132
|
<SelectItem value="sequence">
|
|
@@ -1012,6 +1167,7 @@ export function WorkflowFormView({
|
|
|
1012
1167
|
</SelectItem>
|
|
1013
1168
|
</SelectContent>
|
|
1014
1169
|
</Select>
|
|
1170
|
+
)}
|
|
1015
1171
|
<p className="text-xs text-muted-foreground">How steps execute</p>
|
|
1016
1172
|
</div>
|
|
1017
1173
|
{projects.length > 0 && (
|
|
@@ -1041,6 +1197,74 @@ export function WorkflowFormView({
|
|
|
1041
1197
|
</div>
|
|
1042
1198
|
</FormSectionCard>
|
|
1043
1199
|
|
|
1200
|
+
{/* Input Documents — Document Pool */}
|
|
1201
|
+
{projectId && (
|
|
1202
|
+
<FormSectionCard
|
|
1203
|
+
icon={FileText}
|
|
1204
|
+
title="Input Documents"
|
|
1205
|
+
hint="Attach documents from the project pool as context for this workflow"
|
|
1206
|
+
>
|
|
1207
|
+
<div className="space-y-3">
|
|
1208
|
+
{selectedDocs.length > 0 && (
|
|
1209
|
+
<div className="flex flex-wrap gap-2">
|
|
1210
|
+
{selectedDocs.map((doc) => {
|
|
1211
|
+
const Icon = getFileIcon(doc.mimeType);
|
|
1212
|
+
return (
|
|
1213
|
+
<Badge
|
|
1214
|
+
key={doc.id}
|
|
1215
|
+
variant="secondary"
|
|
1216
|
+
className="flex items-center gap-1.5 pl-2 pr-1 py-1"
|
|
1217
|
+
>
|
|
1218
|
+
<Icon className="h-3 w-3" />
|
|
1219
|
+
<span className="text-xs max-w-[180px] truncate">
|
|
1220
|
+
{doc.originalName}
|
|
1221
|
+
</span>
|
|
1222
|
+
<span className="text-[10px] text-muted-foreground">
|
|
1223
|
+
{formatSize(doc.size)}
|
|
1224
|
+
</span>
|
|
1225
|
+
<button
|
|
1226
|
+
type="button"
|
|
1227
|
+
onClick={() => removeDocument(doc.id)}
|
|
1228
|
+
className="ml-0.5 rounded-full p-0.5 hover:bg-muted transition-colors"
|
|
1229
|
+
aria-label={`Remove ${doc.originalName}`}
|
|
1230
|
+
>
|
|
1231
|
+
<X className="h-3 w-3" />
|
|
1232
|
+
</button>
|
|
1233
|
+
</Badge>
|
|
1234
|
+
);
|
|
1235
|
+
})}
|
|
1236
|
+
</div>
|
|
1237
|
+
)}
|
|
1238
|
+
<Button
|
|
1239
|
+
type="button"
|
|
1240
|
+
variant="outline"
|
|
1241
|
+
size="sm"
|
|
1242
|
+
onClick={() => setPickerOpen(true)}
|
|
1243
|
+
className="gap-1.5"
|
|
1244
|
+
>
|
|
1245
|
+
<Plus className="h-3.5 w-3.5" />
|
|
1246
|
+
{selectedDocs.length > 0
|
|
1247
|
+
? "Add More Documents"
|
|
1248
|
+
: "Attach Documents"}
|
|
1249
|
+
</Button>
|
|
1250
|
+
{selectedDocs.length > 0 && (
|
|
1251
|
+
<p className="text-xs text-muted-foreground">
|
|
1252
|
+
{selectedDocs.length} document{selectedDocs.length !== 1 ? "s" : ""} will be injected as context for all steps
|
|
1253
|
+
</p>
|
|
1254
|
+
)}
|
|
1255
|
+
</div>
|
|
1256
|
+
|
|
1257
|
+
<DocumentPickerSheet
|
|
1258
|
+
open={pickerOpen}
|
|
1259
|
+
onOpenChange={setPickerOpen}
|
|
1260
|
+
projectId={projectId}
|
|
1261
|
+
selectedIds={selectedDocIds}
|
|
1262
|
+
onConfirm={handleDocPickerConfirm}
|
|
1263
|
+
groupBy="workflow"
|
|
1264
|
+
/>
|
|
1265
|
+
</FormSectionCard>
|
|
1266
|
+
)}
|
|
1267
|
+
|
|
1044
1268
|
{isLoop && (
|
|
1045
1269
|
<FormSectionCard icon={RefreshCw} title="Loop Config">
|
|
1046
1270
|
<div className="space-y-4">
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import Link from "next/link";
|
|
4
4
|
import { Card } from "@/components/ui/card";
|
|
5
5
|
import { Badge } from "@/components/ui/badge";
|
|
6
|
-
import { Workflow, Loader2 } from "lucide-react";
|
|
6
|
+
import { Workflow, Loader2, FileText } from "lucide-react";
|
|
7
7
|
import { workflowStatusVariant, patternLabels } from "@/lib/constants/status-colors";
|
|
8
8
|
|
|
9
9
|
export interface WorkflowKanbanItem {
|
|
@@ -16,6 +16,7 @@ export interface WorkflowKanbanItem {
|
|
|
16
16
|
projectName?: string;
|
|
17
17
|
stepProgress: { current: number; total: number };
|
|
18
18
|
currentStepName?: string;
|
|
19
|
+
outputDocCount?: number;
|
|
19
20
|
createdAt: string;
|
|
20
21
|
updatedAt?: string;
|
|
21
22
|
}
|
|
@@ -112,6 +113,12 @@ export function WorkflowKanbanCard({ workflow }: WorkflowKanbanCardProps) {
|
|
|
112
113
|
>
|
|
113
114
|
{workflow.status}
|
|
114
115
|
</Badge>
|
|
116
|
+
{workflow.outputDocCount != null && workflow.outputDocCount > 0 && (
|
|
117
|
+
<span className="text-[11px] text-muted-foreground flex items-center gap-0.5">
|
|
118
|
+
<FileText className="h-3 w-3" />
|
|
119
|
+
{workflow.outputDocCount}
|
|
120
|
+
</span>
|
|
121
|
+
)}
|
|
115
122
|
<div className="flex-1" />
|
|
116
123
|
<span className="text-[11px] text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">
|
|
117
124
|
View →
|
|
@@ -10,6 +10,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
|
10
10
|
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
|
|
11
11
|
import { EmptyState } from "@/components/shared/empty-state";
|
|
12
12
|
import { GitBranch, Pencil, Copy, RotateCcw, Trash2, FileCog, Play } from "lucide-react";
|
|
13
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
13
14
|
import { toast } from "sonner";
|
|
14
15
|
import { workflowStatusVariant, patternLabels } from "@/lib/constants/status-colors";
|
|
15
16
|
import { IconCircle, getWorkflowIconFromName } from "@/lib/constants/card-icons";
|
|
@@ -23,6 +24,9 @@ interface Workflow {
|
|
|
23
24
|
definition: string;
|
|
24
25
|
createdAt: string;
|
|
25
26
|
updatedAt: string;
|
|
27
|
+
taskCount?: number;
|
|
28
|
+
outputDocCount?: number;
|
|
29
|
+
runNumber?: number;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
interface WorkflowListProps {
|
|
@@ -175,6 +179,18 @@ export function WorkflowList({ projects }: WorkflowListProps) {
|
|
|
175
179
|
<span>{patternLabels[pattern] ?? pattern}</span>
|
|
176
180
|
<span>·</span>
|
|
177
181
|
<span>{stepCount} step{stepCount !== 1 ? "s" : ""}</span>
|
|
182
|
+
{wf.taskCount != null && wf.taskCount > 0 && (
|
|
183
|
+
<>
|
|
184
|
+
<span className="text-muted-foreground">·</span>
|
|
185
|
+
<span>{wf.taskCount} task{wf.taskCount !== 1 ? "s" : ""}</span>
|
|
186
|
+
</>
|
|
187
|
+
)}
|
|
188
|
+
{wf.outputDocCount != null && wf.outputDocCount > 0 && (
|
|
189
|
+
<>
|
|
190
|
+
<span className="text-muted-foreground">·</span>
|
|
191
|
+
<span>{wf.outputDocCount} doc{wf.outputDocCount !== 1 ? "s" : ""}</span>
|
|
192
|
+
</>
|
|
193
|
+
)}
|
|
178
194
|
</div>
|
|
179
195
|
{promptPreview && (
|
|
180
196
|
<p className="text-xs text-muted-foreground line-clamp-2 mt-1.5">
|
|
@@ -182,53 +198,82 @@ export function WorkflowList({ projects }: WorkflowListProps) {
|
|
|
182
198
|
</p>
|
|
183
199
|
)}
|
|
184
200
|
<div className="flex items-center justify-between mt-3">
|
|
185
|
-
<
|
|
186
|
-
{wf.status}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
{wf.
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
className="h-7 w-7"
|
|
194
|
-
aria-label="Edit workflow"
|
|
195
|
-
onClick={(e) => { e.stopPropagation(); router.push(`/workflows/${wf.id}/edit`); }}
|
|
196
|
-
>
|
|
197
|
-
<Pencil className="h-3.5 w-3.5" />
|
|
198
|
-
</Button>
|
|
199
|
-
)}
|
|
200
|
-
<Button
|
|
201
|
-
variant="ghost"
|
|
202
|
-
size="icon"
|
|
203
|
-
className="h-7 w-7"
|
|
204
|
-
aria-label="Clone workflow"
|
|
205
|
-
onClick={(e) => { e.stopPropagation(); router.push(`/workflows/${wf.id}/edit?clone=true`); }}
|
|
206
|
-
>
|
|
207
|
-
<Copy className="h-3.5 w-3.5" />
|
|
208
|
-
</Button>
|
|
209
|
-
{(wf.status === "completed" || wf.status === "failed") && (
|
|
210
|
-
<Button
|
|
211
|
-
variant="ghost"
|
|
212
|
-
size="icon"
|
|
213
|
-
className="h-7 w-7"
|
|
214
|
-
aria-label="Re-run workflow"
|
|
215
|
-
onClick={(e) => { e.stopPropagation(); handleRerun(wf.id); }}
|
|
216
|
-
>
|
|
217
|
-
<RotateCcw className="h-3.5 w-3.5" />
|
|
218
|
-
</Button>
|
|
219
|
-
)}
|
|
220
|
-
{wf.status !== "active" && (
|
|
221
|
-
<Button
|
|
222
|
-
variant="ghost"
|
|
223
|
-
size="icon"
|
|
224
|
-
className="h-7 w-7 text-destructive"
|
|
225
|
-
aria-label="Delete workflow"
|
|
226
|
-
onClick={(e) => { e.stopPropagation(); setConfirmDeleteId(wf.id); }}
|
|
227
|
-
>
|
|
228
|
-
<Trash2 className="h-3.5 w-3.5" />
|
|
229
|
-
</Button>
|
|
201
|
+
<div className="flex items-center gap-2">
|
|
202
|
+
<Badge variant={workflowStatusVariant[wf.status] ?? "secondary"}>
|
|
203
|
+
{wf.status}
|
|
204
|
+
</Badge>
|
|
205
|
+
{wf.runNumber != null && wf.runNumber > 0 && (
|
|
206
|
+
<Badge variant="outline" className="text-[10px] font-normal">
|
|
207
|
+
Run #{wf.runNumber}
|
|
208
|
+
</Badge>
|
|
230
209
|
)}
|
|
231
210
|
</div>
|
|
211
|
+
<TooltipProvider>
|
|
212
|
+
<div className="flex items-center gap-1">
|
|
213
|
+
{(wf.status === "draft" || wf.status === "completed" || wf.status === "failed") && (
|
|
214
|
+
<Tooltip>
|
|
215
|
+
<TooltipTrigger asChild>
|
|
216
|
+
<Button
|
|
217
|
+
variant="ghost"
|
|
218
|
+
size="icon"
|
|
219
|
+
className="h-7 w-7"
|
|
220
|
+
aria-label="Edit workflow"
|
|
221
|
+
onClick={(e) => { e.stopPropagation(); router.push(`/workflows/${wf.id}/edit`); }}
|
|
222
|
+
>
|
|
223
|
+
<Pencil className="h-3.5 w-3.5" />
|
|
224
|
+
</Button>
|
|
225
|
+
</TooltipTrigger>
|
|
226
|
+
<TooltipContent>Edit</TooltipContent>
|
|
227
|
+
</Tooltip>
|
|
228
|
+
)}
|
|
229
|
+
<Tooltip>
|
|
230
|
+
<TooltipTrigger asChild>
|
|
231
|
+
<Button
|
|
232
|
+
variant="ghost"
|
|
233
|
+
size="icon"
|
|
234
|
+
className="h-7 w-7"
|
|
235
|
+
aria-label="Clone workflow"
|
|
236
|
+
onClick={(e) => { e.stopPropagation(); router.push(`/workflows/${wf.id}/edit?clone=true`); }}
|
|
237
|
+
>
|
|
238
|
+
<Copy className="h-3.5 w-3.5" />
|
|
239
|
+
</Button>
|
|
240
|
+
</TooltipTrigger>
|
|
241
|
+
<TooltipContent>Clone</TooltipContent>
|
|
242
|
+
</Tooltip>
|
|
243
|
+
{(wf.status === "completed" || wf.status === "failed") && (
|
|
244
|
+
<Tooltip>
|
|
245
|
+
<TooltipTrigger asChild>
|
|
246
|
+
<Button
|
|
247
|
+
variant="ghost"
|
|
248
|
+
size="icon"
|
|
249
|
+
className="h-7 w-7"
|
|
250
|
+
aria-label="Re-run workflow"
|
|
251
|
+
onClick={(e) => { e.stopPropagation(); handleRerun(wf.id); }}
|
|
252
|
+
>
|
|
253
|
+
<RotateCcw className="h-3.5 w-3.5" />
|
|
254
|
+
</Button>
|
|
255
|
+
</TooltipTrigger>
|
|
256
|
+
<TooltipContent>Re-run</TooltipContent>
|
|
257
|
+
</Tooltip>
|
|
258
|
+
)}
|
|
259
|
+
{wf.status !== "active" && (
|
|
260
|
+
<Tooltip>
|
|
261
|
+
<TooltipTrigger asChild>
|
|
262
|
+
<Button
|
|
263
|
+
variant="ghost"
|
|
264
|
+
size="icon"
|
|
265
|
+
className="h-7 w-7 text-destructive"
|
|
266
|
+
aria-label="Delete workflow"
|
|
267
|
+
onClick={(e) => { e.stopPropagation(); setConfirmDeleteId(wf.id); }}
|
|
268
|
+
>
|
|
269
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
270
|
+
</Button>
|
|
271
|
+
</TooltipTrigger>
|
|
272
|
+
<TooltipContent>Delete</TooltipContent>
|
|
273
|
+
</Tooltip>
|
|
274
|
+
)}
|
|
275
|
+
</div>
|
|
276
|
+
</TooltipProvider>
|
|
232
277
|
</div>
|
|
233
278
|
</CardContent>
|
|
234
279
|
</Card>
|