feat(billing): support image output pricing in model forms and details

This commit is contained in:
ZheFox
2026-05-18 03:19:58 +08:00
parent 0b2a8fafce
commit dff17b6cb1
4 changed files with 263 additions and 49 deletions

View File

@@ -844,18 +844,24 @@ function loadModelData() {
searchQuery.value = '' searchQuery.value = ''
expandedProvider.value = null expandedProvider.value = null
const modelTieredPricing = props.model.default_tiered_pricing
? JSON.parse(JSON.stringify(props.model.default_tiered_pricing))
: null
const supportedCapabilities = new Set(props.model.supported_capabilities || [])
if (tieredPricingHasImageOutputPricing(modelTieredPricing)) {
supportedCapabilities.add('image_generation')
}
form.value = { form.value = {
name: props.model.name, name: props.model.name,
display_name: props.model.display_name, display_name: props.model.display_name,
default_price_per_request: props.model.default_price_per_request, default_price_per_request: props.model.default_price_per_request,
supported_capabilities: [...(props.model.supported_capabilities || [])], supported_capabilities: [...supportedCapabilities],
config: props.model.config ? { ...props.model.config } : { streaming: true }, config: props.model.config ? { ...props.model.config } : { streaming: true },
is_active: props.model.is_active, is_active: props.model.is_active,
} }
// 确保 tieredPricing 也被正确设置或重置 // 确保 tieredPricing 也被正确设置或重置
tieredPricing.value = props.model.default_tiered_pricing tieredPricing.value = modelTieredPricing
? JSON.parse(JSON.stringify(props.model.default_tiered_pricing))
: null
loadVideoPricingFromConfig() loadVideoPricingFromConfig()
} }
@@ -896,6 +902,9 @@ async function handleSubmit() {
} else { } else {
caps.delete('cache_1h') caps.delete('cache_1h')
} }
if (tieredPricingHasImageOutputPricing(finalTieredPricing)) {
caps.add('image_generation')
}
form.value.supported_capabilities = caps.size > 0 ? [...caps] : [] form.value.supported_capabilities = caps.size > 0 ? [...caps] : []
// 清理空的 config // 清理空的 config
@@ -925,4 +934,13 @@ async function handleSubmit() {
submitting.value = false submitting.value = false
} }
} }
function tieredPricingHasImageOutputPricing(pricing: TieredPricingConfig | null | undefined): boolean {
if (!pricing) return false
if (pricing.image_output_price_default != null) return true
return Object.values(pricing.image_output_prices || {}).some((prices) => {
if (!prices || typeof prices !== 'object') return false
return Object.values(prices).some((price) => typeof price === 'number' && Number.isFinite(price))
})
}
</script> </script>

View File

