import { useQueryClient } from "@tanstack/react-query"; import type { Dashboard } from "@umt/contracts"; import { Activity, AlertTriangle, ArrowRight, CircleCheck, HardDrive, ListTodo, type LucideIcon, Pause, Play, Radio, ShieldAlert, Sparkles, } from "lucide-react"; import type { ReactNode } from "react"; import { useRef } from "react"; import { Link } from "react-router"; import { toast } from "sonner"; import { useDashboard } from "~/api/dashboard.ts"; import { useUpdateSettings } from "~/api/settings.ts"; import { ConfirmDialog } from "~/components/console/confirm-dialog.tsx"; import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { consolePath } from "~/components/console/nav.ts"; import { PageHeader } from "~/components/console/page-header.tsx"; import { StatTile } from "~/components/console/stat-tile.tsx"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { Button } from "~/components/ui/button.tsx"; import { Switch } from "~/components/ui/switch.tsx"; import { errorMessage } from "~/lib/api.ts"; import { agentRunLabel, decisionKindLabel, downloadLabel, formatRelative, healthLabel, organizationActionLabel, organizationLabel, } from "~/lib/labels.ts"; import { qk } from "~/lib/query-keys.ts"; import { useSse, useSseStatus } from "~/lib/sse.ts"; import { cn, formatBytes } from "~/lib/utils.ts"; /** 首页关心的事件:任意一个到达就(节流)刷新聚合 */ const DASHBOARD_EVENTS = [ "task.created", "task.status_changed", "download.submitted", "download.status_changed", "download.threshold", "organization.changed", "agent.run_status", "feed.fetched", "feed.health_changed", "decision.created", "decision.updated", "settings.changed", ]; function useLiveDashboard() { const qc = useQueryClient(); const timer = useRef | null>(null); useSse(DASHBOARD_EVENTS, () => { if (timer.current) return; timer.current = setTimeout(() => { timer.current = null; qc.invalidateQueries({ queryKey: qk.dashboard }); }, 1500); }); return useDashboard(); } function Section({ title, icon: Icon, to, children, className, }: { title: string; icon: LucideIcon; to?: string; children: ReactNode; className?: string; }) { return (

{title}

{to ? ( 查看 ) : null}
{children}
); } function Row({ children }: { children: ReactNode }) { return
  • {children}
  • ; } // ─── 自动化总开关:显眼状态 + 切换 ─── function AutomationBanner({ a }: { a: Dashboard["automation"] }) { const update = useUpdateSettings(); const setAutomation = async (patch: { enabled?: boolean; readOnlyReconcile?: boolean }, ok: string) => { try { await update.mutateAsync({ automation: patch }); toast.success(ok); } catch (err) { toast.error(errorMessage(err, "切换失败")); throw err; } }; const paused = !a.enabled; const readOnly = a.readOnlyReconcile; return (
    {readOnly ? ( ) : paused ? ( ) : ( )}

    {paused ? "自动化已暂停" : readOnly ? "只读对账中" : "自动化运行中"}

    总开关{paused ? "关" : "开"} {readOnly ? 只读对账 : null}

    {paused ? "自动收录、下载、整理都已停下;查询与手动操作仍可用。" : readOnly ? "恢复后的只读对账:只核对不写入,确认无误后再关掉。" : `${a.activeSubscriptions} 个订阅在追踪,按规则自动收录、下载与入库。`}

    setAutomation({ readOnlyReconcile: v }, v ? "已进入只读对账模式" : "已退出只读对账模式").catch(() => {}) } aria-label="只读对账模式" />
    {paused ? ( ) : ( 暂停自动化 } title="暂停全部自动化?" description="自动收录、下载、整理会停下,已提交的下载任务不受影响。随时可以恢复。" confirmLabel="暂停" onConfirm={() => setAutomation({ enabled: false }, "自动化已暂停")} /> )}
    ); } function Overview({ a }: { a: Dashboard["automation"] }) { const t = a.today; return (

    整理成功 {t.recordsSucceeded} · 失败 {t.recordsFailed} · 满足的版本目标 {a.slotCounts.satisfied} · 需关注{" "} {a.slotCounts.attention}

    ); } function CurrentWork({ w }: { w: Dashboard["currentWork"] }) { const counts = [ { label: "下载中", v: w.downloading }, { label: "已暂停", v: w.paused }, { label: "无进展", v: w.stalled }, { label: "入库中", v: w.importing }, ]; const idle = counts.every((c) => c.v === 0) && w.items.length === 0 && w.agentRuns.length === 0; return (
    {counts.map((c) => ( {c.label} {c.v} ))}
    {idle ? ( ) : (
      {w.items.slice(0, 5).map((it) => { const s = downloadLabel(it.status); return ( {it.name} {Math.round(it.progress * 100)}% {s.label} ); })} {w.agentRuns.slice(0, 3).map((r) => { const s = agentRunLabel(r.status); return ( Agent:{r.goal} {s.label} ); })}
    )}
    ); } function NeedsAttention({ n }: { n: Dashboard["needsAttention"] }) { return (
    {n.total === 0 ? ( ) : ( <>

    {n.total} 项待处理

      {n.byKind.map((k) => ( {decisionKindLabel(k.kind)} {k.count} ))}
    )}
    ); } function CacheSection({ c }: { c: Dashboard["cache"] }) { return (
    {c.volumes.length === 0 ? ( ) : (
      {c.volumes.map((v) => { const pct = v.usedPercent; const over = pct !== null && pct >= v.hardThresholdPercent; const near = pct !== null && pct >= v.resumePercent; return (
    • {v.volumeKey} {pct === null ? "用量未知" : `${pct.toFixed(0)}%${over ? " 已达阈值" : ""}`}

      阈值 {v.hardThresholdPercent}% / 恢复 {v.resumePercent}% · 在途 {formatBytes(v.pendingBytes)} · 可清理{" "} {formatBytes(v.cleanableBytes)} · 保留期锁定 {formatBytes(v.retentionLockedBytes)} {v.pausedByThreshold ? ` · 因阈值暂停 ${v.pausedByThreshold} 个` : ""}

      {v.notes.map((n) => (

      {n}

      ))}
    • ); })}
    )}
    ); } function SourcesSection({ s }: { s: Dashboard["sources"] }) { const rows = [ ...s.sites.map((x) => ({ id: x.id, kind: "站点", name: x.name, enabled: x.enabled, health: x.health, at: x.lastSnapshotAt, err: null as string | null, })), ...s.feeds.map((x) => ({ id: x.id, kind: "Feed", name: x.name, enabled: x.enabled, health: x.health, at: x.lastSuccessAt, err: x.lastError, })), ...s.downloaders.map((x) => ({ id: x.id, kind: "下载器", name: x.name, enabled: x.enabled, health: x.health, at: x.lastSeenAt, err: null, })), ...s.mediaServers.map((x) => ({ id: x.id, kind: "媒体服务器", name: x.name, enabled: x.enabled, health: x.health, at: x.lastSeenAt, err: null, })), ]; return (
    {rows.length === 0 ? ( ) : (
      {rows.slice(0, 8).map((r) => { const h = r.enabled ? healthLabel(r.health) : healthLabel("disabled"); return (

      {r.kind} {r.name}

      {r.err ?

      {r.err}

      : null}
      {formatRelative(r.at)} {h.label}
      ); })}
    )}
    ); } function RecentOutput({ items }: { items: Dashboard["recentOutput"] }) { return (
    {items.length === 0 ? ( ) : (
      {items.slice(0, 6).map((r) => { const s = organizationLabel(r.status); return (
    • {r.title ?? "未命名"} {s.label}

      {organizationActionLabel(r.action)} · {r.counts.succeeded}/{r.counts.total} 个文件 ·{" "} {formatRelative(r.finishedAt)} {r.targetPath ? ` · ${r.targetPath}` : ""}

    • ); })}
    )}
    ); } function LiveIndicator() { const s = useSseStatus(); const tone = s === "open" ? "success" : s === "reconnecting" ? "warning" : "neutral"; const label = s === "open" ? "实时" : s === "reconnecting" ? "重连中" : "连接中"; return {label}; } export default function Home() { const q = useLiveDashboard(); return ( <> {q.data ? ( 更新于 {formatRelative(q.data.generatedAt)} ) : null} } /> {q.isPending ? (
    {["a", "b", "c", "d", "e", "f"].map((k) => (
    ))}
    ) : q.isError ? ( q.refetch()} /> ) : ( <>
    )} ); }