import type { SiteFailureSample, SiteFailureSampleSummary } from "@umt/contracts"; import { Download } from "lucide-react"; import { useState } from "react"; import { useSiteFailureSample } from "~/api/sites.ts"; import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { KvList } from "~/components/console/kv-list.tsx"; import { Button } from "~/components/ui/button.tsx"; import { relTime } from "~/components/workbench/release-fields.ts"; import { failureSampleFileName, failureSampleItems, failureSampleText, failureSampleTitle } from "./site-fields.ts"; // 站点请求失败留样(技术信息里):最近几次「页面结构无法识别」/「人机验证」时站点返回的页面(已脱敏), // 用来分清是站点改版还是被人机验证拦了。列表不带正文,展开时再取。 function download(s: SiteFailureSample) { const url = URL.createObjectURL(new Blob([failureSampleText(s)], { type: "text/plain;charset=utf-8" })); const a = document.createElement("a"); a.href = url; a.download = failureSampleFileName(s); a.click(); URL.revokeObjectURL(url); } function SampleBody({ siteId, sampleId }: { siteId: string; sampleId: string }) { const q = useSiteFailureSample(siteId, sampleId, true); if (q.isPending) return ; if (q.error) return void q.refetch()} />; return (
        {q.data.body || "(页面是空的)"}
      
); } /** 一份样本:标题一行,展开看请求信息与页面 */ export function FailureSampleRow({ siteId, sample }: { siteId: string; sample: SiteFailureSampleSummary }) { const [open, setOpen] = useState(false); return (
  • {open ? (
    ) : null}
  • ); } /** 最近的失败样本(列表由调用方用 useSiteFailureSamples 取);没有就不渲染 */ export function SiteFailureSamples({ siteId, items }: { siteId: string; items: SiteFailureSampleSummary[] }) { if (!items.length) return null; return (

    最近的失败页面(已脱敏,最多保留 5 份)

      {items.map((s) => ( ))}
    ); }