feat(models): 平台自建「模型设置」—— 用户自配厂家 / 条目各自开关 / 共享模型开关(档案 87)

- 官方「设置 → 模型」页在平台环境**必然报错**(判据在**浏览器页面**的 loopback 判定;官方 README 原文 Non-loopback pages get no durable settings)⇒ 该分区对全角色(含 admin)隐藏,用户自配改走平台自建页(插件 0.3.11)
- DB 迁移 V6:credential_vault.route/base_url/api/models + users.shared_model_enabled;并**重定义 getEnabledCredentialKeyRef**(互斥删除后原实现无 ORDER BY ⇒ 「任取一条」)
- 新落地层 src/web/model-landing.ts:spawn 时把「已启用条目」写进实例 .credentials.yaml 与 settings.yaml 的 llm-pi-ai.providers.<route>;字段名与官方包实测对齐(apiKeyEnv / baseURL / api,**不是** protocol);只碰自己写过的 + 一次性交接
- 接口 /api/me/keys、/api/me/keys/:id/toggle、/api/me/models/shared;前端新增「设置 → 模型设置」分区(settings.section id=model-settings / order 100 / 全角色)
- 顺手修两处:ensure-role-profile-patch.cjs 的 --force 整文件覆盖会抹掉 admin 的 disable-hmr 与 workspace-scoped-picker 两个平台块(改为 stripManagedBlock 只替换自己那段);verify-mem-model.mjs 因档案 86 重构而长期失败的陈旧断言

⚠️ 本提交同时包含**档案 86(admin 跨用户实例管理 + 两处改名)**的代码改动 —— 该部分已上线并端到端验证;其 import/register 与本次改动同处 src/web/server.ts、poc/business-plugins/lib/client.js 等文件,按**文件粒度无法拆分**,且不带它会让仓库 tsc 直接失败(缺 src/web/routes/admin-user-ops.ts)。
This commit is contained in:
admin committed 2026-09-14 00:00:15 +08:00
1 parent eb50ca2d5b
commit 918f1d3a51
20 files changed
+2073 -276

No files matched your search

+35
View File
@@ -258,6 +258,40 @@ CREATE TABLE IF NOT EXISTS business_plugins (
);
`
// v6: 用户自配「模型厂家」支持(档案 87)。
// 原先 `credential_vault` 只存一把 key(`key_name` 兼作展示名);用户要按**厂家**配模型,
// 平台还需要知道:**route**(= settings.yaml 里 `llm-pi-ai.providers` 的 dict 键)、
// **endpoint**(`baseURL`)、**协议**(`api`)、**模型清单**(`models`)—— spawn 时按这四样写
// `$DSH_HOME/settings.yaml` 的 `llm-pi-ai.providers.<route>` 与
// `$DSH_HOME/.credentials.yaml` 的 `refs.<REF>`。
// · `base_url` 为空 = **内置 DeepSeek**(只写 refs,不写 settings.yaml)。
// · REF 命名:内置 = `DEEPSEEK_API_KEY`;自定义 = `<ROUTE 大写化>_API_KEY`
// (须匹配 `@deepseek-ai/dsh-credentials` 的 `REF_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/`)。
// · `api` 的合法值只有三个(`dsh-llm-pi-ai` 的 `PROTOCOLS` 键,顺序即默认优先级):
// `openai-completions` / `openai-responses` / `anthropic-messages`。
// · ⚠️ 字段名是 **`api` 不是 `protocol`**;`apiKeyEnv`(不是 `apiKey`);且该 profile
// **不接受** `provider` / `maxRetries` / `maxRetryDelayMs`(会直接抛错)。
// 以上全部为 2026-09-13 读官方包 `[email protected]` 的 `lib/index.js`
// (`const NS = "llm-pi-ai"` / `profile` schema / `PROTOCOLS`)实测结论。
// 另:`users.shared_model_enabled` = 用户**要不要用平台共享模型**(admin 配的那把)——
// 属于用户侧偏好,开关它**不动** admin 的配置(用户口径:条目各自开关,都能同时启用;
// 具体用哪个模型是在 dsh 对话框的模型选择器里挑)。
const SQLITE_V6 = `
ALTER TABLE credential_vault ADD COLUMN route TEXT;
ALTER TABLE credential_vault ADD COLUMN base_url TEXT;
ALTER TABLE credential_vault ADD COLUMN api TEXT;
ALTER TABLE credential_vault ADD COLUMN models TEXT;
ALTER TABLE users ADD COLUMN shared_model_enabled INTEGER NOT NULL DEFAULT 1;
`
const PG_V6 = `
ALTER TABLE credential_vault ADD COLUMN route TEXT;
ALTER TABLE credential_vault ADD COLUMN base_url TEXT;
ALTER TABLE credential_vault ADD COLUMN api TEXT;
ALTER TABLE credential_vault ADD COLUMN models TEXT;
ALTER TABLE users ADD COLUMN shared_model_enabled INTEGER NOT NULL DEFAULT 1;
`
interface Migration {
version: number
name: string
@@ -271,6 +305,7 @@ const MIGRATIONS: readonly Migration[] = [
{ version: 3, name: 'per-user uid', sqlite: SQLITE_V3, pg: PG_V3 },
{ version: 4, name: 'instance desired state', sqlite: SQLITE_V4, pg: PG_V4 },
{ version: 5, name: 'business plugin candidate pool', sqlite: SQLITE_V5, pg: PG_V5 },
{ version: 6, name: 'user model providers', sqlite: SQLITE_V6, pg: PG_V6 },
]
/** Apply unapplied SQLite migrations inside a single transaction. */