81 lines
2.6 KiB
TypeScript
81 lines
2.6 KiB
TypeScript
|
|
"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'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>
|
||
|
|
);
|
||
|
|
}
|