@@ -137,6 +137,7 @@
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<!-- 默认定价 --> <!-- 默认定价 -->
@@ -145,6 +146,68 @@
默认定价 默认定价
</h4> </h4>
<!-- 图片输出计费 -->
<div
v-if="hasImagePricing"
class="space-y-2"
>
<div class="flex items-center justify-between gap-3 text-sm text-muted-foreground">
<div class="flex items-center gap-2">
<span>图片输出计费</span>
<Badge
v-if="imagePricingEntries.length > 0"
variant="outline"
class="text-[10px] h-5 px-1.5"
>
矩阵
</Badge>
</div>
<span
v-if="imageOutputDefaultPrice !== null"
class="text-xs font-mono"
>默认 ${{ imageOutputDefaultPrice.toFixed(6) }}/</span>
</div>
<div
v-if="imagePricingEntries.length > 0"
class="border rounded-lg overflow-hidden"
>
<Table>
<TableHeader>
<TableRow class="bg-muted/30">
<TableHead class="text-xs h-9">
分辨率
</TableHead>
<TableHead
v-for="quality in IMAGE_OUTPUT_QUALITIES"
:key="quality"
class="text-xs h-9 text-right"
>
{{ quality }}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow
v-for="entry in imagePricingEntries"
:key="entry.size"
class="text-xs"
>
<TableCell class="py-2 font-mono">
{{ formatImageSize(entry.size) }}
</TableCell>
<TableCell
v-for="quality in IMAGE_OUTPUT_QUALITIES"
:key="`${entry.size}-${quality}`"
class="py-2 text-right font-mono"
>
{{ formatImagePrice(entry.prices[quality]) }}
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</div>
<!-- 单阶梯固定价格展示 --> <!-- 单阶梯固定价格展示 -->
<div <div
v-if="getTierCount(model.default_tiered_pricing) <= 1" v-if="getTierCount(model.default_tiered_pricing) <= 1"
@@ -561,6 +624,47 @@ const videoPricingEntries = computed(() => {
return sortResolutionEntries(Object.entries(priceByResolution)) return sortResolutionEntries(Object.entries(priceByResolution))
}) })
const IMAGE_OUTPUT_QUALITIES = ['low', 'medium', 'high'] as const
const imageOutputDefaultPrice = computed(() => {
const value = props.model?.default_tiered_pricing?.image_output_price_default
return typeof value === 'number' && Number.isFinite(value) ? value : null
})
const imagePricingEntries = computed(() => {
const prices = props.model?.default_tiered_pricing?.image_output_prices
if (!prices || typeof prices !== 'object') return []
return sortResolutionEntries(Object.entries(prices)).map(([size, qualityPrices]) => ({
size,
prices: normalizeImageQualityPrices(qualityPrices),
})).filter(entry => Object.values(entry.prices).some(price => price !== null))
})
const hasImagePricing = computed(() =>
imageOutputDefaultPrice.value !== null || imagePricingEntries.value.length > 0,
)
function normalizeImageQualityPrices(value: unknown): Record<typeof IMAGE_OUTPUT_QUALITIES[number], number | null> {
const object = value && typeof value === 'object' ? value as Record<string, unknown> : {}
return {
low: toFiniteNumber(object.low),
medium: toFiniteNumber(object.medium),
high: toFiniteNumber(object.high),
}
}
function toFiniteNumber(value: unknown): number | null {
return typeof value === 'number' && Number.isFinite(value) ? value : null
}
function formatImagePrice(value: number | null): string {
return value === null ? '-' : `$${value.toFixed(6)}`
}
function formatImageSize(value: string): string {
return value.replace(/\s*[xX×]\s*/g, ' x ')
}
const detailTab = ref('basic') const detailTab = ref('basic')
// 处理背景点击 // 处理背景点击

View File

