refactor(copy): 术语统一——收录版本、模型服务、智能体等(DEAI-2)

- 版本目标 → 收录版本(前后端用户可见字符串、能力描述、给模型的提示)
- Agent 上游 / 上游 → 模型服务;界面上的 Agent / Agent 会话 → 智能体 / 智能体会话
- 身份歧义 / 识别有歧义 / 无法确定作品 → 没识别出作品;待识别(池) → 未识别
- 建档 → 加入媒体库;资产 → 文件;库根 → 媒体库目录;纳管 → 本工具 / 外部任务;
  哨兵 → 标记文件 / 检查目录;站点快照 → 抓取;Feed / RSS 订阅 / 来源健康 → RSS
- 代码标识符、API 字段名、迁移文件不改;glossary.md 同步;smoke 选择器跟随

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 20:35:43 +08:00
1 parent d282212a6b
commit 4480fc346d
212 files changed
+741 -715

No files matched your search

+1 -1
View File
@@ -17,7 +17,7 @@ export default function AgentRunPage() {
return (
<>
<PageHeader
title="Agent 会话"
title="智能体会话"
description={
run.data ? (
<span className="line-clamp-2 break-words" title={run.data.run.goal}>
+2 -2
View File
@@ -16,8 +16,8 @@ export default function AgentPage() {
return (
<>
<PageHeader
title="Agent 会话"
description="交代目标,Agent 列出步骤后在授权范围内执行并汇报;需要审批的动作停在对应步骤等你批准。"
title="智能体会话"
description="交代目标,智能体列出步骤后在授权范围内执行并汇报;需要审批的动作停在对应步骤等你批准。"
/>
{mock ? (
<div className="mb-4 flex flex-wrap items-center gap-3 rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-caption text-warning">
+4 -4
View File
@@ -40,10 +40,10 @@ const ACTION_LABEL: Record<string, string> = {
"schedule.skip": "跳过定时任务",
"acquisition_plan.manual_submit": "手动提交下载计划",
"engine.run_retry": "重跑任务",
"capability.invoke": "Agent 调用能力",
"capability.invoke": "智能体调用能力",
"site.created": "登记站点",
"site.updated": "修改站点",
"feed_source.created": "新建 Feed",
"feed_source.created": "新建 RSS",
"identity.release_assigned": "指定资源身份",
"schedule.update": "修改定时任务",
"files.mkdir": "新建目录",
@@ -56,11 +56,11 @@ const ACTION_LABEL: Record<string, string> = {
};
const VERB: Record<string, string> = { create: "新建", update: "修改", delete: "删除", test: "测试", verify: "校验" };
const NOUN: Record<string, string> = {
library_root: "库根",
library_root: "媒体库目录",
downloader_instance: "下载器",
media_server_instance: "媒体服务器",
path_mapping: "路径映射",
pi_upstream: "Agent 上游",
pi_upstream: "模型服务",
schedule: "定时任务",
automation_grant: "持续授权",
};
@@ -78,7 +78,7 @@ export default function DownloadPlansPage() {
return (
<>
<PageHeader title="下载面板" description="候选被选中后生成计划,同时预留版本目标、空间与预算。" />
<PageHeader title="下载面板" description="候选被选中后生成计划,同时预留收录版本、空间与预算。" />
<DownloadsTabs />
<FilterBar onReset={f.active ? f.reset : undefined}>
<FilterSelect
@@ -134,7 +134,7 @@ export default function DownloadPlansPage() {
{ label: "预留空间", value: formatBytes(openPlan.reservedBytes, 1) },
{ label: "过期于", value: formatDateTime(openPlan.expiresAt) },
{ label: "创建", value: formatDateTime(openPlan.createdAt) },
{ label: "版本目标", value: openPlan.slotId, mono: true },
{ label: "收录版本", value: openPlan.slotId, mono: true },
{ label: "资源", value: openPlan.sourceReleaseId, mono: true, wide: true },
]}
/>
+3 -3
View File
@@ -109,12 +109,12 @@ export default function DownloadsPage() {
options={Object.entries(DOWNLOAD_STATUS_LABEL).map(([value, label]) => ({ value, label }))}
/>
<FilterSelect
label="纳管"
label="来源"
value={f.values.managed}
onChange={(v) => f.set("managed", v)}
options={[
{ value: "true", label: "纳管任务" },
{ value: "false", label: "未纳管" },
{ value: "true", label: "本工具" },
{ value: "false", label: "其他" },
]}
/>
</FilterBar>
+4 -4
View File
@@ -212,14 +212,14 @@ export default function FilesTrashPage() {
},
{
key: "asset",
header: "关联资产",
header: "媒体库记录",
hideBelow: "lg",
cell: (r) => {
const snap = r.assetSnapshot as { title?: string; contentUnit?: string | null; mediaEntityId?: string } | null;
if (!snap?.title) return <span className="text-muted-foreground">—</span>;
const label = `${snap.title}${snap.contentUnit ? ` ${snap.contentUnit}` : ""}`;
return (
<span className="block max-w-48 text-meta" title="删除时已退役;还原不会自动恢复资产">
<span className="block max-w-48 text-meta" title="删除时已从媒体库移除;还原不会自动恢复">
{snap.mediaEntityId ? (
<Link className="underline underline-offset-2" to={`/console/library/${snap.mediaEntityId}`}>
{label}
@@ -274,7 +274,7 @@ export default function FilesTrashPage() {
<>
<PageHeader
title="回收站"
description="到期后自动清除,到期前可以还原。还原不会恢复资产状态,需要时重新整理。"
description="到期后自动清除,到期前可以还原。还原不会恢复媒体库记录,需要时重新整理。"
actions={
<>
{filesMockEnabled() ? <StatusBadge tone="warning">演示数据</StatusBadge> : null}
@@ -419,7 +419,7 @@ function RestoreToDialog({
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle>还原到…</DialogTitle>
<DialogDescription>{ids.length} 项 · 选一个可写库根下的目录。同名项已存在时不覆盖。</DialogDescription>
<DialogDescription>{ids.length} 项 · 选一个可写媒体库目录下的目录。同名项已存在时不覆盖。</DialogDescription>
</DialogHeader>
<DirPicker roots={roots.data ?? []} value={target} onChange={setTarget} className="max-h-80" />
<DialogFooter>
+7 -5
View File
@@ -97,7 +97,7 @@ import { cn, formatBytes } from "~/lib/utils.ts";
const HEALTH_REASON: Record<string, string> = {
root_missing: "挂载点不存在",
sentinel_missing: "目录里没有哨兵文件,挂载可能没生效",
sentinel_missing: "目录里没有标记文件,挂载可能没生效",
timeout: "探测超时",
permission_denied: "没有读取权限",
};
@@ -398,7 +398,9 @@ export default function FilesPage() {
onSuccess: (r) => {
const ok = r.items.filter((i) => i.status === "ok").length;
toast.success(`探测完成:${ok}/${r.items.length} 个读到媒体信息`, {
description: r.items.some((i) => i.savedToAsset) ? "媒体信息已写入资产" : "未登记文件的结果缓存 24 小时",
description: r.items.some((i) => i.savedToAsset)
? "媒体信息已写入媒体库记录"
: "未登记文件的结果缓存 24 小时",
});
},
onError: (err) => toast.error(filesErrorText(err, "探测失败")),
@@ -540,7 +542,7 @@ export default function FilesPage() {
if (items.every((i) => i.root === target.root && (i.path === target.path || parentRel(i.path) === target.path)))
return;
const tRoot = rootList.find((r) => r.id === target.root);
if (!tRoot?.writable) return toast.error("只能拖到可写的库根");
if (!tRoot?.writable) return toast.error("只能拖到可写的媒体库目录");
setDialog({ kind: "transfer", items, mode: ev.altKey ? "copy" : "move", target });
};
@@ -567,7 +569,7 @@ export default function FilesPage() {
plain
icon={FolderTree}
title="还没有登记目录"
description="在设置里添加库根或下载器路径映射。"
description="在设置里添加媒体库目录或下载器路径映射。"
className="px-2 py-8"
/>
) : null}
@@ -596,7 +598,7 @@ export default function FilesPage() {
</span>
<p className="text-lead font-medium text-foreground">{root.name} 离线</p>
<p className="mt-1.5 max-w-md text-caption text-muted-foreground">
{offline ? filesErrorText(list.error) : healthReasonText(root.healthReason)}。离线期间资产状态不变。
{offline ? filesErrorText(list.error) : healthReasonText(root.healthReason)}。离线期间文件状态不变。
</p>
<p className="mt-1 text-meta text-muted-foreground">上次检查:{relTime(root.checkedAt)}</p>
<Button
+5 -5
View File
@@ -281,7 +281,7 @@ function NeedsMe({ n }: { n: Dashboard["needsAttention"] }) {
);
}
// ─── 媒体概览:作品/季/资产计数 + 版本目标满足率 + 今日产出 ───
// ─── 媒体概览:作品/季/资产计数 + 收录版本满足率 + 今日产出 ───
function MediaOverview({ a }: { a: Dashboard["automation"] }) {
const c = a.slotCounts;
const total = Object.values(c).reduce((x, y) => x + y, 0);
@@ -296,7 +296,7 @@ function MediaOverview({ a }: { a: Dashboard["automation"] }) {
</div>
<div className="mt-3">
<div className="flex items-baseline justify-between text-sm">
<span>版本目标满足率</span>
<span>收录版本满足率</span>
<span className="font-semibold tabular-nums">{rate === null ? "—" : `${rate}%`}</span>
</div>
<div className="mt-1.5 h-1.5 overflow-hidden rounded-full bg-foreground/[0.08]" aria-hidden>
@@ -376,7 +376,7 @@ function PlanStrip() {
失败 <Num tone={s.auto.failed ? "destructive" : undefined}>{s.auto.failed}</Num>
</span>
</PlanCount>
<PlanCount to={planPath({ section: "checks" })} label="Agent 巡检" icon={Bot}>
<PlanCount to={planPath({ section: "checks" })} label="智能体巡检" icon={Bot}>
<span>
启用 <Num>{s.checks.enabled}</Num> / {s.checks.total}
</span>
@@ -464,7 +464,7 @@ function SourcesSection({ s }: { s: Dashboard["sources"] }) {
...siteRows,
...s.feeds.map((x) => ({
id: x.id,
kind: "Feed",
kind: "RSS",
name: x.name,
enabled: x.enabled,
health: x.health,
@@ -491,7 +491,7 @@ function SourcesSection({ s }: { s: Dashboard["sources"] }) {
return (
<Section title="来源健康" icon={Radio} to={consolePath("sites")}>
{rows.length === 0 ? (
<EmptyState plain icon={Radio} title="还没有来源" description="登记站点或新建 Feed 后显示。" className="py-6" />
<EmptyState plain icon={Radio} title="还没有来源" description="登记站点或新建 RSS 后显示。" className="py-6" />
) : (
<>
{accountIssues ? <p className="mb-1 text-meta text-warning">{accountIssues} 个站点的账号需要处理</p> : null}
+5 -5
View File
@@ -78,7 +78,7 @@ function GroupReleases({ d }: { d: Decision }) {
))}
{count > list.length ? (
<li className="text-meta text-muted-foreground">
另有 {count - list.length} 条,可在媒体概览 → 待识别按标题检索
另有 {count - list.length} 条,可在媒体概览 → 未识别按标题检索
</li>
) : null}
</ul>
@@ -94,7 +94,7 @@ function entityOf(d: Decision): string | null {
}
const IMPACT_KEY: Record<string, string> = {
slots: "版本目标",
slots: "收录版本",
units: "内容单元",
files: "文件",
releases: "资源",
@@ -140,7 +140,7 @@ function Evidence({ d }: { d: Decision }) {
<div className="flex flex-wrap items-center gap-1.5">
<span className="inline-flex items-center gap-1 text-meta text-muted-foreground">
<Bot className="h-3.5 w-3.5" />
Agent 已检查
智能体已检查
</span>
{checked.map((c) => (
<Chip key={c}>{c}</Chip>
@@ -303,7 +303,7 @@ function DecisionCard({ d }: { d: Decision }) {
to={consolePath("agent", d.agentRunId)}
className="ml-auto inline-flex items-center text-meta text-muted-foreground hover:text-foreground hover:underline"
>
Agent 调查记录
智能体调查记录
<ChevronRight className="h-3 w-3" />
</Link>
) : null}
@@ -404,7 +404,7 @@ export default function InboxPage() {
<>
<PageHeader
title="待处理"
description="Agent 调查和重试后仍未解决的事项,按作品和问题类型合并"
description="智能体调查和重试后仍未解决的事项,按作品和问题类型合并"
actions={
<>
<span className="hidden items-center gap-1.5 text-meta text-muted-foreground sm:inline-flex">
+13 -11
View File
@@ -83,7 +83,7 @@ import { formatBytes, formatDateTime } from "~/lib/utils.ts";
// 作品详情(REQ §13.1 / §13.4.3):实际路径、外部身份映射及状态、元数据来源、各 Slot、候选拒绝理由、
// 内容单元、下载历史与校验结果,同一页同时展示。
// MD-1(对照 MP 媒体详情):头部背景图 + 海报 + 评分 / 类型 / 状态 + 简介、主创、季折叠与每集行(库内状态按版本目标),
// MD-1(对照 MP 媒体详情):头部背景图 + 海报 + 评分 / 类型 / 状态 + 简介、主创、季折叠与每集行(库内状态按收录版本),
// 头部 / 主创 / 季集组件与仅 TMDB 的作品详情页共用(components/media/media-detail)。
const pad = (n: number) => String(n).padStart(2, "0");
@@ -120,13 +120,13 @@ function PathsPanel({ detail }: { detail: LibraryDetail }) {
const rootOf = (id: string | null) => roots.data?.items.find((r) => r.id === id);
const bindings = detail.pathBindings.filter((b) => b.status !== "superseded");
return (
<Panel title="实际路径与库根" description="路径绑定建立后不随译名变化">
<Panel title="实际路径与媒体库目录" description="路径绑定建立后不随译名变化">
{bindings.length === 0 && detail.assets.length === 0 ? (
<EmptyState
plain
icon={FolderTree}
title="还没有入库文件"
description="版本目标满足后,文件位置显示在这里。"
description="收录版本满足后,文件位置显示在这里。"
className="py-8"
/>
) : (
@@ -139,7 +139,9 @@ function PathsPanel({ detail }: { detail: LibraryDetail }) {
<li key={b.id} className="rounded-lg border border-border/70 px-3 py-2">
<div className="flex flex-wrap items-center gap-2">
<Chip>{b.bindingKind === "season" ? `第 ${b.seasonNumber} 季目录` : "作品目录"}</Chip>
<span className="text-meta text-muted-foreground">库根 {root?.name ?? "未知库根"}</span>
<span className="text-meta text-muted-foreground">
媒体库目录 {root?.name ?? "未知媒体库目录"}
</span>
{b.status === "conflict" ? <StatusBadge tone="destructive">绑定冲突</StatusBadge> : null}
</div>
<p className="mt-1 font-mono text-caption break-all text-foreground">
@@ -171,7 +173,7 @@ function PathsPanel({ detail }: { detail: LibraryDetail }) {
<Tone v={LOCATION_STATUS[l.status]} />
{l.libraryRootId ? (
<span className="text-meta text-muted-foreground">
{rootOf(l.libraryRootId)?.name ?? "库根"}
{rootOf(l.libraryRootId)?.name ?? "媒体库目录"}
</span>
) : null}
</div>
@@ -474,12 +476,12 @@ function MetadataPanel({ detail, entity }: { detail: LibraryDetail; entity: Medi
function SlotsPanel({ detail, units }: { detail: LibraryDetail; units: ContentUnit[] }) {
if (detail.slots.length === 0) {
return (
<Panel title="版本目标">
<Panel title="收录版本">
<EmptyState
plain
icon={CalendarClock}
title="还没有版本目标"
description="新建订阅或版本目标后,按目标补齐和升级。"
title="还没有收录版本"
description="新建订阅或收录版本后,按目标补齐和升级。"
action={
<Button asChild variant="outline" size="sm">
<Link to={consolePath("subscriptions")}>去订阅</Link>
@@ -492,7 +494,7 @@ function SlotsPanel({ detail, units }: { detail: LibraryDetail; units: ContentUn
}
const assetOf = (id: string | null) => detail.assets.find((a) => a.asset.id === id);
return (
<Panel title="版本目标" description="每个目标单独判断硬条件、软偏好和升级">
<Panel title="收录版本" description="每个目标单独判断硬条件、软偏好和升级">
<div className="grid gap-3 xl:grid-cols-2">
{detail.slots.map((s) => {
const cur = assetOf(s.currentAssetId);
@@ -593,7 +595,7 @@ function UnitsPanel({ entity, detail }: { entity: ReturnType<typeof useEntityDet
);
}
// ── 季与集(MD-1):季折叠 + 每集行,库内状态按版本目标 ──
// ── 季与集(MD-1):季折叠 + 每集行,库内状态按收录版本 ──
const CELL_TONE: Record<CellKind, "success" | "warning" | "destructive" | "neutral"> = {
have: "success",
@@ -701,7 +703,7 @@ function EpisodesPanel({ detail, units }: { detail: LibraryDetail; units: Conten
}
>
{rows.length > 1 ? (
<fieldset aria-label="按版本目标看状态" className="m-0 mb-3 flex min-w-0 flex-wrap gap-1.5 border-0 p-0">
<fieldset aria-label="按收录版本看状态" className="m-0 mb-3 flex min-w-0 flex-wrap gap-1.5 border-0 p-0">
{rows.map((r, i) => (
<button
key={r.slotId ?? "any"}
+5 -5
View File
@@ -62,7 +62,7 @@ export default function LibraryPage() {
<>
<PageHeader
title="媒体概览"
description="本地账本:作品、版本目标、资产与外部身份。自动匹配不覆盖人工锁定的外部身份。"
description="本地账本:作品、收录版本、资产与外部身份。自动匹配不覆盖人工锁定的外部身份。"
actions={
view === "unidentified" ? (
<Button asChild variant="outline" size="sm">
@@ -77,7 +77,7 @@ export default function LibraryPage() {
<Tabs value={view} onValueChange={(v) => setSp(new URLSearchParams(VIEW_QUERY[v as View]), { replace: true })}>
<TabsList className="mb-4">
<TabsTrigger value="works">作品</TabsTrigger>
<TabsTrigger value="unidentified">待识别</TabsTrigger>
<TabsTrigger value="unidentified">未识别</TabsTrigger>
<TabsTrigger value="identities">外部身份</TabsTrigger>
<TabsTrigger value="aliases">别名</TabsTrigger>
</TabsList>
@@ -155,10 +155,10 @@ function WorksView() {
</div>
),
},
{ key: "slots", header: "版本目标", cell: (r) => <SlotSummary counts={r.slotCounts} /> },
{ key: "slots", header: "收录版本", cell: (r) => <SlotSummary counts={r.slotCounts} /> },
{
key: "assets",
header: "资产",
header: "文件",
align: "right",
hideBelow: "sm",
cell: (r) => <span className="tabular-nums">{r.assetCount}</span>,
@@ -214,7 +214,7 @@ function WorksView() {
? { title: "没有符合条件的作品", description: "换个关键字或清除筛选。", icon: LibraryIcon }
: {
title: "还没有作品",
description: "扫描库根或新建订阅后,作品会出现在这里。",
description: "扫描媒体库目录或新建订阅后,作品会出现在这里。",
icon: LibraryIcon,
action: (
<Button asChild variant="outline" size="sm">
@@ -89,10 +89,10 @@ function Detail({ d }: { d: TmdbMediaDetail }) {
{ provider: "tmdb", externalId: d.externalId, kind: d.entityType },
{
onSuccess: (r) => {
toast.success(r.created ? `已建档「${r.title}」` : `「${r.title}」已在媒体库`);
toast.success(r.created ? `已加入媒体库「${r.title}」` : `「${r.title}」已在媒体库`);
navigate(consolePath("library", r.mediaEntityId));
},
onError: (e) => toast.error(errorMessage(e, "建档失败")),
onError: (e) => toast.error(errorMessage(e, "加入媒体库失败")),
},
);
@@ -147,7 +147,7 @@ function Detail({ d }: { d: TmdbMediaDetail }) {
) : (
<Button variant="outline" onClick={createEntity} disabled={entity.isPending}>
<FolderPlus />
{entity.isPending ? "建档中…" : "建档不订阅"}
{entity.isPending ? "加入中…" : "加入媒体库"}
</Button>
)}
</div>
@@ -197,13 +197,13 @@ function InventoryPanel({ d }: { d: TmdbMediaDetail }) {
const inv = d.inventory;
const line = slotLine(inv);
return (
<Panel title="库内对比" description="本地账本按 TMDB 身份对应;Emby 查询失败记为未知,不等于缺失">
<Panel title="库中情况">
<dl className="grid gap-4 sm:grid-cols-2">
<div className="min-w-0">
<dt className="text-meta text-muted-foreground">本地账本</dt>
<dt className="text-meta text-muted-foreground">媒体库</dt>
<dd className="mt-1 space-y-1 text-caption text-foreground">
{inv.state === "not_collected" ? (
<p>未收录。订阅或建档后开始追踪。</p>
<p>不在媒体库。</p>
) : (
<>
<p>
@@ -283,7 +283,7 @@ function SeasonsPanel({ d }: { d: TmdbMediaDetail }) {
});
const seasons = [...d.seasons].sort((a, b) => (a.seasonNumber || 999) - (b.seasonNumber || 999));
return (
<Panel title="季与集" description={d.localEntityId ? "本地一列是已入库集数" : "未收录,不显示库内状态"}>
<Panel title="季与集" description={d.localEntityId ? "本地一列是已入库集数" : "不在媒体库"}>
<div className="space-y-2">
{seasons.map((s) => (
<SeasonSection
+3 -2
View File
@@ -23,7 +23,7 @@ const ACTION_LABEL = {
delete: "永久删除",
restore: "从回收站还原",
} as const;
const VIEW_LABEL = { downloader: "下载器视角", local: "执行进程本地", library: "库根相对" } as const;
const VIEW_LABEL = { downloader: "下载器视角", local: "执行进程本地", library: "媒体库目录相对" } as const;
const FILE_STATUS: Record<PublicOrganizationFile["status"], { label: string; tone: StatusTone }> = {
queued: { label: "排队", tone: "neutral" },
running: { label: "执行中", tone: "neutral" },
@@ -160,7 +160,8 @@ export default function OrganizeRecordPage() {
})
}
>
<Bot />让 Agent 排查
<Bot />
让智能体排查
</Button>
) : null}
</>
+18 -20
View File
@@ -49,7 +49,7 @@ const PROMOS = [
];
const RESOLUTION: { value: ResolutionStatus; label: string }[] = [
{ value: "resolved", label: "已识别" },
{ value: "unresolved", label: "待识别" },
{ value: "unresolved", label: "未识别" },
{ value: "ambiguous", label: "有歧义" },
{ value: "unidentifiable", label: "无法识别" },
];
@@ -210,7 +210,7 @@ export default function SiteWorkbenchPage() {
{health ? <StatusBadge tone={health.tone}>健康 {health.text}</StatusBadge> : null}
{!s.enabled ? <StatusBadge tone="warning">站点已停用,只显示历史数据</StatusBadge> : null}
{circuit ? <StatusBadge tone="warning">{circuit}</StatusBadge> : null}
{s.snapshotEnabled && snapshotStale(s) ? <StatusBadge tone="warning">快照过期</StatusBadge> : null}
{s.snapshotEnabled && snapshotStale(s) ? <StatusBadge tone="warning">抓取已过期</StatusBadge> : null}
</span>
) : (
"加载中…"
@@ -254,7 +254,7 @@ export default function SiteWorkbenchPage() {
onClick={() => openAgentDrawer({ label: `站点:${s?.name ?? siteId}`, siteId })}
>
<Bot />
<span className="sr-only sm:not-sr-only">问 Agent</span>
<span className="sr-only sm:not-sr-only">问智能体</span>
</Button>
</>
}
@@ -277,7 +277,7 @@ export default function SiteWorkbenchPage() {
<TabsTrigger value="overview">概览</TabsTrigger>
<TabsTrigger value="resources">资源</TabsTrigger>
<TabsTrigger value="origin">原站视图</TabsTrigger>
<TabsTrigger value="snapshots">快照与 Feed</TabsTrigger>
<TabsTrigger value="snapshots">抓取与 RSS</TabsTrigger>
<TabsTrigger value="accounts">账号</TabsTrigger>
</TabsList>
@@ -302,26 +302,26 @@ export default function SiteWorkbenchPage() {
snap.mutate(
{ id: siteId },
{
onSuccess: () => toast.success("快照已排队"),
onSuccess: () => toast.success("已开始抓取"),
onError: (e) => toast.error(errorMessage(e)),
},
)
}
>
<Camera />
快照
刷新
</Button>
<Button
size="sm"
disabled={!q || !s?.enabled || (live && releases.isFetching)}
title={q ? "跳过本地快照,直接在站点搜索" : "先输入搜索词"}
title={q ? "跳过本地资源,直接在站点搜索" : "先输入搜索词"}
onClick={() => {
if (live) void releases.refetch();
else setLive(true);
}}
>
<Radar className={live && releases.isFetching ? "animate-pulse" : undefined} />
实时补查
去站点搜
</Button>
</>
}
@@ -374,17 +374,15 @@ export default function SiteWorkbenchPage() {
<EmptyState
plain
icon={all.length ? SearchX : Globe}
title={
all.length ? "已加载的结果里没有符合筛选的资源" : q ? "本地快照里没有匹配的资源" : "还没有快照数据"
}
title={all.length ? "已加载的结果里没有符合筛选的资源" : q ? "本地资源里没有匹配的资源" : "还没抓取过"}
description={
all.length
? "H&R、做种、大小、上传时间只筛选已加载的结果。加载更多或放宽条件。"
: coverage?.liveError
? `实时补查失败(${coverage.liveError}),站点上可能仍有该资源。`
? `去站点搜失败(${coverage.liveError}),站点上可能仍有该资源。`
: q
? "点「实时补查」直接在站点搜索。"
: "点「快照」抓取站点列表。"
? "点「去站点搜」直接在站点搜索。"
: "点「刷新」抓取站点列表。"
}
/>
) : (
@@ -437,11 +435,11 @@ export default function SiteWorkbenchPage() {
<div className="space-y-8">
{s ? <SnapshotPanel site={s} /> : <LoadingState rows={3} />}
<DetailSection
title="Feed"
title="RSS"
actions={
<Button size="sm" variant="outline" onClick={() => setCreatingFeed(true)}>
<Plus />
新建 Feed
新建 RSS
</Button>
}
>
@@ -486,12 +484,12 @@ function CoverageBanner({
notes.push({
tone: "warning",
text: coverage.lastSnapshotAt
? `快照已过期(上次 ${relTime(coverage.lastSnapshotAt)})`
: "没有快照,列表只来自 Feed 和实时补查",
? `抓取已过期(上次 ${relTime(coverage.lastSnapshotAt)})`
: "还没抓取过,列表只来自 RSS 和站点搜索",
});
if (coverage.liveError) notes.push({ tone: "warning", text: `实时补查失败:${coverage.liveError}` });
if (coverage.liveError) notes.push({ tone: "warning", text: `去站点搜失败:${coverage.liveError}` });
else if (coverage.live)
notes.push({ tone: "success", text: coverage.coalesced ? "已合并到进行中的同一搜索" : "已实时补查" });
notes.push({ tone: "success", text: coverage.coalesced ? "已合并到进行中的同一搜索" : "站点搜索完成" });
}
if (!notes.length) return null;
return (
+1 -1
View File
@@ -100,7 +100,7 @@ export default function SitesPage() {
<>
<PageHeader
title="站点"
description="凭证、账号数据和健康状态;点站点名看资源、快照与 Feed"
description="凭证、账号数据和健康状态;点站点名看资源、抓取与 RSS"
actions={
<>
<Button variant="outline" size="sm" onClick={() => void sites.refetch()} disabled={sites.isFetching}>
+6 -6
View File
@@ -18,7 +18,7 @@ import { legacyRedirectLoader } from "./redirects.ts";
// 来源健康:跨站点的 Feed 列表与站点健康总表;单个站点的快照、Feed、账号在站点详情。
export const meta = () => [{ title: "来源健康 · UHD 媒体自动化控制台" }];
export const meta = () => [{ title: "RSS · UHD 媒体自动化控制台" }];
// 旧的 ?tab=snapshots&siteId= 跳到站点详情的"快照与 Feed"
export const clientLoader = legacyRedirectLoader;
@@ -69,7 +69,7 @@ export default function SourcesPage() {
},
{
key: "snapshot",
header: "最近快照",
header: "最近抓取",
hideBelow: "sm",
cell: (s) => (
<span className="flex flex-wrap items-center gap-1.5 whitespace-nowrap text-muted-foreground">
@@ -83,12 +83,12 @@ export default function SourcesPage() {
return (
<>
<PageHeader
title="来源健康"
description="单个站点的快照、Feed 和账号在站点页管理。"
title="RSS"
description="单个站点的抓取、RSS 和账号在站点页管理。"
actions={
<Button size="sm" onClick={() => setCreating(true)}>
<Plus />
新建 Feed
新建 RSS
</Button>
}
/>
@@ -108,7 +108,7 @@ export default function SourcesPage() {
}}
/>
</DetailSection>
<DetailSection title="Feed">
<DetailSection title="RSS">
<FilterBar
onReset={f.active ? f.reset : undefined}
actions={
+7 -7
View File
@@ -43,7 +43,7 @@ const LEVEL: Record<HealthLevel, { label: string; tone: StatusTone }> = {
const KIND_LABEL: Record<string, string> = {
api: "API",
worker: "Worker(io)",
"worker:agent": "Worker(Agent)",
"worker:agent": "Worker(智能体)",
all: "单进程(all)",
};
@@ -51,13 +51,13 @@ const KIND_LABEL: Record<string, string> = {
const KIND_NAME: Record<string, string> = {
api: "API",
worker: "io worker",
"worker:agent": "Agent worker",
"worker:agent": "智能体 worker",
all: "单进程",
};
const MOUNT_REASON: Record<string, string> = {
root_missing: "目录不存在",
sentinel_missing: "哨兵文件缺失",
sentinel_missing: "目录不可访问或未挂载",
permission_denied: "没有权限",
error: "检查出错",
timeout: "检查超时",
@@ -227,7 +227,7 @@ function Processes({ d }: { d: SystemHealth }) {
<span className="text-muted-foreground">—</span>
) : (
<span className="whitespace-nowrap tabular-nums">
io {p.concurrency.io ?? 0} · Agent {p.concurrency.agent ?? 0}
io {p.concurrency.io ?? 0} · 智能体 {p.concurrency.agent ?? 0}
</span>
),
},
@@ -371,7 +371,7 @@ function History() {
function Disks({ d }: { d: SystemHealth }) {
const t = d.diskThresholds;
const scope = { data_dir: "数据目录", library_root: "库根", download_dir: "下载目录" } as const;
const scope = { data_dir: "数据目录", library_root: "媒体库目录", download_dir: "下载目录" } as const;
return (
<Card title="磁盘" icon={HardDrive} level={worst(d.disks.map((x) => x.level))}>
<p className="mb-3 text-meta text-muted-foreground">
@@ -428,7 +428,7 @@ function Mounts({ d }: { d: SystemHealth }) {
return (
<Card title="挂载根" icon={HardDrive} level={d.mounts.length ? (bad ? "critical" : "ok") : undefined}>
{d.mounts.length === 0 ? (
<p className="text-caption text-muted-foreground">没有启用的库根或下载目录映射。</p>
<p className="text-caption text-muted-foreground">没有启用的媒体库目录或下载目录映射。</p>
) : (
<ul className="divide-y divide-border/60">
{d.mounts.map((m) => (
@@ -436,7 +436,7 @@ function Mounts({ d }: { d: SystemHealth }) {
<div className="min-w-0 flex-1">
<p className="truncate">
<span className="mr-1.5 text-meta text-muted-foreground">
{m.kind === "library_root" ? "库根" : "下载目录"}
{m.kind === "library_root" ? "媒体库目录" : "下载目录"}
</span>
{m.name}
</p>
@@ -39,7 +39,7 @@ import { errorMessage } from "~/lib/api.ts";
import { actorText } from "~/lib/labels.ts";
import { cn, formatDateTime } from "~/lib/utils.ts";
// 订阅对比视图(FE-5,docs/design/subscriptions.md §2):季页签 → 集 × 版本目标矩阵;版本目标排序与自动补全;订阅信息。
// 订阅对比视图(FE-5,docs/design/subscriptions.md §2):季页签 → 集 × 收录版本矩阵;收录版本排序与自动补全;订阅信息。
export const meta = () => [{ title: "订阅详情 · UHD 媒体自动化控制台" }];
@@ -118,7 +118,7 @@ export default function SubscriptionDetailPage() {
{item?.group ? <StatusBadge>{GROUP_LABEL[item.group]}</StatusBadge> : null}
{item?.upgrading ? <StatusBadge tone="warning">洗版中</StatusBadge> : null}
{item?.awaitingNewSeason ? <StatusBadge tone="success">等待新季</StatusBadge> : null}
{!sub.mediaEntityId ? <StatusBadge tone="warning">尚未建档</StatusBadge> : null}
{!sub.mediaEntityId ? <StatusBadge tone="warning">不在媒体库</StatusBadge> : null}
</div>
{item ? (
<div className="grid max-w-xl gap-2">
@@ -163,7 +163,7 @@ export default function SubscriptionDetailPage() {
<div className="space-y-5">
<Panel
title="覆盖对比"
description={cov ? `按版本目标逐集对比 · 计算于 ${relTime(cov.computedAt)}` : "按版本目标逐集对比"}
description={cov ? `按收录版本逐集对比 · 计算于 ${relTime(cov.computedAt)}` : "按收录版本逐集对比"}
actions={
<Button variant="outline" size="sm" onClick={doRefresh} disabled={refresh.isPending || !sub.mediaEntityId}>
<RefreshCw className={cn(refresh.isPending && "animate-spin")} />
@@ -172,7 +172,7 @@ export default function SubscriptionDetailPage() {
}
>
{!sub.mediaEntityId ? (
<p className="text-caption text-muted-foreground">作品建档后才能对比。建档后自动关联到这个订阅。</p>
<p className="text-caption text-muted-foreground">作品加入媒体库后才能对比,加入后自动关联到这个订阅。</p>
) : covQ.isPending ? (
<LoadingState rows={3} />
) : covQ.isError ? (
@@ -271,7 +271,7 @@ export default function SubscriptionDetailPage() {
/>
</Panel>
{sub.mediaEntityId ? (
<Panel title="版本目标规则" description="条件、阶梯与升级;新建的版本目标排在最后">
<Panel title="收录版本规则" description="条件、阶梯与升级;新建的收录版本排在最后">
<SlotsPanel mediaEntityId={sub.mediaEntityId} subscriptionId={sub.id} />
</Panel>
) : null}
@@ -393,7 +393,7 @@ export default function SubscriptionsPage() {
icon={CalendarClock}
title={filtered || v.group || v.kind ? "没有符合条件的订阅" : "还没有订阅"}
description={
filtered || v.group || v.kind ? undefined : "从媒体库、搜索或 Agent 添加,也可以点「新建订阅」。"
filtered || v.group || v.kind ? undefined : "从媒体库、搜索或智能体添加,也可以点「新建订阅」。"
}
/>
) : grouped ? (
+1 -1
View File
@@ -59,7 +59,7 @@ const OP_KIND: Record<AssetOperation["kind"], string> = {
delete_source: "删除源文件",
delete_target: "删除目标文件",
verify: "校验",
register: "登记资产",
register: "登记入库",
qb_pause: "暂停下载",
qb_resume: "恢复下载",
qb_remove: "移除下载",
+1 -1
View File
@@ -99,7 +99,7 @@ export default function TasksPage() {
<>
<PageHeader
title="任务"
description="每次运行都是一条任务,不论定时、事件、手动还是 Agent 触发。"
description="每次运行都是一条任务,不论定时、事件、手动还是智能体触发。"
actions={
<Button variant="outline" size="sm" onClick={() => qc.invalidateQueries({ queryKey: qk.all("engine") })}>
<RotateCw />