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

@@ -50,17 +50,17 @@ const RISK: Record<RiskLevel, { label: string; tone: StatusTone }> = {
const AUTH: Record<CapabilityAuthState, { label: string; tone: StatusTone; hint: string }> = {
read_only: { label: "无需授权", tone: "neutral", hint: "只读或预览,不落变更,Key 有对应 scope 即可调用" },
granted: { label: "有持续授权", tone: "success", hint: "Agent、MCP、API Key 调用按持续授权放行" },
granted: { label: "有持续授权", tone: "success", hint: "智能体、MCP、API Key 调用按持续授权放行" },
needs_approval: {
label: "需审批",
tone: "warning",
hint: "没有覆盖它的持续授权,Agent、MCP、API Key 调用需要单次审批",
hint: "没有覆盖它的持续授权,智能体、MCP、API Key 调用需要单次审批",
},
agent_only: { label: "仅 Agent 会话", tone: "neutral", hint: "只在 Agent 会话内可用,不经 MCP 暴露" },
agent_only: { label: "仅智能体会话", tone: "neutral", hint: "只在智能体会话内可用,不经 MCP 暴露" },
};
export const CALLER_LABEL: Record<CapabilityCaller, string> = {
agent: "内置 Agent",
agent: "内置智能体",
mcp: "MCP",
rest: "REST",
other: "其他",
@@ -86,7 +86,7 @@ function McpServerBlock() {
section="agent"
anchor="mcp"
title="MCP 服务端"
description="外部 Agent 用 API Key 经 MCP 调用同一套能力。授权、审计、幂等与控制台一致,有副作用的工具没有持续授权或单次审批时被拒绝。"
description="外部智能体用 API Key 经 MCP 调用同一套能力。授权、审计、幂等与控制台一致,有副作用的工具没有持续授权或单次审批时被拒绝。"
>
{status.isPending ? (
<LoadingState rows={3} />
@@ -318,13 +318,13 @@ function CapabilitiesBlock() {
section="agent"
anchor="capabilities"
title="能力清单"
description="Agent、MCP、REST 共用这些能力。调用数与失败率为最近 7 天,含被拒绝的调用。"
description="智能体、MCP、REST 共用这些能力。调用数与失败率为最近 7 天,含被拒绝的调用。"
plain
>
{automationOff ? (
<p className="mb-3 rounded-lg border border-warning/30 bg-warning/10 px-3 py-2 text-caption text-warning">
{tools.data?.readOnlyReconcile ? "只读对账模式" : "自动化总开关已关闭"}
:执行类能力对 Agent、MCP、API Key 一律拒绝,持续授权暂不生效。
:执行类能力对智能体、MCP、API Key 一律拒绝,持续授权暂不生效。
</p>
) : null}
<DataTable
@@ -14,7 +14,7 @@ export const WEBSEARCH_CARD: CardSpec = {
anchor: "websearch",
title: "网页搜索",
description:
"开启后 Agent 可以用 web.search 搜索站外公开信息,用 web.fetch 读取搜索结果里的网页。结果和正文按不可信数据交给模型,web.fetch 不能读内网地址和结果以外的链接。",
"开启后智能体可以用 web.search 搜索站外公开信息,用 web.fetch 读取搜索结果里的网页。结果和正文按不可信数据交给模型,web.fetch 不能读内网地址和结果以外的链接。",
fields: [
{
name: "agent.webSearch.provider",
@@ -63,7 +63,7 @@ export const WEBSEARCH_CARD: CardSpec = {
name: "agent.webSearch.maxResults",
label: "每次条数",
kind: "number",
hint: "1–10,Agent 可以要更少",
hint: "1–10,智能体可以要更少",
visible: (v) => v["agent.webSearch.provider"] !== "none",
},
{
+4 -4
View File
@@ -85,7 +85,7 @@ const BUDGET_CARD: CardSpec = {
const INVESTIGATE_CARD: CardSpec = {
anchor: "investigate",
title: "自动调查",
description: "待处理事项(识别有歧义、下载失败等)创建时,自动发起一次有界调查。",
description: "待处理事项(没识别出作品、下载失败等)创建时,自动发起一次有界调查。",
fields: [
{ name: "agent.autoInvestigate", label: "自动调查", kind: "switch", wide: true },
{
@@ -155,7 +155,7 @@ const ADVANCED_CARD: CardSpec = {
name: "agent.optionsTimeoutSec",
label: "方案选择等待(秒)",
kind: "number",
hint: "Agent 给出方案后等你选择的最长时间",
hint: "智能体给出方案后等你选择的最长时间",
},
{
name: "agent.optionsTimeoutAction",
@@ -163,7 +163,7 @@ const ADVANCED_CARD: CardSpec = {
kind: "select",
options: [
{ value: "recommended", label: "自动选推荐项" },
{ value: "stop", label: "不选,Agent 收尾" },
{ value: "stop", label: "不选,智能体收尾" },
],
},
{
@@ -184,7 +184,7 @@ const ADVANCED_CARD: CardSpec = {
name: "agent.fakeScript",
label: "fake 脚本(JSON)",
kind: "textarea",
hint: "只对 fake 上游生效,留空用内置演示脚本;格式见 docs/runbook/agent.md",
hint: "只对 fake 模型服务生效,留空用内置演示脚本;格式见 docs/runbook/agent.md",
},
],
fromSettings: (s) => ({
@@ -16,7 +16,7 @@ const GiB = 1024 ** 3;
const CACHE_CARD: CardSpec = {
anchor: "cache",
title: "缓存水位",
description: "缓存卷使用率到硬阈值时停止新增下载并暂停纳管任务;降到恢复水位以下只恢复因阈值暂停的任务。",
description: "缓存卷使用率到硬阈值时停止新增下载并暂停本工具的下载任务;降到恢复水位以下只恢复因阈值暂停的任务。",
fields: [
{ name: "cache.hardThresholdPercent", label: "硬阈值(%)", kind: "number" },
{ name: "cache.resumePercent", label: "恢复水位(%)", kind: "number", hint: "低于硬阈值" },
@@ -115,7 +115,7 @@ const STORAGE_BUDGET_CARD: CardSpec = {
gbField("pgDataWarnGb", "数据库告警线", "不高于数据库预算"),
gbField("imagesGb", "图片缓存容量"),
gbField("backupsGb", "备份目录预算"),
gbField("sessionsEvidenceGb", "Agent 会话与截图证据预算"),
gbField("sessionsEvidenceGb", "智能体会话与截图证据预算"),
],
fromSettings: (s) => Object.fromEntries(BUDGET_KEYS.map((k) => [`storage.budgets.${k}`, n(s.storage.budgets[k])])),
toPatch: (v) => ({ storage: { budgets: pick(v, "storage.budgets", BUDGET_KEYS) } }),
@@ -136,17 +136,17 @@ const STORAGE_RETENTION_CARD: CardSpec = {
title: "流水数据保留",
description: "到期由定时清理任务分批删除。单位天,心跳为小时。",
fields: [
dayField("retentionDays", "observation", "Feed 观察"),
dayField("retentionDays", "observation", "RSS 抓取记录"),
dayField("retentionDays", "sseEvent", "实时事件"),
dayField("retentionDays", "heartbeatHours", "进程心跳(小时)"),
dayField("retentionDays", "taskRun", "任务运行记录"),
dayField("retentionDays", "agentEvent", "Agent 会话事件", "只删已结束的会话"),
dayField("retentionDays", "agentEvent", "智能体会话事件", "只删已结束的会话"),
dayField("retentionDays", "audit", "审计日志", "能力调用审计除外"),
dayField("retentionDays", "capabilityStats", "能力调用统计"),
dayField(
"retentionDays",
"identityPoolArchive",
"待识别池归档",
"未识别资源归档",
"未命中的条目这么久没有新证据就归档,仍可手动指定",
),
],
@@ -97,7 +97,7 @@ export function BrowserProvidersBlock() {
section="sites"
anchor="browser"
title="浏览器服务"
description="原站视图和 Agent 打开站点网页用的 camofox-browser。部署自带的一台由环境变量登记(camofox-env)。"
description="原站视图和智能体打开站点网页用的 camofox-browser。部署自带的一台由环境变量登记(camofox-env)。"
plain
>
<ResourceSection<BrowserProviderView>
@@ -257,9 +257,8 @@ export function LibraryRootsTable() {
<ResourceSection<LibraryRoot>
hooks={{ list, create, update, remove } as never}
config={{
noun: "库根",
description:
"登记或改路径时会在目录里写哨兵文件 .uhd-root,据此判断库根是否在线;目录不存在或不可写时无法保存。",
noun: "媒体库目录",
description: "保存时会在目录里放一个标记文件,用来判断目录是否挂载;目录不存在或不可写时无法保存。",
deleteHint: "只删除登记,磁盘上的文件不动。",
columns: [
{
@@ -286,7 +285,7 @@ export function LibraryRootsTable() {
},
{ key: "health", header: "在线", cell: (r) => <Health status={r.healthStatus} at={r.lastHealthAt} /> },
],
rowActions: (r) => <TestButton label="检查哨兵" pending={test.isPending} run={() => test.mutateAsync(r.id)} />,
rowActions: (r) => <TestButton label="检查目录" pending={test.isPending} run={() => test.mutateAsync(r.id)} />,
fields: [
{ name: "name", label: "名称", kind: "text", required: true, placeholder: "例如:电影库" },
{ name: "mediaKind", label: "内容类型", kind: "select", options: MEDIA_KIND },
+1 -1
View File
@@ -42,7 +42,7 @@ const ORGANIZE_CARD: CardSpec = {
export function LibrarySection() {
return (
<div className="grid grid-cols-1 gap-8">
<SettingsBlock section="library" anchor="roots" title="库根" plain>
<SettingsBlock section="library" anchor="roots" title="媒体库目录" plain>
<LibraryRootsTable />
</SettingsBlock>
<SettingsGate>{(s) => <SettingsCard section="library" spec={ORGANIZE_CARD} settings={s} />}</SettingsGate>
@@ -203,7 +203,7 @@ const IDENTITY_CARD: CardSpec = {
anchor: "identity",
title: "资源识别",
description:
"缺省只在资源命中订阅或本地作品别名、新作品收录规则前置条件通过、你手动识别或搜索时查 TMDB;其余进待识别池,不进待处理。",
"缺省只在资源命中订阅或本地作品别名、新作品收录规则前置条件通过、你手动识别或搜索时查 TMDB;其余进未识别资源,不进待处理。",
fields: [
{
name: "identity.eagerTmdbLookup",
@@ -216,7 +216,7 @@ const IDENTITY_CARD: CardSpec = {
name: "identity.autoEnrich",
label: "TMDB 查不到时自动补全元数据",
kind: "switch",
hint: "资源命中订阅或新作品收录规则、TMDB 却没有结果时,自动搜索网页出一份补全建议(每条资源一次,不建作品)。每次约 1–2 次网页搜索、最多读 3 个网页、1 次 Agent 上游调用",
hint: "资源命中订阅或新作品收录规则、TMDB 却没有结果时,自动搜索网页出一份补全建议(每条资源一次,不建作品)。每次约 1–2 次网页搜索、最多读 3 个网页、1 次 模型服务调用",
wide: true,
},
{
+5 -5
View File
@@ -120,7 +120,7 @@ function RootNaming() {
<SettingsBlock
section="library"
anchor="naming-roots"
title="库根命名覆盖"
title="媒体库目录命名覆盖"
description="按类型覆盖全局模板;留空的类型沿用全局。"
>
{roots.isPending ? (
@@ -128,12 +128,12 @@ function RootNaming() {
) : roots.isError ? (
<ErrorState plain error={roots.error} onRetry={() => roots.refetch()} />
) : !root ? (
<p className="text-caption text-muted-foreground">还没有库根。先在上方「库根」添加。</p>
<p className="text-caption text-muted-foreground">还没有媒体库目录。先在上方「媒体库目录」添加。</p>
) : (
<div className="space-y-4">
<div className="flex flex-wrap items-center gap-2">
<Select value={root.id} onValueChange={setRootId}>
<SelectTrigger size="sm" aria-label="库根" className="max-w-full min-w-48">
<SelectTrigger size="sm" aria-label="媒体库目录" className="max-w-full min-w-48">
<SelectValue />
</SelectTrigger>
<SelectContent position="popper">
@@ -177,7 +177,7 @@ function RootNamingEditor({ root }: { root: LibraryRoot }) {
try {
await save.mutateAsync({ id: root.id, naming: body });
setDraft({ ...(body ?? {}) });
toast.success(body ? "库根命名覆盖已保存" : "已清除库根命名覆盖");
toast.success(body ? "媒体库目录命名覆盖已保存" : "已清除媒体库目录命名覆盖");
} catch (err) {
const split = splitNamingErrors(errorMessage(err));
if (split) setErrors(split);
@@ -190,7 +190,7 @@ function RootNamingEditor({ root }: { root: LibraryRoot }) {
<div className="flex flex-wrap items-center gap-2">
{KINDS.map((k) => (
<StatusBadge key={k} tone={saved[k] ? "success" : "neutral"}>
{NAMING_KIND_LABEL[k]}:{saved[k] ? "库根覆盖" : "沿用全局"}
{NAMING_KIND_LABEL[k]}:{saved[k] ? "媒体库目录覆盖" : "沿用全局"}
</StatusBadge>
))}
</div>
@@ -104,7 +104,7 @@ export function PathMappingsPanel({
<p className="truncate">{m.remotePrefix}</p>
<p className="truncate text-muted-foreground">→ {m.localPrefix}</p>
{rootName(m.libraryRootId) ? (
<p className="font-sans text-muted-foreground">库根:{rootName(m.libraryRootId)}</p>
<p className="font-sans text-muted-foreground">媒体库目录:{rootName(m.libraryRootId)}</p>
) : null}
</div>
<StatusBadge tone={h.tone}>{h.label}</StatusBadge>
@@ -175,7 +175,7 @@ export function PathMappingsPanel({
disabled={create.isPending}
/>
<FieldControl
spec={{ name: "libraryRootId", label: "关联库根", kind: "select", options: rootOpts }}
spec={{ name: "libraryRootId", label: "关联媒体库目录", kind: "select", options: rootOpts }}
value={v.libraryRootId}
onChange={(x) => setV((s) => ({ ...s, libraryRootId: x }))}
disabled={create.isPending}
+1 -1
View File
@@ -22,7 +22,7 @@ const SOURCE_TEXT: Record<string, string> = { "stored credential": "已存凭证
/** 上游返回的多是英文原文:先给一句怎么办,原文留作排查 */
const ERROR_HINT: Record<string, string> = {
network: "连不上上游,检查 Base URL、端口和网络",
network: "连不上模型服务,检查 Base URL、端口和网络",
auth: "凭证被拒,检查 API Key 或重新订阅登录",
model: "模型不可用,换一个模型 id",
};
@@ -49,13 +49,13 @@ export function PiUpstreamsBlock() {
<SettingsBlock
section="agent"
anchor="upstream"
title="上游"
description="没指定上游的运行用默认上游。密钥和登录凭证保存后不回显。"
title="模型服务"
description="没指定模型服务的运行用默认模型服务。密钥和登录凭证保存后不回显。"
plain
actions={
<Button size="sm" onClick={() => setWizard(true)}>
<Plus />
新建上游
新建模型服务
</Button>
}
>
@@ -66,7 +66,7 @@ export function PiUpstreamsBlock() {
error={list.error}
onRetry={() => list.refetch()}
onRowClick={(r) => setOpenId(r.id)}
empty={{ title: "还没有上游" }}
empty={{ title: "还没有模型服务" }}
columns={[
{
key: "name",
@@ -181,7 +181,7 @@ function UpstreamDrawer({
const makeDefault = async () => {
const model = v.model.trim();
if (!model) {
setErrors((e) => ({ ...e, defaultModel: "设为默认上游前先选默认模型" }));
setErrors((e) => ({ ...e, defaultModel: "设为默认模型服务前先选默认模型" }));
return;
}
try {
@@ -189,7 +189,7 @@ function UpstreamDrawer({
id: upstream.id,
body: { makeDefault: true, ...(model !== (upstream.defaultModel ?? "") ? { defaultModel: model } : {}) },
});
toast.success(`${upstream.name}已设为默认上游`);
toast.success(`${upstream.name}已设为默认模型服务`);
} catch (err) {
toast.error(errorMessage(err, "设置失败"));
}
@@ -204,7 +204,7 @@ function UpstreamDrawer({
description={
<span className="flex flex-wrap items-center gap-2">
<UpstreamStatusBadge u={upstream} />
{upstream.isDefault ? <StatusBadge tone="success">默认上游</StatusBadge> : null}
{upstream.isDefault ? <StatusBadge tone="success">默认模型服务</StatusBadge> : null}
<span>
{provider?.name ?? upstream.provider} · {AUTH_LABEL[upstream.authKind]}
</span>
@@ -402,11 +402,13 @@ function UpstreamDrawer({
<ConfirmDialog
open={deleting}
onOpenChange={setDeleting}
title={`删除上游「${upstream.name}」?`}
title={`删除模型服务「${upstream.name}」?`}
description={
upstream.isDefault ? "它是默认上游,删除后没有默认上游;凭证一并删除,无法恢复。" : "凭证一并删除,无法恢复。"
upstream.isDefault
? "它是默认模型服务,删除后没有默认模型服务;凭证一并删除,无法恢复。"
: "凭证一并删除,无法恢复。"
}
confirmLabel="删除上游"
confirmLabel="删除模型服务"
destructive
onConfirm={async () => {
try {
@@ -60,9 +60,9 @@ export function PiUpstreamWizard({ open, onClose }: { open: boolean; onClose: ()
const title =
step === "provider"
? "新建 Agent 上游"
? "新建模型服务"
: step === "config"
? `新建上游 · ${provider?.name ?? ""}`
? `新建模型服务 · ${provider?.name ?? ""}`
: (created?.name ?? "");
const description =
step === "provider"
@@ -70,8 +70,8 @@ export function PiUpstreamWizard({ open, onClose }: { open: boolean; onClose: ()
: step === "config"
? "第 2 步,共 3 步:凭证与模型"
: created?.authKind === "oauth"
? "第 3 步,共 3 步:登录并测试。上游已创建。"
: "第 3 步,共 3 步:测试。上游已创建。";
? "第 3 步,共 3 步:登录并测试。模型服务已创建。"
: "第 3 步,共 3 步:测试。模型服务已创建。";
return (
<DetailSheet open={open} onOpenChange={(o) => !o && close()} title={title} description={description} width="lg">
@@ -413,7 +413,7 @@ function VerifyStep({
const finish = async () => {
if (makeDefault && !model.trim()) {
setModelError("设为默认上游前先选默认模型");
setModelError("设为默认模型服务前先选默认模型");
return;
}
const body = {
@@ -422,7 +422,7 @@ function VerifyStep({
};
try {
if (Object.keys(body).length) await update.mutateAsync({ id: upstream.id, body });
if (makeDefault && !upstream.isDefault) toast.success(`${upstream.name}已设为默认上游`);
if (makeDefault && !upstream.isDefault) toast.success(`${upstream.name}已设为默认模型服务`);
onDone();
} catch (err) {
toast.error(errorMessage(err, "保存失败"));
@@ -476,7 +476,7 @@ function VerifyStep({
onCheckedChange={setMakeDefault}
disabled={upstream.isDefault}
/>
{upstream.isDefault ? "已是默认上游" : "设为默认上游"}
{upstream.isDefault ? "已是默认模型服务" : "设为默认模型服务"}
</label>
<Button onClick={finish} disabled={update.isPending}>
{update.isPending ? <LoaderCircle className="animate-spin" /> : null}
+4 -4
View File
@@ -23,15 +23,15 @@ const DEFAULTS_CARD: CardSpec = {
{ name: "sites.defaultRateLimit.concurrency", label: "并发", kind: "number", hint: "同一站点同时进行的请求" },
{
name: "sites.defaultSnapshotIntervalSec",
label: "快照周期(分钟)",
label: "抓取周期(分钟)",
kind: "number",
hint: "站点开了定时快照但没设周期时使用;5–10080",
hint: "站点开了定时抓取但没设周期时使用;5–10080",
},
{
name: "sites.defaultSnapshotMaxPages",
label: "快照页数",
label: "抓取页数",
kind: "number",
hint: "定时和手动快照最多抓几页列表;站点定义更少时按站点;1–20",
hint: "定时和手动抓取最多抓几页列表;站点定义更少时按站点;1–20",
},
{
name: "sites.defaultHealthCheckIntervalSec",