文案(媒体库组件):状态标签按术语表统一,去掉候选/退役/已预留等内部说法
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
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>,
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user