Pipeline/frontend/src/components/dashboard/BotStatusSection.tsx

81 lines
2.6 KiB
TypeScript
Raw Normal View History

2026-05-26 15:41:24 -05:00
"use client";
import { Bot, Clock } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { getLatestBotReport, type BotReportRead } from "@/lib/api/bot";
import { DashboardSection } from "./DashboardSection";
function timeAgo(iso: string): string {
const diff = Math.floor((Date.now() - new Date(iso).getTime()) / 1000);
if (diff < 60) return `${diff}s ago`;
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
return `${Math.floor(diff / 86400)}d ago`;
}
function StatusBadge({ status }: { status: string | null }) {
if (!status) return null;
const styles: Record<string, string> = {
busy: "bg-[color:rgba(251,191,36,0.15)] text-[color:var(--warning)]",
idle: "bg-[color:rgba(52,211,153,0.15)] text-[color:var(--success)]",
error: "bg-[color:rgba(248,113,113,0.12)] text-[color:var(--danger)]",
};
const style = styles[status.toLowerCase()] ?? "bg-[color:var(--surface-strong)] text-muted";
return (
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${style}`}>
{status}
</span>
);
}
function ReportContent({ report }: { report: BotReportRead }) {
return (
<div className="space-y-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="text-2xl font-semibold text-strong truncate">
{report.project ?? "—"}
</p>
{report.task && (
<p className="mt-1 text-sm text-muted truncate">{report.task}</p>
)}
</div>
<StatusBadge status={report.status} />
</div>
<div className="flex items-center gap-1.5 text-xs text-muted">
<Clock className="h-3 w-3" />
{timeAgo(report.reported_at)}
</div>
</div>
);
}
function EmptyState() {
return (
<div className="flex items-center gap-3 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] px-4 py-3 text-sm text-muted">
<Bot className="h-4 w-4 shrink-0" />
Ripley hasn&apos;t reported in yet.
</div>
);
}
export function BotStatusSection() {
const { data: report } = useQuery({
queryKey: ["bot-report-latest"],
queryFn: getLatestBotReport,
refetchInterval: 30_000,
});
return (
<DashboardSection
title="Ripley"
tone="warning"
action={{ label: "Manage keys", href: "/settings/bot-api-keys" }}
infoText="Current project reported by the OpenClaw agent"
>
{report ? <ReportContent report={report} /> : <EmptyState />}
</DashboardSection>
);
}