import { LoaderCircle, Sparkles, Wand2 } from "lucide-react"; import { useEffect } from "react"; import { Link } from "react-router"; import { type FileEntry, type FileRoot, filesErrorText } from "~/api/files.ts"; import { RECORD_STATUS_LABEL, RECORD_STATUS_TONE } from "~/api/records.ts"; import { DetailSection } from "~/components/console/detail-sheet.tsx"; import { KvList } from "~/components/console/kv-list.tsx"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { candidateEntity, LEVEL, reasonsText } from "~/components/organize-wizard/entity-picker.tsx"; import type { WizardEntity } from "~/components/organize-wizard/model.ts"; import { Button } from "~/components/ui/button.tsx"; import { contentUnitLabel } from "./model.ts"; import { useIdentifyLive } from "./use-identify-live.ts"; const KIND: Record = { movie: "电影", series: "剧集", variety: "综艺" }; /** * 详情抽屉"识别"节(§7.1):解析字段 + Top5 候选(等级、理由),每个候选 `按此整理` 打开向导并预填作品。 * 识别只读,不写库。autoRun:从菜单"识别"进入时自动跑一次。 */ export function IdentifyPanel({ root, entry, autoRun, onOrganize, }: { root: FileRoot; entry: FileEntry; autoRun?: boolean; onOrganize?: (preset: WizardEntity | null) => void; }) { const identify = useIdentifyLive(); const mutate = identify.mutate; // biome-ignore lint/correctness/useExhaustiveDependencies: 换文件或 autoRun 时重跑一次 useEffect(() => { identify.reset(); if (autoRun) mutate({ items: [{ root: root.id, path: entry.path }], minSizeBytes: 0 }); }, [autoRun, root.id, entry.path]); const run = () => mutate({ items: [{ root: root.id, path: entry.path }], minSizeBytes: 0 }); const it = identify.data?.items[0]; const isDir = entry.type === "dir"; return ( {identify.isPending ? : } {identify.data ? "重新识别" : "识别"} } > {identify.isPending ? (

匹配本地作品与 TMDB…

) : identify.error ? (

{filesErrorText(identify.error, "识别失败")}

) : !identify.data ? (

按文件名解析,匹配本地作品和 TMDB。只读,不改文件。

) : !it ? (

{isDir ? "目录里没有视频文件。" : "没有识别结果。"}

) : (
{isDir && (identify.data.items.length > 1 || identify.data.truncated) ? (

目录里有 {identify.data.items.length} {identify.data.truncated ? "+" : ""} 个视频,这里只显示第一个。整理时逐项确认。

) : null} {it.records?.count ? (

这个源文件有 {it.records.count} 条整理记录 {it.records.lastStatus ? ( 最近:{RECORD_STATUS_LABEL[it.records.lastStatus]} ) : null} {it.records.lastRecordId ? ( 查看 ) : null}

) : null} {it.skipped === "below_min_size" ? (

低于最小体积,没有查候选。

) : null} {it.warnings.map((w) => (

{w}

))} {it.candidates.length ? (
    {it.candidates.map((c) => { const e = candidateEntity(c); return (
  • {c.posterUrl ? ( ) : ( )}

    {c.title} {c.year ? ` (${c.year})` : ""} 置信 {LEVEL[c.level]?.label ?? c.level} {c.mediaEntityId ? null : 仅 TMDB}

    {[ KIND[c.kind] ?? c.kind, c.externalId ? `${c.provider.toUpperCase()} ${c.externalId}` : null, reasonsText(c.reasons), ] .filter(Boolean) .join(" · ")}

    {onOrganize && e ? ( ) : null}
  • ); })}
) : it.skipped ? null : (

没有可信候选。整理时可以手动搜索作品。

)}
)}
); }