import type { IdentityPoolItem, PoolCandidate } from "@umt/contracts"; import { ArrowRight, BellOff, ExternalLink, LoaderCircle, RotateCw } from "lucide-react"; import { useId, useState } from "react"; import { Link } from "react-router"; import { toast } from "sonner"; import { useAssignPool, useDismissPool, useRetryPool } from "~/api/identity.ts"; import { DetailSection, DetailSheet } from "~/components/console/detail-sheet.tsx"; import { FormField } from "~/components/console/form-field.tsx"; import { JsonView } from "~/components/console/json-view.tsx"; import { KvList } from "~/components/console/kv-list.tsx"; import { consolePath } from "~/components/console/nav.ts"; import { EntityPicker, type PickedEntity } from "~/components/library/identity-entity-picker.tsx"; import { Button } from "~/components/ui/button.tsx"; import { Input } from "~/components/ui/input.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select.tsx"; import { errorMessage } from "~/lib/api.ts"; import { formatDateTime } from "~/lib/utils.ts"; import { EnrichSection } from "./identity-enrich.tsx"; import { EVIDENCE_LEVEL, POOL_REASON, POOL_STATUS } from "./labels.ts"; import { Chip, Tone } from "./parts.tsx"; // 待识别资源详情:候选证据 + 重新识别 + 手动指定。进了待处理的(与订阅相关的歧义 / 无结果)在待处理里选候选; // 没进待处理的(unmatched:未命中订阅,惰性识别没查 TMDB)在这里手动指定 TMDB ID / 本地作品,或忽略。 /** 与订阅 / 本地作品相关的歧义开"待确认身份"事项,相关却无结果开"待识别"事项(同来源同标题合并);unmatched 不进待处理 */ export function inInbox(item: Pick): boolean { return item.status === "pending" && (item.decisionId !== null || hasCandidates(item)); } function hasCandidates(item: Pick): boolean { return (item.reason === "ambiguous" || item.reason === "proposed") && item.candidates.length > 0; } export const INBOX_IDENTITY = `${consolePath("inbox")}?kind=identity_ambiguous`; export const INBOX_UNRESOLVED = `${consolePath("inbox")}?kind=identity_unresolved`; /** 条目对应的待处理筛选链接 */ export function inboxLink(item: Pick): string { return hasCandidates(item) ? INBOX_IDENTITY : INBOX_UNRESOLVED; } /** query 摘要:标题 / 年份 / 季集 */ export function querySummary(q: Record): string { const parts: string[] = []; if (q.title) parts.push(String(q.title)); if (q.year) parts.push(String(q.year)); if (q.season !== undefined && q.season !== null) parts.push(`S${String(q.season).padStart(2, "0")}`); if (q.episode !== undefined && q.episode !== null) parts.push(`E${String(q.episode).padStart(2, "0")}`); if (q.segment) parts.push(String(q.segment)); if (q.cut) parts.push(String(q.cut)); return parts.join(" · ") || "—"; } function CandidateRow({ c, index }: { c: PoolCandidate; index: number }) { return (
  • {index + 1}. {c.title} {c.year ? ` (${c.year})` : ""}

    {c.provider}:{c.entityType}:{c.externalId}

    {EVIDENCE_LEVEL[c.level] ?? c.level} {c.localEntityId ? ( 本地作品 ) : ( 不在媒体库 )}
    {c.evidence.length ? (
      {c.evidence.map((e) => (
    • {e}
    • ))}
    ) : null}
  • ); } /** 手动指定(复用 /identity-pool/{id}/assign,加人工锁)与忽略 */ function ManualAssign({ item }: { item: IdentityPoolItem }) { const uid = useId(); const assign = useAssignPool(); const dismiss = useDismissPool(); const [tmdbType, setTmdbType] = useState<"movie" | "tv">(item.query.season != null ? "tv" : "movie"); const [tmdbId, setTmdbId] = useState(""); const [entity, setEntity] = useState(null); const tmdbOk = /^\d{1,10}$/.test(tmdbId.trim()); const run = async (body: Parameters[0], ok: string) => { try { await assign.mutateAsync(body); toast.success(ok); } catch (err) { toast.error(errorMessage(err, "指定失败")); } }; return (
    setTmdbId(e.target.value)} aria-invalid={tmdbId.trim() !== "" && !tmdbOk} />
    ); } export function PoolSheet({ item, onClose }: { item: IdentityPoolItem | null; onClose: () => void }) { const retry = useRetryPool(); if (!item) return null; const queued = inInbox(item); return ( !o && onClose()} title={item.release.title} description={item.release.subtitle ?? undefined} footer={ <> {queued ? ( ) : null} } > {queued ? (

    已进待处理,在那里选择候选或忽略。

    ) : null} }, { label: "原因", value: }, { label: "解析结果", value: querySummary(item.query), wide: true }, { label: "站点分类", value: item.release.categoryRaw }, { label: "首次发现", value: formatDateTime(item.release.firstSeenAt) }, { label: "尝试次数", value: String(item.attempts) }, { label: "上次尝试", value: formatDateTime(item.lastAttemptAt) }, { label: "下次复查", value: item.nextReviewAt ? formatDateTime(item.nextReviewAt) : "不再自动复查" }, { label: "证据版本", value: item.evidenceVersion, mono: true }, ]} /> {item.status !== "resolved" && !queued ? : null} {item.status === "pending" && item.reason !== "ambiguous" && item.reason !== "proposed" ? ( ) : null} {item.candidates.length ? (
      {item.candidates.map((c, i) => ( ))}
    ) : (

    {item.reason === "unmatched" ? "没有命中订阅或本地作品,未查询 TMDB。点「重新识别」会查一次 TMDB;新增相近别名时自动重试。" : "没有候选。有新证据时自动重试。"}

    )}
    ); }