Files
dsh_shenxian/dsh-server-docs/04-调整方案/101-能力管理-改名与tab分页与卡片三行.md
T
admin 3efd68517f chore: 并入已删除会话的在途成果(防丢失;原会话已删,未做功能验收)
**背景**:这些改动原属本工作区另外几个会话(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 提交边界:用户说"提交",未说"推送")。
2026-09-15 21:17:12 +08:00

152 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 该用户目录才能彻底清干净。