- U4: form save failures toast the first problem and scroll/focus the first invalid field (policy draft, settings cards, resource drawers, subscription form) via lib/form-errors. - U5: FormField keeps 8px between a hint and the next label. - U6: file manager status bar keeps free space left-aligned, clear of the Agent button. - U13: move/copy dialog stops "target is the source folder / inside the source" before requesting a preview (transferTargetProblem + tests). - U14: account popover uses the solid float material. - U15/U16: inbox tile hint shortened; TMDB assign dialog uses the shared Select and labelled fields (type, TMDB ID, note). - Copy: "TMDB 已保存" spacing, API Key block no longer repeats the section title, whole-day intervals read "每 7 天", "最高级别:提示", "延后:已有计划在途", failed records show "未生成目标路径", upstream model column "默认模型". Co-Authored-By: Claude Opus 5.5 <[email protected]>
283 lines
9.8 KiB
TypeScript
283 lines
9.8 KiB
TypeScript
import type {
|
|
AcquisitionPriority,
|
|
DecisionKind,
|
|
DecisionStatus,
|
|
DownloadStatus,
|
|
IdentityCandidateReason,
|
|
IdentityStatus,
|
|
LocationStatus,
|
|
MediaEntityStatus,
|
|
MediaKind,
|
|
OrganizationStatus,
|
|
PolicyStatus,
|
|
PreferenceKind,
|
|
PreferenceOrigin,
|
|
PreferenceStatus,
|
|
SeasonScope,
|
|
Severity,
|
|
SlotStatus,
|
|
SubscriptionMode,
|
|
SubscriptionStatus,
|
|
} from "@umt/contracts";
|
|
import type { StatusTone } from "~/components/console/status-badge.tsx";
|
|
|
|
// W3-2 各页共用的取值 → 中文文案 / 状态语气。状态色只表示状态,文案必填。
|
|
|
|
type ToneLabel = { label: string; tone: StatusTone };
|
|
|
|
export const MEDIA_KIND: Record<MediaKind, string> = {
|
|
movie: "电影",
|
|
series: "剧集",
|
|
variety: "综艺",
|
|
collection: "合集",
|
|
other: "其他",
|
|
};
|
|
|
|
export const ENTITY_STATUS: Record<MediaEntityStatus, ToneLabel> = {
|
|
provisional: { label: "临时建档", tone: "warning" },
|
|
active: { label: "正常", tone: "success" },
|
|
merged: { label: "已合并", tone: "neutral" },
|
|
archived: { label: "已归档", tone: "neutral" },
|
|
};
|
|
|
|
/** Slot 状态(REQ §6.1):每个状态有明确文本,不只靠颜色 */
|
|
export const SLOT_STATUS: Record<SlotStatus, ToneLabel> = {
|
|
empty: { 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" },
|
|
};
|
|
export const SLOT_STATUS_ORDER: SlotStatus[] = [
|
|
"attention",
|
|
"empty",
|
|
"upgrade_pending",
|
|
"reserved",
|
|
"downloading",
|
|
"verifying",
|
|
"satisfied",
|
|
];
|
|
|
|
export const IDENTITY_STATUS: Record<IdentityStatus, ToneLabel> = {
|
|
accepted: { label: "已确认", tone: "success" },
|
|
proposed: { label: "待确认", tone: "warning" },
|
|
conflicted: { label: "冲突", tone: "destructive" },
|
|
retired: { label: "已退役", tone: "neutral" },
|
|
};
|
|
|
|
export const EVIDENCE_LEVEL: Record<string, string> = {
|
|
manual: "人工确认",
|
|
rule_confirmed: "规则确认",
|
|
multi_evidence: "多证据",
|
|
needs_review: "待复核",
|
|
needs_confirm: "待确认",
|
|
rejected: "已排除",
|
|
};
|
|
|
|
export const VERIFY_STATUS: Record<string, ToneLabel> = {
|
|
unverified: { label: "未校验", tone: "neutral" },
|
|
ok: { label: "校验通过", tone: "success" },
|
|
not_found: { label: "外部不存在", tone: "warning" },
|
|
type_mismatch: { label: "类型不符", tone: "destructive" },
|
|
error: { label: "校验出错", tone: "warning" },
|
|
};
|
|
|
|
export const LOCATION_STATUS: Record<LocationStatus, ToneLabel> = {
|
|
available: { label: "可访问", tone: "success" },
|
|
unavailable: { label: "暂不可访问", tone: "warning" },
|
|
suspected_missing: { label: "疑似缺失", tone: "warning" },
|
|
confirmed_missing: { label: "已确认缺失", tone: "destructive" },
|
|
removed: { label: "已移除", tone: "neutral" },
|
|
};
|
|
|
|
export const LOCATION_KIND: Record<string, string> = {
|
|
library: "媒体库",
|
|
download_cache: "下载缓存",
|
|
hardlink: "硬链接",
|
|
external: "外部",
|
|
};
|
|
|
|
export const DOWNLOAD_STATUS: Record<DownloadStatus, ToneLabel> = {
|
|
queued: { label: "排队", tone: "neutral" },
|
|
submitting: { label: "提交中", tone: "neutral" },
|
|
downloading: { label: "下载中", tone: "neutral" },
|
|
paused: { label: "已暂停", tone: "warning" },
|
|
stalled: { label: "无进展", tone: "warning" },
|
|
completed: { label: "下载完成", tone: "neutral" },
|
|
importing: { label: "入库中", tone: "neutral" },
|
|
imported: { label: "已入库", tone: "success" },
|
|
failed: { label: "失败", tone: "destructive" },
|
|
removed: { label: "已移除", tone: "neutral" },
|
|
missing: { label: "下载器中缺失", tone: "warning" },
|
|
};
|
|
|
|
export const ORG_STATUS: Record<OrganizationStatus, ToneLabel> = {
|
|
queued: { label: "排队", tone: "neutral" },
|
|
running: { label: "整理中", tone: "neutral" },
|
|
succeeded: { label: "核验通过", tone: "success" },
|
|
partial: { label: "部分成功", tone: "warning" },
|
|
failed: { label: "失败", tone: "destructive" },
|
|
cancelled: { label: "已取消", tone: "neutral" },
|
|
};
|
|
|
|
export const CANDIDATE_DECISION: Record<string, ToneLabel> = {
|
|
accept: { label: "接受", tone: "success" },
|
|
upgrade: { label: "可升级", tone: "success" },
|
|
reject: { label: "拒绝", tone: "destructive" },
|
|
unknown_field: { label: "字段未知", tone: "warning" },
|
|
skipped: { label: "跳过", tone: "neutral" },
|
|
deferred: { label: "延后:已有计划在途", tone: "warning" },
|
|
};
|
|
|
|
export const DECISION_KIND: Record<DecisionKind, string> = {
|
|
identity_ambiguous: "身份歧义",
|
|
identity_unresolved: "待识别",
|
|
download_stalled: "下载无进展",
|
|
download_failed: "下载失败",
|
|
path_conflict: "路径冲突",
|
|
space_insufficient: "空间不足",
|
|
approval_required: "需要审批",
|
|
metadata_conflict: "元数据冲突",
|
|
source_unreachable: "来源不可达",
|
|
other: "其他",
|
|
};
|
|
|
|
export const DECISION_STATUS: Record<DecisionStatus, ToneLabel> = {
|
|
open: { label: "待处理", tone: "warning" },
|
|
investigating: { label: "Agent 调查中", tone: "neutral" },
|
|
awaiting_user: { label: "等你决定", tone: "warning" },
|
|
resolved: { label: "已处理", tone: "success" },
|
|
dismissed: { label: "已忽略", tone: "neutral" },
|
|
expired: { label: "已过期", tone: "neutral" },
|
|
};
|
|
|
|
export const SEVERITY: Record<Severity, ToneLabel> = {
|
|
info: { label: "提示", tone: "neutral" },
|
|
warning: { label: "警告", tone: "warning" },
|
|
critical: { label: "严重", tone: "destructive" },
|
|
};
|
|
|
|
export const POOL_REASON: Record<IdentityCandidateReason, ToneLabel> = {
|
|
ambiguous: { label: "多个候选", tone: "warning" },
|
|
no_match: { label: "没有匹配", tone: "neutral" },
|
|
proposed: { label: "待确认", tone: "warning" },
|
|
lookup_failed: { label: "查询失败", tone: "destructive" },
|
|
};
|
|
|
|
export const POOL_STATUS: Record<string, ToneLabel> = {
|
|
pending: { label: "待识别", tone: "warning" },
|
|
resolved: { label: "已识别", tone: "success" },
|
|
dismissed: { label: "已忽略", tone: "neutral" },
|
|
archived: { label: "已归档", tone: "neutral" },
|
|
};
|
|
|
|
export const PREF_KIND: Record<PreferenceKind, { label: string; hint: string }> = {
|
|
soft: { label: "软偏好", hint: "影响排序,不排除" },
|
|
hard: { label: "硬规则", hint: "命中即排除" },
|
|
version: { label: "版本偏好", hint: "同一作品内选哪种版本" },
|
|
target: { label: "版本目标", hint: "为作品新建一个版本目标" },
|
|
};
|
|
|
|
export const PREF_ORIGIN: Record<PreferenceOrigin, string> = {
|
|
manual: "手动",
|
|
conversation: "对话确认",
|
|
agent_inferred: "Agent 推断",
|
|
};
|
|
|
|
export const PREF_STATUS: Record<PreferenceStatus, ToneLabel> = {
|
|
active: { label: "生效中", tone: "success" },
|
|
disabled: { label: "已停用", tone: "neutral" },
|
|
deleted: { label: "已删除", tone: "neutral" },
|
|
};
|
|
|
|
export const POLICY_STATUS: Record<PolicyStatus, ToneLabel> = {
|
|
draft: { label: "草案", tone: "warning" },
|
|
published: { label: "已发布", tone: "success" },
|
|
superseded: { label: "已被替代", tone: "neutral" },
|
|
rolled_back: { label: "已回滚", tone: "neutral" },
|
|
archived: { label: "已归档", tone: "neutral" },
|
|
};
|
|
|
|
export const SUB_MODE: Record<SubscriptionMode, string> = { once: "单次收录", continuous: "持续追踪" };
|
|
export const SEASON_SCOPE: Record<SeasonScope, string> = {
|
|
current_season: "仅本季",
|
|
following_seasons: "接续后续季",
|
|
};
|
|
export const PRIORITY: Record<AcquisitionPriority, string> = {
|
|
immediate: "立即",
|
|
tracking: "追更",
|
|
new_work: "新作品",
|
|
upgrade: "升级",
|
|
};
|
|
export const SUB_STATUS: Record<SubscriptionStatus, ToneLabel> = {
|
|
active: { label: "进行中", tone: "success" },
|
|
paused: { label: "已暂停", tone: "warning" },
|
|
pending: { label: "待定(元数据未就绪)", tone: "warning" },
|
|
completed: { label: "已完成", tone: "neutral" },
|
|
cancelled: { label: "已取消", tone: "neutral" },
|
|
};
|
|
export const SUB_ORIGIN: Record<string, string> = { user: "用户", agent: "Agent", policy: "策略", import: "导入" };
|
|
|
|
export const METADATA_SOURCE: Record<string, string> = {
|
|
tmdb: "TMDB",
|
|
user: "人工",
|
|
agent: "Agent",
|
|
nfo: "NFO",
|
|
site: "站点",
|
|
import: "导入",
|
|
};
|
|
|
|
/** 可覆盖/锁定的作品字段(contracts EntityMetadataField) */
|
|
export const ENTITY_FIELD_LABEL: Record<string, string> = {
|
|
title: "标题",
|
|
originalTitle: "原名",
|
|
year: "年份",
|
|
overview: "简介",
|
|
releaseDate: "上映/首播日期",
|
|
originalLanguage: "原始语言",
|
|
originCountry: "出品地区",
|
|
posterPath: "海报",
|
|
};
|
|
|
|
export const UNIT_KIND: Record<string, string> = { season: "季", episode: "集", special: "特别篇", part: "分段" };
|
|
export const SEGMENT: Record<string, string> = {
|
|
full: "",
|
|
upper: "上",
|
|
middle: "中",
|
|
lower: "下",
|
|
unknown: "分段未知",
|
|
};
|
|
export const CONTENT_TYPE: Record<string, string> = {
|
|
main: "正片",
|
|
extra: "花絮",
|
|
behind_the_scenes: "幕后",
|
|
performance_only: "纯享",
|
|
preview: "预告",
|
|
reaction: "reaction",
|
|
unknown: "类型未知",
|
|
};
|
|
|
|
export const opts = <K extends string>(rec: Record<K, string | { label: string }>) =>
|
|
(Object.keys(rec) as K[]).map((value) => {
|
|
const v = rec[value];
|
|
return { value, label: typeof v === "string" ? v : v.label };
|
|
});
|
|
|
|
/** 版本目标 / 候选的原因串(如 `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: "部分核验入库",
|
|
hard_conditions_met: "硬条件全部满足",
|
|
};
|
|
export function slotReasonText(raw: string): string {
|
|
return raw
|
|
.split(";")
|
|
.filter((p) => !p.startsWith("inflight:"))
|
|
.map((p) => SLOT_REASON[p] ?? p)
|
|
.join(";");
|
|
}
|