优化 API 配置:一个弹窗完成接口、Key 与模型选择

- 去掉预设供应商,直接选择接口类型并填写 Base URL、API Key
- 可在保存前从接口拉取模型,搜索并勾选多个;也可手动输入模型名
- 适配中转站:漏写 /v1(Gemini 为 /v1beta)时自动补全,同时发送 Bearer 与官方鉴权头
- 保存时服务端同步连接下的模型列表;编辑时不填 Key 也可拉取
- 单模型测试与参数设置;手机端布局优化

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jocay
2026-10-18 02:23:09 +08:00
parent a472a86069
commit 283f40892a
10 changed files with 409 additions and 250 deletions
+40
View File
@@ -0,0 +1,40 @@
import { and, eq, inArray } from 'drizzle-orm'
import type { ProviderType } from '#shared/types'
const TYPE_NAME: Record<ProviderType, string> = {
'openai-compatible': 'OpenAI 兼容',
'anthropic': 'Anthropic',
'google': 'Gemini'
}
/** 未填写名称时,用 Base URL 的域名作为连接名称 */
export function defaultProviderName(type: ProviderType, baseUrl: string) {
try {
if (baseUrl.trim()) return new URL(baseUrl.trim()).host
}
catch {}
return TYPE_NAME[type]
}
export function normalizeModelIds(ids: string[]) {
return [...new Set(ids.map(s => s.trim()).filter(Boolean))]
}
/**
* 把连接下的模型同步为给定列表:新增缺少的,删除不在列表中的。
* 被删除模型的 AI 成员会因外键 ON DELETE SET NULL 变为“未指定模型”。
*/
export function syncProviderModels(providerId: string, modelIds: string[]) {
const db = useDb()
const wanted = normalizeModelIds(modelIds)
const existing = db.select().from(tables.models).where(eq(tables.models.providerId, providerId)).all()
const toDelete = existing.filter(m => !wanted.includes(m.modelId)).map(m => m.id)
const toAdd = wanted.filter(id => !existing.some(m => m.modelId === id))
if (toDelete.length) {
db.delete(tables.models).where(and(eq(tables.models.providerId, providerId), inArray(tables.models.id, toDelete))).run()
}
if (toAdd.length) {
db.insert(tables.models).values(toAdd.map(modelId => ({ id: newId(), providerId, label: modelId, modelId }))).run()
}
return { added: toAdd.length, removed: toDelete.length }
}