mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat(billing): support image output pricing in model forms and details
This commit is contained in:
@@ -844,18 +844,24 @@ function loadModelData() {
|
||||
searchQuery.value = ''
|
||||
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 = {
|
||||
name: props.model.name,
|
||||
display_name: props.model.display_name,
|
||||
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 },
|
||||
is_active: props.model.is_active,
|
||||
}
|
||||
// 确保 tieredPricing 也被正确设置或重置
|
||||
tieredPricing.value = props.model.default_tiered_pricing
|
||||
? JSON.parse(JSON.stringify(props.model.default_tiered_pricing))
|
||||
: null
|
||||
tieredPricing.value = modelTieredPricing
|
||||
loadVideoPricingFromConfig()
|
||||
}
|
||||
|
||||
@@ -896,6 +902,9 @@ async function handleSubmit() {
|
||||
} else {
|
||||
caps.delete('cache_1h')
|
||||
}
|
||||
if (tieredPricingHasImageOutputPricing(finalTieredPricing)) {
|
||||
caps.add('image_generation')
|
||||
}
|
||||
form.value.supported_capabilities = caps.size > 0 ? [...caps] : []
|
||||
|
||||
// 清理空的 config
|
||||
@@ -925,4 +934,13 @@ async function handleSubmit() {
|
||||
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>
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 默认定价 -->
|
||||
@@ -145,6 +146,68 @@
|
||||
默认定价
|
||||
</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
|
||||
v-if="getTierCount(model.default_tiered_pricing) <= 1"
|
||||
@@ -561,6 +624,47 @@ const videoPricingEntries = computed(() => {
|
||||
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')
|
||||
|
||||
// 处理背景点击
|
||||
|
||||
@@ -158,30 +158,55 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="grid grid-cols-[96px_repeat(3,minmax(0,1fr))] gap-2 text-xs text-muted-foreground">
|
||||
<span />
|
||||
<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>low</span>
|
||||
<span>medium</span>
|
||||
<span>high</span>
|
||||
<span />
|
||||
</div>
|
||||
<div
|
||||
v-for="size in IMAGE_OUTPUT_SIZES"
|
||||
:key="size.value"
|
||||
class="grid grid-cols-[96px_repeat(3,minmax(0,1fr))] gap-2 items-center"
|
||||
v-for="row in imageOutputPriceRows"
|
||||
:key="row.id"
|
||||
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
|
||||
v-for="quality in IMAGE_OUTPUT_QUALITIES"
|
||||
:key="`${size.value}-${quality}`"
|
||||
:model-value="getImageOutputPrice(size.value, quality)"
|
||||
:key="`${row.id}-${quality}`"
|
||||
:model-value="getImageOutputPrice(row, quality)"
|
||||
type="number"
|
||||
step="0.001"
|
||||
min="0"
|
||||
class="h-8"
|
||||
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>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="w-full"
|
||||
@click="addImageOutputSizeRow"
|
||||
>
|
||||
<Plus class="w-4 h-4 mr-2" />
|
||||
添加分辨率
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -202,13 +227,13 @@ import { Button, Input, Label } from '@/components/ui'
|
||||
import type { TieredPricingConfig, PricingTier } from '@/api/endpoints/types'
|
||||
|
||||
type ImageOutputQuality = 'low' | 'medium' | 'high'
|
||||
type ImageOutputPriceRow = {
|
||||
id: string
|
||||
size: string
|
||||
prices: Partial<Record<ImageOutputQuality, number>>
|
||||
}
|
||||
|
||||
const IMAGE_OUTPUT_SIZES = [
|
||||
{ value: '1024x1024', label: '1024 x 1024' },
|
||||
{ value: '1536x1024', label: '1536 x 1024' },
|
||||
{ value: '1024x1536', label: '1024 x 1536' },
|
||||
] as const
|
||||
|
||||
const DEFAULT_IMAGE_OUTPUT_SIZES = ['1024x1024', '1536x1024', '1024x1536']
|
||||
const IMAGE_OUTPUT_QUALITIES: ImageOutputQuality[] = ['low', 'medium', 'high']
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -223,8 +248,10 @@ const emit = defineEmits<{
|
||||
|
||||
// 本地状态
|
||||
const localTiers = ref<PricingTier[]>([])
|
||||
const imageOutputPrices = ref<Record<string, Record<string, number | undefined>>>({})
|
||||
const imageOutputPriceRows = ref<ImageOutputPriceRow[]>([])
|
||||
const imageOutputPriceDefault = ref<string>('')
|
||||
const lastEmittedPricingJson = ref<string>('')
|
||||
let imageOutputPriceRowId = 0
|
||||
|
||||
// 跟踪每个阶梯的缓存价格是否被手动设置
|
||||
const cacheManuallySet = reactive<Record<number, { creation: boolean; read: boolean; cache1h: boolean }>>({})
|
||||
@@ -247,9 +274,12 @@ const customInputValue = reactive<Record<number, string>>({})
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
if (lastEmittedPricingJson.value && JSON.stringify(newValue ?? null) === lastEmittedPricingJson.value) {
|
||||
return
|
||||
}
|
||||
if (newValue?.tiers) {
|
||||
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
|
||||
? String(newValue.image_output_price_default)
|
||||
: ''
|
||||
@@ -268,7 +298,7 @@ watch(
|
||||
input_price_per_1m: 0,
|
||||
output_price_per_1m: 0,
|
||||
}]
|
||||
imageOutputPrices.value = {}
|
||||
imageOutputPriceRows.value = createImageOutputPriceRows(null)
|
||||
imageOutputPriceDefault.value = ''
|
||||
cacheManuallySet[0] = { creation: false, read: false, cache1h: false }
|
||||
}
|
||||
@@ -434,7 +464,9 @@ function syncToParent() {
|
||||
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
|
||||
}
|
||||
|
||||
function cloneImageOutputPrices(value: TieredPricingConfig['image_output_prices']): Record<string, Record<string, number | undefined>> {
|
||||
const out: Record<string, Record<string, number | undefined>> = {}
|
||||
if (!value || typeof value !== 'object') return out
|
||||
function createImageOutputPriceRows(value: TieredPricingConfig['image_output_prices']): ImageOutputPriceRow[] {
|
||||
const rows: ImageOutputPriceRow[] = []
|
||||
if (!value || typeof value !== 'object') {
|
||||
return DEFAULT_IMAGE_OUTPUT_SIZES.map(size => createImageOutputPriceRow(size))
|
||||
}
|
||||
for (const [size, prices] of Object.entries(value)) {
|
||||
if (!prices || typeof prices !== 'object') continue
|
||||
const rowPrices: Partial<Record<ImageOutputQuality, number>> = {}
|
||||
for (const quality of IMAGE_OUTPUT_QUALITIES) {
|
||||
const price = (prices as Record<string, unknown>)[quality]
|
||||
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>> {
|
||||
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) {
|
||||
const price = prices[quality]
|
||||
const price = row.prices[quality]
|
||||
if (price != null && Number.isFinite(price)) {
|
||||
out[size] = { ...(out[size] || {}), [quality]: price }
|
||||
}
|
||||
@@ -533,25 +584,44 @@ function parseOptionalFloat(value: string | number): number | null {
|
||||
return Number.isFinite(number) ? number : null
|
||||
}
|
||||
|
||||
function getImageOutputPrice(size: string, quality: ImageOutputQuality): string | number {
|
||||
return imageOutputPrices.value[size]?.[quality] ?? ''
|
||||
function normalizeImageOutputSize(size: string): string {
|
||||
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 current = { ...(imageOutputPrices.value[size] || {}) }
|
||||
if (price == null) {
|
||||
delete current[quality]
|
||||
delete row.prices[quality]
|
||||
} else {
|
||||
current[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
|
||||
row.prices[quality] = price
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
@@ -438,6 +438,7 @@ watch(() => props.open, async (newOpen) => {
|
||||
// 编辑模式:填充表单
|
||||
// 使用有效配置(合并全局模型的默认值)供用户查看和编辑
|
||||
const effectiveConfig = props.editingModel.effective_config || props.editingModel.config || {}
|
||||
const supportsImageGeneration = modelSupportsImageGeneration(props.editingModel)
|
||||
form.value = {
|
||||
global_model_id: props.editingModel.global_model_id || '',
|
||||
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_streaming: props.editingModel.supports_streaming ?? 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
|
||||
}
|
||||
// 从有效配置中加载视频费用
|
||||
@@ -544,6 +545,9 @@ function modelSupportsImageGeneration(model: {
|
||||
supported_capabilities?: string[] | null
|
||||
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
|
||||
} | null | undefined): boolean {
|
||||
if (!model) return false
|
||||
@@ -554,6 +558,18 @@ function modelSupportsImageGeneration(model: {
|
||||
|| config.image_generation === true
|
||||
|| config.model_type === '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') {
|
||||
@@ -696,7 +712,11 @@ function _copyVideoPricingFromSelectedGlobal() {
|
||||
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 displayName = form.value.manual_global_model_display_name.trim() || modelName
|
||||
const supportedCapabilities = [
|
||||
@@ -704,7 +724,7 @@ async function createManualGlobalModel(finalTieredPricing: TieredPricingConfig |
|
||||
form.value.supports_function_calling === true ? 'function_calling' : null,
|
||||
form.value.supports_streaming === true ? 'streaming' : 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)
|
||||
|
||||
return createGlobalModel({
|
||||
@@ -763,6 +783,8 @@ async function handleSubmit() {
|
||||
try {
|
||||
// 获取包含自动计算缓存价格的最终数据
|
||||
const finalTieredPricing = tieredPricingEditorRef.value?.getFinalPricing() ?? tieredPricing.value
|
||||
const supportsImageGeneration = isImageGenerationEnabled.value
|
||||
|| tieredPricingHasImageOutputPricing(finalTieredPricing)
|
||||
|
||||
// Apply billing (video) pricing into config.
|
||||
applyVideoPricingToConfig(form.value.config)
|
||||
@@ -781,14 +803,14 @@ async function handleSubmit() {
|
||||
supportsFunctionCalling: form.value.supports_function_calling,
|
||||
supportsStreaming: form.value.supports_streaming,
|
||||
supportsExtendedThinking: form.value.supports_extended_thinking,
|
||||
supportsImageGeneration: form.value.supports_image_generation,
|
||||
supportsImageGeneration,
|
||||
isActive: form.value.is_active
|
||||
}))
|
||||
showSuccess('模型配置已更新')
|
||||
} else {
|
||||
// 添加模式:只有用户修改了配置才提交 tiered_pricing,否则保持继承关系
|
||||
const selectedModel = manualGlobalModelMode.value
|
||||
? await createManualGlobalModel(finalTieredPricing, cleanConfig)
|
||||
? await createManualGlobalModel(finalTieredPricing, cleanConfig, supportsImageGeneration)
|
||||
: availableGlobalModels.value.find(m => m.id === form.value.global_model_id)
|
||||
if (!selectedModel) {
|
||||
showError('请选择模型,或切换到手动添加后填写模型ID', '错误')
|
||||
@@ -806,7 +828,7 @@ async function handleSubmit() {
|
||||
supportsFunctionCalling: form.value.supports_function_calling,
|
||||
supportsStreaming: form.value.supports_streaming,
|
||||
supportsExtendedThinking: form.value.supports_extended_thinking,
|
||||
supportsImageGeneration: form.value.supports_image_generation,
|
||||
supportsImageGeneration,
|
||||
isActive: form.value.is_active
|
||||
}))
|
||||
showSuccess('模型已添加')
|
||||
|
||||
Reference in New Issue
Block a user