feat(scrape): 入库后自动刮削缺省开启,没排上时在整理记录上记「未刮削(原因)」
- scrape_record 状态加 skipped(迁移 0074);目标不在媒体库、库根只登记、作品没有 TMDB id 也没有本地元数据、排队失败时 留一条挂在整理记录上的「未刮削」记录(同原因只记一次),设置关闭不记; - 缺省开启 scrapeAfterImport,只对有 TMDB id 或本地元数据的作品排; - 已结束记录重放与收尾同一口径补排刮削(按整理记录幂等); - 整理详情「整理后刮削」显示未刮削原因,并列出失败与非预期跳过(缺元数据、认不出集号)的文件。 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
8c4c7d028a
commit
cdf62dbf02
12 files changed
+320
-49
No files matched your search
@@ -21,6 +21,7 @@ export const SCRAPE_STATUS: Record<ScrapeRecord["status"], { label: string; tone
|
||||
succeeded: { label: "已完成", tone: "success" },
|
||||
partial: { label: "部分完成", tone: "warning" },
|
||||
failed: { label: "失败", tone: "destructive" },
|
||||
skipped: { label: "未刮削", tone: "neutral" },
|
||||
};
|
||||
|
||||
const ACTION: Record<ScrapeFileResult["action"], { label: string; tone: StatusTone }> = {
|
||||
@@ -82,6 +83,8 @@ function FileRow({ f }: { f: ScrapeFileResult }) {
|
||||
|
||||
/** 不必逐条列出的跳过:已存在(不是本工具写的 / 只补缺失)、内容没变 */
|
||||
const QUIET_SKIPS = new Set(["scrape_exists", "scrape_unchanged"]);
|
||||
/** 预期内的跳过(已存在、没变、被人改过);其余跳过(缺元数据、认不出集号)让记录算部分完成 */
|
||||
export const EXPECTED_SCRAPE_SKIPS = new Set([...QUIET_SKIPS, "scrape_user_modified"]);
|
||||
|
||||
/** 一条刮削记录:状态、目录与识别结果、计数、逐文件结果(缺省不列"已存在"跳过的) */
|
||||
export function ScrapeRecordView({ record: s, showPath = false }: { record: ScrapeRecord; showPath?: boolean }) {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { FilesScrapeRecordsResult } from "@umt/contracts";
|
||||
import { FilesScrapeRecordsResult, type ScrapeFileResult } from "@umt/contracts";
|
||||
import { Link } from "react-router";
|
||||
import { apiParsed } from "~/api/parse.ts";
|
||||
import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
|
||||
import { PaginatedList } from "~/components/console/paginated-list.tsx";
|
||||
import { StatusBadge } from "~/components/console/status-badge.tsx";
|
||||
import { SCRAPE_STATUS, scrapeCounts } from "~/components/library/scrape-panel.tsx";
|
||||
import { EXPECTED_SCRAPE_SKIPS, SCRAPE_STATUS, scrapeCounts } from "~/components/library/scrape-panel.tsx";
|
||||
import { reasonLine } from "~/lib/reasons.ts";
|
||||
import { formatDateTime } from "~/lib/utils.ts";
|
||||
|
||||
/** 失败的文件,以及缺元数据、认不出集号等不在预期内的跳过 */
|
||||
const unfinished = (files: ScrapeFileResult[]) =>
|
||||
files.filter((f) => f.action === "failed" || (f.action === "skipped" && !EXPECTED_SCRAPE_SKIPS.has(f.reason ?? "")));
|
||||
|
||||
/** 整理与刮削分别展示结果,避免将刮削失败当作文件搬运失败。 */
|
||||
export function OrganizeScrapeHistory({ recordId }: { recordId: string }) {
|
||||
const query = useQuery({
|
||||
@@ -39,21 +43,36 @@ export function OrganizeScrapeHistory({ recordId }: { recordId: string }) {
|
||||
<p className="break-all font-mono text-meta text-muted-foreground">
|
||||
{record.match?.title ?? record.path} · {record.path || "/"}
|
||||
</p>
|
||||
<p className="text-meta text-muted-foreground">{scrapeCounts(record) || "等待写入 NFO 与图片"}</p>
|
||||
{record.reason ? <p className="text-meta text-destructive">{reasonLine(record.reason)}</p> : null}
|
||||
{record.files.some((file) => file.action === "failed") ? (
|
||||
{record.status === "skipped" ? null : (
|
||||
<p className="text-meta text-muted-foreground">{scrapeCounts(record) || "等待写入 NFO 与图片"}</p>
|
||||
)}
|
||||
{record.reason ? (
|
||||
<p
|
||||
className={
|
||||
record.status === "skipped" ? "text-meta text-muted-foreground" : "text-meta text-destructive"
|
||||
}
|
||||
>
|
||||
{reasonLine(record.reason)}
|
||||
</p>
|
||||
) : null}
|
||||
{unfinished(record.files).length ? (
|
||||
<details className="text-meta">
|
||||
<summary className="cursor-pointer text-muted-foreground">失败文件</summary>
|
||||
<summary className="cursor-pointer text-muted-foreground">失败或没写的文件</summary>
|
||||
<PaginatedList
|
||||
rows={record.files.filter((file) => file.action === "failed")}
|
||||
label="刮削失败文件"
|
||||
rows={unfinished(record.files)}
|
||||
label="刮削失败或跳过的文件"
|
||||
resetKey={record.id}
|
||||
as="ul"
|
||||
className="mt-2 space-y-1"
|
||||
>
|
||||
{(file) => (
|
||||
<li key={`${file.path}:${file.role}`} className="break-all text-destructive">
|
||||
{file.path} · {reasonLine(file.reason) || "写入失败"}
|
||||
<li
|
||||
key={`${file.path}:${file.role}`}
|
||||
className={
|
||||
file.action === "failed" ? "break-all text-destructive" : "break-all text-muted-foreground"
|
||||
}
|
||||
>
|
||||
{file.path} · {reasonLine(file.reason) || (file.action === "failed" ? "写入失败" : "跳过")}
|
||||
</li>
|
||||
)}
|
||||
</PaginatedList>
|
||||
|
||||
Reference in new issue
Block a user