diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3fd85ead4..3c249f497 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,12 @@
- 项目开源协议由 AGPL-3.0 调整为 MIT,保留上游及项目贡献者版权声明。
- 修复任务中心等资源入口仍由应用服务器转发对象存储媒体的问题:配置 CDN 加速域名后,登录资源、公开画布分享和管理员媒体预览统一重定向到 CDN;内部 Blob 读取保留同源代理兼容。
+## v1.1.2
+
+- 修复后台逻辑模型默认参数未应用到前台创作端的问题,支持默认画面比例、视频时长和输出分辨率正确初始化。
+- 恢复图片模型完整尺寸/比例能力,并兼容视频 `720` 与 `720p` 分辨率配置。
+- 移除逻辑模型能力配置中占用空间较大的无能力来源警告卡片,简化后台空状态。
+
## v1.0.59
- 画布媒体工作流支持批量连线、失败重试与模型能力增强。
diff --git a/VERSION b/VERSION
index 1e440c0b6..0f1acbd56 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-v1.0.59
+v1.1.2
diff --git a/backend/internal/service/logical_models.go b/backend/internal/service/logical_models.go
index ae3c0f1a8..1d0237f01 100644
--- a/backend/internal/service/logical_models.go
+++ b/backend/internal/service/logical_models.go
@@ -165,6 +165,11 @@ func (s *Service) PublicLogicalModels(intent *ModelRequestIntent) ([]PublicLogic
func publicLogicalModel(cached cachedLogicalModel, available bool) PublicLogicalModel {
item := cached.Model
+ routeSpecs := make([]CapabilitySpec, 0, len(cached.Routes))
+ for _, route := range cached.Routes {
+ routeSpecs = append(routeSpecs, route.VariantSpec)
+ }
+ productSpec := capabilitySpecWithRoutePresets(cached.ProductSpec, routeSpecs)
variants := make([]CapabilitySpec, 0, len(cached.Routes))
seen := make(map[string]bool, len(cached.Routes))
for _, route := range cached.Routes {
@@ -177,7 +182,37 @@ func publicLogicalModel(cached cachedLogicalModel, available bool) PublicLogical
variants = append(variants, route.VariantSpec)
}
}
- return PublicLogicalModel{ID: item.ID, Code: item.Code, Name: item.Name, Icon: item.Icon, Description: item.Description, Capability: item.Capability, SortOrder: item.SortOrder, PricePolicy: item.PricePolicy, BillingMode: item.BillingMode, UnitPriceMicrocredits: item.UnitPriceMicrocredits, InputPriceMicrocredits: item.InputPriceMicrocredits, OutputPriceMicrocredits: item.OutputPriceMicrocredits, CachedPriceMicrocredits: item.CachedPriceMicrocredits, CapabilitySpec: cached.ProductSpec, CapabilityProfiles: variants, DefaultOptions: cached.Defaults, Available: available}
+ return PublicLogicalModel{ID: item.ID, Code: item.Code, Name: item.Name, Icon: item.Icon, Description: item.Description, Capability: item.Capability, SortOrder: item.SortOrder, PricePolicy: item.PricePolicy, BillingMode: item.BillingMode, UnitPriceMicrocredits: item.UnitPriceMicrocredits, InputPriceMicrocredits: item.InputPriceMicrocredits, OutputPriceMicrocredits: item.OutputPriceMicrocredits, CachedPriceMicrocredits: item.CachedPriceMicrocredits, CapabilitySpec: productSpec, CapabilityProfiles: variants, DefaultOptions: cached.Defaults, Available: available}
+}
+
+// capabilitySpecWithRoutePresets repairs old front-model snapshots that stored
+// only `*` for a custom size. The wildcard remains for matching custom values,
+// while route presets are restored for admin and creator-side selectors.
+func capabilitySpecWithRoutePresets(spec CapabilitySpec, routes []CapabilitySpec) CapabilitySpec {
+ result := spec
+ result.Options = make(map[string]OptionConstraint, len(spec.Options))
+ for name, constraint := range spec.Options {
+ if !isWildcardOptionConstraint(constraint) {
+ result.Options[name] = constraint
+ continue
+ }
+ values := append([]any(nil), constraint.Values...)
+ seen := make(map[string]bool, len(values))
+ for _, value := range values {
+ seen[normalizedScalar(value)] = true
+ }
+ for _, route := range routes {
+ for _, value := range route.Options[name].Values {
+ key := normalizedScalar(value)
+ if key != "" && !seen[key] {
+ seen[key] = true
+ values = append(values, value)
+ }
+ }
+ }
+ result.Options[name] = OptionConstraint{Values: values}
+ }
+ return result
}
// capabilityFingerprint 用规范化后的结构去重能力画像;不能直接依赖原始 JSON,
@@ -250,10 +285,6 @@ func (s *Service) buildAdminLogicalModel(item model.LogicalModel, graph *reposit
if err != nil {
return nil, err
}
- defaults, err := decodeLogicalDefaults(graph.Revision.DefaultOptionsJSON, productSpec)
- if err != nil {
- return nil, err
- }
variantByID := make(map[string]model.PhysicalCapabilityVariant, len(graph.Variants))
for _, variant := range graph.Variants {
variantByID[variant.ID] = variant
@@ -262,7 +293,7 @@ func (s *Service) buildAdminLogicalModel(item model.LogicalModel, graph *reposit
for _, channelModel := range graph.ChannelModels {
channelModelByID[channelModel.ID] = channelModel
}
- admin := AdminLogicalModel{PublicLogicalModel: publicLogicalModel(cachedLogicalModel{Model: item, ProductSpec: productSpec, Defaults: defaults}, false), Enabled: item.Enabled, ActiveRevisionID: graph.Revision.ID, RevisionVersion: graph.Revision.Version, Routes: []AdminLogicalRoute{}}
+ admin := AdminLogicalModel{PublicLogicalModel: publicLogicalModel(cachedLogicalModel{Model: item, ProductSpec: productSpec, Defaults: map[string]any{}}, false), Enabled: item.Enabled, ActiveRevisionID: graph.Revision.ID, RevisionVersion: graph.Revision.Version, Routes: []AdminLogicalRoute{}}
for _, route := range graph.Routes {
variant, variantOK := variantByID[route.PhysicalVariantID]
if !variantOK {
@@ -281,6 +312,20 @@ func (s *Service) buildAdminLogicalModel(item model.LogicalModel, graph *reposit
available := structurallyAvailable && (item.PricePolicy != "channel" || channelModel.PriceConfigured)
admin.Routes = append(admin.Routes, AdminLogicalRoute{ID: route.ID, PhysicalVariantID: variant.ID, PhysicalVariantName: variant.Name, ChannelModelID: channelModel.ID, ChannelID: channelModel.ChannelID, PhysicalModelKey: channelModel.ModelKey, PhysicalModelName: channelModel.DisplayName, Enabled: route.Enabled, Priority: route.Priority, Weight: route.Weight, Available: available, structurallyAvailable: structurallyAvailable, CapabilitySpec: variantSpec})
}
+ routeSpecs := make([]CapabilitySpec, 0, len(admin.Routes))
+ for _, route := range admin.Routes {
+ routeSpecs = append(routeSpecs, route.CapabilitySpec)
+ }
+ productSpec = capabilitySpecWithRoutePresets(productSpec, routeSpecs)
+ defaults, err := decodeLogicalDefaults(graph.Revision.DefaultOptionsJSON, productSpec)
+ if err != nil {
+ return nil, err
+ }
+ admin.PublicLogicalModel = publicLogicalModel(cachedLogicalModel{Model: item, ProductSpec: productSpec, Defaults: defaults}, false)
+ // publicLogicalModel above has no route list; admin routes are already attached
+ // and the enriched product spec is the source used by the editor.
+ admin.CapabilitySpec = productSpec
+ admin.DefaultOptions = defaults
structuralRouteSpecs := structuralAdminRouteSpecs(admin.Routes)
settlementRouteSpecs := settlementReadyAdminRouteSpecs(admin.Routes)
admin.ConfigurationError = logicalModelConfigurationError(productSpec, structuralRouteSpecs)
@@ -532,9 +577,18 @@ func normalizeLogicalDefaults(spec CapabilitySpec, defaults map[string]any) (map
return nil, BadAuthRequest("默认参数存在重复别名:" + name)
}
constraint, ok := spec.Options[name]
- if !ok || !matchOptionConstraint(constraint, value) {
+ if !ok || !matchOptionConstraint(name, constraint, value) {
return nil, BadAuthRequest("默认参数 " + name + " 不在前台模型能力范围内")
}
+ // `*` 只表示允许任意自定义值,不能作为创作端默认参数发送。
+ if normalizedScalar(value) == "*" {
+ for _, candidate := range constraint.Values {
+ if normalizedScalar(candidate) != "*" {
+ value = candidate
+ break
+ }
+ }
+ }
result[name] = value
}
return result, nil
@@ -751,14 +805,11 @@ func validateVariantWithinChannelModel(channelModel model.ChannelModel, spec Cap
}
}
physical := map[string]OptionConstraint{
- "size": anyValues(image.Size.Values),
+ "size": imageSizeOptionConstraint(image.Size),
"quality": anyValues(image.Quality.Values),
"transparentBackground": boolValues(image.TransparentBackground.Supported),
"count": numericRange(1, float64(image.MaxOutputs), 1),
}
- if image.Size.AllowCustom {
- physical["size"] = OptionConstraint{Values: []any{"*"}}
- }
return validateKnownVariantOptions(spec.Options, physical)
case "video":
if profile == nil || profile.Video == nil {
@@ -884,13 +935,13 @@ func optionConstraintCovered(candidate OptionConstraint, name string, routeSpecs
return false
}
for _, routeConstraint := range routeConstraints {
- if len(routeConstraint.Values) == 1 && normalizedScalar(routeConstraint.Values[0]) == "*" {
+ if isWildcardOptionConstraint(routeConstraint) {
return true
}
}
if len(candidate.Values) > 0 {
for _, value := range candidate.Values {
- if !optionValueSupported(value, routeConstraints) {
+ if !optionValueSupported(name, value, routeConstraints) {
return false
}
}
@@ -900,7 +951,7 @@ func optionConstraintCovered(candidate OptionConstraint, name string, routeSpecs
return false
}
if math.Abs(*candidate.Max-*candidate.Min) < 1e-9 {
- return optionValueSupported(*candidate.Min, routeConstraints)
+ return optionValueSupported(name, *candidate.Min, routeConstraints)
}
if candidate.Step == nil {
return continuousOptionRangeCovered(*candidate.Min, *candidate.Max, routeConstraints)
@@ -910,7 +961,7 @@ func optionConstraintCovered(candidate OptionConstraint, name string, routeSpecs
if count <= 10000 {
for index := 0; index < count; index++ {
value := *candidate.Min + float64(index)*step
- if !optionValueSupported(value, routeConstraints) {
+ if !optionValueSupported(name, value, routeConstraints) {
return false
}
}
@@ -933,12 +984,21 @@ func optionConstraintCovered(candidate OptionConstraint, name string, routeSpecs
return false
}
-func optionValueSupported(value any, constraints []OptionConstraint) bool {
+func optionValueSupported(name string, value any, constraints []OptionConstraint) bool {
for _, constraint := range constraints {
- if len(constraint.Values) == 1 && normalizedScalar(constraint.Values[0]) == "*" {
+ if isWildcardOptionConstraint(constraint) {
return true
}
- if matchOptionConstraint(constraint, value) {
+ if matchOptionConstraint(name, constraint, value) {
+ return true
+ }
+ }
+ return false
+}
+
+func isWildcardOptionConstraint(constraint OptionConstraint) bool {
+ for _, value := range constraint.Values {
+ if normalizedScalar(value) == "*" {
return true
}
}
diff --git a/backend/internal/service/logical_models_test.go b/backend/internal/service/logical_models_test.go
index 832c18035..864a46873 100644
--- a/backend/internal/service/logical_models_test.go
+++ b/backend/internal/service/logical_models_test.go
@@ -40,6 +40,31 @@ func TestMatchCapabilityAcceptsWildcardOptionValues(t *testing.T) {
}
}
+func TestMatchCapabilityAcceptsWildcardAlongsidePresetOptionValues(t *testing.T) {
+ intent := ModelRequestIntent{Capability: "image", Options: map[string]any{"size": "2:1"}}
+ spec := CapabilitySpec{
+ Version: 1,
+ Capability: "image",
+ Options: map[string]OptionConstraint{"size": {Values: []any{"1:1", "9:16", "*"}}},
+ }
+ match := MatchCapability(spec, intent)
+ if !match.Matched {
+ t.Fatalf("wildcard alongside presets did not match custom value: %#v", match)
+ }
+}
+
+func TestMatchCapabilityTreatsVideoResolutionSuffixAsEquivalent(t *testing.T) {
+ spec := CapabilitySpec{
+ Version: 1,
+ Capability: "video",
+ Options: map[string]OptionConstraint{"vquality": {Values: []any{"720p"}}},
+ }
+ intent := ModelRequestIntent{Capability: "video", Options: map[string]any{"vquality": "720"}}
+ if match := MatchCapability(spec, intent); !match.Matched {
+ t.Fatalf("720 and 720p should match the same video resolution: %#v", match)
+ }
+}
+
func TestValidateProductSpecWithinRoutesRejectsUnsupportedCapabilityValue(t *testing.T) {
routes := []CapabilitySpec{
{
diff --git a/backend/internal/service/model_capability.go b/backend/internal/service/model_capability.go
index 223cc70e4..e36ce8bd2 100644
--- a/backend/internal/service/model_capability.go
+++ b/backend/internal/service/model_capability.go
@@ -174,6 +174,15 @@ func defaultImageSizeValues() []string {
}
}
+// legacyImageSizeValues 用于修复旧数据中仅保存了 "*" 的图片尺寸能力。
+// 这组值是前后台共同展示的基础预设,不能让历史通配符配置继续污染用户生成参数。
+func legacyImageSizeValues() []string {
+ return []string{
+ "1:1", "3:2", "2:3", "4:3", "3:4", "16:9", "21:9", "9:16",
+ "1024x1024", "1536x1024", "1024x1536",
+ }
+}
+
func DefaultModelCapabilityConfigForModel(protocol string, modelName string) *ModelCapabilityConfig {
// 文本模型是否支持视觉输入不能从协议或模型名可靠推断,默认关闭,由管理员按真实上游能力开启。
text := &TextCapabilityConfig{References: TextReferenceConfig{PromptMaxChars: 32000}}
@@ -297,11 +306,7 @@ func CapabilitySpecFromModelCapabilityConfig(config *ModelCapabilityConfig, capa
addInputConstraint(spec.Inputs, "mask", 0, 1)
}
if image.Size.Parameter != "none" {
- if image.Size.AllowCustom {
- spec.Options["size"] = OptionConstraint{Values: []any{"*"}}
- } else {
- spec.Options["size"] = anyValues(image.Size.Values)
- }
+ spec.Options["size"] = imageSizeOptionConstraint(image.Size)
}
if image.Quality.Supported {
spec.Options["quality"] = anyValues(image.Quality.Values)
@@ -350,6 +355,34 @@ func CapabilitySpecFromModelCapabilityConfig(config *ModelCapabilityConfig, capa
return spec, nil
}
+// imageSizeOptionConstraint 保留可见的标准尺寸/比例,同时用 * 表示允许自定义。
+// * 不能替代标准值,否则管理端只能看到一个没有业务含义的通配符。
+func imageSizeOptionConstraint(size ImageSizeConfig) OptionConstraint {
+ values := make([]string, 0, len(size.Values)+1)
+ seen := make(map[string]struct{}, len(size.Values)+1)
+ for _, value := range size.Values {
+ value = strings.TrimSpace(value)
+ if value == "" || value == "*" {
+ continue
+ }
+ if _, exists := seen[value]; exists {
+ continue
+ }
+ seen[value] = struct{}{}
+ values = append(values, value)
+ }
+ if size.AllowCustom {
+ if len(values) == 0 {
+ for _, value := range legacyImageSizeValues() {
+ seen[value] = struct{}{}
+ values = append(values, value)
+ }
+ }
+ values = append(values, "*")
+ }
+ return anyValues(values)
+}
+
func addInputConstraint(inputs map[string]InputConstraint, name string, min int, max int) {
if min <= 0 && max <= 0 {
return
diff --git a/backend/internal/service/model_capability_test.go b/backend/internal/service/model_capability_test.go
index 08419db2f..d8d3666e2 100644
--- a/backend/internal/service/model_capability_test.go
+++ b/backend/internal/service/model_capability_test.go
@@ -83,6 +83,36 @@ func TestDefaultVideoCapabilityUsesProtocolSpecificResolutionTiers(t *testing.T)
}
}
+func TestCapabilitySpecFromModelCapabilityConfigRestoresLegacyWildcardImageSizes(t *testing.T) {
+ config := &ModelCapabilityConfig{
+ Version: 1,
+ Image: &ImageCapabilityConfig{
+ Size: ImageSizeConfig{Parameter: "size", Values: []string{"*"}, AllowCustom: true},
+ },
+ }
+
+ spec, err := CapabilitySpecFromModelCapabilityConfig(config, "image")
+ if err != nil {
+ t.Fatalf("CapabilitySpecFromModelCapabilityConfig() error = %v", err)
+ }
+ constraint, ok := spec.Options["size"]
+ if !ok {
+ t.Fatal("size constraint is missing")
+ }
+ values := make(map[string]int)
+ for _, value := range constraint.Values {
+ values[fmt.Sprint(value)]++
+ }
+ for _, value := range legacyImageSizeValues() {
+ if values[value] != 1 {
+ t.Fatalf("size constraint missing %q: %v", value, constraint.Values)
+ }
+ }
+ if values["*"] != 1 {
+ t.Fatalf("size constraint wildcard count = %d, values = %v", values["*"], constraint.Values)
+ }
+}
+
func TestNormalizeResolutionSupportsCommonAliases(t *testing.T) {
tests := map[string]string{
"1440": "1440p",
@@ -145,8 +175,8 @@ func TestCapabilitySpecFromModelCapabilityConfigProjectsImageSizeOnce(t *testing
if err != nil {
t.Fatalf("CapabilitySpecFromModelCapabilityConfig() error = %v", err)
}
- if got := spec.Options["size"].Values; len(got) != 1 || got[0] != "*" {
- t.Fatalf("size projection = %#v, want wildcard", got)
+ if got := spec.Options["size"].Values; len(got) != 3 || got[0] != "1:1" || got[1] != "16:9" || got[2] != "*" {
+ t.Fatalf("size projection = %#v, want configured values plus wildcard", got)
}
if got := spec.Inputs["image"].Max; got != 3 {
t.Fatalf("image input max = %d, want 3", got)
@@ -169,8 +199,8 @@ func TestCapabilitySpecFromModelCapabilityConfigProjectsCustomImageSizeAsWildcar
if err != nil {
t.Fatalf("CapabilitySpecFromModelCapabilityConfig() error = %v", err)
}
- if got := spec.Options["size"].Values; len(got) != 1 || got[0] != "*" {
- t.Fatalf("custom size projection = %#v, want wildcard", got)
+ if got := spec.Options["size"].Values; len(got) != 2 || got[0] != "1:1" || got[1] != "*" {
+ t.Fatalf("custom size projection = %#v, want configured value plus wildcard", got)
}
}
diff --git a/backend/internal/service/model_router.go b/backend/internal/service/model_router.go
index f4c71251a..c1f003ea3 100644
--- a/backend/internal/service/model_router.go
+++ b/backend/internal/service/model_router.go
@@ -241,7 +241,7 @@ func MatchCapability(spec CapabilitySpec, intent ModelRequestIntent) CapabilityM
reasons = append(reasons, "不支持参数 "+capabilityOptionLabel(name))
continue
}
- if !matchOptionConstraint(constraint, value) {
+ if !matchOptionConstraint(name, constraint, value) {
reasons = append(reasons, "参数 "+capabilityOptionLabel(name)+"超出支持范围")
}
}
@@ -294,13 +294,13 @@ func capabilityOptionLabel(name string) string {
}
}
-func matchOptionConstraint(constraint OptionConstraint, value any) bool {
+func matchOptionConstraint(name string, constraint OptionConstraint, value any) bool {
if len(constraint.Values) > 0 {
for _, candidate := range constraint.Values {
if normalizedScalar(candidate) == "*" {
return true
}
- if normalizedScalar(candidate) == normalizedScalar(value) {
+ if capabilityOptionValuesEqual(name, candidate, value) {
return true
}
}
@@ -323,6 +323,16 @@ func matchOptionConstraint(constraint OptionConstraint, value any) bool {
return true
}
+func capabilityOptionValuesEqual(name string, candidate any, value any) bool {
+ left := normalizedScalar(candidate)
+ right := normalizedScalar(value)
+ if canonicalCapabilityOptionName(name) == "vquality" {
+ left = strings.TrimSuffix(left, "p")
+ right = strings.TrimSuffix(right, "p")
+ }
+ return left == right
+}
+
func normalizedScalar(value any) string {
switch typed := value.(type) {
case string:
@@ -519,11 +529,6 @@ func (s *Service) loadRouteCatalog() (*routeCatalogSnapshot, error) {
log.Printf("logical model omitted from route catalog id=%s: invalid product capability: %v", item.ID, decodeErr)
continue
}
- defaults, defaultsErr := decodeLogicalDefaults(graph.Revision.DefaultOptionsJSON, productSpec)
- if defaultsErr != nil {
- log.Printf("logical model omitted from route catalog id=%s: invalid defaults: %v", item.ID, defaultsErr)
- continue
- }
variantByID := make(map[string]model.PhysicalCapabilityVariant, len(graph.Variants))
for _, variant := range graph.Variants {
variantByID[variant.ID] = variant
@@ -532,7 +537,7 @@ func (s *Service) loadRouteCatalog() (*routeCatalogSnapshot, error) {
for _, channelModel := range graph.ChannelModels {
channelModelByID[channelModel.ID] = channelModel
}
- cached := cachedLogicalModel{Model: item, Revision: *graph.Revision, ProductSpec: productSpec, Defaults: defaults}
+ cached := cachedLogicalModel{Model: item, Revision: *graph.Revision, ProductSpec: productSpec, Defaults: map[string]any{}}
for _, route := range graph.Routes {
variant, ok := variantByID[route.PhysicalVariantID]
if !ok || !variant.Enabled {
@@ -552,6 +557,18 @@ func (s *Service) loadRouteCatalog() (*routeCatalogSnapshot, error) {
}
cached.Routes = append(cached.Routes, cachedLogicalRoute{Route: route, Variant: variant, VariantSpec: variantSpec, ChannelModel: channelModel})
}
+ routeSpecs := make([]CapabilitySpec, 0, len(cached.Routes))
+ for _, route := range cached.Routes {
+ routeSpecs = append(routeSpecs, route.VariantSpec)
+ }
+ productSpec = capabilitySpecWithRoutePresets(productSpec, routeSpecs)
+ defaults, defaultsErr := decodeLogicalDefaults(graph.Revision.DefaultOptionsJSON, productSpec)
+ if defaultsErr != nil {
+ log.Printf("logical model omitted from route catalog id=%s: invalid defaults: %v", item.ID, defaultsErr)
+ continue
+ }
+ cached.ProductSpec = productSpec
+ cached.Defaults = defaults
snapshot.Models[item.ID] = cached
snapshot.Ordered = append(snapshot.Ordered, item.ID)
}
diff --git a/web/src/components/image-settings-panel.tsx b/web/src/components/image-settings-panel.tsx
index e6126d9eb..58aee479d 100644
--- a/web/src/components/image-settings-panel.tsx
+++ b/web/src/components/image-settings-panel.tsx
@@ -59,11 +59,14 @@ export function ImageSettingsPanel({ config, onConfigChange, theme, showTitle =
const count = Math.max(1, Math.min(effectiveMaxCount, Number(normalized.count)));
const activeSize = normalized.size;
const pixelSizeValues = profile.size.values.filter((value) => value.trim().toLowerCase() !== "auto");
- const usesResolutionPicker = supportsImageResolutionPresets(profile.size);
- const resolutionOptions = usesResolutionPicker ? buildImageResolutionOptions(pixelSizeValues) : [];
+ const hasResolutionPresets = supportsImageResolutionPresets(profile.size);
+ const resolutionOptions = hasResolutionPresets ? buildImageResolutionOptions(pixelSizeValues) : [];
const activeResolution = activeSize === "auto" ? undefined : imageResolutionOption(resolutionOptions, activeSize);
const activeRatio = activeResolution?.ratio || imageRatioForSize(activeSize);
- const resolutionChoices = usesResolutionPicker ? imageResolutionChoices(profile.size.values) : [];
+ const resolutionChoices = hasResolutionPresets ? imageResolutionChoices(profile.size.values) : [];
+ // 只有一个分辨率层级时,分辨率切换器没有实际选择意义;更重要的是不能因此把比例列表裁剪成当前层级的 3 个像素尺寸。
+ // 例如历史 `*` 配置恢复为标准值后,虽然包含 1024x1024/1536x1024/1024x1536,实际仍应展示完整的比例和尺寸选项。
+ const usesResolutionPicker = resolutionChoices.length > 1;
const availableAspects: AspectOption[] = usesResolutionPicker && activeSize === "auto"
? []
: usesResolutionPicker && activeResolution
@@ -158,7 +161,7 @@ export function ImageSettingsPanel({ config, onConfigChange, theme, showTitle =
: null}
{availableAspects.length ?
-
宽高比
+
尺寸或比例
{availableAspects.map((item) => (