@a5c-ai/babysitter-observer-dashboard 1.0.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/LICENSE +21 -0
- package/README.md +490 -0
- package/next.config.mjs +25 -0
- package/package.json +104 -0
- package/postcss.config.mjs +8 -0
- package/src/app/actions/__tests__/approve-breakpoint.test.ts +246 -0
- package/src/app/actions/approve-breakpoint.ts +145 -0
- package/src/app/api/config/route.ts +137 -0
- package/src/app/api/digest/route.ts +45 -0
- package/src/app/api/runs/[runId]/events/route.ts +56 -0
- package/src/app/api/runs/[runId]/route.ts +84 -0
- package/src/app/api/runs/[runId]/tasks/[effectId]/route.ts +44 -0
- package/src/app/api/runs/route.ts +48 -0
- package/src/app/api/stream/route.ts +136 -0
- package/src/app/api/test/route.ts +1 -0
- package/src/app/api/version/route.ts +57 -0
- package/src/app/globals.css +555 -0
- package/src/app/icon.svg +20 -0
- package/src/app/layout.tsx +39 -0
- package/src/app/not-found.tsx +16 -0
- package/src/app/page.tsx +120 -0
- package/src/app/runs/[runId]/page.tsx +279 -0
- package/src/cli.ts +271 -0
- package/src/components/breakpoint/__tests__/breakpoint-approval.test.tsx +212 -0
- package/src/components/breakpoint/__tests__/breakpoint-panel.test.tsx +130 -0
- package/src/components/breakpoint/__tests__/file-preview.test.tsx +313 -0
- package/src/components/breakpoint/breakpoint-approval.tsx +138 -0
- package/src/components/breakpoint/breakpoint-panel.tsx +95 -0
- package/src/components/breakpoint/file-preview.tsx +215 -0
- package/src/components/dashboard/.gitkeep +0 -0
- package/src/components/dashboard/__tests__/breakpoint-banner.test.tsx +177 -0
- package/src/components/dashboard/__tests__/catch-up-banner.test.tsx +141 -0
- package/src/components/dashboard/__tests__/executive-summary-banner.test.tsx +164 -0
- package/src/components/dashboard/__tests__/kpi-grid.test.tsx +101 -0
- package/src/components/dashboard/__tests__/pagination-controls.test.tsx +125 -0
- package/src/components/dashboard/__tests__/project-accordion.test.tsx +97 -0
- package/src/components/dashboard/__tests__/project-list-view.test.tsx +174 -0
- package/src/components/dashboard/__tests__/project-search-input.test.tsx +110 -0
- package/src/components/dashboard/__tests__/project-section-header.test.tsx +91 -0
- package/src/components/dashboard/__tests__/project-section.test.tsx +151 -0
- package/src/components/dashboard/__tests__/run-card.test.tsx +164 -0
- package/src/components/dashboard/__tests__/run-filter-bar.test.tsx +109 -0
- package/src/components/dashboard/__tests__/run-list.test.tsx +123 -0
- package/src/components/dashboard/__tests__/search-filter.test.tsx +150 -0
- package/src/components/dashboard/__tests__/virtualized-run-list.test.tsx +179 -0
- package/src/components/dashboard/breakpoint-banner.tsx +301 -0
- package/src/components/dashboard/catch-up-banner.tsx +88 -0
- package/src/components/dashboard/executive-summary-banner.tsx +174 -0
- package/src/components/dashboard/global-search.tsx +323 -0
- package/src/components/dashboard/kpi-grid.tsx +140 -0
- package/src/components/dashboard/pagination-controls.tsx +100 -0
- package/src/components/dashboard/project-accordion.tsx +72 -0
- package/src/components/dashboard/project-health-card.tsx +536 -0
- package/src/components/dashboard/project-list-view.tsx +246 -0
- package/src/components/dashboard/project-search-input.tsx +41 -0
- package/src/components/dashboard/project-section-header.tsx +73 -0
- package/src/components/dashboard/project-section.tsx +89 -0
- package/src/components/dashboard/run-card.tsx +218 -0
- package/src/components/dashboard/run-filter-bar.tsx +100 -0
- package/src/components/dashboard/run-list.tsx +77 -0
- package/src/components/dashboard/search-filter.tsx +69 -0
- package/src/components/dashboard/virtualized-run-list.tsx +130 -0
- package/src/components/details/.gitkeep +0 -0
- package/src/components/details/__tests__/agent-panel.test.tsx +236 -0
- package/src/components/details/__tests__/json-tree.test.tsx +347 -0
- package/src/components/details/__tests__/log-viewer.test.tsx +168 -0
- package/src/components/details/__tests__/task-detail.test.tsx +212 -0
- package/src/components/details/__tests__/timing-panel.test.tsx +271 -0
- package/src/components/details/agent-panel.tsx +234 -0
- package/src/components/details/json-tree/categorize.ts +131 -0
- package/src/components/details/json-tree/index.tsx +120 -0
- package/src/components/details/json-tree/json-node.tsx +223 -0
- package/src/components/details/json-tree/smart-summary.tsx +596 -0
- package/src/components/details/json-tree/tree-controls.tsx +47 -0
- package/src/components/details/json-tree.tsx +9 -0
- package/src/components/details/log-viewer.tsx +140 -0
- package/src/components/details/task-detail.tsx +114 -0
- package/src/components/details/timing-panel.tsx +247 -0
- package/src/components/events/.gitkeep +0 -0
- package/src/components/events/__tests__/event-item.test.tsx +211 -0
- package/src/components/events/__tests__/event-stream.test.tsx +225 -0
- package/src/components/events/event-item.tsx +121 -0
- package/src/components/events/event-stream.tsx +260 -0
- package/src/components/notifications/.gitkeep +0 -0
- package/src/components/notifications/__tests__/notification-panel.test.tsx +287 -0
- package/src/components/notifications/__tests__/notification-provider.test.tsx +585 -0
- package/src/components/notifications/__tests__/toast-stack.test.tsx +217 -0
- package/src/components/notifications/notification-panel.tsx +124 -0
- package/src/components/notifications/notification-provider.tsx +175 -0
- package/src/components/notifications/toast-stack.tsx +75 -0
- package/src/components/pipeline/.gitkeep +0 -0
- package/src/components/pipeline/__tests__/parallel-group.test.tsx +88 -0
- package/src/components/pipeline/__tests__/pipeline-view.test.tsx +345 -0
- package/src/components/pipeline/__tests__/step-card.test.tsx +330 -0
- package/src/components/pipeline/parallel-group.tsx +39 -0
- package/src/components/pipeline/pipeline-view.tsx +197 -0
- package/src/components/pipeline/step-card.tsx +166 -0
- package/src/components/providers/event-stream-provider.tsx +29 -0
- package/src/components/providers.tsx +24 -0
- package/src/components/shared/.gitkeep +0 -0
- package/src/components/shared/__tests__/empty-state.test.tsx +49 -0
- package/src/components/shared/__tests__/friendly-id.test.tsx +47 -0
- package/src/components/shared/__tests__/kbd.test.tsx +45 -0
- package/src/components/shared/__tests__/kind-badge.test.tsx +71 -0
- package/src/components/shared/__tests__/metrics-row.test.tsx +74 -0
- package/src/components/shared/__tests__/outcome-banner.test.tsx +71 -0
- package/src/components/shared/__tests__/progress-bar.test.tsx +89 -0
- package/src/components/shared/__tests__/session-pill.test.tsx +62 -0
- package/src/components/shared/__tests__/settings-modal.test.tsx +201 -0
- package/src/components/shared/__tests__/shortcuts-help.test.tsx +103 -0
- package/src/components/shared/__tests__/status-badge.test.tsx +98 -0
- package/src/components/shared/__tests__/theme-provider.test.tsx +100 -0
- package/src/components/shared/__tests__/truncated-id.test.tsx +53 -0
- package/src/components/shared/app-footer.tsx +80 -0
- package/src/components/shared/app-header.tsx +160 -0
- package/src/components/shared/empty-state.tsx +18 -0
- package/src/components/shared/error-boundary.tsx +81 -0
- package/src/components/shared/friendly-id.tsx +48 -0
- package/src/components/shared/kbd.tsx +15 -0
- package/src/components/shared/kind-badge.tsx +51 -0
- package/src/components/shared/metrics-row.tsx +106 -0
- package/src/components/shared/outcome-banner.tsx +56 -0
- package/src/components/shared/progress-bar.tsx +42 -0
- package/src/components/shared/session-pill.tsx +69 -0
- package/src/components/shared/settings-modal.tsx +509 -0
- package/src/components/shared/shortcuts-help.tsx +113 -0
- package/src/components/shared/status-badge.tsx +110 -0
- package/src/components/shared/theme-provider.tsx +46 -0
- package/src/components/shared/truncated-id.tsx +51 -0
- package/src/components/ui/.gitkeep +0 -0
- package/src/components/ui/__tests__/accordion.test.tsx +96 -0
- package/src/components/ui/__tests__/badge.test.tsx +69 -0
- package/src/components/ui/__tests__/button.test.tsx +113 -0
- package/src/components/ui/__tests__/tabs.test.tsx +75 -0
- package/src/components/ui/__tests__/tooltip.test.tsx +90 -0
- package/src/components/ui/accordion.tsx +61 -0
- package/src/components/ui/badge.tsx +25 -0
- package/src/components/ui/button.tsx +40 -0
- package/src/components/ui/card.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +35 -0
- package/src/components/ui/separator.tsx +24 -0
- package/src/components/ui/tabs.tsx +64 -0
- package/src/components/ui/tooltip.tsx +37 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/__tests__/use-animated-number.test.ts +184 -0
- package/src/hooks/__tests__/use-batched-updates.test.ts +315 -0
- package/src/hooks/__tests__/use-event-stream.test.ts +243 -0
- package/src/hooks/__tests__/use-keyboard.test.ts +217 -0
- package/src/hooks/__tests__/use-notifications.test.ts +230 -0
- package/src/hooks/__tests__/use-polling.test.ts +274 -0
- package/src/hooks/__tests__/use-project-runs.test.ts +163 -0
- package/src/hooks/__tests__/use-projects.test.ts +248 -0
- package/src/hooks/__tests__/use-run-dashboard.test.ts +168 -0
- package/src/hooks/__tests__/use-run-detail.test.ts +273 -0
- package/src/hooks/__tests__/use-smart-polling.test.ts +305 -0
- package/src/hooks/use-animated-number.ts +87 -0
- package/src/hooks/use-batched-updates.ts +150 -0
- package/src/hooks/use-event-stream.ts +150 -0
- package/src/hooks/use-keyboard.ts +45 -0
- package/src/hooks/use-notifications.ts +82 -0
- package/src/hooks/use-persisted-state.ts +60 -0
- package/src/hooks/use-polling.ts +60 -0
- package/src/hooks/use-project-runs.ts +51 -0
- package/src/hooks/use-projects.ts +26 -0
- package/src/hooks/use-run-dashboard.ts +207 -0
- package/src/hooks/use-run-detail.ts +77 -0
- package/src/hooks/use-smart-polling.ts +144 -0
- package/src/lib/.gitkeep +0 -0
- package/src/lib/__tests__/cn.test.ts +69 -0
- package/src/lib/__tests__/config-loader.test.ts +210 -0
- package/src/lib/__tests__/config.test.ts +561 -0
- package/src/lib/__tests__/error-handler.test.ts +143 -0
- package/src/lib/__tests__/fetcher.test.ts +517 -0
- package/src/lib/__tests__/global-registry.test.ts +214 -0
- package/src/lib/__tests__/parser.test.ts +1532 -0
- package/src/lib/__tests__/path-resolver.test.ts +112 -0
- package/src/lib/__tests__/run-cache.test.ts +591 -0
- package/src/lib/__tests__/server-init.test.ts +512 -0
- package/src/lib/__tests__/source-discovery.test.ts +246 -0
- package/src/lib/__tests__/utils.test.ts +160 -0
- package/src/lib/__tests__/watcher.test.ts +227 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/config-loader.ts +195 -0
- package/src/lib/config.ts +20 -0
- package/src/lib/error-handler.ts +76 -0
- package/src/lib/fetcher.ts +394 -0
- package/src/lib/global-registry.ts +117 -0
- package/src/lib/parser.ts +794 -0
- package/src/lib/path-resolver.ts +16 -0
- package/src/lib/run-cache.ts +404 -0
- package/src/lib/server-init.ts +226 -0
- package/src/lib/services/__tests__/run-query-service.test.ts +819 -0
- package/src/lib/services/run-query-service.ts +286 -0
- package/src/lib/source-discovery.ts +216 -0
- package/src/lib/utils.ts +103 -0
- package/src/lib/watcher.ts +265 -0
- package/src/test/fixtures.ts +269 -0
- package/src/test/mocks/handlers.ts +110 -0
- package/src/test/mocks/server.ts +17 -0
- package/src/test/setup.ts +200 -0
- package/src/test/test-utils.tsx +36 -0
- package/src/types/.gitkeep +0 -0
- package/src/types/breakpoint.ts +17 -0
- package/src/types/index.ts +214 -0
- package/tsconfig.json +50 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { cn } from "@/lib/cn";
|
|
2
|
+
import type { TaskKind } from "@/types";
|
|
3
|
+
import { Bot, Terminal, Puzzle, Hand, Clock, Cog } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
const kindConfig: Record<TaskKind, { icon: React.ReactNode; color: string; bgTint: string }> = {
|
|
6
|
+
agent: {
|
|
7
|
+
icon: <Bot className="h-3 w-3" />,
|
|
8
|
+
color: "text-primary",
|
|
9
|
+
bgTint: "bg-primary-muted",
|
|
10
|
+
},
|
|
11
|
+
node: {
|
|
12
|
+
icon: <Cog className="h-3 w-3" />,
|
|
13
|
+
color: "text-warning",
|
|
14
|
+
bgTint: "bg-warning-muted",
|
|
15
|
+
},
|
|
16
|
+
shell: {
|
|
17
|
+
icon: <Terminal className="h-3 w-3" />,
|
|
18
|
+
color: "text-foreground-secondary",
|
|
19
|
+
bgTint: "bg-background-secondary",
|
|
20
|
+
},
|
|
21
|
+
skill: {
|
|
22
|
+
icon: <Puzzle className="h-3 w-3" />,
|
|
23
|
+
color: "text-info",
|
|
24
|
+
bgTint: "bg-info-muted",
|
|
25
|
+
},
|
|
26
|
+
breakpoint: {
|
|
27
|
+
icon: <Hand className="h-3 w-3" />,
|
|
28
|
+
color: "text-warning",
|
|
29
|
+
bgTint: "bg-warning-muted",
|
|
30
|
+
},
|
|
31
|
+
sleep: {
|
|
32
|
+
icon: <Clock className="h-3 w-3" />,
|
|
33
|
+
color: "text-foreground-muted",
|
|
34
|
+
bgTint: "bg-background-secondary",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function KindBadge({ kind, className }: { kind: TaskKind; className?: string }) {
|
|
39
|
+
const config = kindConfig[kind] || kindConfig.agent;
|
|
40
|
+
return (
|
|
41
|
+
<span className={cn(
|
|
42
|
+
"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs leading-tight font-medium uppercase tracking-wider",
|
|
43
|
+
config.bgTint,
|
|
44
|
+
config.color,
|
|
45
|
+
className
|
|
46
|
+
)}>
|
|
47
|
+
{config.icon}
|
|
48
|
+
{kind === "breakpoint" ? "approval" : kind}
|
|
49
|
+
</span>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Clock, CheckCircle2, Percent, RefreshCw, Loader2, Pause } from "lucide-react";
|
|
2
|
+
import { cn } from "@/lib/cn";
|
|
3
|
+
import { formatDuration } from "@/lib/utils";
|
|
4
|
+
import type { Run } from "@/types";
|
|
5
|
+
|
|
6
|
+
interface MetricsRowProps {
|
|
7
|
+
run: Run;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface MetricCardProps {
|
|
11
|
+
icon: React.ReactNode;
|
|
12
|
+
label: string;
|
|
13
|
+
value: string;
|
|
14
|
+
valueColor?: string;
|
|
15
|
+
testId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function MetricCard({ icon, label, value, valueColor, testId }: MetricCardProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div data-testid={testId} className="bg-background-secondary/60 rounded-lg px-4 py-2.5 flex items-center gap-3">
|
|
21
|
+
<div className="text-foreground-muted">{icon}</div>
|
|
22
|
+
<div className="flex flex-col">
|
|
23
|
+
<span className="text-xs leading-tight text-foreground-muted uppercase tracking-wider font-medium">{label}</span>
|
|
24
|
+
<span className={cn("text-lg font-semibold tabular-nums", valueColor || "text-foreground")}>{value}</span>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function MetricsRow({ run }: MetricsRowProps) {
|
|
31
|
+
const isTerminal = run.status === "completed" || run.status === "failed";
|
|
32
|
+
const isRunning = run.status === "pending";
|
|
33
|
+
const isWaiting = run.status === "waiting";
|
|
34
|
+
|
|
35
|
+
const successRate =
|
|
36
|
+
run.totalTasks > 0
|
|
37
|
+
? Math.round((run.completedTasks / run.totalTasks) * 100)
|
|
38
|
+
: 0;
|
|
39
|
+
|
|
40
|
+
// Count unique iterations from invocationKeys
|
|
41
|
+
const iterationKeys = new Set(run.tasks.map((t) => t.invocationKey));
|
|
42
|
+
const iterationCount = iterationKeys.size;
|
|
43
|
+
|
|
44
|
+
// Semantic color for success rate (neon palette)
|
|
45
|
+
const successRateColor = successRate === 100
|
|
46
|
+
? "text-success"
|
|
47
|
+
: successRate >= 80
|
|
48
|
+
? "text-foreground"
|
|
49
|
+
: successRate >= 50
|
|
50
|
+
? "text-warning"
|
|
51
|
+
: "text-error";
|
|
52
|
+
|
|
53
|
+
// Duration display and color
|
|
54
|
+
const durationValue = isTerminal ? formatDuration(run.duration) : formatDuration(run.duration) || "...";
|
|
55
|
+
const durationColor = isRunning ? "text-info" : isWaiting ? "text-warning" : undefined;
|
|
56
|
+
|
|
57
|
+
// Status-specific icon for duration
|
|
58
|
+
const durationIcon = isRunning
|
|
59
|
+
? <Loader2 className="h-4 w-4 animate-spin-smooth text-info drop-shadow-[var(--drop-glow-cyan)]" />
|
|
60
|
+
: isWaiting
|
|
61
|
+
? <Pause className="h-4 w-4 text-warning" />
|
|
62
|
+
: <Clock className="h-4 w-4" />;
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div data-testid="metrics-row" className={cn(
|
|
66
|
+
"flex items-center divide-x divide-primary/10 px-4 py-3 border-b border-border bg-background",
|
|
67
|
+
(isRunning || isWaiting) && "opacity-90"
|
|
68
|
+
)}>
|
|
69
|
+
<div className="flex items-center pr-3">
|
|
70
|
+
<MetricCard
|
|
71
|
+
icon={durationIcon}
|
|
72
|
+
label="Total Duration"
|
|
73
|
+
value={durationValue}
|
|
74
|
+
valueColor={durationColor}
|
|
75
|
+
testId="metric-total-duration"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
<div className="flex items-center px-3">
|
|
79
|
+
<MetricCard
|
|
80
|
+
icon={<CheckCircle2 className="h-4 w-4" />}
|
|
81
|
+
label="Tasks"
|
|
82
|
+
value={`${run.completedTasks}/${run.totalTasks}`}
|
|
83
|
+
valueColor="text-success"
|
|
84
|
+
testId="metric-tasks"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="flex items-center px-3">
|
|
88
|
+
<MetricCard
|
|
89
|
+
icon={<Percent className="h-4 w-4" />}
|
|
90
|
+
label="Success Rate"
|
|
91
|
+
value={`${successRate}%`}
|
|
92
|
+
valueColor={successRateColor}
|
|
93
|
+
testId="metric-success-rate"
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
<div className="flex items-center pl-3">
|
|
97
|
+
<MetricCard
|
|
98
|
+
icon={<RefreshCw className="h-4 w-4" />}
|
|
99
|
+
label="Iterations"
|
|
100
|
+
value={String(iterationCount)}
|
|
101
|
+
testId="metric-iterations"
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CheckCircle2, XCircle } from "lucide-react";
|
|
2
|
+
import { formatDuration } from "@/lib/utils";
|
|
3
|
+
import type { Run } from "@/types";
|
|
4
|
+
|
|
5
|
+
interface OutcomeBannerProps {
|
|
6
|
+
run: Run;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function OutcomeBanner({ run }: OutcomeBannerProps) {
|
|
10
|
+
if (run.status === "completed") {
|
|
11
|
+
return (
|
|
12
|
+
<div data-testid="outcome-banner" data-status="completed" className="bg-success-muted border-b-2 border-success/30 px-5 py-4 shadow-glow-success">
|
|
13
|
+
<div className="flex items-center gap-3 text-success">
|
|
14
|
+
<CheckCircle2 className="h-5 w-5 shrink-0 drop-shadow-[var(--drop-glow-success)]" />
|
|
15
|
+
<span className="text-base font-medium">
|
|
16
|
+
Completed in {formatDuration(run.duration)}
|
|
17
|
+
</span>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (run.status === "failed") {
|
|
24
|
+
const failedTask = run.tasks.find((t) => t.status === "error");
|
|
25
|
+
|
|
26
|
+
// Determine step name: prefer task-level label, then run-level failedStep, then "process error" for run-level failures
|
|
27
|
+
const stepName = failedTask?.label || run.failedStep || (run.failureMessage ? "process error" : "unknown step");
|
|
28
|
+
|
|
29
|
+
// Determine error message: prefer task-level error, then run-level failure message from RUN_FAILED event
|
|
30
|
+
const rawErrorMessage = failedTask?.error?.message || run.failureMessage || "An error occurred";
|
|
31
|
+
|
|
32
|
+
// Format the error message — if it looks like JSON, try to extract the most relevant part
|
|
33
|
+
let errorMessage = rawErrorMessage;
|
|
34
|
+
if (rawErrorMessage.startsWith("{") || rawErrorMessage.startsWith("[")) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(rawErrorMessage);
|
|
37
|
+
errorMessage = parsed.message || parsed.error || parsed.reason || rawErrorMessage;
|
|
38
|
+
} catch {
|
|
39
|
+
// Not valid JSON, use as-is
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div data-testid="outcome-banner" data-status="failed" className="bg-error-muted border-b-2 border-error/30 px-5 py-4 shadow-glow-error">
|
|
45
|
+
<div className="flex items-center gap-3 text-error">
|
|
46
|
+
<XCircle className="h-5 w-5 shrink-0 drop-shadow-[var(--drop-glow-error)]" />
|
|
47
|
+
<span className="text-base font-medium">
|
|
48
|
+
Failed at step: {stepName} — {errorMessage}
|
|
49
|
+
</span>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cn } from "@/lib/cn";
|
|
2
|
+
|
|
3
|
+
type ProgressVariant = "default" | "success" | "error" | "warning";
|
|
4
|
+
|
|
5
|
+
const variantStyles: Record<ProgressVariant, string> = {
|
|
6
|
+
default: "bg-primary",
|
|
7
|
+
success: "bg-success",
|
|
8
|
+
error: "bg-error",
|
|
9
|
+
warning: "bg-warning",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const variantGlow: Record<ProgressVariant, string> = {
|
|
13
|
+
default: "shadow-progress-glow-primary",
|
|
14
|
+
success: "shadow-progress-glow-success",
|
|
15
|
+
error: "shadow-progress-glow-error",
|
|
16
|
+
warning: "shadow-progress-glow-warning",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface ProgressBarProps {
|
|
20
|
+
value: number; // 0-100
|
|
21
|
+
variant?: ProgressVariant;
|
|
22
|
+
glow?: boolean;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ProgressBar({ value, variant = "default", glow = false, className }: ProgressBarProps) {
|
|
27
|
+
const clamped = Math.min(100, Math.max(0, value));
|
|
28
|
+
const isComplete = clamped === 100;
|
|
29
|
+
return (
|
|
30
|
+
<div className={cn("h-2 w-full overflow-hidden rounded-full bg-background-muted", className)}>
|
|
31
|
+
<div
|
|
32
|
+
className={cn(
|
|
33
|
+
"h-full transition-all duration-500 ease-out",
|
|
34
|
+
variantStyles[variant],
|
|
35
|
+
glow && variantGlow[variant],
|
|
36
|
+
isComplete ? "rounded-full" : "rounded-l-full"
|
|
37
|
+
)}
|
|
38
|
+
style={{ width: `${clamped}%` }}
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
|
+
import { formatShortId } from "@/lib/utils";
|
|
6
|
+
import {
|
|
7
|
+
Tooltip,
|
|
8
|
+
TooltipContent,
|
|
9
|
+
TooltipProvider,
|
|
10
|
+
TooltipTrigger,
|
|
11
|
+
} from "@/components/ui/tooltip";
|
|
12
|
+
|
|
13
|
+
interface SessionPillProps {
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
active?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function SessionPill({ sessionId, active = false, className }: SessionPillProps) {
|
|
20
|
+
const [copied, setCopied] = useState(false);
|
|
21
|
+
|
|
22
|
+
if (!sessionId) return null;
|
|
23
|
+
|
|
24
|
+
const handleCopy = (e: React.MouseEvent) => {
|
|
25
|
+
e.stopPropagation();
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
navigator.clipboard.writeText(sessionId);
|
|
28
|
+
setCopied(true);
|
|
29
|
+
setTimeout(() => setCopied(false), 1500);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<TooltipProvider>
|
|
34
|
+
<Tooltip>
|
|
35
|
+
<TooltipTrigger asChild>
|
|
36
|
+
<span
|
|
37
|
+
className={cn(
|
|
38
|
+
"relative inline-flex items-center gap-1.5 rounded-full bg-background-secondary px-2.5 py-0.5 text-xs font-mono text-foreground-muted cursor-pointer transition-all hover:bg-background-tertiary select-none",
|
|
39
|
+
active && "shadow-neon-glow-cyan-sm",
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
onClick={handleCopy}
|
|
43
|
+
>
|
|
44
|
+
<span
|
|
45
|
+
className={cn(
|
|
46
|
+
"h-1.5 w-1.5 rounded-full",
|
|
47
|
+
active ? "bg-secondary shadow-[0_0_6px_var(--secondary)] animate-pulse-dot" : "bg-foreground-muted/40"
|
|
48
|
+
)}
|
|
49
|
+
/>
|
|
50
|
+
{formatShortId(sessionId, 4)}
|
|
51
|
+
{copied && (
|
|
52
|
+
<span className="absolute -top-7 left-1/2 -translate-x-1/2 rounded-md bg-primary px-2 py-0.5 text-xs leading-tight font-sans font-medium text-primary-foreground whitespace-nowrap animate-slide-in-right">
|
|
53
|
+
Copied!
|
|
54
|
+
</span>
|
|
55
|
+
)}
|
|
56
|
+
</span>
|
|
57
|
+
</TooltipTrigger>
|
|
58
|
+
<TooltipContent>
|
|
59
|
+
<p className="font-mono text-xs">
|
|
60
|
+
{copied ? "Copied!" : `Session: ${sessionId}`}
|
|
61
|
+
</p>
|
|
62
|
+
{!copied && (
|
|
63
|
+
<p className="text-foreground-muted text-xs leading-tight mt-0.5">Click to copy</p>
|
|
64
|
+
)}
|
|
65
|
+
</TooltipContent>
|
|
66
|
+
</Tooltip>
|
|
67
|
+
</TooltipProvider>
|
|
68
|
+
);
|
|
69
|
+
}
|