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

+34 -2
View File
@@ -39,9 +39,12 @@ import {
getEnabledCredentialKeyRef as getEnabledCredentialKeyRefSync,
getEnabledPluginIds as getEnabledPluginIdsSync,
getOrCreateWorkspace as getOrCreateWorkspaceSync,
getSharedModelEnabled as getSharedModelEnabledSync,
listBusinessPlugins as listBusinessPluginsSync,
listCredentialKeys as listCredentialKeysSync,
listCredentialLandingRows as listCredentialLandingRowsSync,
listDomains as listDomainsSync,
listEnabledCredentialKeys as listEnabledCredentialKeysSync,
listInstancesByRole as listInstancesByRoleSync,
listPublicUsers as listPublicUsersSync,
listUsersWithoutUid as listUsersWithoutUidSync,
@@ -50,8 +53,10 @@ import {
setDomainVerified as setDomainVerifiedSync,
setFolderPlugins as setFolderPluginsSync,
setInstanceStatus as setInstanceStatusSync,
setSharedModelEnabled as setSharedModelEnabledSync,
setUserRole as setUserRoleSync,
setUserUid as setUserUidSync,
toggleCredentialKey as toggleCredentialKeySync,
upsertBusinessPlugin as upsertBusinessPluginSync,
upsertDomain as upsertDomainSync,
upsertInstance as upsertInstanceSync,
@@ -59,6 +64,8 @@ import {
import type {
BusinessPlugin,
CredentialKey,
CredentialKeyMeta,
CredentialLandingRow,
CreateSessionInput,
CreateUserInput,
Domain,
@@ -229,13 +236,26 @@ export class SqliteAdapter implements DbAdapter {
return listCredentialKeysSync(this.db, userId)
}
async listEnabledCredentialKeys(userId: string): Promise<CredentialKey[]> {
return listEnabledCredentialKeysSync(this.db, userId)
}
async listCredentialLandingRows(userId: string): Promise<CredentialLandingRow[]> {
return listCredentialLandingRowsSync(this.db, userId)
}
async getEnabledCredentialKeyRef(userId: string): Promise<string | null> {
return getEnabledCredentialKeyRefSync(this.db, userId)
}
async setCredentialKey(userId: string, name: string, encryptedRef: string): Promise<CredentialKey> {
async setCredentialKey(
userId: string,
name: string,
encryptedRef: string,
meta?: CredentialKeyMeta,
): Promise<CredentialKey> {
try {
return setCredentialKeySync(this.db, userId, name, encryptedRef)
return setCredentialKeySync(this.db, userId, name, encryptedRef, meta)
} catch (e) {
mapSqliteError(e)
}
@@ -245,6 +265,18 @@ export class SqliteAdapter implements DbAdapter {
return selectCredentialKeySync(this.db, userId, id)
}
async toggleCredentialKey(userId: string, id: string, enabled: boolean): Promise<boolean> {
return toggleCredentialKeySync(this.db, userId, id, enabled)
}
async getSharedModelEnabled(userId: string): Promise<boolean> {
return getSharedModelEnabledSync(this.db, userId)
}
async setSharedModelEnabled(userId: string, enabled: boolean): Promise<boolean> {
return setSharedModelEnabledSync(this.db, userId, enabled)
}
async deleteCredentialKey(userId: string, id: string): Promise<boolean> {
return deleteCredentialKeySync(this.db, userId, id)
}