# 101 · 「能力管理」:分区改名 + 页内 tab 分页 + 插件卡片三行描述 + DeepSeek 改名 - 日期:2026-09-15 - 触发:用户三句话 —— ①「设置中 功能管理改为能力管理」②「能力管理页面改造为 tab可切换 插件和技能分别进行操作」③「插件卡片中增加中文用途描述 显示三行」④「内置 DeepSeek,去掉内置就叫 DeepSeek 就行」 - 对象:`poc/business-plugins`(`@dsh-local/business-plugins`)client bundle 的「功能管理」section - 状态:✅ 已实施并投放(`0.3.21 → 0.3.22`) - 前置:档案 **100**(「我的技能」并入本 section)+ `交接单/archive/T01` > **TL;DR**|**结论**:分区改名「**能力管理**」(label 级),页内改为 **tab 分页**(功能插件 / 我的技能,各自独立操作),插件卡片的**中文用途描述从 1 行放宽到 3 行**(`.bp-plugDesc`),并把「**内置 DeepSeek → DeepSeek**」。 > **关键**:全部落在**用户可见文案 / 版式**这一层 —— **代码标识符、section id、包名、API 路径、词典键名一律不动**(素材库 U10)。 > **不做**:不改服务端;不动「我的技能」的机制(仍是 watch 即时生效 / 两阶段替换 / 锁定行无可点动作)。 --- ## 一、四项改动 ### 1.1 分区改名:功能管理 → 能力管理 | 位置 | 旧 | 新 | |---|---|---| | `section.label`(zh) | 功能管理 | **能力管理** | | `section.label`(en) | Feature management | **Capability management** | ⛔ **只改 label**:`id: "business-plugins"`、包名 `@dsh-local/business-plugins`、`/api/plugins/*`、词典键名、CSS 类名一律不动 —— 与档案 60(功能插件→功能管理)同一口径。 ### 1.2 页内 tab 分页(插件 / 技能各自操作) ``` ❙ 能力管理 ┌ 功能插件 3 ┐┌ 我的技能 2 ┐ ← 下划线式 tab(复用 .pa-tabs/.pa-tab/.pa-tcnt),各带计数 ┬──────────────────────────────── │ 【功能插件页】内存条 / 搜索·全选·清空 / 卡片网格 / 应用更改 ← 内容与改造前逐项一致 │ 【我的技能页】说明 + [+ 上传技能] / 上传面板 / 技能行列表 ``` - **实现**:`isPlugins = tab === "plugins"`(`useState("plugins")`,**默认落插件页** = 与改造前一致);插件块 `if (isPlugins)`、技能块 `if (!isPlugins)`,四段门控成对。 - **复用而非新造**(U2):tab 直接用既有 `.pa-tabs/.pa-tab/.pa-tcnt`(下划线式;规范 §4.4「数据页用下划线式」),**没有新增样式**。 - **计数**:插件页签 = 候选池条目数,技能页签 = 列表条目数(含共享)—— 不点进去也知道各类有多少。 - **技能页去掉重复标题**:`❙ 我的技能` 竖条标题删掉(标题已由 tab 承担),只留「生效方式说明(左)+ 上传入口(右)」工具行(`.bp-tabHead`);避免同页出现两个同名标签。 - 可访问性:`role="tablist"` / `role="tab"` / `aria-selected`。 ### 1.3 插件卡片:中文用途描述 = 3 行 - 新增 `.bp-plugDesc`:`display:-webkit-box` + `-webkit-line-clamp:3` + `-webkit-box-orient:vertical` + `overflow:hidden` + **显式 `white-space:normal`**。 - ⚠️ `white-space:normal` 必须显式写 —— 旧版单行截断用 `nowrap`,不覆盖会让多行截断失效(一眼看不出来的静默回退)。 - `title` 仍挂全文,hover 可看完整。 - 为什么:单行 ellipsis 会把「这个插件到底干什么」截掉大半;候选池入库时已优先取官方目录的**中文**说明,值得多给两行。 ### 1.4 「内置 DeepSeek」→「DeepSeek」 | 词典键 | 旧(zh) | 新(zh) | |---|---|---| | `ms.kindBuiltin` | 内置 DeepSeek | **DeepSeek** | | `ms.optBuiltin` | 内置 DeepSeek(平台共享) | **DeepSeek** | | `ms.builtinPickHint` | 内置 DeepSeek:平台内置通道,固定官方端点,无需填 API 地址。 | **DeepSeek:官方通道,固定官方端点,无需填 API 地址。** | | `ms.tagBuiltin` | 内置 | **官方** | | `ms.builtinHint` | 平台内置通道,无需端点 | **官方通道,无需端点** | | `ms.catUnreadable` / `ms.catEmpty` | …当前只能使用内置 DeepSeek 或自定义提供方… | …当前只能使用 **DeepSeek** 或自定义提供方… | en 侧同批(`Built-in DeepSeek` → `DeepSeek`;`Built-in` → `Official`)。 **顺带修掉的误导**:原选项名写的「(平台共享)」**是错的** —— 该选项走的是**内置通道(固定官方端点)**,但**必须填用户自己的 API 密钥**(`keyAddSchema` 里 `apiKey` 是 `required + minLength:1`;输入框 placeholder 也写着「输入你的 DeepSeek API 密钥」)。叫「平台共享」会让人以为不用填 key —— 这正是用户「添加模型也不能添加内置模型 没有对应KEY」困惑的来源。 --- ## 二、与「平台共享模型」的关系(本次一并澄清,**重要,别再混**) | 名称 | 是什么 | 谁的 | 要不要自己的 key | 能删吗 | |---|---|---|---|---| | **平台共享模型 `deepseek-main`**(页内**卡片**) | admin 名下**已启用**的 key 条目(`sharedKeyInfo().name` = admin 启用列表里的第一条名) | **平台 / admin** | ❌ 不要(用户不配 key 时回落到它) | ❌ **只能开关**(卡片上只有一个「停用/启用共享模型」,走 `/api/me/models/shared`) | | **`DeepSeek`**(「新增提供方」下拉里的选项) | 内置通道(固定官方端点)**给用户自己加条目**用的 provider 类型 | **用户自己** | ✅ **要**(否则 400) | 用户可删自己的条目 | ⇒ **不是一回事**:「平台共享模型 `deepseek-main`」是**平台那条 key**;「DeepSeek」是**你自己新建条目时选的通道**。另:官方 pi-ai 目录里的 `deepseek` 被后端**显式排除**(`src/web/routes/auth.ts` 注释原文:「平台已有『内置 DeepSeek』入口…再列一个同名选项只会让用户分不清哪个生效」)—— 所以下拉里不会出现第二个 DeepSeek。 --- ## 三、实现落点 | 文件 | 改动 | |---|---| | `poc/business-plugins/lib/client.js` | ① zh/en:`section.label` 改名 + `cap.tabPlugins` 新词条 + `ms.*` 6 条措辞(内置→DeepSeek);② section 内新增 `tab` state + tab 条渲染 + 四段门控;③ 技能页工具行 `.bp-tabHead`(去重复标题);④ 插件卡片描述改 `.bp-plugDesc`;⑤ 注入 CSS:`.bp-tabHead` / `.bp-plugDesc` | | `poc/business-plugins/package.json` | 版本 `0.3.21 → 0.3.22`(含 ①②③④ 四项)+ `description` 追加本轮条目 | | `scripts/verify-my-skills.mjs` | 新增 **20 条**断言:改名生效 / 旧名不再是 label / tab 条与两个页签 / 计数 / `aria-selected` / 门控成对(`isPlugins` 2 处 + `!isPlugins` 2 处)/ `.bp-plugDesc` 三件套 / `title` 仍在 | | `scripts/verify-platform-admin-section.mjs` | 跟进改名:断言「条目名显示为 DeepSeek、不再有『内置 DeepSeek』」+ 3 处测试名/注释里的旧分区名 | **零服务端改动**;未动官方包。 --- ## 四、验收 | # | 口径 | 结果 | |---|---|---| | A | `npm run verify` | ✅ 全绿(zh/en 各 **367** 键、引用键全部已声明;含新增 20 条断言) | | B | 产物 | ✅ `business-plugins-0.3.22.tgz` 与服务器 sha256 一致(`a5a55b41…`) | | C | 投放 | 见 §五 | | D | 06 §7 三段式 + 浏览器 | 见 §五 | --- ## 五、实施与投放记录 ### 5.1 投放 ``` 产物:dsh-local-business-plugins-0.3.22.tgz 72,882 B sha256 a5a55b41…(与服务器一致) ssh bt-server 'cd /opt/dshs && node scripts/ensure-biz-plugins.cjs --all --restart' admin: 版本落后(0.3.21 → 0.3.22),升级中… ✓ bundles=7(含 @dsh-local/business-plugins: true) guest: 版本落后(0.3.21 → 0.3.22),升级中… ✓ bundles=6(含 @dsh-local/business-plugins: true) 已停 0 个实例 scope(下次访问自动拉起) ``` ⚠️ **同号覆盖说明**:`0.3.22` 在本次投放前**从未安装到任何 profile**(两实例都是 0.3.21)⇒ 打包后(含 1.4 的措辞微调)**覆盖同一版本号重发是安全的** —— 若已装过 0.3.22,则必须升到 0.3.23 (否则 pnpm 判定"无需更新",静默不生效,档案 18 踩坑 1)。 ### 5.2 磁盘层(06 §7.3 ①)✅ | 实例 profile | 版本 | `能力管理` | `cap.tabPlugins` | `bp-plugDesc` | 旧 `section.label: "功能管理"` | |---|---|---|---|---|---| | admin(`4092b965…`) | **0.3.22** | 3 | 3 | 3 | **0** | | guest(`cce6d1cd…`) | **0.3.22** | 3 | 3 | 3 | **0** | ### 5.3 本地校验 ✅ `npm run verify` 全绿:zh/en 各 **367** 键、引用键全部已声明;`verify-my-skills.mjs` 新增 **20** 条 (改名生效 / 旧名不再是任何 label / tab 条与两个页签 / 计数 / `aria-selected` / 门控成对 (`isPlugins` 2 处 + `!isPlugins` 2 处)/ `.bp-plugDesc` 三件套 / `title` 仍在);`verify-platform-admin-section.mjs` 的「条目名 = DeepSeek」断言同步跟新。 ### 5.4 ⚠️ 实例侧 / 浏览器验收:**本轮未完成**(原因与下一步写清) **阻塞点(客观,非"将就")**:平台已切**集群模式** ⇒ 1. **新用户按容量落 Worker `w-106`**,不在 47 上 ⇒ 本机脚本「进实例 → 抓壳页 → 取 combo → 请求 bundle」 这条路径拿不到带新包的壳页(实测 `comboHasBundle: false`,47 上也无该用户目录); 2. **47 → 106 无 SSH 路由**(106 是经反向隧道连到 47 的)⇒ 从 47 侧探不到 106 上的 profile/实例。 **已做到的**:磁盘层(§5.2)已确认两实例 profile 里就是新代码 —— 而实例壳页的 combo 是对 `profile/node_modules/**/client.js` 的**拼接**,所以"服务端会返回新内容"由 §5.2 强推出,但**未经端到端实测**。 **回头解决的条件**:一旦拿到「新用户落在哪台 Worker / 该 Worker 的 SSH 或 agent 通道」, 就用档案 100 §8.4 那套(`poc-ui-user.cjs` + `agent-browser`)把三段式与视觉验收补上。 ### 5.5 本轮附带的两个**实测事实**(高复用,已进 `PLAYBOOK §9`) 1. **控制面库 = PostgreSQL(集群模式)**:`DSHS_DB_URL=postgres://dshs:…@127.0.0.1:15432/dshs`, `DSHS_DEPLOY_MODE=cluster`。**`/var/lib/dshs/dshs.db`(SQLite)是回滚用的旧库、平台不读** ⇒ 临时用户审批写 SQLite = 白写(实测:`register` 201,但 `login` 403 `pending_review`);且 `users` 表在 PG 里的主键列是 **`id`**(不是 `user_id`),清理脚本别照抄 SQLite 时代的列名。 2. **一次性用户的残留**:`41a9480e-5269-463d-acce-79aeee74ced7`(`pocuimz6rb`)的 PG 行**已删**, 但其 **home 目录留在 106 上**(47 无该目录、无路由)⇒ 需在 106 侧 `rm -rf` 该用户目录才能彻底清干净。