feat(web): W3-2 library, subscriptions and rules, preferences, decisions inbox, identity and metadata
- library list (kind/status/search, cursor paging) and detail: paths and roots, external identity mapping with lock, field-level metadata sources and locks, slot cards with rejection reasons, content units (provisional flag), downloads with verification; remap/lock/refresh/merge actions - subscriptions: list/create/update, slot rules editor (hard/soft/upgrade/completion), policy revision draft -> preview -> publish (grant warning) -> rollback - preferences: tag CRUD, disable/enable, suppression records and lift, history timeline - inbox: grouped by entity and kind, evidence/suggestion/impact/preview, resolve options, SSE refresh - identity: pool assign/dismiss/retry, external identity revisions and lock, title alias management - all responses validated against contracts zod schemas Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e5e42b1f0d
commit
ef73abe3df
25 files changed
+6670
-19
No files matched your search
@@ -0,0 +1,263 @@
|
||||
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" },
|
||||
};
|
||||
|
||||
export const DECISION_KIND: Record<DecisionKind, string> = {
|
||||
identity_ambiguous: "身份歧义",
|
||||
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" },
|
||||
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 };
|
||||
});
|
||||
Reference in new issue
Block a user