fix(frontend): stabilize image generation overrides in model forms

This commit is contained in:
ZheFox
2026-05-18 03:39:55 +08:00
parent a05ae94cea
commit f5ace4fd6d
3 changed files with 45 additions and 7 deletions

View File

@@ -2,8 +2,8 @@
<input <input
type="checkbox" type="checkbox"
:class="checkboxClass" :class="checkboxClass"
:checked="isChecked"
v-bind="$attrs" v-bind="$attrs"
:checked="isChecked"
@change="handleChange" @change="handleChange"
> >
</template> </template>

View File

@@ -543,6 +543,7 @@ const defaultForm = (): FormData => ({
}) })
const form = ref<FormData>(defaultForm()) const form = ref<FormData>(defaultForm())
const imageGenerationExplicitOverride = ref<boolean | null>(null)
const isEmbeddingEnabled = computed(() => { const isEmbeddingEnabled = computed(() => {
return form.value.supported_capabilities?.includes('embedding') === true return form.value.supported_capabilities?.includes('embedding') === true
@@ -551,11 +552,15 @@ const isEmbeddingEnabled = computed(() => {
}) })
const isImageGenerationEnabled = computed(() => { const isImageGenerationEnabled = computed(() => {
if (imageGenerationExplicitOverride.value !== null) {
return imageGenerationExplicitOverride.value
}
return form.value.supported_capabilities?.includes('image_generation') === true return form.value.supported_capabilities?.includes('image_generation') === true
|| form.value.config?.image_generation === true || form.value.config?.image_generation === true
|| form.value.config?.model_type === 'image' || form.value.config?.model_type === 'image'
|| (Array.isArray(form.value.config?.api_formats) || (Array.isArray(form.value.config?.api_formats)
&& form.value.config.api_formats.some((format) => String(format).endsWith(':image'))) && form.value.config.api_formats.some((format) => String(format).endsWith(':image')))
|| tieredPricingHasImageOutputPricing(tieredPricing.value)
}) })
const KEEP_FALSE_CONFIG_KEYS = new Set(['streaming']) const KEEP_FALSE_CONFIG_KEYS = new Set(['streaming'])
@@ -602,6 +607,7 @@ function setEmbeddingEnabled(enabled: boolean) {
function setImageGenerationEnabled(value: boolean | 'indeterminate') { function setImageGenerationEnabled(value: boolean | 'indeterminate') {
const enabled = value === true const enabled = value === true
imageGenerationExplicitOverride.value = enabled
const caps = new Set(form.value.supported_capabilities || []) const caps = new Set(form.value.supported_capabilities || [])
if (enabled) { if (enabled) {
caps.add('image_generation') caps.add('image_generation')
@@ -767,6 +773,7 @@ watch(() => props.open, (isOpen) => {
// 选择模型并填充表单 // 选择模型并填充表单
function selectModel(model: ModelsDevModelItem) { function selectModel(model: ModelsDevModelItem) {
imageGenerationExplicitOverride.value = null
selectedModel.value = model selectedModel.value = model
expandedProvider.value = model.providerId expandedProvider.value = model.providerId
form.value.name = model.modelId form.value.name = model.modelId
@@ -815,6 +822,7 @@ function selectModel(model: ModelsDevModelItem) {
// 清除选择(手动填写) // 清除选择(手动填写)
function clearSelection() { function clearSelection() {
imageGenerationExplicitOverride.value = null
selectedModel.value = null selectedModel.value = null
form.value = defaultForm() form.value = defaultForm()
tieredPricing.value = null tieredPricing.value = null
@@ -828,6 +836,7 @@ function handleLogoError(event: Event) {
// 重置表单 // 重置表单
function resetForm() { function resetForm() {
imageGenerationExplicitOverride.value = null
form.value = defaultForm() form.value = defaultForm()
tieredPricing.value = null tieredPricing.value = null
videoResolutionPrices.value = [] videoResolutionPrices.value = []
@@ -839,6 +848,7 @@ function resetForm() {
// 加载模型数据(编辑模式) // 加载模型数据(编辑模式)
function loadModelData() { function loadModelData() {
if (!props.model) return if (!props.model) return
imageGenerationExplicitOverride.value = null
// 先重置创建模式的残留状态 // 先重置创建模式的残留状态
selectedModel.value = null selectedModel.value = null
searchQuery.value = '' searchQuery.value = ''
@@ -937,10 +947,19 @@ async function handleSubmit() {
function tieredPricingHasImageOutputPricing(pricing: TieredPricingConfig | null | undefined): boolean { function tieredPricingHasImageOutputPricing(pricing: TieredPricingConfig | null | undefined): boolean {
if (!pricing) return false if (!pricing) return false
if (pricing.image_output_price_default != null) return true if (toFinitePrice(pricing.image_output_price_default) !== null) return true
return Object.values(pricing.image_output_prices || {}).some((prices) => { return Object.values(pricing.image_output_prices || {}).some((prices) => {
if (!prices || typeof prices !== 'object') return false if (!prices || typeof prices !== 'object') return false
return Object.values(prices).some((price) => typeof price === 'number' && Number.isFinite(price)) return Object.values(prices).some((price) => toFinitePrice(price) !== null)
}) })
} }
function toFinitePrice(value: unknown): number | null {
if (typeof value === 'number' && Number.isFinite(value)) return value
if (typeof value === 'string' && value.trim()) {
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : null
}
return null
}
</script> </script>

View File

@@ -353,12 +353,16 @@ const editingModelSupportsImageGeneration = computed(() => {
}) })
const isImageGenerationEnabled = computed(() => { const isImageGenerationEnabled = computed(() => {
if (imageGenerationExplicitOverride.value !== null) {
return imageGenerationExplicitOverride.value
}
if (form.value.supports_image_generation !== undefined) { if (form.value.supports_image_generation !== undefined) {
return form.value.supports_image_generation === true return form.value.supports_image_generation === true
} }
return isEditing.value const supportsImageGeneration = isEditing.value
? editingModelSupportsImageGeneration.value ? editingModelSupportsImageGeneration.value
: selectedGlobalModelSupportsImageGeneration.value : selectedGlobalModelSupportsImageGeneration.value
return supportsImageGeneration || tieredPricingHasImageOutputPricing(tieredPricing.value)
}) })
// 1h 缓存定价始终显示 // 1h 缓存定价始终显示
@@ -422,6 +426,7 @@ const form = ref({
supports_image_generation: undefined as boolean | undefined, supports_image_generation: undefined as boolean | undefined,
is_active: true is_active: true
}) })
const imageGenerationExplicitOverride = ref<boolean | null>(null)
const canSubmitCreate = computed(() => { const canSubmitCreate = computed(() => {
if (isEditing.value) return true if (isEditing.value) return true
@@ -502,6 +507,7 @@ watch(tieredPricing, (newValue) => {
// 重置表单 // 重置表单
function resetForm() { function resetForm() {
imageGenerationExplicitOverride.value = null
form.value = { form.value = {
global_model_id: '', global_model_id: '',
provider_model_name: '', provider_model_name: '',
@@ -526,6 +532,8 @@ function resetForm() {
} }
function handleGlobalModelSelect(value: string) { function handleGlobalModelSelect(value: string) {
imageGenerationExplicitOverride.value = null
form.value.supports_image_generation = undefined
form.value.global_model_id = value form.value.global_model_id = value
const selectedModel = availableGlobalModels.value.find(model => model.id === value) const selectedModel = availableGlobalModels.value.find(model => model.id === value)
form.value.provider_model_name = selectedModel?.name || form.value.provider_model_name form.value.provider_model_name = selectedModel?.name || form.value.provider_model_name
@@ -565,15 +573,26 @@ function modelSupportsImageGeneration(model: {
function tieredPricingHasImageOutputPricing(pricing: TieredPricingConfig | null | undefined): boolean { function tieredPricingHasImageOutputPricing(pricing: TieredPricingConfig | null | undefined): boolean {
if (!pricing) return false if (!pricing) return false
if (pricing.image_output_price_default != null) return true if (toFinitePrice(pricing.image_output_price_default) !== null) return true
return Object.values(pricing.image_output_prices || {}).some((prices) => { return Object.values(pricing.image_output_prices || {}).some((prices) => {
if (!prices || typeof prices !== 'object') return false if (!prices || typeof prices !== 'object') return false
return Object.values(prices).some((price) => typeof price === 'number' && Number.isFinite(price)) return Object.values(prices).some((price) => toFinitePrice(price) !== null)
}) })
} }
function toFinitePrice(value: unknown): number | null {
if (typeof value === 'number' && Number.isFinite(value)) return value
if (typeof value === 'string' && value.trim()) {
const parsed = Number(value)
return Number.isFinite(parsed) ? parsed : null
}
return null
}
function setImageGenerationEnabled(value: boolean | 'indeterminate') { function setImageGenerationEnabled(value: boolean | 'indeterminate') {
form.value.supports_image_generation = value === true const enabled = value === true
imageGenerationExplicitOverride.value = enabled
form.value.supports_image_generation = enabled
} }
function getNested(obj: Record<string, unknown>, path: string): unknown { function getNested(obj: Record<string, unknown>, path: string): unknown {