@@ -158,30 +158,55 @@
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<div class="grid grid-cols-[96px_repeat(3,minmax(0,1fr))] gap-2 text-xs text-muted-foreground"> <div class="grid grid-cols-[minmax(120px,1.1fr)_repeat(3,minmax(0,1fr))_32px] gap-2 text-xs text-muted-foreground">
<span /> <span>分辨率</span>
<span>low</span> <span>low</span>
<span>medium</span> <span>medium</span>
<span>high</span> <span>high</span>
<span />
</div> </div>
<div <div
v-for="size in IMAGE_OUTPUT_SIZES" v-for="row in imageOutputPriceRows"
:key="size.value" :key="row.id"
class="grid grid-cols-[96px_repeat(3,minmax(0,1fr))] gap-2 items-center" class="grid grid-cols-[minmax(120px,1.1fr)_repeat(3,minmax(0,1fr))_32px] gap-2 items-center"
> >
<span class="text-xs text-muted-foreground">{{ size.label }}</span> <Input
:model-value="row.size"
class="h-8 font-mono text-xs"
placeholder="1024x1024"
@update:model-value="(v) => updateImageOutputSize(row.id, v)"
/>
<Input <Input
v-for="quality in IMAGE_OUTPUT_QUALITIES" v-for="quality in IMAGE_OUTPUT_QUALITIES"
:key="`${size.value}-${quality}`" :key="`${row.id}-${quality}`"
:model-value="getImageOutputPrice(size.value, quality)" :model-value="getImageOutputPrice(row, quality)"
type="number" type="number"
step="0.001" step="0.001"
min="0" min="0"
class="h-8" class="h-8"
placeholder="0" placeholder="0"
@update:model-value="(v) => updateImageOutputPrice(size.value, quality, v)" @update:model-value="(v) => updateImageOutputPrice(row.id, quality, v)"
/> />
<Button
type="button"
variant="ghost"
size="sm"
class="h-8 w-8 p-0"
@click="removeImageOutputSizeRow(row.id)"
>
<X class="w-4 h-4 text-muted-foreground hover:text-destructive" />
</Button>
</div> </div>
<Button
type="button"
variant="outline"
size="sm"
class="w-full"
@click="addImageOutputSizeRow"
>
<Plus class="w-4 h-4 mr-2" />
添加分辨率
</Button>
</div> </div>
</div> </div>
@@ -202,13 +227,13 @@ import { Button, Input, Label } from '@/components/ui'
import type { TieredPricingConfig, PricingTier } from '@/api/endpoints/types' import type { TieredPricingConfig, PricingTier } from '@/api/endpoints/types'
type ImageOutputQuality = 'low' | 'medium' | 'high' type ImageOutputQuality = 'low' | 'medium' | 'high'
type ImageOutputPriceRow = {
id: string
size: string
prices: Partial<Record<ImageOutputQuality, number>>
}
const IMAGE_OUTPUT_SIZES = [ const DEFAULT_IMAGE_OUTPUT_SIZES = ['1024x1024', '1536x1024', '1024x1536']
{ value: '1024x1024', label: '1024 x 1024' },
{ value: '1536x1024', label: '1536 x 1024' },
{ value: '1024x1536', label: '1024 x 1536' },
] as const
const IMAGE_OUTPUT_QUALITIES: ImageOutputQuality[] = ['low', 'medium', 'high'] const IMAGE_OUTPUT_QUALITIES: ImageOutputQuality[] = ['low', 'medium', 'high']
const props = defineProps<{ const props = defineProps<{
@@ -223,8 +248,10 @@ const emit = defineEmits<{
// 本地状态 // 本地状态
const localTiers = ref<PricingTier[]>([]) const localTiers = ref<PricingTier[]>([])
const imageOutputPrices = ref<Record<string, Record<string, number | undefined>>>({}) const imageOutputPriceRows = ref<ImageOutputPriceRow[]>([])
const imageOutputPriceDefault = ref<string>('') const imageOutputPriceDefault = ref<string>('')
const lastEmittedPricingJson = ref<string>('')
let imageOutputPriceRowId = 0
// 跟踪每个阶梯的缓存价格是否被手动设置 // 跟踪每个阶梯的缓存价格是否被手动设置
const cacheManuallySet = reactive<Record<number, { creation: boolean; read: boolean; cache1h: boolean }>>({}) const cacheManuallySet = reactive<Record<number, { creation: boolean; read: boolean; cache1h: boolean }>>({})
@@ -247,9 +274,12 @@ const customInputValue = reactive<Record<number, string>>({})
watch( watch(
() => props.modelValue, () => props.modelValue,
(newValue) => { (newValue) => {
if (lastEmittedPricingJson.value && JSON.stringify(newValue ?? null) === lastEmittedPricingJson.value) {
return
}
if (newValue?.tiers) { if (newValue?.tiers) {
localTiers.value = newValue.tiers.map(t => ({ ...t })) localTiers.value = newValue.tiers.map(t => ({ ...t }))
imageOutputPrices.value = cloneImageOutputPrices(newValue.image_output_prices) imageOutputPriceRows.value = createImageOutputPriceRows(newValue.image_output_prices)
imageOutputPriceDefault.value = newValue.image_output_price_default != null imageOutputPriceDefault.value = newValue.image_output_price_default != null
? String(newValue.image_output_price_default) ? String(newValue.image_output_price_default)
: '' : ''
@@ -268,7 +298,7 @@ watch(
input_price_per_1m: 0, input_price_per_1m: 0,
output_price_per_1m: 0, output_price_per_1m: 0,
}] }]
imageOutputPrices.value = {} imageOutputPriceRows.value = createImageOutputPriceRows(null)
imageOutputPriceDefault.value = '' imageOutputPriceDefault.value = ''
cacheManuallySet[0] = { creation: false, read: false, cache1h: false } cacheManuallySet[0] = { creation: false, read: false, cache1h: false }
} }
@@ -434,7 +464,9 @@ function syncToParent() {
return tier return tier
}) })
emit('update:modelValue', buildPricingConfig(tiers)) const value = buildPricingConfig(tiers)
lastEmittedPricingJson.value = JSON.stringify(value ?? null)
emit('update:modelValue', value)
} }
// 获取最终提交的数据(包含自动计算的缓存价格) // 获取最终提交的数据(包含自动计算的缓存价格)
@@ -499,26 +531,45 @@ function buildPricingConfig(tiers: PricingTier[]): TieredPricingConfig {
return config return config
} }
function cloneImageOutputPrices(value: TieredPricingConfig['image_output_prices']): Record<string, Record<string, number | undefined>> { function createImageOutputPriceRows(value: TieredPricingConfig['image_output_prices']): ImageOutputPriceRow[] {
const out: Record<string, Record<string, number | undefined>> = {} const rows: ImageOutputPriceRow[] = []
if (!value || typeof value !== 'object') return out if (!value || typeof value !== 'object') {
return DEFAULT_IMAGE_OUTPUT_SIZES.map(size => createImageOutputPriceRow(size))
}
for (const [size, prices] of Object.entries(value)) { for (const [size, prices] of Object.entries(value)) {
if (!prices || typeof prices !== 'object') continue if (!prices || typeof prices !== 'object') continue
const rowPrices: Partial<Record<ImageOutputQuality, number>> = {}
for (const quality of IMAGE_OUTPUT_QUALITIES) { for (const quality of IMAGE_OUTPUT_QUALITIES) {
const price = (prices as Record<string, unknown>)[quality] const price = (prices as Record<string, unknown>)[quality]
if (typeof price === 'number' && Number.isFinite(price)) { if (typeof price === 'number' && Number.isFinite(price)) {
out[size] = { ...(out[size] || {}), [quality]: price } rowPrices[quality] = price
} }
} }
rows.push(createImageOutputPriceRow(size, rowPrices))
}
if (rows.length > 0) return rows
return DEFAULT_IMAGE_OUTPUT_SIZES.map(size => createImageOutputPriceRow(size))
}
function createImageOutputPriceRow(
size = '',
prices: Partial<Record<ImageOutputQuality, number>> = {},
): ImageOutputPriceRow {
imageOutputPriceRowId += 1
return {
id: `image-output-size-${imageOutputPriceRowId}`,
size,
prices: { ...prices },
} }
return out
} }
function normalizedImageOutputPrices(): Record<string, Record<string, number>> { function normalizedImageOutputPrices(): Record<string, Record<string, number>> {
const out: Record<string, Record<string, number>> = {} const out: Record<string, Record<string, number>> = {}
for (const [size, prices] of Object.entries(imageOutputPrices.value)) { for (const row of imageOutputPriceRows.value) {
const size = normalizeImageOutputSize(row.size)
if (!size) continue
for (const quality of IMAGE_OUTPUT_QUALITIES) { for (const quality of IMAGE_OUTPUT_QUALITIES) {
const price = prices[quality] const price = row.prices[quality]
if (price != null && Number.isFinite(price)) { if (price != null && Number.isFinite(price)) {
out[size] = { ...(out[size] || {}), [quality]: price } out[size] = { ...(out[size] || {}), [quality]: price }
} }
@@ -533,25 +584,44 @@ function parseOptionalFloat(value: string | number): number | null {
return Number.isFinite(number) ? number : null return Number.isFinite(number) ? number : null
} }
function getImageOutputPrice(size: string, quality: ImageOutputQuality): string | number { function normalizeImageOutputSize(size: string): string {
return imageOutputPrices.value[size]?.[quality] ?? '' return String(size || '').trim().replace(/\s*[xX×]\s*/g, 'x')
} }
function updateImageOutputPrice(size: string, quality: ImageOutputQuality, value: string | number) { function getImageOutputPrice(row: ImageOutputPriceRow, quality: ImageOutputQuality): string | number {
return row.prices[quality] ?? ''
}
function updateImageOutputSize(rowId: string, value: string | number) {
const row = imageOutputPriceRows.value.find(item => item.id === rowId)
if (!row) return
row.size = normalizeImageOutputSize(String(value ?? ''))
imageOutputPriceRows.value = [...imageOutputPriceRows.value]
syncToParent()
}
function updateImageOutputPrice(rowId: string, quality: ImageOutputQuality, value: string | number) {
const row = imageOutputPriceRows.value.find(item => item.id === rowId)
if (!row) return
const price = parseOptionalFloat(value) const price = parseOptionalFloat(value)
const current = { ...(imageOutputPrices.value[size] || {}) }
if (price == null) { if (price == null) {
delete current[quality] delete row.prices[quality]
} else { } else {
current[quality] = price row.prices[quality] = price
}
if (Object.values(current).some(v => v != null)) {
imageOutputPrices.value = { ...imageOutputPrices.value, [size]: current }
} else {
const next = { ...imageOutputPrices.value }
delete next[size]
imageOutputPrices.value = next
} }
imageOutputPriceRows.value = [...imageOutputPriceRows.value]
syncToParent()
}
function addImageOutputSizeRow() {
const usedSizes = new Set(imageOutputPriceRows.value.map(row => normalizeImageOutputSize(row.size)).filter(Boolean))
const suggestedSize = DEFAULT_IMAGE_OUTPUT_SIZES.find(size => !usedSizes.has(size)) || ''
imageOutputPriceRows.value = [...imageOutputPriceRows.value, createImageOutputPriceRow(suggestedSize)]
syncToParent()
}
function removeImageOutputSizeRow(rowId: string) {
imageOutputPriceRows.value = imageOutputPriceRows.value.filter(row => row.id !== rowId)
syncToParent() syncToParent()
} }

View File

@@ -438,6 +438,7 @@ watch(() => props.open, async (newOpen) => {
// 编辑模式:填充表单 // 编辑模式:填充表单
// 使用有效配置(合并全局模型的默认值)供用户查看和编辑 // 使用有效配置(合并全局模型的默认值)供用户查看和编辑
const effectiveConfig = props.editingModel.effective_config || props.editingModel.config || {} const effectiveConfig = props.editingModel.effective_config || props.editingModel.config || {}
const supportsImageGeneration = modelSupportsImageGeneration(props.editingModel)
form.value = { form.value = {
global_model_id: props.editingModel.global_model_id || '', global_model_id: props.editingModel.global_model_id || '',
provider_model_name: props.editingModel.provider_model_name || '', provider_model_name: props.editingModel.provider_model_name || '',
@@ -450,7 +451,7 @@ watch(() => props.open, async (newOpen) => {
supports_function_calling: props.editingModel.supports_function_calling ?? undefined, supports_function_calling: props.editingModel.supports_function_calling ?? undefined,
supports_streaming: props.editingModel.supports_streaming ?? undefined, supports_streaming: props.editingModel.supports_streaming ?? undefined,
supports_extended_thinking: props.editingModel.supports_extended_thinking ?? undefined, supports_extended_thinking: props.editingModel.supports_extended_thinking ?? undefined,
supports_image_generation: props.editingModel.supports_image_generation ?? undefined, supports_image_generation: supportsImageGeneration ? true : props.editingModel.supports_image_generation ?? undefined,
is_active: props.editingModel.is_active is_active: props.editingModel.is_active
} }
// 从有效配置中加载视频费用 // 从有效配置中加载视频费用
@@ -544,6 +545,9 @@ function modelSupportsImageGeneration(model: {
supported_capabilities?: string[] | null supported_capabilities?: string[] | null
supports_image_generation?: boolean | null supports_image_generation?: boolean | null
effective_supports_image_generation?: boolean | null effective_supports_image_generation?: boolean | null
default_tiered_pricing?: TieredPricingConfig | null
tiered_pricing?: TieredPricingConfig | null
effective_tiered_pricing?: TieredPricingConfig | null
config?: Record<string, unknown> | null config?: Record<string, unknown> | null
} | null | undefined): boolean { } | null | undefined): boolean {
if (!model) return false if (!model) return false
@@ -554,6 +558,18 @@ function modelSupportsImageGeneration(model: {
|| config.image_generation === true || config.image_generation === true
|| config.model_type === 'image' || config.model_type === 'image'
|| (Array.isArray(config.api_formats) && config.api_formats.some((format) => String(format).endsWith(':image'))) || (Array.isArray(config.api_formats) && config.api_formats.some((format) => String(format).endsWith(':image')))
|| tieredPricingHasImageOutputPricing(model.default_tiered_pricing)
|| tieredPricingHasImageOutputPricing(model.tiered_pricing)
|| tieredPricingHasImageOutputPricing(model.effective_tiered_pricing)
}
function tieredPricingHasImageOutputPricing(pricing: TieredPricingConfig | null | undefined): boolean {
if (!pricing) return false
if (pricing.image_output_price_default != null) return true
return Object.values(pricing.image_output_prices || {}).some((prices) => {
if (!prices || typeof prices !== 'object') return false
return Object.values(prices).some((price) => typeof price === 'number' && Number.isFinite(price))
})
} }
function setImageGenerationEnabled(value: boolean | 'indeterminate') { function setImageGenerationEnabled(value: boolean | 'indeterminate') {
@@ -696,7 +712,11 @@ function _copyVideoPricingFromSelectedGlobal() {
configTouched.value = true configTouched.value = true
} }
async function createManualGlobalModel(finalTieredPricing: TieredPricingConfig | null, cleanConfig: Record<string, unknown> | undefined): Promise<GlobalModelResponse> { async function createManualGlobalModel(
finalTieredPricing: TieredPricingConfig | null,
cleanConfig: Record<string, unknown> | undefined,
supportsImageGeneration: boolean,
): Promise<GlobalModelResponse> {
const modelName = form.value.manual_global_model_name.trim() const modelName = form.value.manual_global_model_name.trim()
const displayName = form.value.manual_global_model_display_name.trim() || modelName const displayName = form.value.manual_global_model_display_name.trim() || modelName
const supportedCapabilities = [ const supportedCapabilities = [
@@ -704,7 +724,7 @@ async function createManualGlobalModel(finalTieredPricing: TieredPricingConfig |
form.value.supports_function_calling === true ? 'function_calling' : null, form.value.supports_function_calling === true ? 'function_calling' : null,
form.value.supports_streaming === true ? 'streaming' : null, form.value.supports_streaming === true ? 'streaming' : null,
form.value.supports_extended_thinking === true ? 'extended_thinking' : null, form.value.supports_extended_thinking === true ? 'extended_thinking' : null,
form.value.supports_image_generation === true ? 'image_generation' : null, supportsImageGeneration ? 'image_generation' : null,
].filter((capability): capability is string => capability !== null) ].filter((capability): capability is string => capability !== null)
return createGlobalModel({ return createGlobalModel({
@@ -763,6 +783,8 @@ async function handleSubmit() {
try { try {
// 获取包含自动计算缓存价格的最终数据 // 获取包含自动计算缓存价格的最终数据
const finalTieredPricing = tieredPricingEditorRef.value?.getFinalPricing() ?? tieredPricing.value const finalTieredPricing = tieredPricingEditorRef.value?.getFinalPricing() ?? tieredPricing.value
const supportsImageGeneration = isImageGenerationEnabled.value
|| tieredPricingHasImageOutputPricing(finalTieredPricing)
// Apply billing (video) pricing into config. // Apply billing (video) pricing into config.
applyVideoPricingToConfig(form.value.config) applyVideoPricingToConfig(form.value.config)
@@ -781,14 +803,14 @@ async function handleSubmit() {
supportsFunctionCalling: form.value.supports_function_calling, supportsFunctionCalling: form.value.supports_function_calling,
supportsStreaming: form.value.supports_streaming, supportsStreaming: form.value.supports_streaming,
supportsExtendedThinking: form.value.supports_extended_thinking, supportsExtendedThinking: form.value.supports_extended_thinking,
supportsImageGeneration: form.value.supports_image_generation, supportsImageGeneration,
isActive: form.value.is_active isActive: form.value.is_active
})) }))
showSuccess('模型配置已更新') showSuccess('模型配置已更新')
} else { } else {
// 添加模式:只有用户修改了配置才提交 tiered_pricing否则保持继承关系 // 添加模式:只有用户修改了配置才提交 tiered_pricing否则保持继承关系
const selectedModel = manualGlobalModelMode.value const selectedModel = manualGlobalModelMode.value
? await createManualGlobalModel(finalTieredPricing, cleanConfig) ? await createManualGlobalModel(finalTieredPricing, cleanConfig, supportsImageGeneration)
: availableGlobalModels.value.find(m => m.id === form.value.global_model_id) : availableGlobalModels.value.find(m => m.id === form.value.global_model_id)
if (!selectedModel) { if (!selectedModel) {
showError('请选择模型或切换到手动添加后填写模型ID', '错误') showError('请选择模型或切换到手动添加后填写模型ID', '错误')
@@ -806,7 +828,7 @@ async function handleSubmit() {
supportsFunctionCalling: form.value.supports_function_calling, supportsFunctionCalling: form.value.supports_function_calling,
supportsStreaming: form.value.supports_streaming, supportsStreaming: form.value.supports_streaming,
supportsExtendedThinking: form.value.supports_extended_thinking, supportsExtendedThinking: form.value.supports_extended_thinking,
supportsImageGeneration: form.value.supports_image_generation, supportsImageGeneration,
isActive: form.value.is_active isActive: form.value.is_active
})) }))
showSuccess('模型已添加') showSuccess('模型已添加')