文案(媒体库组件):状态标签按术语表统一,去掉候选/退役/已预留等内部说法

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-07 01:48:33 +08:00
1 parent 542f107566
commit 9c7ce109ea
10 files changed
+48 -48

No files matched your search

@@ -102,13 +102,13 @@ export function IdentityDialog({
<DialogHeader>
<DialogTitle>修改外部身份</DialogTitle>
<DialogDescription>
同类型的旧 TMDB 身份会退役,保留修订记录。加人工锁后,自动匹配和刷新不再改动。
旧的 TMDB 身份停用,修订记录保留。加人工锁后,自动匹配和刷新不会再改。
</DialogDescription>
</DialogHeader>
<form onSubmit={submit} className="grid gap-3">
{usable.length ? (
<div className="grid gap-1.5">
<p className="text-caption font-medium text-foreground">候选</p>
<p className="text-caption font-medium text-foreground">已记录的身份</p>
<ul className="grid gap-1.5">
{usable.map((c) => (
<li key={c.id}>
@@ -227,7 +227,7 @@ export function FieldLockDialog({
if (field === "year") {
const n = Number(r.data.value);
if (!Number.isInteger(n) || n < 1870 || n > 2200) {
setErrors({ value: "年份须是 1870–2200 的整数" });
setErrors({ value: "年份填 1870–2200 之间的整数" });
return;
}
v = n;
@@ -150,7 +150,7 @@ export function EnrichSection({ item }: { item: IdentityPoolItem }) {
{!out ? (
<p className="text-caption text-muted-foreground">
TMDB
查不到时,按标题搜索网页:读平台和百科网站,其它网站只用搜索摘要,抽取字段并附出处和置信度。先看建议,确认后才建作品。
查不到时,按标题搜索网页,从平台和百科页面抽取字段,附出处和置信度。确认后才建作品。
</p>
) : (
<div className="space-y-3">
@@ -160,12 +160,12 @@ function ManualAssign({ item }: { item: IdentityPoolItem }) {
await dismiss.mutateAsync({ id: item.id });
toast.success("已忽略");
} catch (err) {
toast.error(errorMessage(err, "操作失败"));
toast.error(errorMessage(err, "忽略失败"));
}
}}
>
<BellOff />
忽略此资源
忽略这个资源
</Button>
</DetailSection>
);
@@ -229,7 +229,7 @@ export function PoolSheet({ item, onClose }: { item: IdentityPoolItem | null; on
<EnrichSection key={item.id} item={item} />
) : null}
<DetailSection title={`候选(${item.candidates.length})`}>
<DetailSection title={`候选作品(${item.candidates.length})`}>
{item.candidates.length ? (
<ul className="space-y-2">
{item.candidates.map((c, i) => (
@@ -245,7 +245,7 @@ export function PoolSheet({ item, onClose }: { item: IdentityPoolItem | null; on
<p className="text-caption text-muted-foreground">
{item.reason === "unmatched"
? "没有命中订阅或本地作品,未查询 TMDB。点「重新识别」会查一次 TMDB;新增相近别名时自动重试。"
: "没有候选。有新证据时自动重试。"}
: "没有候选作品。有新证据时自动重试。"}
</p>
)}
</DetailSection>
@@ -73,7 +73,7 @@ export function PoolTab() {
},
{
key: "cands",
header: "候选",
header: "候选作品",
hideBelow: "sm",
align: "right",
cell: (r) => <span className="tabular-nums">{r.candidates.length}</span>,
+17 -17
View File
@@ -43,12 +43,12 @@ export const ENTITY_STATUS: Record<MediaEntityStatus, ToneLabel> = {
/** Slot 状态(REQ §6.1):每个状态有明确文本,不只靠颜色 */
export const SLOT_STATUS: Record<SlotStatus, ToneLabel> = {
empty: { label: "空缺", tone: "neutral" },
reserved: { label: "已预留", tone: "neutral" },
reserved: { label: "准备下载", tone: "neutral" },
downloading: { label: "下载中", tone: "neutral" },
verifying: { label: "核验中", tone: "neutral" },
satisfied: { label: "已满足", tone: "success" },
upgrade_pending: { label: "等待升级", tone: "warning" },
attention: { label: "需处理", tone: "destructive" },
attention: { label: "需要处理", tone: "destructive" },
};
export const SLOT_STATUS_ORDER: SlotStatus[] = [
"attention",
@@ -64,7 +64,7 @@ export const IDENTITY_STATUS: Record<IdentityStatus, ToneLabel> = {
accepted: { label: "已确认", tone: "success" },
proposed: { label: "待确认", tone: "warning" },
conflicted: { label: "冲突", tone: "destructive" },
retired: { label: "已退役", tone: "neutral" },
retired: { label: "已停用", tone: "neutral" },
};
export const EVIDENCE_LEVEL: Record<string, string> = {
@@ -100,7 +100,7 @@ export const LOCATION_KIND: Record<string, string> = {
};
export const DOWNLOAD_STATUS: Record<DownloadStatus, ToneLabel> = {
queued: { label: "排队", tone: "neutral" },
queued: { label: "排队中", tone: "neutral" },
submitting: { label: "提交中", tone: "neutral" },
downloading: { label: "下载中", tone: "neutral" },
paused: { label: "已暂停", tone: "warning" },
@@ -110,15 +110,15 @@ export const DOWNLOAD_STATUS: Record<DownloadStatus, ToneLabel> = {
imported: { label: "已整理", tone: "success" },
failed: { label: "失败", tone: "destructive" },
removed: { label: "已移除", tone: "neutral" },
missing: { label: "下载器中缺失", tone: "warning" },
missing: { label: "下载器里没有", tone: "warning" },
};
export const ORG_STATUS: Record<OrganizationStatus, ToneLabel> = {
queued: { label: "排队", tone: "neutral" },
queued: { label: "排队中", tone: "neutral" },
running: { label: "整理中", tone: "neutral" },
succeeded: { label: "核验通过", tone: "success" },
partial: { label: "部分成功", tone: "warning" },
failed: { label: "失败", tone: "destructive" },
succeeded: { label: "已整理", tone: "success" },
partial: { label: "部分整理", tone: "warning" },
failed: { label: "整理失败", tone: "destructive" },
cancelled: { label: "已取消", tone: "neutral" },
};
@@ -127,8 +127,8 @@ export const CANDIDATE_DECISION: Record<string, ToneLabel> = {
upgrade: { label: "可升级", tone: "success" },
reject: { label: "拒绝", tone: "destructive" },
unknown_field: { label: "字段未知", tone: "warning" },
skipped: { label: "跳过", tone: "neutral" },
deferred: { label: "延后:已有计划在途", tone: "warning" },
skipped: { label: "已跳过", tone: "neutral" },
deferred: { label: "延后:已有下载在进行", tone: "warning" },
};
export const DECISION_KIND: Record<DecisionKind, string> = {
@@ -141,7 +141,7 @@ export const DECISION_KIND: Record<DecisionKind, string> = {
space_insufficient: "空间不足",
approval_required: "需要审批",
metadata_conflict: "元数据冲突",
source_unreachable: "来源不可达",
source_unreachable: "来源连不上",
downloader_offline: "下载器连不上",
cache_volume_unknown: "下载目录读不到容量",
other: "其他",
@@ -163,7 +163,7 @@ export const SEVERITY: Record<Severity, ToneLabel> = {
};
export const POOL_REASON: Record<IdentityCandidateReason, ToneLabel> = {
ambiguous: { label: "多个候选", tone: "warning" },
ambiguous: { label: "匹配到多部作品", tone: "warning" },
no_match: { label: "没有匹配", tone: "neutral" },
proposed: { label: "待确认", tone: "warning" },
lookup_failed: { label: "查询失败", tone: "destructive" },
@@ -207,7 +207,7 @@ export const POLICY_STATUS: Record<PolicyStatus, ToneLabel> = {
export const SUB_MODE: Record<SubscriptionMode, string> = { once: "单次收录", continuous: "持续追踪" };
export const SEASON_SCOPE: Record<SeasonScope, string> = {
current_season: "仅本季",
following_seasons: "接续后续季",
following_seasons: "包括后续季",
};
export const PRIORITY: Record<AcquisitionPriority, string> = {
immediate: "立即",
@@ -336,9 +336,9 @@ export const opts = <K extends string>(rec: Record<K, string | { label: string }
/** 收录版本 / 候选的原因串(如 `blocked:no_main_video`、`deferred:slot_busy;inflight:<id>`)→ 中文;不认识的原样显示 */
const SLOT_REASON: Record<string, string> = {
"blocked:no_main_video": "没有可整理的主视频",
"deferred:slot_busy": "已有计划在途,等它结束再评估",
verified: "已核验整理",
verified_partial: "部分核验整理",
"deferred:slot_busy": "已有下载在进行,结束后再评估",
verified: "已整理",
verified_partial: "部分整理",
hard_conditions_met: "硬条件全部满足",
};
export function slotReasonText(raw: string): string {
@@ -108,8 +108,8 @@ export function PreferenceFormDialog({
<DialogTitle>{tag ? "编辑偏好" : "添加偏好"}</DialogTitle>
<DialogDescription>
{tag
? "修改作用范围或规则会留下旧语义的抑制记录,智能体不会按旧证据加回。"
: "手动添加等同对话确认。之前取消过的偏好,重新添加会解除抑制。"}
? "改了作用范围或规则后,智能体不会再按旧证据把它加回来。"
: "手动添加和在对话里确认效果相同。之前取消过的偏好,重新添加后恢复生效。"}
</DialogDescription>
</DialogHeader>
<form onSubmit={submit} className="grid gap-2" noValidate>
@@ -26,7 +26,7 @@ export const SCRAPE_STATUS: Record<ScrapeRecord["status"], { label: string; tone
const ACTION: Record<ScrapeFileResult["action"], { label: string; tone: StatusTone }> = {
written: { label: "已写入", tone: "success" },
skipped: { label: "跳过", tone: "neutral" },
skipped: { label: "已跳过", tone: "neutral" },
failed: { label: "失败", tone: "destructive" },
};
@@ -144,7 +144,7 @@ export function ScrapePanel({ detail }: { detail: LibraryDetail }) {
scrape.mutateAsync(mode ? { mode } : {}).then(
(r) => toast.success(r.deduplicated ? "已在排队" : "已提交刮削"),
(err) => {
toast.error(errorMessage(err, "刮削没有提交"));
toast.error(errorMessage(err, "刮削提交失败"));
throw err;
},
);
@@ -78,7 +78,7 @@ export function SlotCard({
</dl>
{latest.length ? (
<div className="mt-3 border-t border-border/60 pt-3">
<p className="mb-2 text-meta text-muted-foreground">最近候选及选用理由(每个资源最近一次评估)</p>
<p className="mb-2 text-meta text-muted-foreground">最近评估的资源和理由(每个资源只列最近一次)</p>
<ul className="space-y-2">
{latest.map((c) => (
<li key={c.sourceReleaseId} className="min-w-0">
@@ -53,7 +53,7 @@ const KIND_OPTS: { value: string; label: string }[] = SubscriptionMediaKind.opti
}));
export const PUBLISH_NOTICE =
"发布后,作用范围内的下载、追更和整理按此策略自动执行,不再逐次审批。升级替换、媒体清理和下载缓存清理仍需单独审批。旧的已发布版本被替代,授权随之失效。";
"发布后,作用范围内的下载、追更和整理按这个策略自动执行,不再逐次审批;升级替换、媒体清理和下载缓存清理仍要单独审批。上一个已发布版本的授权随之失效。";
type TemplateDraft = { key: string; name: string; rules: SlotRulesDraft; open: boolean };
type PolicyDraft = {
@@ -164,7 +164,7 @@ function buildPolicy(d: PolicyDraft):
const strict = AcquisitionPolicyRules.strict().safeParse(rules);
if (!strict.success && !Object.keys(errors).length) {
const i = strict.error.issues[0];
errors.rules = `规则结构不合法:${i?.path.join(".")} ${i?.message}`;
errors.rules = `规则格式有误:${i?.path.join(".")} ${i?.message}`;
}
if (Object.keys(errors).length || !meta.success) return { ok: false, errors };
return {
@@ -308,7 +308,7 @@ function PolicyGroup({
) : (
<Button variant="outline" size="sm" onClick={() => onDraftFrom(head)}>
<FilePlus2 />
基于 r{head.revision} 新建草案
从 r{head.revision} 新建草案
</Button>
)
}
@@ -449,7 +449,7 @@ function PolicyDetailSheet({
>
{!pv?.success ? (
<p className="rounded-lg border border-warning/30 bg-warning/10 px-3 py-2 text-caption text-warning">
这个草案还没有预览。先预览可以看到影响范围。
还没有预览。预览后能看到影响范围。
</p>
) : null}
</ConfirmDialog>
@@ -458,14 +458,14 @@ function PolicyDetailSheet({
<>
<Button variant="outline" onClick={() => onDraftFrom(rev)}>
<FilePlus2 />
基于此版本新建草案
从这个版本新建草案
</Button>
{rev.status === "superseded" || rev.status === "rolled_back" ? (
<ConfirmDialog
trigger={
<Button variant="outline">
<Undo2 />
回滚到此版本
回滚到这个版本
</Button>
}
title={`回滚到 r${rev.revision}?`}
@@ -538,7 +538,7 @@ function PreviewBlock({ value }: { value: JsonObject | null }) {
if (!value)
return (
<p className="text-caption text-muted-foreground">
还没有预览。预览用最近已识别的资源评估模板,不会执行任何操作。
还没有预览。预览用最近识别的资源试算模板,不会下载或整理。
</p>
);
const p = PolicyPreview.safeParse(value);
@@ -565,7 +565,7 @@ function PreviewBlock({ value }: { value: JsonObject | null }) {
))}
</div>
<p className="text-meta text-muted-foreground">
计算于 {formatDateTime(v.evaluatedAt)};接受/排除按"资源 × 模板"计数。
计算于 {formatDateTime(v.evaluatedAt)};接受 / 排除按「资源 × 模板」计数。
</p>
{v.samples.length ? (
<ul className="divide-y divide-border/60 rounded-lg border border-border/70">
@@ -586,7 +586,7 @@ function PreviewBlock({ value }: { value: JsonObject | null }) {
))}
</ul>
) : (
<p className="text-caption text-muted-foreground">没有可用于回放的已识别资源。</p>
<p className="text-caption text-muted-foreground">没有可回放的已识别资源。</p>
)}
</div>
);
@@ -659,7 +659,7 @@ function PolicyEditorSheet({
open
onOpenChange={(o) => !o && !pending && onClose()}
width="lg"
title={editId ? `编辑草案 r${base?.revision}` : base ? `基于 r${base.revision} 新建草案` : "新建收录策略草案"}
title={editId ? `编辑草案 r${base?.revision}` : base ? `从 r${base.revision} 新建草案` : "新建收录策略草案"}
description="保存为草案,发布后才生效。"
footer={
<>
@@ -66,7 +66,7 @@ export function SlotsPanel({
plain
icon={Target}
title="还没有收录版本"
description="没有时按已发布收录策略的默认模板创建,也可以手动新建。"
description="订阅时按已发布收录策略的默认模板自动创建,也可以手动新建。"
className="py-8"
/>
) : (
@@ -80,7 +80,7 @@ export function SlotsPanel({
<span>
{[s.currentAsset.resolution, s.currentAsset.sourceType, s.currentAsset.releaseGroup]
.filter(Boolean)
.join(" · ") || "交付特征未知"}
.join(" · ") || "规格未知"}
{s.currentAsset.sizeBytes ? ` · ${formatBytes(s.currentAsset.sizeBytes, 1)}` : ""}
{s.currentAsset.status !== "active" ? ` · ${s.currentAsset.status}` : ""}
</span>
@@ -140,7 +140,7 @@ function SlotEditorSheet({
const errs: Record<string, string> = {};
if (!meta.success) for (const i of meta.error.issues) errs[String(i.path[0])] ??= i.message;
if (!rules.ok) Object.assign(errs, rules.errors);
if (requireDistinct && !distinctGroup.trim()) errs.distinctGroup = "要求独立文件时须填写分组";
if (requireDistinct && !distinctGroup.trim()) errs.distinctGroup = "要求独立文件时要填组合分组";
setErrors(errs);
if (Object.keys(errs).length || !meta.success || !rules.ok) return;
const common = {
@@ -180,7 +180,7 @@ function SlotEditorSheet({
width="lg"
title={slot ? `编辑收录版本 ${slot.name}` : "新建收录版本"}
description={
slot ? `当前 r${slot.revision}。保存时若已被修改,会提示刷新。` : "不满足硬条件的资源不收;软偏好决定先收哪个。"
slot ? `当前 r${slot.revision}。保存时如果已被别处修改,会提示刷新。` : "不满足硬条件的资源不收;软偏好决定先收哪个。"
}
footer={
<>
@@ -195,7 +195,7 @@ function SlotEditorSheet({
>
<div className="grid gap-3 sm:grid-cols-2">
<FormField
label="目标名称"
label="名称"
htmlFor={`${id}-name`}
required
error={errors.name}
@@ -213,7 +213,7 @@ function SlotEditorSheet({
label="组合分组"
htmlFor={`${id}-grp`}
error={errors.distinctGroup}
hint="同组目标可要求由不同文件满足"
hint="同组的收录版本可以要求由不同文件满足"
>
<Input
id={`${id}-grp`}
@@ -226,7 +226,7 @@ function SlotEditorSheet({
<div className="flex items-center justify-between gap-3 rounded-lg border border-border px-3 py-2">
<div className="min-w-0">
<p className="text-caption font-medium text-foreground">要求独立文件</p>
<p className="text-meta text-muted-foreground">同一个文件不能同时满足两个目标</p>
<p className="text-meta text-muted-foreground">同一个文件不能同时满足同组的两个收录版本</p>
</div>
<Switch checked={requireDistinct} onCheckedChange={setRequireDistinct} aria-label="要求独立文件" />
</div>