From a15d81360bdc8a93f1ba39bfd884932bffb3e68c Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:49:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=88(=E6=95=B4=E7=90=86=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E4=B8=8E=E9=98=9F=E5=88=97):=E6=8C=89=20human-copy=20?= =?UTF-8?q?=E9=87=8D=E5=86=99=E9=98=9F=E5=88=97=E9=9D=A2=E6=9D=BF=E3=80=81?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=A1=AE=E8=AE=A4=E3=80=81=E6=95=B4=E7=90=86?= =?UTF-8?q?=E6=9D=A5=E6=BA=90=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 队列面板与按钮统一叫「队列」,阶段文字去掉「登记媒体库」「NFO / 图片」等重复说明 - 「选择整理策略」改「选择整理来源」(策略是收录策略的词),「来源配置」统一为「整理来源」 - 扫描存量结果只列非零项;失败 Toast 写清是哪个动作失败 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- .../organize/delete-records-dialog.tsx | 12 ++++---- .../organize/file-source-selection.tsx | 14 ++++----- .../components/organize/organize-queue.tsx | 30 +++++++++---------- .../organize/organize-scrape-history.tsx | 8 ++--- .../components/organize/queue-model.test.ts | 14 ++++----- .../app/components/organize/queue-model.ts | 18 +++++------ .../components/organize/record-attention.tsx | 12 ++++---- .../components/organize/source-selection.tsx | 30 +++++++++++++------ 8 files changed, 74 insertions(+), 64 deletions(-) diff --git a/apps/web/app/components/organize/delete-records-dialog.tsx b/apps/web/app/components/organize/delete-records-dialog.tsx index 4f63aadb..fd78b424 100644 --- a/apps/web/app/components/organize/delete-records-dialog.tsx +++ b/apps/web/app/components/organize/delete-records-dialog.tsx @@ -86,7 +86,7 @@ export function DeleteRecordsDialog({ setDst(e.target.checked)} /> 同时删除媒体库里的文件 - 整理后放进媒体库的文件,会从媒体库移除。 + 整理到媒体库目录里的文件。 @@ -127,8 +127,8 @@ function Summary({ src: boolean; withFiles: boolean; }) { - if (error) return

{errorMessage(error, "核对失败")}

; - if (!plan) return

{pending ? "正在核对要删的文件…" : null}

; + if (error) return

{errorMessage(error, "检查失败")}

; + if (!plan) return

{pending ? "检查要删的文件…" : null}

; const files = [ plan.sourceFiles ? `${plan.sourceFiles} 个源文件` : null, plan.targetFiles ? `${plan.targetFiles} 个媒体库文件` : null, @@ -137,15 +137,15 @@ function Summary({
{files.length ? (

- {files.join("、")}({formatBytes(plan.bytes, 1)})会移入回收站,可以在回收站还原。 + {files.join("、")}({formatBytes(plan.bytes, 1)})会移入回收站,可以还原。 {plan.assets ? `其中 ${plan.assets} 个是已整理文件,会从媒体库移除。` : ""}

) : (

{withFiles ? "没有找到还在的文件,只删记录。" : "只删记录,文件不动。"}

)} - {src && plan.sourceFiles ?

下载器里的任务不会删除。

: null} + {src && plan.sourceFiles ?

下载器里的任务不受影响。

: null} {plan.autoRetry ? ( -

{plan.autoRetry} 个下载任务没有别的整理记录了,几分钟后会重新自动整理。

+

{plan.autoRetry} 个下载任务没有别的整理记录了,几分钟后会自动重新整理。

) : null}
); diff --git a/apps/web/app/components/organize/file-source-selection.tsx b/apps/web/app/components/organize/file-source-selection.tsx index a51925a1..43eb1941 100644 --- a/apps/web/app/components/organize/file-source-selection.tsx +++ b/apps/web/app/components/organize/file-source-selection.tsx @@ -24,7 +24,7 @@ export function FileSourceSelection({ const profile = matches[0]; onSelect( profile - ? { ...source, sourceConfigId: profile.id, defaultAction: profile.action, label: `来源 · ${profile.name}` } + ? { ...source, sourceConfigId: profile.id, defaultAction: profile.action, label: `整理来源 · ${profile.name}` } : source, ); }, [profiles.isSuccess, matches, source, onSelect]); @@ -32,14 +32,14 @@ export function FileSourceSelection({ !open && onClose()}> - {profiles.isPending ? "读取整理来源…" : "选择整理策略"} + {profiles.isPending ? "读取整理来源…" : "选择整理来源"} - 匹配来源配置后,使用其整理方式、目标目录与分类规则。媒体类型将在整理预览时核对。 + 整理时沿用所选整理来源的整理方式、目标目录和分类。 {profiles.isPending ? (

- 正在检查所选文件对应的来源配置… + 匹配整理来源…

) : profiles.error ? ( <> @@ -62,19 +62,19 @@ export function FileSourceSelection({ ...source, sourceConfigId: profile.id, defaultAction: profile.action, - label: `来源 · ${profile.name}`, + label: `整理来源 · ${profile.name}`, }) } > {profile.name} · {profile.action === "copy" ? "复制" : "移动"} · {profile.category ?? "自动分类"} - {!profile.enabled ? " · 自动监控已停用" : ""} + {!profile.enabled ? " · 已停用" : ""} ))} ) : null} {!profiles.isPending && (profiles.error || matches.length > 1) ? ( ) : null}
diff --git a/apps/web/app/components/organize/organize-queue.tsx b/apps/web/app/components/organize/organize-queue.tsx index 5dd93957..03c86f74 100644 --- a/apps/web/app/components/organize/organize-queue.tsx +++ b/apps/web/app/components/organize/organize-queue.tsx @@ -30,15 +30,15 @@ function ScrapeQueueItem({ run }: { run: EngineRun }) { return (
  • - {record?.match?.title ?? "媒体库刮削"} + {record?.match?.title ?? "刮削"} 刮削 {status} {relTime(run.createdAt)}
    - 目标目录 / 文件 + 位置

    - {record ? `${record.root} / ${record.path || "(根目录)"}` : "读取刮削目标中…"} + {record ? `${record.root} / ${record.path || "(登记目录本身)"}` : "加载中…"}

    @@ -59,12 +59,12 @@ function ScrapeQueueItem({ run }: { run: EngineRun }) { className="hover:text-foreground hover:underline" to={`/console/organize/${record.organizationRecordId}`} > - 关联整理记录 + 整理记录 ) : null}
    {detail.error || scrape.error ? ( -

    刮削详细信息暂时无法读取,任务状态仍会自动刷新。

    +

    刮削详情读取失败,状态仍会自动刷新。

    ) : null}
  • ); @@ -83,7 +83,7 @@ function QueueItem({ run }: { run: EngineRun }) { const download = useDownloadTask(downloadId); const record = recordQuery.data ?? matchingRecord; const progress = record ? recordFileProgress(record) : null; - const title = record ? recordTitle(record) : (download.data?.name ?? run.business?.title ?? "核验整理任务"); + const title = record ? recordTitle(record) : (download.data?.name ?? run.business?.title ?? "整理任务"); const source = record?.source.path ?? download.data?.contentPath ?? download.data?.savePath; const status = run.status === "pending" ? "排队" : run.status === "sleeping" ? "等待" : "执行中"; return ( @@ -99,13 +99,13 @@ function QueueItem({ run }: { run: EngineRun }) {

    {source ?? (detail.isFetching || download.isFetching || recordQuery.isFetching - ? "读取来源信息中…" - : "来源信息暂不可用")} + ? "加载中…" + : "读取不到来源")}

    目标 -

    {record?.target.path ?? "核验与生成整理计划后确定"}

    +

    {record?.target.path ?? "生成整理计划后确定"}

    @@ -131,7 +131,7 @@ function QueueItem({ run }: { run: EngineRun }) { ) : null}
    {detail.error || recordQuery.error || related.error || download.error ? ( -

    部分详细信息暂时无法读取,任务状态仍会自动刷新。

    +

    部分详情读取失败,状态仍会自动刷新。

    ) : null} ); @@ -159,7 +159,7 @@ export function OrganizeQueue() { resetKey: "organize-queue", }); return ( -
    +
    ); diff --git a/apps/web/app/components/organize/queue-model.test.ts b/apps/web/app/components/organize/queue-model.test.ts index 2f9a2faa..be7a3463 100644 --- a/apps/web/app/components/organize/queue-model.test.ts +++ b/apps/web/app/components/organize/queue-model.test.ts @@ -8,24 +8,22 @@ describe("organize queue", () => { recordId: undefined, }); expect(organizeQueueStage({ status: "pending" })).toBe("等待执行"); - expect(organizeQueueStage({ status: "running" })).toBe("核验文件与生成整理计划"); + expect(organizeQueueStage({ status: "running" })).toBe("核验文件,生成整理计划"); }); it("distinguishes file transfer, plan waiting, and workflow waiting", () => { expect(organizeRunInput({ recordId: "record" }).recordId).toBe("record"); expect(organizeQueueStage({ status: "running" }, { status: "queued" })).toContain("等待搬运"); expect(organizeQueueStage({ status: "running" }, { status: "running" })).toContain("搬运文件"); - expect(organizeQueueStage({ status: "sleeping" }, { status: "running" })).toBe("等待继续执行"); + expect(organizeQueueStage({ status: "sleeping" }, { status: "running" })).toBe("等待继续"); }); it("keeps the independent scrape stage distinct after importing finishes", () => { expect(organizeRunInput({ input: { scrapeRecordId: "scrape" } }).scrapeRecordId).toBe("scrape"); - expect(scrapeQueueStage({ status: "running" })).toBe("刮削 NFO / 图片"); + expect(scrapeQueueStage({ status: "running" })).toBe("刮削中"); expect(scrapeQueueStage({ status: "pending" })).toContain("等待刮削"); }); - it("原地入库显示核验与登记,不显示搬运", () => { - expect(organizeQueueStage({ status: "running" }, { status: "running", action: "in_place" })).toBe( - "核验原文件与登记媒体库", - ); - expect(organizeQueueStage({ status: "running" }, { status: "queued", action: "in_place" })).toBe("等待原地登记"); + it("原地整理不显示搬运", () => { + expect(organizeQueueStage({ status: "running" }, { status: "running", action: "in_place" })).toBe("原地整理中"); + expect(organizeQueueStage({ status: "running" }, { status: "queued", action: "in_place" })).toBe("等待原地整理"); }); it("does not describe a finished business record as transferring", () => { expect(organizeQueueStage({ status: "running" }, { status: "succeeded" })).toContain("收尾"); diff --git a/apps/web/app/components/organize/queue-model.ts b/apps/web/app/components/organize/queue-model.ts index fa1c94ff..ee0e47d8 100644 --- a/apps/web/app/components/organize/queue-model.ts +++ b/apps/web/app/components/organize/queue-model.ts @@ -20,18 +20,18 @@ export function organizeQueueStage( run: Pick, record?: Pick & Partial>, ) { - if (run.status === "sleeping") return "等待继续执行"; + if (run.status === "sleeping") return "等待继续"; if (run.status === "pending") return "等待执行"; - if (!record) return "核验文件与生成整理计划"; + if (!record) return "核验文件,生成整理计划"; if (record.action === "in_place" && (record.status === "queued" || record.status === "running")) - return record.status === "queued" ? "等待原地登记" : "核验原文件与登记媒体库"; - if (record.status === "queued") return "整理计划已生成,等待搬运"; - if (record.status === "running") return "搬运文件与登记媒体库"; - return "整理已结束,完成任务收尾"; + return record.status === "queued" ? "等待原地整理" : "原地整理中"; + if (record.status === "queued") return "等待搬运文件"; + if (record.status === "running") return "搬运文件中"; + return "收尾中"; } export function scrapeQueueStage(run: Pick) { - if (run.status === "pending") return "等待刮削 NFO / 图片"; - if (run.status === "sleeping") return "等待继续刮削 NFO / 图片"; - return "刮削 NFO / 图片"; + if (run.status === "pending") return "等待刮削"; + if (run.status === "sleeping") return "等待继续刮削"; + return "刮削中"; } diff --git a/apps/web/app/components/organize/record-attention.tsx b/apps/web/app/components/organize/record-attention.tsx index ca7ea6ce..a939bd51 100644 --- a/apps/web/app/components/organize/record-attention.tsx +++ b/apps/web/app/components/organize/record-attention.tsx @@ -27,7 +27,7 @@ type Rec = Pick identify.mutateAsync(r.id).then( - (x) => toast.success(x.queued ? "已让智能体识别,认出来会自动重新整理" : "智能体已经在识别了"), - (err) => toast.error(errorMessage(err, "操作失败")), + (x) => toast.success(x.queued ? "已交给智能体识别" : "智能体正在识别"), + (err) => toast.error(errorMessage(err, "交给智能体识别失败")), ) } > @@ -112,14 +112,14 @@ export function RecordAttentionActions({ 忽略 } - title="忽略这条记录的问题?" - description="不再算需要处理,文件和记录都不动;之后可以随时重新整理。" + title="忽略这个问题?" + description="这条记录不再算需要处理,文件和记录不变。之后仍可重新整理。" confirmLabel="忽略" onConfirm={() => dismiss.mutateAsync(r.id).then( () => toast.success("已忽略"), (err) => { - toast.error(errorMessage(err, "操作失败")); + toast.error(errorMessage(err, "忽略失败")); throw err; }, ) diff --git a/apps/web/app/components/organize/source-selection.tsx b/apps/web/app/components/organize/source-selection.tsx index 51757404..72921db3 100644 --- a/apps/web/app/components/organize/source-selection.tsx +++ b/apps/web/app/components/organize/source-selection.tsx @@ -17,9 +17,21 @@ export function stockWizardSource(profile: OrganizeSourceView, stock: OrganizeSt sourceConfigId: profile.id, defaultAction: profile.action, label: `整理存量 · ${profile.name}`, - scanSummary: `找到 ${stock.items.length} 个文件,跳过 ${stock.skipped} 个,不可读取 ${stock.unavailable} 个${stock.truncated ? ";扫描已达到上限,仅显示本批结果" : ""}。本次扫描不改变自动整理基线,请先预览再执行。`, + scanSummary: `找到 ${stock.items.length} 个文件${stockNotes(stock)}。`, }; } +/** 扫描结果的补充说明:只写非零项 */ +function stockNotes(r: Pick): string { + return [ + r.skipped ? `跳过 ${r.skipped} 个` : "", + r.unavailable ? `${r.unavailable} 个读取失败` : "", + r.truncated ? "达到扫描上限,只列出这一批" : "", + ] + .filter(Boolean) + .map((x) => `,${x}`) + .join(""); +} + export function SourceSelection({ onClose, onSelect, @@ -36,7 +48,7 @@ export function SourceSelection({ mode="source" allowRoot={false} onClose={() => setManual(false)} - onSelect={(ref) => onSelect({ kind: "files", items: [ref], label: "手动选择来源目录" })} + onSelect={(ref) => onSelect({ kind: "files", items: [ref], label: "手动选择的目录" })} /> ); return ( @@ -44,19 +56,19 @@ export function SourceSelection({ 选择整理来源 - 扫描来源存量后,进入文件识别、预览与执行。扫描不会改变自动整理基线。 + 扫描来源目录里已有的文件,预览后再整理。扫描不改变自动整理的基线。
    - {sources.isPending ?

    读取来源中…

    : null} + {sources.isPending ?

    加载中…

    : null} {sources.error ? (

    - 来源配置读取失败,仍可手动选择目录。 + 读取整理来源失败。可以手动选择目录

    ) : null} {sources.data?.items.length ? ( @@ -74,7 +86,7 @@ export function SourceSelection({ r.items.length ? onSelect(stockWizardSource(p, r)) : toast.info( - `没有可整理文件;跳过 ${r.skipped},不可读取 ${r.unavailable}${r.truncated ? ",扫描已达到上限" : ""}`, + `没有可整理的文件${stockNotes(r)}`, ), onError: (e) => toast.error(filesErrorText(e, "扫描存量失败")), }, @@ -89,7 +101,7 @@ export function SourceSelection({ ) : null}
    {sources.isSuccess && !sources.data.items.length ? ( - 去设置新增整理来源 + 新增整理来源 ) : null}