stagent 0.1.11 → 0.1.13
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/README.md +74 -49
- package/package.json +3 -2
- package/public/readme/cost-usage-list.png +0 -0
- package/public/readme/dashboard-bulk-select.png +0 -0
- package/public/readme/dashboard-card-edit.png +0 -0
- package/public/readme/dashboard-create-form-ai-applied.png +0 -0
- package/public/readme/dashboard-create-form-ai-assist.png +0 -0
- package/public/readme/dashboard-create-form-empty.png +0 -0
- package/public/readme/dashboard-create-form-filled.png +0 -0
- package/public/readme/dashboard-filtered.png +0 -0
- package/public/readme/dashboard-list.png +0 -0
- package/public/readme/dashboard-workflow-confirm.png +0 -0
- package/public/readme/home-below-fold.png +0 -0
- package/public/readme/home-list.png +0 -0
- package/public/readme/inbox-list.png +0 -0
- package/public/readme/playbook-list.png +0 -0
- package/public/readme/profiles-list.png +0 -0
- package/public/readme/settings-list.png +0 -0
- package/public/readme/workflows-list.png +0 -0
- package/src/__tests__/e2e/blueprint.test.ts +63 -0
- package/src/__tests__/e2e/cross-runtime.test.ts +77 -0
- package/src/__tests__/e2e/helpers.ts +286 -0
- package/src/__tests__/e2e/parallel-workflow.test.ts +120 -0
- package/src/__tests__/e2e/sequence-workflow.test.ts +109 -0
- package/src/__tests__/e2e/setup.ts +156 -0
- package/src/__tests__/e2e/single-task.test.ts +170 -0
- package/src/app/api/command-palette/recent/route.ts +41 -18
- package/src/app/api/context/batch/route.ts +44 -0
- package/src/app/api/permissions/presets/route.ts +80 -0
- package/src/app/api/playbook/status/route.ts +15 -0
- package/src/app/api/profiles/route.ts +23 -20
- package/src/app/api/settings/pricing/route.ts +15 -0
- package/src/app/api/tasks/[id]/route.ts +54 -3
- package/src/app/api/workflows/[id]/route.ts +43 -4
- package/src/app/api/workflows/[id]/status/route.ts +70 -2
- package/src/app/api/workflows/from-assist/route.ts +6 -32
- package/src/app/costs/page.tsx +53 -43
- package/src/app/dashboard/page.tsx +59 -21
- package/src/app/documents/[id]/page.tsx +10 -8
- package/src/app/globals.css +11 -0
- package/src/app/page.tsx +60 -3
- package/src/app/playbook/[slug]/page.tsx +76 -0
- package/src/app/playbook/page.tsx +54 -0
- package/src/app/profiles/page.tsx +7 -4
- package/src/app/settings/page.tsx +2 -2
- package/src/app/tasks/[id]/page.tsx +22 -2
- package/src/components/costs/cost-dashboard.tsx +226 -320
- package/src/components/dashboard/activity-feed.tsx +6 -2
- package/src/components/dashboard/greeting.tsx +3 -1
- package/src/components/dashboard/priority-queue.tsx +58 -9
- package/src/components/dashboard/stats-cards.tsx +16 -2
- package/src/components/documents/document-chip-bar.tsx +183 -0
- package/src/components/documents/document-content-renderer.tsx +146 -0
- package/src/components/documents/document-detail-view.tsx +16 -239
- package/src/components/documents/image-zoom-view.tsx +60 -0
- package/src/components/documents/smart-extracted-text.tsx +47 -0
- package/src/components/documents/utils.ts +70 -0
- package/src/components/notifications/batch-proposal-review.tsx +150 -0
- package/src/components/notifications/inbox-list.tsx +4 -5
- package/src/components/notifications/notification-item.tsx +73 -6
- package/src/components/notifications/pending-approval-host.tsx +63 -14
- package/src/components/playbook/adoption-heatmap.tsx +69 -0
- package/src/components/playbook/journey-card.tsx +110 -0
- package/src/components/playbook/playbook-action-button.tsx +22 -0
- package/src/components/playbook/playbook-browser.tsx +143 -0
- package/src/components/playbook/playbook-card.tsx +102 -0
- package/src/components/playbook/playbook-detail-view.tsx +225 -0
- package/src/components/playbook/playbook-homepage.tsx +142 -0
- package/src/components/playbook/playbook-toc.tsx +90 -0
- package/src/components/playbook/playbook-updated-badge.tsx +23 -0
- package/src/components/playbook/related-docs.tsx +30 -0
- package/src/components/profiles/__tests__/learned-context-panel.test.tsx +175 -0
- package/src/components/profiles/context-proposal-review.tsx +7 -3
- package/src/components/profiles/learned-context-panel.tsx +116 -8
- package/src/components/profiles/profile-browser.tsx +1 -0
- package/src/components/profiles/profile-card.tsx +16 -8
- package/src/components/profiles/profile-detail-view.tsx +12 -4
- package/src/components/settings/__tests__/auth-config-section.test.tsx +147 -0
- package/src/components/settings/api-key-form.tsx +5 -43
- package/src/components/settings/auth-config-section.tsx +10 -6
- package/src/components/settings/auth-status-badge.tsx +8 -0
- package/src/components/settings/budget-guardrails-section.tsx +403 -620
- package/src/components/settings/connection-test-control.tsx +63 -0
- package/src/components/settings/permissions-section.tsx +85 -75
- package/src/components/settings/permissions-sections.tsx +24 -0
- package/src/components/settings/presets-section.tsx +159 -0
- package/src/components/settings/pricing-registry-panel.tsx +164 -0
- package/src/components/shared/app-sidebar.tsx +4 -2
- package/src/components/shared/command-palette.tsx +30 -0
- package/src/components/shared/light-markdown.tsx +134 -0
- package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +1 -1
- package/src/components/tasks/ai-assist-panel.tsx +108 -78
- package/src/components/tasks/content-preview.tsx +2 -1
- package/src/components/tasks/kanban-board.tsx +57 -5
- package/src/components/tasks/kanban-column.tsx +34 -23
- package/src/components/tasks/task-bento-cell.tsx +50 -0
- package/src/components/tasks/task-bento-grid.tsx +155 -0
- package/src/components/tasks/task-card.tsx +14 -16
- package/src/components/tasks/task-chip-bar.tsx +207 -0
- package/src/components/tasks/task-detail-view.tsx +42 -190
- package/src/components/tasks/task-result-renderer.tsx +33 -0
- package/src/components/workflows/blueprint-gallery.tsx +19 -12
- package/src/components/workflows/blueprint-preview.tsx +8 -1
- package/src/components/workflows/loop-status-view.tsx +2 -4
- package/src/components/workflows/swarm-dashboard.tsx +2 -3
- package/src/components/workflows/workflow-confirmation-view.tsx +2 -7
- package/src/components/workflows/workflow-full-output.tsx +80 -0
- package/src/components/workflows/workflow-kanban-card.tsx +121 -0
- package/src/components/workflows/workflow-list.tsx +47 -42
- package/src/components/workflows/workflow-status-view.tsx +163 -16
- package/src/lib/agents/learned-context.ts +27 -15
- package/src/lib/agents/learning-session.ts +354 -0
- package/src/lib/agents/pattern-extractor.ts +19 -0
- package/src/lib/agents/profiles/__tests__/sort.test.ts +42 -0
- package/src/lib/agents/profiles/sort.ts +7 -0
- package/src/lib/constants/card-icons.tsx +202 -0
- package/src/lib/constants/prose-styles.ts +7 -0
- package/src/lib/constants/settings.ts +1 -0
- package/src/lib/constants/task-status.ts +3 -0
- package/src/lib/db/schema.ts +3 -0
- package/src/lib/docs/adoption.ts +105 -0
- package/src/lib/docs/journey-tracker.ts +21 -0
- package/src/lib/docs/reader.ts +107 -0
- package/src/lib/docs/types.ts +54 -0
- package/src/lib/docs/usage-stage.ts +60 -0
- package/src/lib/documents/context-builder.ts +41 -0
- package/src/lib/notifications/actionable.ts +18 -10
- package/src/lib/queries/chart-data.ts +20 -1
- package/src/lib/settings/__tests__/budget-guardrails.test.ts +86 -24
- package/src/lib/settings/budget-guardrails.ts +213 -85
- package/src/lib/settings/permission-presets.ts +150 -0
- package/src/lib/settings/runtime-setup.ts +71 -0
- package/src/lib/usage/__tests__/ledger.test.ts +2 -2
- package/src/lib/usage/__tests__/pricing-registry.test.ts +78 -0
- package/src/lib/usage/ledger.ts +1 -1
- package/src/lib/usage/pricing-registry.ts +570 -0
- package/src/lib/usage/pricing.ts +15 -95
- package/src/lib/utils/__tests__/learned-context-history.test.ts +171 -0
- package/src/lib/utils/learned-context-history.ts +150 -0
- package/src/lib/validators/__tests__/settings.test.ts +23 -16
- package/src/lib/validators/settings.ts +3 -9
- package/src/lib/workflows/engine.ts +75 -61
- package/src/lib/workflows/types.ts +2 -0
- package/tsconfig.json +2 -1
- package/src/components/documents/document-preview.tsx +0 -68
|
@@ -2,39 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback } from "react";
|
|
4
4
|
import { useRouter } from "next/navigation";
|
|
5
|
-
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
6
|
-
import { Badge } from "@/components/ui/badge";
|
|
7
|
-
import { Button } from "@/components/ui/button";
|
|
8
|
-
import { Separator } from "@/components/ui/separator";
|
|
9
5
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
10
|
-
import { Play, Square, RotateCcw, ArrowRight, FastForward, Trash2, Pencil } from "lucide-react";
|
|
11
6
|
import { toast } from "sonner";
|
|
12
7
|
import { ConfirmDialog } from "@/components/shared/confirm-dialog";
|
|
13
|
-
import { ContentPreview } from "./content-preview";
|
|
14
8
|
import { TaskAttachments } from "./task-attachments";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import type { TaskItem } from "./task-card";
|
|
9
|
+
import { TaskChipBar } from "./task-chip-bar";
|
|
10
|
+
import { TaskBentoGrid } from "./task-bento-grid";
|
|
11
|
+
import { TaskResultRenderer } from "./task-result-renderer";
|
|
19
12
|
import { TaskEditDialog } from "./task-edit-dialog";
|
|
13
|
+
import type { TaskItem } from "./task-card";
|
|
20
14
|
import type { DocumentRow } from "@/lib/db/schema";
|
|
21
15
|
|
|
22
|
-
function detectContentType(content: string): "text" | "markdown" | "code" | "json" | "unknown" {
|
|
23
|
-
if (content.startsWith("{") || content.startsWith("[")) {
|
|
24
|
-
try { JSON.parse(content); return "json"; } catch { /* not json */ }
|
|
25
|
-
}
|
|
26
|
-
if (content.includes("```") || content.includes("# ") || content.includes("**")) return "markdown";
|
|
27
|
-
if (content.includes("function ") || content.includes("const ") || content.includes("import ")) return "code";
|
|
28
|
-
return "text";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const priorityLabels: Record<number, string> = {
|
|
32
|
-
0: "P0 - Critical",
|
|
33
|
-
1: "P1 - High",
|
|
34
|
-
2: "P2 - Medium",
|
|
35
|
-
3: "P3 - Low",
|
|
36
|
-
};
|
|
37
|
-
|
|
38
16
|
interface TaskDetailViewProps {
|
|
39
17
|
taskId: string;
|
|
40
18
|
initialTask?: TaskItem;
|
|
@@ -76,8 +54,6 @@ export function TaskDetailView({ taskId, initialTask }: TaskDetailViewProps) {
|
|
|
76
54
|
}, [taskId, fetchDocs]);
|
|
77
55
|
|
|
78
56
|
useEffect(() => {
|
|
79
|
-
// If server provided initial data, only fetch supplementary data (docs)
|
|
80
|
-
// and skip the redundant task refresh
|
|
81
57
|
if (!initialTask) refresh();
|
|
82
58
|
fetchDocs();
|
|
83
59
|
}, [refresh, fetchDocs, initialTask]);
|
|
@@ -201,8 +177,8 @@ export function TaskDetailView({ taskId, initialTask }: TaskDetailViewProps) {
|
|
|
201
177
|
if (!loaded) {
|
|
202
178
|
return (
|
|
203
179
|
<div className="space-y-4">
|
|
204
|
-
<Skeleton className="h-
|
|
205
|
-
<Skeleton className="h-
|
|
180
|
+
<Skeleton className="h-24 w-full" />
|
|
181
|
+
<Skeleton className="h-20 w-full" />
|
|
206
182
|
<Skeleton className="h-48 w-full" />
|
|
207
183
|
</div>
|
|
208
184
|
);
|
|
@@ -216,174 +192,50 @@ export function TaskDetailView({ taskId, initialTask }: TaskDetailViewProps) {
|
|
|
216
192
|
const outputDocs = docs.filter((doc) => doc.direction === "output");
|
|
217
193
|
|
|
218
194
|
return (
|
|
219
|
-
<div className="space-y-
|
|
220
|
-
{
|
|
221
|
-
<div className="flex items-center justify-between">
|
|
222
|
-
<div>
|
|
223
|
-
<h1 className="text-2xl font-bold">{task.title}</h1>
|
|
224
|
-
<div className="flex items-center gap-2 mt-1">
|
|
225
|
-
<Badge variant={taskStatusVariant[task.status] ?? "secondary"}>
|
|
226
|
-
{task.status}
|
|
227
|
-
</Badge>
|
|
228
|
-
<span className="text-sm text-muted-foreground">
|
|
229
|
-
{priorityLabels[task.priority] ?? `P${task.priority}`}
|
|
230
|
-
</span>
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
<div className="flex items-center gap-2">
|
|
234
|
-
{(task.status === "planned" || task.status === "queued") && (
|
|
235
|
-
<Button size="sm" variant="outline" onClick={() => setEditOpen(true)} disabled={loading}>
|
|
236
|
-
<Pencil className="h-3.5 w-3.5 mr-1" />
|
|
237
|
-
Edit
|
|
238
|
-
</Button>
|
|
239
|
-
)}
|
|
240
|
-
{task.status === "planned" && (
|
|
241
|
-
<Button size="sm" onClick={() => handleStatusChange("queued")} disabled={loading}>
|
|
242
|
-
<ArrowRight className="h-3.5 w-3.5 mr-1" />
|
|
243
|
-
{loading ? "Queueing..." : "Queue"}
|
|
244
|
-
</Button>
|
|
245
|
-
)}
|
|
246
|
-
{task.status === "queued" && (
|
|
247
|
-
<Button size="sm" onClick={handleExecute} disabled={loading}>
|
|
248
|
-
<Play className="h-3.5 w-3.5 mr-1" />
|
|
249
|
-
{loading ? "Starting..." : "Run"}
|
|
250
|
-
</Button>
|
|
251
|
-
)}
|
|
252
|
-
{task.status === "running" && (
|
|
253
|
-
<Button
|
|
254
|
-
size="sm"
|
|
255
|
-
variant="destructive"
|
|
256
|
-
onClick={() => setConfirmCancel(true)}
|
|
257
|
-
disabled={loading}
|
|
258
|
-
>
|
|
259
|
-
<Square className="h-3.5 w-3.5 mr-1" />
|
|
260
|
-
{loading ? "Cancelling..." : "Cancel"}
|
|
261
|
-
</Button>
|
|
262
|
-
)}
|
|
263
|
-
{(task.status === "failed" || task.status === "cancelled") && (
|
|
264
|
-
<>
|
|
265
|
-
{task.sessionId && task.resumeCount < MAX_RESUME_COUNT && (
|
|
266
|
-
<Button size="sm" onClick={handleResume} disabled={loading}>
|
|
267
|
-
<FastForward className="h-3.5 w-3.5 mr-1" />
|
|
268
|
-
{loading ? "Resuming..." : "Resume"}
|
|
269
|
-
</Button>
|
|
270
|
-
)}
|
|
271
|
-
<Button
|
|
272
|
-
size="sm"
|
|
273
|
-
variant={task.sessionId ? "outline" : "default"}
|
|
274
|
-
onClick={() => handleStatusChange("queued")}
|
|
275
|
-
disabled={loading}
|
|
276
|
-
>
|
|
277
|
-
<RotateCcw className="h-3.5 w-3.5 mr-1" />
|
|
278
|
-
{loading ? "Retrying..." : "Retry"}
|
|
279
|
-
</Button>
|
|
280
|
-
</>
|
|
281
|
-
)}
|
|
282
|
-
{task.status !== "running" && (
|
|
283
|
-
<Button
|
|
284
|
-
size="sm"
|
|
285
|
-
variant="ghost"
|
|
286
|
-
className="text-muted-foreground hover:text-destructive"
|
|
287
|
-
onClick={() => setConfirmDelete(true)}
|
|
288
|
-
disabled={loading}
|
|
289
|
-
>
|
|
290
|
-
<Trash2 className="h-3.5 w-3.5 mr-1" />
|
|
291
|
-
Delete
|
|
292
|
-
</Button>
|
|
293
|
-
)}
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
195
|
+
<div className="space-y-4" aria-live="polite">
|
|
196
|
+
{error && <p className="text-sm text-destructive">{error}</p>}
|
|
296
197
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
198
|
+
<TaskChipBar
|
|
199
|
+
task={task}
|
|
200
|
+
loading={loading}
|
|
201
|
+
onEdit={() => setEditOpen(true)}
|
|
202
|
+
onQueue={() => handleStatusChange("queued")}
|
|
203
|
+
onRun={handleExecute}
|
|
204
|
+
onCancel={() => setConfirmCancel(true)}
|
|
205
|
+
onResume={handleResume}
|
|
206
|
+
onRetry={() => handleStatusChange("queued")}
|
|
207
|
+
onDelete={() => setConfirmDelete(true)}
|
|
208
|
+
/>
|
|
300
209
|
|
|
301
|
-
{
|
|
302
|
-
{task.description && (
|
|
303
|
-
<Card>
|
|
304
|
-
<CardHeader className="pb-2">
|
|
305
|
-
<CardTitle className="text-sm font-medium">Description</CardTitle>
|
|
306
|
-
</CardHeader>
|
|
307
|
-
<CardContent>
|
|
308
|
-
<p className="text-sm text-muted-foreground whitespace-pre-wrap">
|
|
309
|
-
{task.description}
|
|
310
|
-
</p>
|
|
311
|
-
</CardContent>
|
|
312
|
-
</Card>
|
|
313
|
-
)}
|
|
210
|
+
<TaskBentoGrid task={task} docs={docs} />
|
|
314
211
|
|
|
315
|
-
{/* Agent Info */}
|
|
316
|
-
{(task.assignedAgent || task.agentProfile) && (
|
|
317
|
-
<Card>
|
|
318
|
-
<CardHeader className="pb-2">
|
|
319
|
-
<CardTitle className="text-sm font-medium">Agent</CardTitle>
|
|
320
|
-
</CardHeader>
|
|
321
|
-
<CardContent className="space-y-1">
|
|
322
|
-
{task.assignedAgent && (
|
|
323
|
-
<p className="text-sm text-muted-foreground">{task.assignedAgent}</p>
|
|
324
|
-
)}
|
|
325
|
-
{task.agentProfile && (
|
|
326
|
-
<p className="text-sm text-muted-foreground">
|
|
327
|
-
Profile: {task.agentProfile}
|
|
328
|
-
</p>
|
|
329
|
-
)}
|
|
330
|
-
</CardContent>
|
|
331
|
-
</Card>
|
|
332
|
-
)}
|
|
333
|
-
|
|
334
|
-
{/* Documents */}
|
|
335
212
|
{docs.length > 0 && (
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
documents={outputDocs}
|
|
353
|
-
title={`Generated Outputs (${outputDocs.length})`}
|
|
354
|
-
onDeleted={fetchDocs}
|
|
355
|
-
/>
|
|
356
|
-
)}
|
|
357
|
-
</CardContent>
|
|
358
|
-
</Card>
|
|
213
|
+
<div className="surface-card-muted rounded-lg p-4 space-y-4">
|
|
214
|
+
{inputDocs.length > 0 && (
|
|
215
|
+
<TaskAttachments
|
|
216
|
+
documents={inputDocs}
|
|
217
|
+
title={`Input Attachments (${inputDocs.length})`}
|
|
218
|
+
onDeleted={fetchDocs}
|
|
219
|
+
/>
|
|
220
|
+
)}
|
|
221
|
+
{outputDocs.length > 0 && (
|
|
222
|
+
<TaskAttachments
|
|
223
|
+
documents={outputDocs}
|
|
224
|
+
title={`Generated Outputs (${outputDocs.length})`}
|
|
225
|
+
onDeleted={fetchDocs}
|
|
226
|
+
/>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
359
229
|
)}
|
|
360
230
|
|
|
361
|
-
{
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
</CardTitle>
|
|
368
|
-
</CardHeader>
|
|
369
|
-
<CardContent>
|
|
370
|
-
{task.status === "failed" ? (
|
|
371
|
-
<pre className="text-xs text-muted-foreground bg-muted p-3 rounded-md overflow-auto max-h-60 whitespace-pre-wrap">
|
|
372
|
-
{task.result}
|
|
373
|
-
</pre>
|
|
374
|
-
) : (
|
|
375
|
-
<ContentPreview content={task.result} contentType={detectContentType(task.result)} />
|
|
376
|
-
)}
|
|
377
|
-
</CardContent>
|
|
378
|
-
</Card>
|
|
231
|
+
{(task.description || task.result) && (
|
|
232
|
+
<TaskResultRenderer
|
|
233
|
+
description={task.description}
|
|
234
|
+
result={task.result}
|
|
235
|
+
status={task.status}
|
|
236
|
+
/>
|
|
379
237
|
)}
|
|
380
238
|
|
|
381
|
-
{/* Timestamps */}
|
|
382
|
-
<div className="text-xs text-muted-foreground">
|
|
383
|
-
<p>Created: {formatTimestamp(task.createdAt)}</p>
|
|
384
|
-
<p>Updated: {formatTimestamp(task.updatedAt)}</p>
|
|
385
|
-
</div>
|
|
386
|
-
|
|
387
239
|
<ConfirmDialog
|
|
388
240
|
open={confirmCancel}
|
|
389
241
|
onOpenChange={setConfirmCancel}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Separator } from "@/components/ui/separator";
|
|
2
|
+
import { LightMarkdown } from "@/components/shared/light-markdown";
|
|
3
|
+
import { ExpandableResult } from "@/components/workflows/workflow-status-view";
|
|
4
|
+
|
|
5
|
+
interface TaskResultRendererProps {
|
|
6
|
+
description: string | null;
|
|
7
|
+
result: string | null;
|
|
8
|
+
status: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TaskResultRenderer({ description, result, status }: TaskResultRendererProps) {
|
|
12
|
+
if (!description && !result) return null;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="prose-reader-surface">
|
|
16
|
+
{description && (
|
|
17
|
+
<LightMarkdown content={description} textSize="sm" />
|
|
18
|
+
)}
|
|
19
|
+
{description && result && <Separator className="my-4" />}
|
|
20
|
+
{result && (
|
|
21
|
+
status === "failed" ? (
|
|
22
|
+
<pre className="text-xs text-destructive bg-destructive/5 p-3 rounded-md overflow-auto max-h-60 whitespace-pre-wrap">
|
|
23
|
+
{result}
|
|
24
|
+
</pre>
|
|
25
|
+
) : (
|
|
26
|
+
<div className="max-h-[70vh] overflow-y-auto">
|
|
27
|
+
<ExpandableResult result={result} />
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
)}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -11,6 +11,7 @@ import { EmptyState } from "@/components/shared/empty-state";
|
|
|
11
11
|
import { Button } from "@/components/ui/button";
|
|
12
12
|
import { Search, Layers, Plus } from "lucide-react";
|
|
13
13
|
import { patternLabels } from "@/lib/constants/status-colors";
|
|
14
|
+
import { IconCircle, getWorkflowIconFromName } from "@/lib/constants/card-icons";
|
|
14
15
|
import type { WorkflowBlueprint } from "@/lib/workflows/blueprints/types";
|
|
15
16
|
|
|
16
17
|
const difficultyColors: Record<string, string> = {
|
|
@@ -114,19 +115,25 @@ export function BlueprintGallery() {
|
|
|
114
115
|
}}
|
|
115
116
|
>
|
|
116
117
|
<CardHeader className="pb-1">
|
|
117
|
-
<div className="flex items-center
|
|
118
|
-
<
|
|
119
|
-
{bp.name}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
118
|
+
<div className="flex items-center gap-3">
|
|
119
|
+
<IconCircle
|
|
120
|
+
icon={getWorkflowIconFromName(bp.name, bp.pattern).icon}
|
|
121
|
+
colors={getWorkflowIconFromName(bp.name, bp.pattern).colors}
|
|
122
|
+
/>
|
|
123
|
+
<div className="flex min-w-0 flex-1 items-center justify-between">
|
|
124
|
+
<CardTitle className="truncate text-sm font-medium">
|
|
125
|
+
{bp.name}
|
|
126
|
+
</CardTitle>
|
|
127
|
+
<div className="flex shrink-0 items-center gap-1.5">
|
|
128
|
+
<Badge variant={bp.domain === "work" ? "default" : "secondary"}>
|
|
129
|
+
{bp.domain}
|
|
128
130
|
</Badge>
|
|
129
|
-
|
|
131
|
+
{bp.difficulty && (
|
|
132
|
+
<Badge variant="outline" className={`text-xs ${difficultyColors[bp.difficulty] ?? ""}`}>
|
|
133
|
+
{bp.difficulty}
|
|
134
|
+
</Badge>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
130
137
|
</div>
|
|
131
138
|
</div>
|
|
132
139
|
</CardHeader>
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { Loader2, Play } from "lucide-react";
|
|
20
20
|
import { toast } from "sonner";
|
|
21
21
|
import { patternLabels } from "@/lib/constants/status-colors";
|
|
22
|
+
import { IconCircle, getWorkflowIconFromName } from "@/lib/constants/card-icons";
|
|
22
23
|
import type { WorkflowBlueprint, BlueprintVariable } from "@/lib/workflows/blueprints/types";
|
|
23
24
|
|
|
24
25
|
interface BlueprintPreviewProps {
|
|
@@ -76,7 +77,13 @@ export function BlueprintPreview({
|
|
|
76
77
|
<div className="space-y-6 max-w-2xl">
|
|
77
78
|
{/* Header */}
|
|
78
79
|
<div>
|
|
79
|
-
<
|
|
80
|
+
<div className="flex items-center gap-3">
|
|
81
|
+
<IconCircle
|
|
82
|
+
icon={getWorkflowIconFromName(blueprint.name, blueprint.pattern).icon}
|
|
83
|
+
colors={getWorkflowIconFromName(blueprint.name, blueprint.pattern).colors}
|
|
84
|
+
/>
|
|
85
|
+
<h1 className="text-2xl font-bold tracking-tight">{blueprint.name}</h1>
|
|
86
|
+
</div>
|
|
80
87
|
<p className="text-sm text-muted-foreground mt-1">{blueprint.description}</p>
|
|
81
88
|
<div className="flex flex-wrap gap-1.5 mt-3">
|
|
82
89
|
<Badge variant={blueprint.domain === "work" ? "default" : "secondary"}>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { Badge } from "@/components/ui/badge";
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { ExpandableResult } from "./workflow-status-view";
|
|
6
7
|
import {
|
|
7
8
|
CheckCircle,
|
|
8
9
|
Circle,
|
|
@@ -233,10 +234,7 @@ export function LoopStatusView({
|
|
|
233
234
|
<p className="text-xs text-destructive">{iter.error}</p>
|
|
234
235
|
)}
|
|
235
236
|
{iter.result && (
|
|
236
|
-
<
|
|
237
|
-
{iter.result.slice(0, 1000)}
|
|
238
|
-
{iter.result.length > 1000 ? "..." : ""}
|
|
239
|
-
</p>
|
|
237
|
+
<ExpandableResult result={iter.result} />
|
|
240
238
|
)}
|
|
241
239
|
{iter.taskId && (
|
|
242
240
|
<a
|
|
@@ -5,6 +5,7 @@ import { Brain, GitBranch, MessageSquareMore, RotateCcw } from "lucide-react";
|
|
|
5
5
|
import { toast } from "sonner";
|
|
6
6
|
import { Badge } from "@/components/ui/badge";
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { ExpandableResult } from "./workflow-status-view";
|
|
8
9
|
import type { SwarmConfig } from "@/lib/workflows/types";
|
|
9
10
|
|
|
10
11
|
interface StepWithState {
|
|
@@ -101,9 +102,7 @@ export function SwarmDashboard({
|
|
|
101
102
|
<p className="text-xs text-destructive">{step.state.error}</p>
|
|
102
103
|
)}
|
|
103
104
|
{step.state.result && step.state.status === "completed" && (
|
|
104
|
-
<
|
|
105
|
-
{step.state.result.slice(0, 320)}
|
|
106
|
-
</p>
|
|
105
|
+
<ExpandableResult result={step.state.result} />
|
|
107
106
|
)}
|
|
108
107
|
{canRetry && (
|
|
109
108
|
<Button
|
|
@@ -188,11 +188,6 @@ export function WorkflowConfirmationView({
|
|
|
188
188
|
priority,
|
|
189
189
|
assignedAgent: assignedAgent || undefined,
|
|
190
190
|
executeImmediately,
|
|
191
|
-
parentTask: {
|
|
192
|
-
title: steps[0].title,
|
|
193
|
-
description: steps[0].description,
|
|
194
|
-
agentProfile: steps[0].profile === "auto" ? undefined : steps[0].profile,
|
|
195
|
-
},
|
|
196
191
|
}),
|
|
197
192
|
});
|
|
198
193
|
|
|
@@ -205,8 +200,8 @@ export function WorkflowConfirmationView({
|
|
|
205
200
|
const data = await res.json();
|
|
206
201
|
toast.success(
|
|
207
202
|
executeImmediately
|
|
208
|
-
? `
|
|
209
|
-
: `
|
|
203
|
+
? `Workflow started (${data.taskIds.length} steps)`
|
|
204
|
+
: `Workflow created as draft (${data.taskIds.length} steps)`,
|
|
210
205
|
{
|
|
211
206
|
action: {
|
|
212
207
|
label: "View workflow",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import ReactMarkdown from "react-markdown";
|
|
4
|
+
import remarkGfm from "remark-gfm";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
7
|
+
import { Copy, Download, FileText } from "lucide-react";
|
|
8
|
+
import { toast } from "sonner";
|
|
9
|
+
import { PROSE_READER } from "@/lib/constants/prose-styles";
|
|
10
|
+
|
|
11
|
+
interface StepOutput {
|
|
12
|
+
name: string;
|
|
13
|
+
result: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface WorkflowFullOutputProps {
|
|
17
|
+
workflowName: string;
|
|
18
|
+
steps: StepOutput[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function WorkflowFullOutput({ workflowName, steps }: WorkflowFullOutputProps) {
|
|
22
|
+
const completedSteps = steps.filter((s) => s.result);
|
|
23
|
+
|
|
24
|
+
if (completedSteps.length === 0) return null;
|
|
25
|
+
|
|
26
|
+
const fullMarkdown = completedSteps
|
|
27
|
+
.map((step) => `## ${step.name}\n\n${step.result}`)
|
|
28
|
+
.join("\n\n---\n\n");
|
|
29
|
+
|
|
30
|
+
function copyAll() {
|
|
31
|
+
navigator.clipboard.writeText(fullMarkdown);
|
|
32
|
+
toast.success("Full output copied");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function downloadAll() {
|
|
36
|
+
const blob = new Blob([fullMarkdown], { type: "text/markdown" });
|
|
37
|
+
const url = URL.createObjectURL(blob);
|
|
38
|
+
const a = document.createElement("a");
|
|
39
|
+
a.href = url;
|
|
40
|
+
a.download = `${workflowName.replace(/\s+/g, "-").toLowerCase()}-output.md`;
|
|
41
|
+
a.click();
|
|
42
|
+
URL.revokeObjectURL(url);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Card>
|
|
47
|
+
<CardHeader>
|
|
48
|
+
<div className="flex items-center justify-between">
|
|
49
|
+
<div className="flex items-center gap-2">
|
|
50
|
+
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
51
|
+
<CardTitle className="text-base">Full Output</CardTitle>
|
|
52
|
+
</div>
|
|
53
|
+
<div className="flex gap-1">
|
|
54
|
+
<Button variant="ghost" size="sm" onClick={copyAll}>
|
|
55
|
+
<Copy className="h-3 w-3 mr-1" />
|
|
56
|
+
Copy All
|
|
57
|
+
</Button>
|
|
58
|
+
<Button variant="ghost" size="sm" onClick={downloadAll}>
|
|
59
|
+
<Download className="h-3 w-3 mr-1" />
|
|
60
|
+
Download .md
|
|
61
|
+
</Button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</CardHeader>
|
|
65
|
+
<CardContent>
|
|
66
|
+
<div className="prose-reader-surface space-y-6">
|
|
67
|
+
{completedSteps.map((step, i) => (
|
|
68
|
+
<div key={i}>
|
|
69
|
+
{i > 0 && <hr className="border-border" />}
|
|
70
|
+
<h3 className="text-sm font-semibold mb-3">{step.name}</h3>
|
|
71
|
+
<div className={PROSE_READER}>
|
|
72
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{step.result}</ReactMarkdown>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
))}
|
|
76
|
+
</div>
|
|
77
|
+
</CardContent>
|
|
78
|
+
</Card>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { Card } from "@/components/ui/card";
|
|
5
|
+
import { Badge } from "@/components/ui/badge";
|
|
6
|
+
import { Workflow, Loader2 } from "lucide-react";
|
|
7
|
+
import { workflowStatusVariant, patternLabels } from "@/lib/constants/status-colors";
|
|
8
|
+
|
|
9
|
+
export interface WorkflowKanbanItem {
|
|
10
|
+
type: "workflow";
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
status: string;
|
|
14
|
+
pattern: string;
|
|
15
|
+
projectName?: string;
|
|
16
|
+
stepProgress: { current: number; total: number };
|
|
17
|
+
currentStepName?: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface WorkflowKanbanCardProps {
|
|
22
|
+
workflow: WorkflowKanbanItem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const statusStripBg: Record<string, string> = {
|
|
26
|
+
draft: "bg-muted/40 border-t-border/30",
|
|
27
|
+
active: "bg-status-running/8 border-t-status-running/15",
|
|
28
|
+
completed: "bg-status-completed/10 border-t-status-completed/20",
|
|
29
|
+
failed: "bg-status-failed/10 border-t-status-failed/20",
|
|
30
|
+
paused: "bg-status-warning/8 border-t-status-warning/15",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function WorkflowKanbanCard({ workflow }: WorkflowKanbanCardProps) {
|
|
34
|
+
const isActive = workflow.status === "active";
|
|
35
|
+
const isFailed = workflow.status === "failed";
|
|
36
|
+
const progressPct =
|
|
37
|
+
workflow.stepProgress.total > 0
|
|
38
|
+
? (workflow.stepProgress.current / workflow.stepProgress.total) * 100
|
|
39
|
+
: 0;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Link href={`/workflows/${workflow.id}`} className="block">
|
|
43
|
+
<Card
|
|
44
|
+
role="button"
|
|
45
|
+
aria-label={`${workflow.name}, ${patternLabels[workflow.pattern] ?? workflow.pattern}, ${workflow.status}`}
|
|
46
|
+
className={`surface-card cursor-pointer transition-shadow hover:shadow-md group overflow-hidden py-0 gap-0 ${
|
|
47
|
+
isFailed
|
|
48
|
+
? "border-l-4 border-l-destructive"
|
|
49
|
+
: isActive
|
|
50
|
+
? "border-l-4 border-l-primary"
|
|
51
|
+
: ""
|
|
52
|
+
}`}
|
|
53
|
+
>
|
|
54
|
+
<div className="p-3">
|
|
55
|
+
<div className="flex items-start gap-2">
|
|
56
|
+
<Workflow
|
|
57
|
+
className={`mt-0.5 h-4 w-4 shrink-0 ${
|
|
58
|
+
isFailed ? "text-destructive" : "text-primary"
|
|
59
|
+
}`}
|
|
60
|
+
aria-hidden="true"
|
|
61
|
+
/>
|
|
62
|
+
<div className="min-w-0 flex-1">
|
|
63
|
+
<p className="text-sm font-medium line-clamp-2">{workflow.name}</p>
|
|
64
|
+
{workflow.projectName && (
|
|
65
|
+
<p className="text-xs text-muted-foreground mt-0.5 truncate">
|
|
66
|
+
{workflow.projectName}
|
|
67
|
+
</p>
|
|
68
|
+
)}
|
|
69
|
+
<div className="flex items-center gap-2 mt-1.5">
|
|
70
|
+
<Badge variant="outline" className="text-xs gap-1">
|
|
71
|
+
{patternLabels[workflow.pattern] ?? workflow.pattern}
|
|
72
|
+
</Badge>
|
|
73
|
+
{workflow.stepProgress.total > 0 && (
|
|
74
|
+
<span className="text-xs text-muted-foreground">
|
|
75
|
+
{workflow.stepProgress.current}/{workflow.stepProgress.total}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{/* Progress bar */}
|
|
81
|
+
{workflow.stepProgress.total > 0 && (
|
|
82
|
+
<div className="mt-2 h-1.5 w-full rounded-full bg-muted overflow-hidden">
|
|
83
|
+
<div
|
|
84
|
+
className={`h-full rounded-full transition-all ${
|
|
85
|
+
isFailed ? "bg-destructive" : "bg-primary"
|
|
86
|
+
}`}
|
|
87
|
+
style={{ width: `${progressPct}%` }}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{/* Current step indicator */}
|
|
93
|
+
{isActive && workflow.currentStepName && (
|
|
94
|
+
<div className="flex items-center gap-1 mt-1.5">
|
|
95
|
+
<Loader2 className="h-3 w-3 animate-spin text-primary shrink-0" />
|
|
96
|
+
<span className="text-xs text-muted-foreground truncate">
|
|
97
|
+
{workflow.currentStepName}
|
|
98
|
+
</span>
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{/* Status strip */}
|
|
106
|
+
<div className={`flex items-center h-7 px-3 border-t transition-colors ${statusStripBg[workflow.status] ?? statusStripBg.draft}`}>
|
|
107
|
+
<Badge
|
|
108
|
+
variant={workflowStatusVariant[workflow.status] ?? "secondary"}
|
|
109
|
+
className="text-[11px] h-5"
|
|
110
|
+
>
|
|
111
|
+
{workflow.status}
|
|
112
|
+
</Badge>
|
|
113
|
+
<div className="flex-1" />
|
|
114
|
+
<span className="text-[11px] text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity">
|
|
115
|
+
View →
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
</Card>
|
|
119
|
+
</Link>
|
|
120
|
+
);
|
|
121
|
+
}
|