stagent 0.9.5 → 0.10.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/README.md +5 -42
- package/dist/cli.js +42 -18
- package/docs/.coverage-gaps.json +13 -55
- package/docs/.last-generated +1 -1
- package/docs/features/provider-runtimes.md +4 -0
- package/docs/features/schedules.md +32 -4
- package/docs/features/settings.md +28 -5
- package/docs/features/tables.md +9 -2
- package/docs/features/workflows.md +10 -4
- package/docs/journeys/developer.md +15 -1
- package/docs/journeys/personal-use.md +21 -4
- package/docs/superpowers/plans/2026-04-07-instance-bootstrap.md +1691 -0
- package/docs/superpowers/plans/2026-04-08-schedule-orchestration.md +2983 -0
- package/docs/superpowers/plans/2026-04-11-schedule-maxturns-api-control.md +551 -0
- package/docs/superpowers/plans/2026-04-11-task-create-profile-validation.md +864 -0
- package/docs/superpowers/plans/2026-04-11-task-runtime-stagent-mcp-injection.md +739 -0
- package/docs/superpowers/specs/2026-04-08-chat-sse-resilience-hotfix-design.md +201 -0
- package/docs/superpowers/specs/2026-04-08-schedule-orchestration-design.md +371 -0
- package/docs/superpowers/specs/2026-04-08-swarm-visibility-design.md +213 -0
- package/package.json +3 -2
- package/src/__tests__/instrumentation-smoke.test.ts +15 -0
- package/src/app/analytics/page.tsx +1 -21
- package/src/app/api/chat/conversations/[id]/messages/route.ts +22 -1
- package/src/app/api/diagnostics/chat-streams/route.ts +65 -0
- package/src/app/api/instance/config/route.ts +41 -0
- package/src/app/api/instance/init/route.ts +34 -0
- package/src/app/api/instance/upgrade/check/route.ts +26 -0
- package/src/app/api/instance/upgrade/route.ts +96 -0
- package/src/app/api/instance/upgrade/status/route.ts +35 -0
- package/src/app/api/memory/route.ts +0 -11
- package/src/app/api/notifications/route.ts +4 -2
- package/src/app/api/projects/[id]/route.ts +5 -155
- package/src/app/api/projects/__tests__/delete-project.test.ts +10 -19
- package/src/app/api/schedules/[id]/execute/route.ts +111 -0
- package/src/app/api/schedules/[id]/route.ts +9 -1
- package/src/app/api/schedules/__tests__/execute-route.test.ts +118 -0
- package/src/app/api/schedules/route.ts +3 -12
- package/src/app/api/settings/openai/login/route.ts +22 -0
- package/src/app/api/settings/openai/logout/route.ts +7 -0
- package/src/app/api/settings/openai/route.ts +21 -1
- package/src/app/api/settings/providers/route.ts +35 -8
- package/src/app/api/tables/[id]/enrich/__tests__/route.test.ts +153 -0
- package/src/app/api/tables/[id]/enrich/plan/route.ts +98 -0
- package/src/app/api/tables/[id]/enrich/route.ts +147 -0
- package/src/app/api/tables/[id]/enrich/runs/route.ts +25 -0
- package/src/app/api/tasks/[id]/execute/route.ts +0 -21
- package/src/app/api/workflows/[id]/resume/route.ts +59 -0
- package/src/app/api/workflows/[id]/status/route.ts +22 -8
- package/src/app/api/workspace/context/route.ts +2 -0
- package/src/app/api/workspace/fix-data-dir/route.ts +81 -0
- package/src/app/chat/page.tsx +11 -0
- package/src/app/inbox/page.tsx +12 -5
- package/src/app/layout.tsx +42 -21
- package/src/app/page.tsx +0 -2
- package/src/app/settings/page.tsx +6 -9
- package/src/components/chat/__tests__/chat-session-provider.test.tsx +408 -0
- package/src/components/chat/chat-command-popover.tsx +2 -2
- package/src/components/chat/chat-input.tsx +2 -3
- package/src/components/chat/chat-session-provider.tsx +720 -0
- package/src/components/chat/chat-shell.tsx +92 -401
- package/src/components/instance/__tests__/instance-section.test.tsx +125 -0
- package/src/components/instance/instance-section.tsx +382 -0
- package/src/components/instance/upgrade-badge.tsx +219 -0
- package/src/components/notifications/__tests__/batch-proposal-review.test.tsx +95 -0
- package/src/components/notifications/__tests__/notification-item.test.tsx +106 -0
- package/src/components/notifications/batch-proposal-review.tsx +20 -5
- package/src/components/notifications/inbox-list.tsx +11 -2
- package/src/components/notifications/notification-item.tsx +56 -2
- package/src/components/notifications/pending-approval-host.tsx +56 -37
- package/src/components/schedules/schedule-create-sheet.tsx +19 -1
- package/src/components/schedules/schedule-edit-sheet.tsx +20 -1
- package/src/components/schedules/schedule-form.tsx +31 -0
- package/src/components/settings/__tests__/providers-runtimes-section.test.tsx +149 -0
- package/src/components/settings/auth-method-selector.tsx +19 -4
- package/src/components/settings/auth-status-badge.tsx +28 -3
- package/src/components/settings/openai-chatgpt-auth-control.tsx +278 -0
- package/src/components/settings/openai-runtime-section.tsx +7 -1
- package/src/components/settings/providers-runtimes-section.tsx +138 -19
- package/src/components/shared/app-sidebar.tsx +4 -3
- package/src/components/shared/command-palette.tsx +4 -5
- package/src/components/shared/theme-toggle.tsx +5 -24
- package/src/components/shared/workspace-indicator.tsx +61 -2
- package/src/components/tables/__tests__/table-enrichment-sheet.test.tsx +130 -0
- package/src/components/tables/table-create-sheet.tsx +4 -0
- package/src/components/tables/table-enrichment-runs.tsx +103 -0
- package/src/components/tables/table-enrichment-sheet.tsx +538 -0
- package/src/components/tables/table-spreadsheet.tsx +29 -5
- package/src/components/tables/table-toolbar.tsx +10 -1
- package/src/components/tasks/kanban-board.tsx +1 -0
- package/src/components/tasks/kanban-column.tsx +53 -14
- package/src/components/tasks/task-bento-grid.tsx +19 -0
- package/src/components/tasks/task-card.tsx +26 -3
- package/src/components/tasks/task-chip-bar.tsx +24 -0
- package/src/components/tasks/task-result-renderer.tsx +1 -1
- package/src/components/workflows/delay-step-body.tsx +109 -0
- package/src/components/workflows/hooks/use-workflow-status.ts +50 -0
- package/src/components/workflows/loop-status-view.tsx +1 -1
- package/src/components/workflows/shared/step-result.tsx +78 -0
- package/src/components/workflows/shared/workflow-header.tsx +141 -0
- package/src/components/workflows/shared/workflow-loading-skeleton.tsx +36 -0
- package/src/components/workflows/swarm-dashboard.tsx +2 -15
- package/src/components/workflows/views/loop-pattern-view.tsx +137 -0
- package/src/components/workflows/views/sequence-pattern-view.tsx +511 -0
- package/src/components/workflows/workflow-form-view.tsx +133 -16
- package/src/components/workflows/workflow-status-view.tsx +30 -740
- package/src/instrumentation-node.ts +94 -0
- package/src/instrumentation.ts +4 -48
- package/src/lib/agents/__tests__/claude-agent.test.ts +199 -0
- package/src/lib/agents/__tests__/execution-manager.test.ts +1 -27
- package/src/lib/agents/__tests__/failure-reason.test.ts +68 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +0 -11
- package/src/lib/agents/__tests__/learning-session.test.ts +158 -0
- package/src/lib/agents/__tests__/pattern-extractor.test.ts +48 -0
- package/src/lib/agents/claude-agent.ts +155 -18
- package/src/lib/agents/execution-manager.ts +0 -35
- package/src/lib/agents/learned-context.ts +0 -12
- package/src/lib/agents/learning-session.ts +18 -5
- package/src/lib/agents/profiles/__tests__/registry.test.ts +6 -4
- package/src/lib/agents/profiles/builtins/upgrade-assistant/SKILL.md +70 -0
- package/src/lib/agents/profiles/builtins/upgrade-assistant/profile.yaml +32 -0
- package/src/lib/agents/runtime/__tests__/openai-codex-auth.test.ts +118 -0
- package/src/lib/agents/runtime/codex-app-server-client.ts +11 -5
- package/src/lib/agents/runtime/openai-codex-auth.ts +389 -0
- package/src/lib/agents/runtime/openai-codex.ts +29 -60
- package/src/lib/agents/runtime/types.ts +8 -0
- package/src/lib/book/chapter-mapping.ts +11 -0
- package/src/lib/book/content.ts +10 -0
- package/src/lib/chat/__tests__/active-streams.test.ts +49 -0
- package/src/lib/chat/__tests__/finalize-safety-net.test.ts +139 -0
- package/src/lib/chat/__tests__/reconcile.test.ts +137 -0
- package/src/lib/chat/__tests__/stream-telemetry.test.ts +151 -0
- package/src/lib/chat/active-streams.ts +27 -0
- package/src/lib/chat/codex-engine.ts +16 -17
- package/src/lib/chat/context-builder.ts +5 -3
- package/src/lib/chat/engine.ts +50 -3
- package/src/lib/chat/reconcile.ts +117 -0
- package/src/lib/chat/stagent-tools.ts +1 -0
- package/src/lib/chat/stream-telemetry.ts +132 -0
- package/src/lib/chat/suggested-prompts.ts +28 -1
- package/src/lib/chat/system-prompt.ts +26 -1
- package/src/lib/chat/tool-catalog.ts +2 -1
- package/src/lib/chat/tools/__tests__/enrich-table-tool.test.ts +127 -0
- package/src/lib/chat/tools/__tests__/schedule-tools.test.ts +261 -0
- package/src/lib/chat/tools/__tests__/task-tools.test.ts +352 -0
- package/src/lib/chat/tools/__tests__/workflow-tools-dedup.test.ts +217 -0
- package/src/lib/chat/tools/document-tools.ts +29 -13
- package/src/lib/chat/tools/helpers.ts +39 -0
- package/src/lib/chat/tools/notification-tools.ts +9 -5
- package/src/lib/chat/tools/project-tools.ts +33 -0
- package/src/lib/chat/tools/schedule-tools.ts +44 -11
- package/src/lib/chat/tools/table-tools.ts +71 -0
- package/src/lib/chat/tools/task-tools.ts +84 -20
- package/src/lib/chat/tools/workflow-tools.ts +234 -32
- package/src/lib/constants/settings.ts +8 -18
- package/src/lib/data/__tests__/clear.test.ts +56 -2
- package/src/lib/data/clear.ts +20 -15
- package/src/lib/data/delete-project.ts +171 -0
- package/src/lib/db/__tests__/bootstrap.test.ts +1 -1
- package/src/lib/db/bootstrap.ts +45 -16
- package/src/lib/db/index.ts +5 -0
- package/src/lib/db/migrations/0009_add_app_instances.sql +25 -0
- package/src/lib/db/migrations/0024_add_workflow_resume_at.sql +10 -0
- package/src/lib/db/migrations/0025_drop_app_instances.sql +3 -0
- package/src/lib/db/migrations/0026_drop_license.sql +3 -0
- package/src/lib/db/migrations/meta/_journal.json +21 -0
- package/src/lib/db/schema.ts +68 -23
- package/src/lib/environment/workspace-context.ts +13 -1
- package/src/lib/import/dedup.ts +4 -54
- package/src/lib/instance/__tests__/bootstrap.test.ts +362 -0
- package/src/lib/instance/__tests__/detect.test.ts +115 -0
- package/src/lib/instance/__tests__/fingerprint.test.ts +48 -0
- package/src/lib/instance/__tests__/git-ops.test.ts +95 -0
- package/src/lib/instance/__tests__/settings.test.ts +83 -0
- package/src/lib/instance/__tests__/upgrade-poller.test.ts +131 -0
- package/src/lib/instance/bootstrap.ts +270 -0
- package/src/lib/instance/detect.ts +49 -0
- package/src/lib/instance/fingerprint.ts +78 -0
- package/src/lib/instance/git-ops.ts +95 -0
- package/src/lib/instance/settings.ts +61 -0
- package/src/lib/instance/types.ts +77 -0
- package/src/lib/instance/upgrade-poller.ts +153 -0
- package/src/lib/notifications/__tests__/visibility.test.ts +51 -0
- package/src/lib/notifications/visibility.ts +33 -0
- package/src/lib/schedules/__tests__/collision-check.test.ts +93 -0
- package/src/lib/schedules/__tests__/config.test.ts +62 -0
- package/src/lib/schedules/__tests__/firing-metrics.test.ts +99 -0
- package/src/lib/schedules/__tests__/integration.test.ts +82 -0
- package/src/lib/schedules/__tests__/slot-claim.test.ts +242 -0
- package/src/lib/schedules/__tests__/tick-scheduler.test.ts +102 -0
- package/src/lib/schedules/__tests__/turn-budget.test.ts +228 -0
- package/src/lib/schedules/collision-check.ts +105 -0
- package/src/lib/schedules/config.ts +53 -0
- package/src/lib/schedules/scheduler.ts +232 -13
- package/src/lib/schedules/slot-claim.ts +105 -0
- package/src/lib/settings/__tests__/openai-auth.test.ts +101 -0
- package/src/lib/settings/__tests__/openai-login-manager.test.ts +64 -0
- package/src/lib/settings/__tests__/runtime-setup.test.ts +33 -0
- package/src/lib/settings/openai-auth.ts +105 -10
- package/src/lib/settings/openai-login-manager.ts +260 -0
- package/src/lib/settings/runtime-setup.ts +14 -4
- package/src/lib/tables/__tests__/enrichment-planner.test.ts +124 -0
- package/src/lib/tables/__tests__/enrichment.test.ts +147 -0
- package/src/lib/tables/enrichment-planner.ts +454 -0
- package/src/lib/tables/enrichment.ts +328 -0
- package/src/lib/tables/query-builder.ts +5 -2
- package/src/lib/tables/trigger-evaluator.ts +3 -2
- package/src/lib/theme.ts +71 -0
- package/src/lib/usage/ledger.ts +2 -18
- package/src/lib/util/__tests__/similarity.test.ts +106 -0
- package/src/lib/util/similarity.ts +77 -0
- package/src/lib/utils/format-timestamp.ts +24 -0
- package/src/lib/utils/stagent-paths.ts +12 -0
- package/src/lib/validators/__tests__/blueprint.test.ts +172 -0
- package/src/lib/validators/__tests__/settings.test.ts +10 -0
- package/src/lib/validators/blueprint.ts +70 -9
- package/src/lib/validators/profile.ts +2 -2
- package/src/lib/validators/settings.ts +3 -1
- package/src/lib/workflows/__tests__/delay.test.ts +196 -0
- package/src/lib/workflows/__tests__/engine.test.ts +8 -0
- package/src/lib/workflows/__tests__/loop-executor.test.ts +54 -0
- package/src/lib/workflows/__tests__/post-action.test.ts +108 -0
- package/src/lib/workflows/blueprints/instantiator.ts +22 -1
- package/src/lib/workflows/blueprints/types.ts +10 -2
- package/src/lib/workflows/delay.ts +106 -0
- package/src/lib/workflows/engine.ts +207 -4
- package/src/lib/workflows/loop-executor.ts +349 -24
- package/src/lib/workflows/post-action.ts +91 -0
- package/src/lib/workflows/types.ts +166 -1
- package/src/app/api/license/checkout/route.ts +0 -28
- package/src/app/api/license/portal/route.ts +0 -26
- package/src/app/api/license/route.ts +0 -89
- package/src/app/api/license/usage/route.ts +0 -63
- package/src/app/api/marketplace/browse/route.ts +0 -15
- package/src/app/api/marketplace/import/route.ts +0 -28
- package/src/app/api/marketplace/publish/route.ts +0 -40
- package/src/app/api/onboarding/email/route.ts +0 -53
- package/src/app/api/settings/telemetry/route.ts +0 -14
- package/src/app/api/sync/export/route.ts +0 -54
- package/src/app/api/sync/restore/route.ts +0 -37
- package/src/app/api/sync/sessions/route.ts +0 -24
- package/src/app/auth/callback/route.ts +0 -73
- package/src/app/marketplace/page.tsx +0 -19
- package/src/components/analytics/analytics-gate-card.tsx +0 -101
- package/src/components/marketplace/blueprint-card.tsx +0 -61
- package/src/components/marketplace/marketplace-browser.tsx +0 -131
- package/src/components/onboarding/email-capture-card.tsx +0 -104
- package/src/components/settings/activation-form.tsx +0 -95
- package/src/components/settings/cloud-account-section.tsx +0 -147
- package/src/components/settings/cloud-sync-section.tsx +0 -155
- package/src/components/settings/subscription-section.tsx +0 -410
- package/src/components/settings/telemetry-section.tsx +0 -80
- package/src/components/shared/premium-gate-overlay.tsx +0 -50
- package/src/components/shared/schedule-gate-dialog.tsx +0 -64
- package/src/components/shared/upgrade-banner.tsx +0 -112
- package/src/hooks/use-supabase-auth.ts +0 -79
- package/src/lib/billing/email.ts +0 -54
- package/src/lib/billing/products.ts +0 -80
- package/src/lib/billing/stripe.ts +0 -101
- package/src/lib/cloud/supabase-browser.ts +0 -32
- package/src/lib/cloud/supabase-client.ts +0 -56
- package/src/lib/license/__tests__/features.test.ts +0 -56
- package/src/lib/license/__tests__/key-format.test.ts +0 -88
- package/src/lib/license/__tests__/manager.test.ts +0 -64
- package/src/lib/license/__tests__/tier-limits.test.ts +0 -79
- package/src/lib/license/cloud-validation.ts +0 -60
- package/src/lib/license/features.ts +0 -44
- package/src/lib/license/key-format.ts +0 -101
- package/src/lib/license/limit-check.ts +0 -111
- package/src/lib/license/limit-queries.ts +0 -51
- package/src/lib/license/manager.ts +0 -345
- package/src/lib/license/notifications.ts +0 -59
- package/src/lib/license/tier-limits.ts +0 -71
- package/src/lib/marketplace/marketplace-client.ts +0 -107
- package/src/lib/sync/cloud-sync.ts +0 -235
- package/src/lib/telemetry/conversion-events.ts +0 -71
- package/src/lib/telemetry/queue.ts +0 -122
- package/src/lib/validators/license.ts +0 -33
|
@@ -5,21 +5,8 @@ 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 "./
|
|
9
|
-
import type { SwarmConfig } from "@/lib/workflows/types";
|
|
10
|
-
|
|
11
|
-
interface StepWithState {
|
|
12
|
-
id: string;
|
|
13
|
-
name: string;
|
|
14
|
-
prompt: string;
|
|
15
|
-
state: {
|
|
16
|
-
stepId: string;
|
|
17
|
-
status: string;
|
|
18
|
-
taskId?: string;
|
|
19
|
-
result?: string;
|
|
20
|
-
error?: string;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
8
|
+
import { ExpandableResult } from "./shared/step-result";
|
|
9
|
+
import type { StepWithState, SwarmConfig } from "@/lib/workflows/types";
|
|
23
10
|
|
|
24
11
|
interface SwarmDashboardProps {
|
|
25
12
|
workflowId: string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
import { LoopStatusView } from "../loop-status-view";
|
|
7
|
+
import { WorkflowFullOutput } from "../workflow-full-output";
|
|
8
|
+
import { WorkflowHeader } from "../shared/workflow-header";
|
|
9
|
+
import type {
|
|
10
|
+
WorkflowStatusResponse,
|
|
11
|
+
} from "@/lib/workflows/types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loop workflow subview. Consumes the `pattern: "loop"` arm of the status
|
|
15
|
+
* API discriminated union. Delegates iteration rendering to the existing
|
|
16
|
+
* LoopStatusView component; adds the loop-prompt display and the Full Output
|
|
17
|
+
* sheet (which reads from `loopState.iterations[].result` rather than from
|
|
18
|
+
* `steps[].state.result` — the latter doesn't exist on the loop arm).
|
|
19
|
+
*
|
|
20
|
+
* Per TDR-031: this is the only place in the view layer that is allowed to
|
|
21
|
+
* read `data.loopState`, because it is the only place narrowed to the loop
|
|
22
|
+
* arm.
|
|
23
|
+
*
|
|
24
|
+
* The Full Output sheet fix is the spec's headline behavior change: before
|
|
25
|
+
* this refactor, `completedStepOutputs` was computed in the god component
|
|
26
|
+
* BEFORE the pattern dispatch, so loop workflows either crashed (pre-PR #6)
|
|
27
|
+
* or got an empty array (post-PR #6). Now the loop subview reads iterations
|
|
28
|
+
* directly, so a completed table enrichment workflow actually shows its
|
|
29
|
+
* per-iteration outputs.
|
|
30
|
+
*/
|
|
31
|
+
export function LoopPatternView({
|
|
32
|
+
data,
|
|
33
|
+
onRefresh,
|
|
34
|
+
onRequestDelete,
|
|
35
|
+
}: {
|
|
36
|
+
data: Extract<WorkflowStatusResponse, { pattern: "loop" }>;
|
|
37
|
+
onRefresh: () => Promise<void>;
|
|
38
|
+
onRequestDelete: () => void;
|
|
39
|
+
}) {
|
|
40
|
+
const [executing, setExecuting] = useState(false);
|
|
41
|
+
|
|
42
|
+
const handleExecute = useCallback(async () => {
|
|
43
|
+
setExecuting(true);
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch(`/api/workflows/${data.id}/execute`, { method: "POST" });
|
|
46
|
+
if (res.ok) {
|
|
47
|
+
toast.success("Workflow started");
|
|
48
|
+
await onRefresh();
|
|
49
|
+
} else {
|
|
50
|
+
const err = await res.json().catch(() => null);
|
|
51
|
+
toast.error(err?.error ?? "Failed to start workflow");
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
setExecuting(false);
|
|
55
|
+
}
|
|
56
|
+
}, [data.id, onRefresh]);
|
|
57
|
+
|
|
58
|
+
const handleRerun = useCallback(async () => {
|
|
59
|
+
setExecuting(true);
|
|
60
|
+
try {
|
|
61
|
+
const res = await fetch(`/api/workflows/${data.id}/execute`, { method: "POST" });
|
|
62
|
+
if (res.ok) {
|
|
63
|
+
toast.success("Workflow re-started");
|
|
64
|
+
await onRefresh();
|
|
65
|
+
} else {
|
|
66
|
+
const err = await res.json().catch(() => null);
|
|
67
|
+
toast.error(err?.error ?? "Failed to re-run workflow");
|
|
68
|
+
}
|
|
69
|
+
} finally {
|
|
70
|
+
setExecuting(false);
|
|
71
|
+
}
|
|
72
|
+
}, [data.id, onRefresh]);
|
|
73
|
+
|
|
74
|
+
// Loop workflows expose their completed outputs as `loopState.iterations[]`
|
|
75
|
+
// rather than `steps[].state`. Map each completed iteration to the shape
|
|
76
|
+
// `WorkflowFullOutput` expects. This is the bug fix — previously the Full
|
|
77
|
+
// Output sheet was silently empty for every table enrichment run because
|
|
78
|
+
// the old god component only read from `steps[].state` (which doesn't
|
|
79
|
+
// exist on loop responses).
|
|
80
|
+
const completedIterationOutputs = useMemo(() => {
|
|
81
|
+
const iterations = data.loopState?.iterations ?? [];
|
|
82
|
+
return iterations
|
|
83
|
+
.filter((iter) => iter.status === "completed" && iter.result && iter.result.trim() !== "")
|
|
84
|
+
.map((iter) => ({
|
|
85
|
+
name: `Iteration ${iter.iteration}`,
|
|
86
|
+
result: iter.result!,
|
|
87
|
+
}));
|
|
88
|
+
}, [data.loopState]);
|
|
89
|
+
|
|
90
|
+
const loopPromptText = data.steps[0]?.prompt;
|
|
91
|
+
const showFullOutput =
|
|
92
|
+
data.status === "completed" && completedIterationOutputs.length > 0;
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="space-y-6">
|
|
96
|
+
<Card>
|
|
97
|
+
<WorkflowHeader
|
|
98
|
+
data={data}
|
|
99
|
+
executing={executing}
|
|
100
|
+
// Loop workflows have their own start/pause controls inside
|
|
101
|
+
// LoopStatusView — the header's Execute button would be redundant
|
|
102
|
+
// and confusing.
|
|
103
|
+
canExecute={false}
|
|
104
|
+
onExecute={handleExecute}
|
|
105
|
+
onRerun={handleRerun}
|
|
106
|
+
onDelete={onRequestDelete}
|
|
107
|
+
/>
|
|
108
|
+
<CardContent>
|
|
109
|
+
{loopPromptText && (
|
|
110
|
+
<div className="mb-4">
|
|
111
|
+
<p className="text-xs font-medium text-muted-foreground mb-1.5">Loop Prompt</p>
|
|
112
|
+
<div className="rounded-md border bg-muted/50 p-3">
|
|
113
|
+
<p className="text-sm whitespace-pre-wrap">{loopPromptText}</p>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
{data.loopConfig && (
|
|
118
|
+
<LoopStatusView
|
|
119
|
+
workflowId={data.id}
|
|
120
|
+
workflowStatus={data.status}
|
|
121
|
+
loopConfig={data.loopConfig}
|
|
122
|
+
loopState={data.loopState ?? null}
|
|
123
|
+
onRefresh={() => void onRefresh()}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
</CardContent>
|
|
127
|
+
</Card>
|
|
128
|
+
|
|
129
|
+
{showFullOutput && (
|
|
130
|
+
<WorkflowFullOutput
|
|
131
|
+
workflowName={data.name}
|
|
132
|
+
steps={completedIterationOutputs}
|
|
133
|
+
/>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { Badge } from "@/components/ui/badge";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Card, CardContent } from "@/components/ui/card";
|
|
8
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
9
|
+
import {
|
|
10
|
+
CheckCircle,
|
|
11
|
+
Circle,
|
|
12
|
+
Loader2,
|
|
13
|
+
XCircle,
|
|
14
|
+
ShieldQuestion,
|
|
15
|
+
Clock3,
|
|
16
|
+
GitBranch,
|
|
17
|
+
MessageSquareMore,
|
|
18
|
+
FileText,
|
|
19
|
+
Paperclip,
|
|
20
|
+
ArrowRight,
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
import { toast } from "sonner";
|
|
23
|
+
import { SwarmDashboard } from "../swarm-dashboard";
|
|
24
|
+
import { WorkflowFullOutput } from "../workflow-full-output";
|
|
25
|
+
import { DelayStepBody } from "../delay-step-body";
|
|
26
|
+
import { WorkflowHeader } from "../shared/workflow-header";
|
|
27
|
+
import { ExpandableResult, DocumentList } from "../shared/step-result";
|
|
28
|
+
import type {
|
|
29
|
+
WorkflowStatusResponse,
|
|
30
|
+
WorkflowStatusDocument,
|
|
31
|
+
StepWithState,
|
|
32
|
+
} from "@/lib/workflows/types";
|
|
33
|
+
|
|
34
|
+
const stepStatusIcons: Record<string, React.ReactNode> = {
|
|
35
|
+
pending: <Circle className="h-4 w-4 text-muted-foreground" />,
|
|
36
|
+
running: <Loader2 className="h-4 w-4 text-status-running animate-spin" />,
|
|
37
|
+
completed: <CheckCircle className="h-4 w-4 text-status-completed" />,
|
|
38
|
+
failed: <XCircle className="h-4 w-4 text-destructive" />,
|
|
39
|
+
waiting_approval: <ShieldQuestion className="h-4 w-4 text-status-warning" />,
|
|
40
|
+
waiting_dependencies: <Clock3 className="h-4 w-4 text-status-warning" />,
|
|
41
|
+
delayed: <Clock3 className="h-4 w-4 text-status-warning" />,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Non-loop workflow subview. Consumes the sequence/parallel/swarm/
|
|
46
|
+
* planner-executor/checkpoint arm of the status API discriminated union.
|
|
47
|
+
* Renders either a sequential step list (with delay-step support), a parallel
|
|
48
|
+
* branches + synthesis layout, or delegates to SwarmDashboard — all three
|
|
49
|
+
* share the same `steps: StepWithState[]` shape, so branching stays inside
|
|
50
|
+
* this subview.
|
|
51
|
+
*
|
|
52
|
+
* Optimistic Execute/Rerun state is owned here so the Execute button can flip
|
|
53
|
+
* immediately without a round-trip through the router. The hook's `setData`
|
|
54
|
+
* is the mechanism for pushing optimistic state back into the shared polling
|
|
55
|
+
* cache; subsequent poll ticks will overwrite it with authoritative data.
|
|
56
|
+
*
|
|
57
|
+
* Per TDR-031, this subview never touches `.state` on anything except
|
|
58
|
+
* `StepWithState` (which by the union's type guarantees `.state` is present).
|
|
59
|
+
*/
|
|
60
|
+
export function SequencePatternView({
|
|
61
|
+
data,
|
|
62
|
+
setData,
|
|
63
|
+
onRefresh,
|
|
64
|
+
onRequestDelete,
|
|
65
|
+
}: {
|
|
66
|
+
data: Extract<WorkflowStatusResponse, { pattern: Exclude<WorkflowStatusResponse["pattern"], "loop"> }>;
|
|
67
|
+
setData: (updater: (current: WorkflowStatusResponse | null) => WorkflowStatusResponse | null) => void;
|
|
68
|
+
onRefresh: () => Promise<void>;
|
|
69
|
+
onRequestDelete: () => void;
|
|
70
|
+
}) {
|
|
71
|
+
const [executing, setExecuting] = useState(false);
|
|
72
|
+
|
|
73
|
+
const handleExecute = useCallback(async () => {
|
|
74
|
+
setExecuting(true);
|
|
75
|
+
// Optimistic update — immediately show "active" status so the UI doesn't
|
|
76
|
+
// feel laggy while the POST and next poll tick complete.
|
|
77
|
+
setData((current) => {
|
|
78
|
+
if (!current || current.pattern === "loop") return current;
|
|
79
|
+
return {
|
|
80
|
+
...current,
|
|
81
|
+
status: "active",
|
|
82
|
+
steps: current.steps.map((step, index): StepWithState => {
|
|
83
|
+
if (current.pattern === "swarm") {
|
|
84
|
+
const lastIndex = current.steps.length - 1;
|
|
85
|
+
return {
|
|
86
|
+
...step,
|
|
87
|
+
state: {
|
|
88
|
+
...step.state,
|
|
89
|
+
status:
|
|
90
|
+
index === 0
|
|
91
|
+
? "running"
|
|
92
|
+
: index === lastIndex
|
|
93
|
+
? "waiting_dependencies"
|
|
94
|
+
: "pending",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (current.pattern === "parallel") {
|
|
99
|
+
const isJoin = !!step.dependsOn?.length;
|
|
100
|
+
return {
|
|
101
|
+
...step,
|
|
102
|
+
state: {
|
|
103
|
+
...step.state,
|
|
104
|
+
status: isJoin
|
|
105
|
+
? "waiting_dependencies"
|
|
106
|
+
: index === 0
|
|
107
|
+
? "running"
|
|
108
|
+
: "pending",
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return index === 0
|
|
113
|
+
? { ...step, state: { ...step.state, status: "running" } }
|
|
114
|
+
: step;
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetch(`/api/workflows/${data.id}/execute`, { method: "POST" });
|
|
120
|
+
if (res.ok) {
|
|
121
|
+
toast.success("Workflow started");
|
|
122
|
+
await onRefresh();
|
|
123
|
+
} else {
|
|
124
|
+
const err = await res.json().catch(() => null);
|
|
125
|
+
toast.error(err?.error ?? "Failed to start workflow");
|
|
126
|
+
await onRefresh(); // Revert optimistic update on failure
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
setExecuting(false);
|
|
130
|
+
}
|
|
131
|
+
}, [data.id, onRefresh, setData]);
|
|
132
|
+
|
|
133
|
+
const handleRerun = useCallback(async () => {
|
|
134
|
+
setExecuting(true);
|
|
135
|
+
try {
|
|
136
|
+
const res = await fetch(`/api/workflows/${data.id}/execute`, { method: "POST" });
|
|
137
|
+
if (res.ok) {
|
|
138
|
+
toast.success("Workflow re-started");
|
|
139
|
+
await onRefresh();
|
|
140
|
+
} else {
|
|
141
|
+
const err = await res.json().catch(() => null);
|
|
142
|
+
toast.error(err?.error ?? "Failed to re-run workflow");
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
setExecuting(false);
|
|
146
|
+
}
|
|
147
|
+
}, [data.id, onRefresh]);
|
|
148
|
+
|
|
149
|
+
// At this point on the non-loop arm, `state` is guaranteed present — no
|
|
150
|
+
// optional chaining needed. This is the AC that PR #6's optional chaining
|
|
151
|
+
// patched; the discriminated union makes the patch unnecessary.
|
|
152
|
+
const completedStepOutputs = data.steps
|
|
153
|
+
.filter((s) => s.state.result && s.state.status === "completed")
|
|
154
|
+
.map((s) => ({ name: s.name, result: s.state.result! }));
|
|
155
|
+
|
|
156
|
+
const hasStepDocs = !!data.stepDocuments && Object.keys(data.stepDocuments).length > 0;
|
|
157
|
+
const hasParentDocs = !!data.parentDocuments && data.parentDocuments.length > 0;
|
|
158
|
+
|
|
159
|
+
const parallelBranches =
|
|
160
|
+
data.pattern === "parallel"
|
|
161
|
+
? data.steps.filter((step) => !step.dependsOn?.length)
|
|
162
|
+
: [];
|
|
163
|
+
const synthesisStep =
|
|
164
|
+
data.pattern === "parallel"
|
|
165
|
+
? (data.steps.find((step) => step.dependsOn?.length) ?? null)
|
|
166
|
+
: null;
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div className="space-y-6">
|
|
170
|
+
<Card>
|
|
171
|
+
<WorkflowHeader
|
|
172
|
+
data={data}
|
|
173
|
+
executing={executing}
|
|
174
|
+
canExecute={true}
|
|
175
|
+
onExecute={handleExecute}
|
|
176
|
+
onRerun={handleRerun}
|
|
177
|
+
onDelete={onRequestDelete}
|
|
178
|
+
/>
|
|
179
|
+
<CardContent>
|
|
180
|
+
{data.pattern === "swarm" ? (
|
|
181
|
+
<SwarmDashboard
|
|
182
|
+
workflowId={data.id}
|
|
183
|
+
workflowStatus={data.status}
|
|
184
|
+
steps={data.steps}
|
|
185
|
+
swarmConfig={data.swarmConfig}
|
|
186
|
+
onRefresh={onRefresh}
|
|
187
|
+
stepStatusIcons={stepStatusIcons}
|
|
188
|
+
/>
|
|
189
|
+
) : (
|
|
190
|
+
<div className="space-y-4" aria-live="polite">
|
|
191
|
+
{data.pattern === "parallel" && parallelBranches.length > 0 ? (
|
|
192
|
+
<>
|
|
193
|
+
<section className="space-y-3">
|
|
194
|
+
<div className="flex items-center gap-2">
|
|
195
|
+
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
|
196
|
+
<p className="text-sm font-medium">Parallel Branches</p>
|
|
197
|
+
<Badge variant="secondary" className="text-xs">
|
|
198
|
+
{parallelBranches.length}
|
|
199
|
+
</Badge>
|
|
200
|
+
</div>
|
|
201
|
+
<div className="grid gap-3 md:grid-cols-2">
|
|
202
|
+
{parallelBranches.map((step, index) => (
|
|
203
|
+
<div
|
|
204
|
+
key={step.id}
|
|
205
|
+
className="surface-card-muted rounded-lg border border-border/50 p-4"
|
|
206
|
+
>
|
|
207
|
+
<div className="flex items-start gap-3">
|
|
208
|
+
<div className="mt-0.5">
|
|
209
|
+
{stepStatusIcons[step.state.status] ?? stepStatusIcons.pending}
|
|
210
|
+
</div>
|
|
211
|
+
<div className="min-w-0 flex-1">
|
|
212
|
+
<div className="flex items-center gap-2">
|
|
213
|
+
<Badge variant="secondary" className="text-[11px]">
|
|
214
|
+
Branch {index + 1}
|
|
215
|
+
</Badge>
|
|
216
|
+
<span className="text-sm font-medium">{step.name}</span>
|
|
217
|
+
</div>
|
|
218
|
+
<p className="mt-1 text-xs text-muted-foreground line-clamp-2">
|
|
219
|
+
{step.prompt}
|
|
220
|
+
</p>
|
|
221
|
+
{step.state.error && (
|
|
222
|
+
<p className="mt-2 text-xs text-destructive">
|
|
223
|
+
{step.state.error}
|
|
224
|
+
</p>
|
|
225
|
+
)}
|
|
226
|
+
{step.state.result && step.state.status === "completed" && (
|
|
227
|
+
<ExpandableResult result={step.state.result} />
|
|
228
|
+
)}
|
|
229
|
+
{step.state.taskId && data.stepDocuments?.[step.state.taskId] && (
|
|
230
|
+
<DocumentList
|
|
231
|
+
docs={data.stepDocuments[step.state.taskId]}
|
|
232
|
+
label="Generated Files"
|
|
233
|
+
/>
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
</section>
|
|
241
|
+
|
|
242
|
+
{synthesisStep && (
|
|
243
|
+
<section className="space-y-3">
|
|
244
|
+
<div className="flex items-center gap-2">
|
|
245
|
+
<MessageSquareMore className="h-4 w-4 text-muted-foreground" />
|
|
246
|
+
<p className="text-sm font-medium">Synthesis Step</p>
|
|
247
|
+
</div>
|
|
248
|
+
<div className="surface-card-muted rounded-lg border border-border/50 p-4">
|
|
249
|
+
<div className="flex items-start gap-3">
|
|
250
|
+
<div className="mt-0.5">
|
|
251
|
+
{stepStatusIcons[synthesisStep.state.status] ??
|
|
252
|
+
stepStatusIcons.pending}
|
|
253
|
+
</div>
|
|
254
|
+
<div className="min-w-0 flex-1">
|
|
255
|
+
<div className="flex items-center gap-2">
|
|
256
|
+
<Badge variant="outline" className="text-[11px]">
|
|
257
|
+
join
|
|
258
|
+
</Badge>
|
|
259
|
+
<span className="text-sm font-medium">
|
|
260
|
+
{synthesisStep.name}
|
|
261
|
+
</span>
|
|
262
|
+
</div>
|
|
263
|
+
<p className="mt-1 text-xs text-muted-foreground line-clamp-2">
|
|
264
|
+
{synthesisStep.prompt}
|
|
265
|
+
</p>
|
|
266
|
+
<p className="mt-2 text-xs text-muted-foreground">
|
|
267
|
+
Waits for all {parallelBranches.length} branches before running.
|
|
268
|
+
</p>
|
|
269
|
+
{synthesisStep.state.error && (
|
|
270
|
+
<p className="mt-2 text-xs text-destructive">
|
|
271
|
+
{synthesisStep.state.error}
|
|
272
|
+
</p>
|
|
273
|
+
)}
|
|
274
|
+
{synthesisStep.state.result &&
|
|
275
|
+
synthesisStep.state.status === "completed" && (
|
|
276
|
+
<ExpandableResult result={synthesisStep.state.result} />
|
|
277
|
+
)}
|
|
278
|
+
{synthesisStep.state.taskId && data.stepDocuments?.[synthesisStep.state.taskId] && (
|
|
279
|
+
<DocumentList
|
|
280
|
+
docs={data.stepDocuments[synthesisStep.state.taskId]}
|
|
281
|
+
label="Generated Files"
|
|
282
|
+
/>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</section>
|
|
288
|
+
)}
|
|
289
|
+
</>
|
|
290
|
+
) : (
|
|
291
|
+
<div className="space-y-3">
|
|
292
|
+
{data.steps.map((step, index) => {
|
|
293
|
+
const isDelayStep = !!step.delayDuration;
|
|
294
|
+
const isActiveDelay = isDelayStep && step.state.status === "delayed";
|
|
295
|
+
return (
|
|
296
|
+
<div key={`${step.id}-${index}`} className="flex items-start gap-3">
|
|
297
|
+
<div className="mt-0.5 flex flex-col items-center">
|
|
298
|
+
{isDelayStep && step.state.status === "pending" ? (
|
|
299
|
+
<Clock3 className="h-4 w-4 text-muted-foreground" />
|
|
300
|
+
) : (
|
|
301
|
+
stepStatusIcons[step.state.status] ?? stepStatusIcons.pending
|
|
302
|
+
)}
|
|
303
|
+
{index < data.steps.length - 1 && (
|
|
304
|
+
<div className="mt-1 h-6 w-px bg-border" />
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
307
|
+
<div className="flex-1 min-w-0">
|
|
308
|
+
<div className="flex items-center gap-2">
|
|
309
|
+
<span className="text-sm font-medium">{step.name}</span>
|
|
310
|
+
{isDelayStep && (
|
|
311
|
+
<Badge variant="secondary" className="text-[10px] uppercase tracking-wide">
|
|
312
|
+
Delay
|
|
313
|
+
</Badge>
|
|
314
|
+
)}
|
|
315
|
+
{step.requiresApproval && !isDelayStep && (
|
|
316
|
+
<Badge variant="outline" className="text-xs">
|
|
317
|
+
checkpoint
|
|
318
|
+
</Badge>
|
|
319
|
+
)}
|
|
320
|
+
</div>
|
|
321
|
+
{isDelayStep ? (
|
|
322
|
+
<DelayStepBody
|
|
323
|
+
workflowId={data.id}
|
|
324
|
+
delayDuration={step.delayDuration!}
|
|
325
|
+
stepStatus={step.state.status}
|
|
326
|
+
resumeAt={isActiveDelay ? data.resumeAt ?? null : null}
|
|
327
|
+
/>
|
|
328
|
+
) : (
|
|
329
|
+
<div className="flex items-center gap-1.5 mt-0.5">
|
|
330
|
+
<p className="text-xs text-muted-foreground truncate">
|
|
331
|
+
{step.prompt.slice(0, 100)}
|
|
332
|
+
{step.prompt.length > 100 ? "..." : ""}
|
|
333
|
+
</p>
|
|
334
|
+
{step.state.taskId && (
|
|
335
|
+
<a
|
|
336
|
+
href={`/tasks/${step.state.taskId}`}
|
|
337
|
+
className="text-[10px] text-primary hover:underline shrink-0"
|
|
338
|
+
>
|
|
339
|
+
view task
|
|
340
|
+
</a>
|
|
341
|
+
)}
|
|
342
|
+
</div>
|
|
343
|
+
)}
|
|
344
|
+
{hasParentDocs && index === 0 && !isDelayStep && (
|
|
345
|
+
<div className="flex items-center gap-1 mt-1">
|
|
346
|
+
<Badge variant="outline" className="text-[10px] px-1.5 py-0">
|
|
347
|
+
{data.parentDocuments!.length} doc{data.parentDocuments!.length !== 1 ? "s" : ""} attached
|
|
348
|
+
</Badge>
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
{step.state.error && (
|
|
352
|
+
<p className="text-xs text-destructive mt-1">
|
|
353
|
+
{step.state.error}
|
|
354
|
+
</p>
|
|
355
|
+
)}
|
|
356
|
+
{step.state.result && step.state.status === "completed" && !isDelayStep && (
|
|
357
|
+
<ExpandableResult result={step.state.result} />
|
|
358
|
+
)}
|
|
359
|
+
{step.state.taskId && data.stepDocuments?.[step.state.taskId] && (
|
|
360
|
+
<DocumentList
|
|
361
|
+
docs={data.stepDocuments[step.state.taskId]}
|
|
362
|
+
label="Generated Files"
|
|
363
|
+
/>
|
|
364
|
+
)}
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
);
|
|
368
|
+
})}
|
|
369
|
+
</div>
|
|
370
|
+
)}
|
|
371
|
+
</div>
|
|
372
|
+
)}
|
|
373
|
+
|
|
374
|
+
{(hasParentDocs || hasStepDocs) && (
|
|
375
|
+
<div className="mt-6 pt-4 border-t border-border/50">
|
|
376
|
+
<div className="flex items-center gap-2 mb-3">
|
|
377
|
+
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
|
378
|
+
<p className="text-sm font-medium">Documents</p>
|
|
379
|
+
</div>
|
|
380
|
+
{hasParentDocs && (
|
|
381
|
+
<DocumentList docs={data.parentDocuments!} label="Input Files" />
|
|
382
|
+
)}
|
|
383
|
+
{hasStepDocs &&
|
|
384
|
+
Object.entries(data.stepDocuments!).map(([taskId, docs]) => {
|
|
385
|
+
const step = data.steps.find((s) => s.state.taskId === taskId);
|
|
386
|
+
return (
|
|
387
|
+
<DocumentList
|
|
388
|
+
key={taskId}
|
|
389
|
+
docs={docs}
|
|
390
|
+
label={step ? `Output: ${step.name}` : "Output Files"}
|
|
391
|
+
/>
|
|
392
|
+
);
|
|
393
|
+
})}
|
|
394
|
+
</div>
|
|
395
|
+
)}
|
|
396
|
+
</CardContent>
|
|
397
|
+
</Card>
|
|
398
|
+
|
|
399
|
+
{data.status === "completed" && completedStepOutputs.length > 0 && (
|
|
400
|
+
<WorkflowFullOutput workflowName={data.name} steps={completedStepOutputs} />
|
|
401
|
+
)}
|
|
402
|
+
|
|
403
|
+
{data.status === "completed" && hasStepDocs && (
|
|
404
|
+
<OutputDock stepDocuments={data.stepDocuments!} steps={data.steps} />
|
|
405
|
+
)}
|
|
406
|
+
</div>
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Output Dock — selectable output documents for chaining into a new workflow */
|
|
411
|
+
function OutputDock({
|
|
412
|
+
stepDocuments,
|
|
413
|
+
steps,
|
|
414
|
+
}: {
|
|
415
|
+
stepDocuments: Record<string, WorkflowStatusDocument[]>;
|
|
416
|
+
steps: StepWithState[];
|
|
417
|
+
}) {
|
|
418
|
+
const router = useRouter();
|
|
419
|
+
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
420
|
+
|
|
421
|
+
const allOutputDocs = Object.entries(stepDocuments).flatMap(([taskId, docs]) => {
|
|
422
|
+
const step = steps.find((s) => s.state.taskId === taskId);
|
|
423
|
+
return docs.map((doc) => ({
|
|
424
|
+
...doc,
|
|
425
|
+
stepName: step?.name ?? "Unknown Step",
|
|
426
|
+
}));
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (allOutputDocs.length === 0) return null;
|
|
430
|
+
|
|
431
|
+
function toggleDoc(id: string) {
|
|
432
|
+
setSelectedIds((prev) => {
|
|
433
|
+
const next = new Set(prev);
|
|
434
|
+
if (next.has(id)) next.delete(id);
|
|
435
|
+
else next.add(id);
|
|
436
|
+
return next;
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function selectAll() {
|
|
441
|
+
setSelectedIds(new Set(allOutputDocs.map((d) => d.id)));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function chainIntoNewWorkflow() {
|
|
445
|
+
if (selectedIds.size === 0) return;
|
|
446
|
+
const params = new URLSearchParams({ inputDocs: [...selectedIds].join(",") });
|
|
447
|
+
router.push(`/workflows/new?${params}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return (
|
|
451
|
+
<Card>
|
|
452
|
+
<div className="px-6 pt-6 pb-3">
|
|
453
|
+
<div className="flex items-center justify-between">
|
|
454
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
455
|
+
<ArrowRight className="h-4 w-4" />
|
|
456
|
+
Chain Output Documents
|
|
457
|
+
</div>
|
|
458
|
+
<Button variant="ghost" size="sm" onClick={selectAll} className="text-xs">
|
|
459
|
+
Select All
|
|
460
|
+
</Button>
|
|
461
|
+
</div>
|
|
462
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
463
|
+
Select output documents to use as inputs in a new workflow
|
|
464
|
+
</p>
|
|
465
|
+
</div>
|
|
466
|
+
<CardContent>
|
|
467
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 mb-4">
|
|
468
|
+
{allOutputDocs.map((doc) => {
|
|
469
|
+
const isChecked = selectedIds.has(doc.id);
|
|
470
|
+
return (
|
|
471
|
+
<div
|
|
472
|
+
key={doc.id}
|
|
473
|
+
role="button"
|
|
474
|
+
tabIndex={0}
|
|
475
|
+
onClick={() => toggleDoc(doc.id)}
|
|
476
|
+
onKeyDown={(e) => {
|
|
477
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
478
|
+
e.preventDefault();
|
|
479
|
+
toggleDoc(doc.id);
|
|
480
|
+
}
|
|
481
|
+
}}
|
|
482
|
+
className={`flex items-center gap-3 p-3 rounded-lg text-left transition-colors border cursor-pointer ${
|
|
483
|
+
isChecked
|
|
484
|
+
? "bg-accent/50 border-accent"
|
|
485
|
+
: "hover:bg-muted/50 border-border/50"
|
|
486
|
+
}`}
|
|
487
|
+
>
|
|
488
|
+
<Checkbox
|
|
489
|
+
checked={isChecked}
|
|
490
|
+
onCheckedChange={() => toggleDoc(doc.id)}
|
|
491
|
+
/>
|
|
492
|
+
<FileText className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
|
493
|
+
<div className="flex-1 min-w-0">
|
|
494
|
+
<p className="text-sm font-medium truncate">{doc.originalName}</p>
|
|
495
|
+
<p className="text-xs text-muted-foreground truncate">{doc.stepName}</p>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
);
|
|
499
|
+
})}
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
{selectedIds.size > 0 && (
|
|
503
|
+
<Button onClick={chainIntoNewWorkflow} className="w-full gap-2" size="sm">
|
|
504
|
+
<ArrowRight className="h-4 w-4" />
|
|
505
|
+
Chain {selectedIds.size} Document{selectedIds.size !== 1 ? "s" : ""} Into New Workflow
|
|
506
|
+
</Button>
|
|
507
|
+
)}
|
|
508
|
+
</CardContent>
|
|
509
|
+
</Card>
|
|
510
|
+
);
|
|
511
|
+
}
|