**背景**:这些改动原属本工作区另外几个会话(T01/T02 等),**那些会话已被用户删除** ⇒
工作树里的成果处于"无主"状态,一次错误 checkout / 覆盖即**永久丢失** ⇒ 代入库保全。
口径遵循本项目**先例**(`39a1f2e` / `b617cdb`:**别人的活,代入库并在提交信息里注明**)。
**内容**:档案 101「能力管理」改名 + 页内 tab 分页|档案 102 语言切换搬入「用户设置」|
`07-实例UI分区登记表.md`|`scripts/find-ui*.mjs`(UI 元素定位工具)|`poc/portal-entry/`(0.5.3)|
`src/web/locale-pref.ts` + `home-files.ts`(语言偏好持久化)|`test/locale-pref.test.mjs`|
`package.json`|`BRIEF.md` / `INDEX.md` / `docs-manifest.json` / `03-路线图与待办.md` / 档案 100 增量。
**已做最小健全性检查**(⚠️ **未跑完整构建 / 单测** —— 那是原会话的验收职责,本次只求"不丢"):
- JSON 合法:`package.json` / `poc/business-plugins/package.json` / `docs-manifest.json` ✓
- 4 个 TS 文件 `{}`/`()` 配平 ✓;新增文件均非空 ✓
- 规模:13 文件改动 +340/−210,新增 12 条
**未 push**(按 §4 提交边界:用户说"提交",未说"推送")。
11 KiB
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 ⚠️ 实例侧 / 浏览器验收:本轮未完成(原因与下一步写清)
阻塞点(客观,非"将就"):平台已切集群模式 ⇒
- 新用户按容量落 Worker
w-106,不在 47 上 ⇒ 本机脚本「进实例 → 抓壳页 → 取 combo → 请求 bundle」 这条路径拿不到带新包的壳页(实测comboHasBundle: false,47 上也无该用户目录); - 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)
- 控制面库 = PostgreSQL(集群模式):
DSHS_DB_URL=postgres://dshs:…@127.0.0.1:15432/dshs,DSHS_DEPLOY_MODE=cluster。/var/lib/dshs/dshs.db(SQLite)是回滚用的旧库、平台不读 ⇒ 临时用户审批写 SQLite = 白写(实测:register201,但login403pending_review);且users表在 PG 里的主键列是id(不是user_id),清理脚本别照抄 SQLite 时代的列名。 - 一次性用户的残留:
41a9480e-5269-463d-acce-79aeee74ced7(pocuimz6rb)的 PG 行已删, 但其 home 目录留在 106 上(47 无该目录、无路由)⇒ 需在 106 侧rm -rf该用户目录才能彻底清干净。