@@ -163,6 +163,18 @@
{ { opt . label } }
< / button >
< / div >
< div
v-if = "canToggleAuthChannelMismatch(format)"
class = "flex items-center gap-1"
title = "允许客户端认证方式不一致时使用"
@click.stop
>
< Switch
:model-value = "isAuthChannelMismatchAllowed(format)"
class = "scale-75"
@ update : model -value = " ( value ) = > setAuthChannelMismatchAllowed ( format , value ) "
/>
</div>
<div
class=" flex items - center text - xs text - muted - foreground gap - 1 "
@click.stop
@@ -478,6 +490,25 @@ function sanitizeAuthTypeByFormat(
return sanitized
}
function sanitizeAllowAuthChannelMismatchFormats(
formats: string[] | null | undefined,
selectedFormats = form.value.api_formats
): string[] {
if (!formats) return []
const selected = new Set(selectedFormats.map(normalizeApiFormat))
const seen = new Set<string>()
const sanitized: string[] = []
for (const format of formats) {
const normalizedFormat = normalizeApiFormat(format)
if (!normalizedFormat || !selected.has(normalizedFormat) || seen.has(normalizedFormat)) {
continue
}
seen.add(normalizedFormat)
sanitized.push(normalizedFormat)
}
return sanitized
}
function getDefaultApiFormats(): string[] {
const endpointFormat = props.endpoint?.api_format
if (endpointFormat) {
@@ -549,12 +580,35 @@ function setFormatAuthType(format: string, authType: RawSecretAuthType) {
form.value.auth_type_by_format = sanitizeAuthTypeByFormat(next)
}
function canToggleAuthChannelMismatch(format: string): boolean {
return canOverrideFormatAuth(format)
}
function isAuthChannelMismatchAllowed(format: string): boolean {
return form.value.allow_auth_channel_mismatch_formats.includes(normalizeApiFormat(format))
}
function setAuthChannelMismatchAllowed(format: string, allowed: boolean) {
const normalizedFormat = normalizeApiFormat(format)
const next = new Set(form.value.allow_auth_channel_mismatch_formats.map(normalizeApiFormat))
if (allowed) {
next.add(normalizedFormat)
} else {
next.delete(normalizedFormat)
}
form.value.allow_auth_channel_mismatch_formats = sanitizeAllowAuthChannelMismatchFormats([...next])
}
function buildAuthTypeByFormatPayload(): Record<string, RawSecretAuthType> | null {
const sanitized = sanitizeAuthTypeByFormat(form.value.auth_type_by_format)
return Object.keys(sanitized).length > 0 ? sanitized : null
}
function buildAllowAuthChannelMismatchFormatsPayload(): string[] {
const sanitized = sanitizeAllowAuthChannelMismatchFormats(form.value.allow_auth_channel_mismatch_formats)
return sanitized
}
const serviceAccountDescription = computed(() => (
props.editingKey
? '留空表示不修改; JSON 格式,包含 project_id、private_key 等字段'
@@ -566,6 +620,10 @@ function getDefaultAuthType(): ProviderKeyFormAuthType {
return authTypeOptions.value[0]?.value || 'api_key'
}
function getDefaultAllowAuthChannelMismatchFormats(formats = getDefaultApiFormats()): string[] {
return sanitizeAllowAuthChannelMismatchFormats(formats, formats)
}
// 显示自动获取模型警告:编辑模式下,原本未启用但现在启用,且已有 allowed_models
const showAutoFetchWarning = computed(() => {
if (!props.editingKey) return false
@@ -632,6 +690,7 @@ const form = ref({
api_key: '', // 标准 API Key
auth_type: 'api_key' as ProviderKeyFormAuthType, // 认证类型
auth_type_by_format: {} as Record<string, RawSecretAuthType>,
allow_auth_channel_mismatch_formats: [] as string[],
auth_config_text: '', // Service Account JSON 文本(用于表单输入)
api_formats: [] as string[], // 支持的 API 格式列表
rate_multipliers: {} as Record<string, number>, // 按 API 格式的成本倍率
@@ -661,6 +720,9 @@ watch(
form.value.api_formats = [...filtered]
}
form.value.auth_type_by_format = sanitizeAuthTypeByFormat(form.value.auth_type_by_format)
form.value.allow_auth_channel_mismatch_formats = sanitizeAllowAuthChannelMismatchFormats(
form.value.allow_auth_channel_mismatch_formats
)
},
{ immediate: true }
)
@@ -676,6 +738,10 @@ watch(
if (filtered.length !== form.value.api_formats.length) {
form.value.api_formats = [...filtered]
form.value.auth_type_by_format = sanitizeAuthTypeByFormat(form.value.auth_type_by_format, filtered)
form.value.allow_auth_channel_mismatch_formats = sanitizeAllowAuthChannelMismatchFormats(
form.value.allow_auth_channel_mismatch_formats,
filtered
)
return
}
@@ -683,6 +749,8 @@ watch(
const defaults = getDefaultApiFormats()
if (defaults.length > 0) {
form.value.api_formats = defaults
form.value.allow_auth_channel_mismatch_formats =
getDefaultAllowAuthChannelMismatchFormats(defaults)
}
}
},
@@ -708,10 +776,14 @@ function toggleApiFormat(format: string) {
if (index === -1) {
// 添加格式
form.value.api_formats.push(format)
setAuthChannelMismatchAllowed(format, true)
} else {
// 移除格式,但保留倍率配置(用户可能只是临时取消)
form.value.api_formats.splice(index, 1)
}
form.value.allow_auth_channel_mismatch_formats = sanitizeAllowAuthChannelMismatchFormats(
form.value.allow_auth_channel_mismatch_formats
)
}
// 更新指定格式的成本倍率
@@ -738,13 +810,16 @@ function updateRateMultiplier(format: string, value: string | number) {
// 重置表单
function resetForm() {
formNonce.value = createFieldNonce()
const defaultApiFormats = getDefaultApiFormats()
form.value = {
name: '',
api_key: '',
auth_type: getDefaultAuthType(),
auth_type_by_format: {},
allow_auth_channel_mismatch_formats:
getDefaultAllowAuthChannelMismatchFormats(defaultApiFormats),
auth_config_text: '',
api_formats: getD efaultApiFormats() ,
api_formats: d efaultApiFormats,
rate_multipliers: {},
internal_priority: 10,
rpm_limit: undefined,
@@ -766,6 +841,9 @@ function clearForNextAdd() {
form.value.api_key = ''
form.value.auth_config_text = ''
form.value.auth_type_by_format = sanitizeAuthTypeByFormat(form.value.auth_type_by_format)
form.value.allow_auth_channel_mismatch_formats = sanitizeAllowAuthChannelMismatchFormats(
form.value.allow_auth_channel_mismatch_formats
)
}
// 加载密钥数据(编辑模式)
@@ -781,6 +859,10 @@ function loadKeyData() {
props.editingKey.api_formats || [],
normalizeFormAuthType(props.editingKey.auth_type)
),
allow_auth_channel_mismatch_formats: sanitizeAllowAuthChannelMismatchFormats(
props.editingKey.allow_auth_channel_mismatch_formats || [],
props.editingKey.api_formats || []
),
auth_config_text: '', // auth_config 不返回给前端,编辑时需要重新输入
api_formats: props.editingKey.api_formats?.length > 0
? sanitizeApiFormats(
@@ -873,6 +955,9 @@ async function handleSave() {
}
form.value.api_formats = sanitizeApiFormats(form.value.api_formats)
form.value.allow_auth_channel_mismatch_formats = sanitizeAllowAuthChannelMismatchFormats(
form.value.allow_auth_channel_mismatch_formats
)
// 验证至少选择一个 API 格式
if (form.value.api_formats.length === 0) {
@@ -905,6 +990,7 @@ async function handleSave() {
// 准备认证相关数据
const authConfig = parseAuthConfig()
const authTypeByFormat = buildAuthTypeByFormatPayload()
const allowAuthChannelMismatchFormats = buildAllowAuthChannelMismatchFormatsPayload()
if (props.editingKey) {
const shouldClearAllowedModels = !!props.editingKey.auto_fetch_models && !form.value.auto_fetch_models
@@ -916,6 +1002,7 @@ async function handleSave() {
name : form . value . name ,
auth _type : form . value . auth _type ,
auth _type _by _format : authTypeByFormat ,
allow _auth _channel _mismatch _formats : allowAuthChannelMismatchFormats ,
rate _multipliers : rateMultipliersData ,
internal _priority : form . value . internal _priority ,
rpm _limit : form . value . rpm _limit ,
@@ -947,6 +1034,7 @@ async function handleSave() {
api _key : form . value . api _key ,
auth _type : form . value . auth _type ,
auth _type _by _format : authTypeByFormat ,
allow _auth _channel _mismatch _formats : allowAuthChannelMismatchFormats ,
auth _config : authConfig || undefined ,
name : form . value . name ,
rate _multipliers : rateMultipliersData ,