Merge pull request #1231 from LvyuanW/bulk-openai-passthrough-worktree
Support bulk editing for OpenAI passthrough
This commit is contained in:
@@ -31,6 +31,57 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- OpenAI passthrough -->
|
||||||
|
<div
|
||||||
|
v-if="allOpenAIPassthroughCapable"
|
||||||
|
class="border-t border-gray-200 pt-4 dark:border-dark-600"
|
||||||
|
>
|
||||||
|
<div class="mb-3 flex items-center justify-between">
|
||||||
|
<div class="flex-1 pr-4">
|
||||||
|
<label
|
||||||
|
id="bulk-edit-openai-passthrough-label"
|
||||||
|
class="input-label mb-0"
|
||||||
|
for="bulk-edit-openai-passthrough-enabled"
|
||||||
|
>
|
||||||
|
{{ t('admin.accounts.openai.oauthPassthrough') }}
|
||||||
|
</label>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('admin.accounts.openai.oauthPassthroughDesc') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
v-model="enableOpenAIPassthrough"
|
||||||
|
id="bulk-edit-openai-passthrough-enabled"
|
||||||
|
type="checkbox"
|
||||||
|
aria-controls="bulk-edit-openai-passthrough-body"
|
||||||
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="bulk-edit-openai-passthrough-body"
|
||||||
|
:class="!enableOpenAIPassthrough && 'pointer-events-none opacity-50'"
|
||||||
|
role="group"
|
||||||
|
aria-labelledby="bulk-edit-openai-passthrough-label"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
id="bulk-edit-openai-passthrough-toggle"
|
||||||
|
type="button"
|
||||||
|
:class="[
|
||||||
|
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||||
|
openaiPassthroughEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||||
|
]"
|
||||||
|
@click="openaiPassthroughEnabled = !openaiPassthroughEnabled"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||||
|
openaiPassthroughEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Base URL (API Key only) -->
|
<!-- Base URL (API Key only) -->
|
||||||
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||||
<div class="mb-3 flex items-center justify-between">
|
<div class="mb-3 flex items-center justify-between">
|
||||||
@@ -89,100 +140,30 @@
|
|||||||
role="group"
|
role="group"
|
||||||
aria-labelledby="bulk-edit-model-restriction-label"
|
aria-labelledby="bulk-edit-model-restriction-label"
|
||||||
>
|
>
|
||||||
<!-- Mode Toggle -->
|
<div
|
||||||
<div class="mb-4 flex gap-2">
|
v-if="isOpenAIModelRestrictionDisabled"
|
||||||
<button
|
class="rounded-lg bg-amber-50 p-3 dark:bg-amber-900/20"
|
||||||
type="button"
|
>
|
||||||
:class="[
|
<p class="text-xs text-amber-700 dark:text-amber-400">
|
||||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
{{ t('admin.accounts.openai.modelRestrictionDisabledByPassthrough') }}
|
||||||
modelRestrictionMode === 'whitelist'
|
|
||||||
? 'bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400'
|
|
||||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
|
||||||
]"
|
|
||||||
@click="modelRestrictionMode = 'whitelist'"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="mr-1.5 inline h-4 w-4"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{{ t('admin.accounts.modelWhitelist') }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
:class="[
|
|
||||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
|
||||||
modelRestrictionMode === 'mapping'
|
|
||||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
|
||||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
|
||||||
]"
|
|
||||||
@click="modelRestrictionMode = 'mapping'"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="mr-1.5 inline h-4 w-4"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{{ t('admin.accounts.modelMapping') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Whitelist Mode -->
|
|
||||||
<div v-if="modelRestrictionMode === 'whitelist'">
|
|
||||||
<div class="mb-3 rounded-lg bg-blue-50 p-3 dark:bg-blue-900/20">
|
|
||||||
<p class="text-xs text-blue-700 dark:text-blue-400">
|
|
||||||
<svg
|
|
||||||
class="mr-1 inline h-4 w-4"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{{ t('admin.accounts.selectAllowedModels') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ModelWhitelistSelector
|
|
||||||
v-model="allowedModels"
|
|
||||||
:platforms="selectedPlatforms"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
|
||||||
<span v-if="allowedModels.length === 0">{{
|
|
||||||
t('admin.accounts.supportsAllModels')
|
|
||||||
}}</span>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mapping Mode -->
|
<template v-else>
|
||||||
<div v-else>
|
<!-- Mode Toggle -->
|
||||||
<div class="mb-3 rounded-lg bg-purple-50 p-3 dark:bg-purple-900/20">
|
<div class="mb-4 flex gap-2">
|
||||||
<p class="text-xs text-purple-700 dark:text-purple-400">
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="[
|
||||||
|
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||||
|
modelRestrictionMode === 'whitelist'
|
||||||
|
? 'bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400'
|
||||||
|
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||||
|
]"
|
||||||
|
@click="modelRestrictionMode = 'whitelist'"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
class="mr-1 inline h-4 w-4"
|
class="mr-1.5 inline h-4 w-4"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -191,96 +172,177 @@
|
|||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ t('admin.accounts.mapRequestModels') }}
|
{{ t('admin.accounts.modelWhitelist') }}
|
||||||
</p>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="[
|
||||||
|
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||||
|
modelRestrictionMode === 'mapping'
|
||||||
|
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
||||||
|
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
||||||
|
]"
|
||||||
|
@click="modelRestrictionMode = 'mapping'"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="mr-1.5 inline h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{{ t('admin.accounts.modelMapping') }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Model Mapping List -->
|
<!-- Whitelist Mode -->
|
||||||
<div v-if="modelMappings.length > 0" class="mb-3 space-y-2">
|
<div v-if="modelRestrictionMode === 'whitelist'">
|
||||||
<div
|
<div class="mb-3 rounded-lg bg-blue-50 p-3 dark:bg-blue-900/20">
|
||||||
v-for="(mapping, index) in modelMappings"
|
<p class="text-xs text-blue-700 dark:text-blue-400">
|
||||||
:key="index"
|
<svg
|
||||||
class="flex items-center gap-2"
|
class="mr-1 inline h-4 w-4"
|
||||||
>
|
fill="none"
|
||||||
<input
|
viewBox="0 0 24 24"
|
||||||
v-model="mapping.from"
|
stroke="currentColor"
|
||||||
type="text"
|
>
|
||||||
class="input flex-1"
|
|
||||||
:placeholder="t('admin.accounts.requestModel')"
|
|
||||||
/>
|
|
||||||
<svg
|
|
||||||
class="h-4 w-4 flex-shrink-0 text-gray-400"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M14 5l7 7m0 0l-7 7m7-7H3"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<input
|
|
||||||
v-model="mapping.to"
|
|
||||||
type="text"
|
|
||||||
class="input flex-1"
|
|
||||||
:placeholder="t('admin.accounts.actualModel')"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="rounded-lg p-2 text-red-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
|
|
||||||
@click="removeModelMapping(index)"
|
|
||||||
>
|
|
||||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
{{ t('admin.accounts.selectAllowedModels') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ModelWhitelistSelector
|
||||||
|
v-model="allowedModels"
|
||||||
|
:platforms="selectedPlatforms"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||||
|
<span v-if="allowedModels.length === 0">{{
|
||||||
|
t('admin.accounts.supportsAllModels')
|
||||||
|
}}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mapping Mode -->
|
||||||
|
<div v-else>
|
||||||
|
<div class="mb-3 rounded-lg bg-purple-50 p-3 dark:bg-purple-900/20">
|
||||||
|
<p class="text-xs text-purple-700 dark:text-purple-400">
|
||||||
|
<svg
|
||||||
|
class="mr-1 inline h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{{ t('admin.accounts.mapRequestModels') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Model Mapping List -->
|
||||||
|
<div v-if="modelMappings.length > 0" class="mb-3 space-y-2">
|
||||||
|
<div
|
||||||
|
v-for="(mapping, index) in modelMappings"
|
||||||
|
:key="index"
|
||||||
|
class="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="mapping.from"
|
||||||
|
type="text"
|
||||||
|
class="input flex-1"
|
||||||
|
:placeholder="t('admin.accounts.requestModel')"
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
class="h-4 w-4 flex-shrink-0 text-gray-400"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M14 5l7 7m0 0l-7 7m7-7H3"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
v-model="mapping.to"
|
||||||
|
type="text"
|
||||||
|
class="input flex-1"
|
||||||
|
:placeholder="t('admin.accounts.actualModel')"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="rounded-lg p-2 text-red-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20"
|
||||||
|
@click="removeModelMapping(index)"
|
||||||
|
>
|
||||||
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mb-3 w-full rounded-lg border-2 border-dashed border-gray-300 px-4 py-2 text-gray-600 transition-colors hover:border-gray-400 hover:text-gray-700 dark:border-dark-500 dark:text-gray-400 dark:hover:border-dark-400 dark:hover:text-gray-300"
|
||||||
|
@click="addModelMapping"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="mr-1 inline h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 4v16m8-8H4"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{{ t('admin.accounts.addMapping') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Quick Add Buttons -->
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
v-for="preset in filteredPresets"
|
||||||
|
:key="preset.label"
|
||||||
|
type="button"
|
||||||
|
:class="['rounded-lg px-3 py-1 text-xs transition-colors', preset.color]"
|
||||||
|
@click="addPresetMapping(preset.from, preset.to)"
|
||||||
|
>
|
||||||
|
+ {{ preset.label }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="mb-3 w-full rounded-lg border-2 border-dashed border-gray-300 px-4 py-2 text-gray-600 transition-colors hover:border-gray-400 hover:text-gray-700 dark:border-dark-500 dark:text-gray-400 dark:hover:border-dark-400 dark:hover:text-gray-300"
|
|
||||||
@click="addModelMapping"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="mr-1 inline h-4 w-4"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M12 4v16m8-8H4"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{{ t('admin.accounts.addMapping') }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Quick Add Buttons -->
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<button
|
|
||||||
v-for="preset in filteredPresets"
|
|
||||||
:key="preset.label"
|
|
||||||
type="button"
|
|
||||||
:class="['rounded-lg px-3 py-1 text-xs transition-colors', preset.color]"
|
|
||||||
@click="addPresetMapping(preset.from, preset.to)"
|
|
||||||
>
|
|
||||||
+ {{ preset.label }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -865,7 +927,6 @@ import {
|
|||||||
resolveOpenAIWSModeConcurrencyHintKey
|
resolveOpenAIWSModeConcurrencyHintKey
|
||||||
} from '@/utils/openaiWsMode'
|
} from '@/utils/openaiWsMode'
|
||||||
import type { OpenAIWSMode } from '@/utils/openaiWsMode'
|
import type { OpenAIWSMode } from '@/utils/openaiWsMode'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
show: boolean
|
show: boolean
|
||||||
accountIds: number[]
|
accountIds: number[]
|
||||||
@@ -887,6 +948,15 @@ const appStore = useAppStore()
|
|||||||
// Platform awareness
|
// Platform awareness
|
||||||
const isMixedPlatform = computed(() => props.selectedPlatforms.length > 1)
|
const isMixedPlatform = computed(() => props.selectedPlatforms.length > 1)
|
||||||
|
|
||||||
|
const allOpenAIPassthroughCapable = computed(() => {
|
||||||
|
return (
|
||||||
|
props.selectedPlatforms.length === 1 &&
|
||||||
|
props.selectedPlatforms[0] === 'openai' &&
|
||||||
|
props.selectedTypes.length > 0 &&
|
||||||
|
props.selectedTypes.every(t => t === 'oauth' || t === 'apikey')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const allOpenAIOAuth = computed(() => {
|
const allOpenAIOAuth = computed(() => {
|
||||||
return (
|
return (
|
||||||
props.selectedPlatforms.length === 1 &&
|
props.selectedPlatforms.length === 1 &&
|
||||||
@@ -939,6 +1009,7 @@ const enablePriority = ref(false)
|
|||||||
const enableRateMultiplier = ref(false)
|
const enableRateMultiplier = ref(false)
|
||||||
const enableStatus = ref(false)
|
const enableStatus = ref(false)
|
||||||
const enableGroups = ref(false)
|
const enableGroups = ref(false)
|
||||||
|
const enableOpenAIPassthrough = ref(false)
|
||||||
const enableOpenAIWSMode = ref(false)
|
const enableOpenAIWSMode = ref(false)
|
||||||
const enableRpmLimit = ref(false)
|
const enableRpmLimit = ref(false)
|
||||||
|
|
||||||
@@ -961,6 +1032,7 @@ const priority = ref(1)
|
|||||||
const rateMultiplier = ref(1)
|
const rateMultiplier = ref(1)
|
||||||
const status = ref<'active' | 'inactive'>('active')
|
const status = ref<'active' | 'inactive'>('active')
|
||||||
const groupIds = ref<number[]>([])
|
const groupIds = ref<number[]>([])
|
||||||
|
const openaiPassthroughEnabled = ref(false)
|
||||||
const openaiOAuthResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
const openaiOAuthResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
||||||
const rpmLimitEnabled = ref(false)
|
const rpmLimitEnabled = ref(false)
|
||||||
const bulkBaseRpm = ref<number | null>(null)
|
const bulkBaseRpm = ref<number | null>(null)
|
||||||
@@ -988,6 +1060,13 @@ const statusOptions = computed(() => [
|
|||||||
{ value: 'active', label: t('common.active') },
|
{ value: 'active', label: t('common.active') },
|
||||||
{ value: 'inactive', label: t('common.inactive') }
|
{ value: 'inactive', label: t('common.inactive') }
|
||||||
])
|
])
|
||||||
|
const isOpenAIModelRestrictionDisabled = computed(
|
||||||
|
() =>
|
||||||
|
allOpenAIPassthroughCapable.value &&
|
||||||
|
enableOpenAIPassthrough.value &&
|
||||||
|
openaiPassthroughEnabled.value
|
||||||
|
)
|
||||||
|
|
||||||
const openAIWSModeOptions = computed(() => [
|
const openAIWSModeOptions = computed(() => [
|
||||||
{ value: OPENAI_WS_MODE_OFF, label: t('admin.accounts.openai.wsModeOff') },
|
{ value: OPENAI_WS_MODE_OFF, label: t('admin.accounts.openai.wsModeOff') },
|
||||||
{ value: OPENAI_WS_MODE_PASSTHROUGH, label: t('admin.accounts.openai.wsModePassthrough') }
|
{ value: OPENAI_WS_MODE_PASSTHROUGH, label: t('admin.accounts.openai.wsModePassthrough') }
|
||||||
@@ -1123,7 +1202,15 @@ const buildUpdatePayload = (): Record<string, unknown> | null => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableModelRestriction.value) {
|
if (enableOpenAIPassthrough.value) {
|
||||||
|
const extra = ensureExtra()
|
||||||
|
extra.openai_passthrough = openaiPassthroughEnabled.value
|
||||||
|
if (!openaiPassthroughEnabled.value) {
|
||||||
|
extra.openai_oauth_passthrough = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableModelRestriction.value && !isOpenAIModelRestrictionDisabled.value) {
|
||||||
// 统一使用 model_mapping 字段
|
// 统一使用 model_mapping 字段
|
||||||
if (modelRestrictionMode.value === 'whitelist') {
|
if (modelRestrictionMode.value === 'whitelist') {
|
||||||
// 白名单模式:将模型转换为 model_mapping 格式(key=value)
|
// 白名单模式:将模型转换为 model_mapping 格式(key=value)
|
||||||
@@ -1243,6 +1330,7 @@ const handleSubmit = async () => {
|
|||||||
|
|
||||||
const hasAnyFieldEnabled =
|
const hasAnyFieldEnabled =
|
||||||
enableBaseUrl.value ||
|
enableBaseUrl.value ||
|
||||||
|
enableOpenAIPassthrough.value ||
|
||||||
enableModelRestriction.value ||
|
enableModelRestriction.value ||
|
||||||
enableCustomErrorCodes.value ||
|
enableCustomErrorCodes.value ||
|
||||||
enableInterceptWarmup.value ||
|
enableInterceptWarmup.value ||
|
||||||
@@ -1345,11 +1433,13 @@ watch(
|
|||||||
enableRateMultiplier.value = false
|
enableRateMultiplier.value = false
|
||||||
enableStatus.value = false
|
enableStatus.value = false
|
||||||
enableGroups.value = false
|
enableGroups.value = false
|
||||||
|
enableOpenAIPassthrough.value = false
|
||||||
enableOpenAIWSMode.value = false
|
enableOpenAIWSMode.value = false
|
||||||
enableRpmLimit.value = false
|
enableRpmLimit.value = false
|
||||||
|
|
||||||
// Reset all values
|
// Reset all values
|
||||||
baseUrl.value = ''
|
baseUrl.value = ''
|
||||||
|
openaiPassthroughEnabled.value = false
|
||||||
modelRestrictionMode.value = 'whitelist'
|
modelRestrictionMode.value = 'whitelist'
|
||||||
allowedModels.value = []
|
allowedModels.value = []
|
||||||
modelMappings.value = []
|
modelMappings.value = []
|
||||||
|
|||||||
@@ -130,6 +130,25 @@ describe('BulkEditAccountModal', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('OpenAI 账号批量编辑可开启自动透传', async () => {
|
||||||
|
const wrapper = mountModal({
|
||||||
|
selectedPlatforms: ['openai'],
|
||||||
|
selectedTypes: ['oauth']
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.get('#bulk-edit-openai-passthrough-enabled').setValue(true)
|
||||||
|
await wrapper.get('#bulk-edit-openai-passthrough-toggle').trigger('click')
|
||||||
|
await wrapper.get('#bulk-edit-account-form').trigger('submit.prevent')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledWith([1, 2], {
|
||||||
|
extra: {
|
||||||
|
openai_passthrough: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('OpenAI OAuth 批量编辑应提交 OAuth 专属 WS mode 字段', async () => {
|
it('OpenAI OAuth 批量编辑应提交 OAuth 专属 WS mode 字段', async () => {
|
||||||
const wrapper = mountModal({
|
const wrapper = mountModal({
|
||||||
selectedPlatforms: ['openai'],
|
selectedPlatforms: ['openai'],
|
||||||
@@ -158,4 +177,44 @@ describe('BulkEditAccountModal', () => {
|
|||||||
|
|
||||||
expect(wrapper.find('#bulk-edit-openai-ws-mode-enabled').exists()).toBe(false)
|
expect(wrapper.find('#bulk-edit-openai-ws-mode-enabled').exists()).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('OpenAI 账号批量编辑可关闭自动透传', async () => {
|
||||||
|
const wrapper = mountModal({
|
||||||
|
selectedPlatforms: ['openai'],
|
||||||
|
selectedTypes: ['apikey']
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.get('#bulk-edit-openai-passthrough-enabled').setValue(true)
|
||||||
|
await wrapper.get('#bulk-edit-account-form').trigger('submit.prevent')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledWith([1, 2], {
|
||||||
|
extra: {
|
||||||
|
openai_passthrough: false,
|
||||||
|
openai_oauth_passthrough: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('开启 OpenAI 自动透传时不再同时提交模型限制', async () => {
|
||||||
|
const wrapper = mountModal({
|
||||||
|
selectedPlatforms: ['openai'],
|
||||||
|
selectedTypes: ['oauth']
|
||||||
|
})
|
||||||
|
|
||||||
|
await wrapper.get('#bulk-edit-openai-passthrough-enabled').setValue(true)
|
||||||
|
await wrapper.get('#bulk-edit-openai-passthrough-toggle').trigger('click')
|
||||||
|
await wrapper.get('#bulk-edit-model-restriction-enabled').setValue(true)
|
||||||
|
await wrapper.get('#bulk-edit-account-form').trigger('submit.prevent')
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledTimes(1)
|
||||||
|
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledWith([1, 2], {
|
||||||
|
extra: {
|
||||||
|
openai_passthrough: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(wrapper.text()).toContain('admin.accounts.openai.modelRestrictionDisabledByPassthrough')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user