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) => ( +
{sourceError ? : null} @@ -237,10 +256,37 @@ export function CapabilityScopeEditor({ capability, sourceSpecs, value, onChange const selected = current.inputs?.[definition.name]; return (
-
{definition.label}
线路支持 {limit.min}-{limit.max} {definition.unit}
-
updateInput(current, definition.name, enabled ? limit : undefined, update)} />
-
updateInput(current, definition.name, { min: next ?? limit.min, max: selected?.max ?? limit.max }, update)} />
-
updateInput(current, definition.name, { min: selected?.min ?? limit.min, max: next ?? limit.max }, update)} />
+
+
{definition.label}
+
+ 线路支持 {limit.min}-{limit.max} {definition.unit} +
+
+
+ updateInput(current, definition.name, enabled ? limit : undefined, update)} /> +
+
+ updateInput(current, definition.name, { min: next ?? limit.min, max: selected?.max ?? limit.max }, update)} + /> +
+
+ updateInput(current, definition.name, { min: selected?.min ?? limit.min, max: next ?? limit.max }, update)} + /> +
); })} @@ -252,7 +298,13 @@ export function CapabilityScopeEditor({ capability, sourceSpecs, value, onChange
{sourceOptions.map((definition) => ( - updateOption(current, definition.name, constraint, update)} /> + updateOption(current, definition.name, constraint, update)} + /> ))}
@@ -273,13 +325,23 @@ export function DefaultOptionsEditor({ spec, value, onChange }: { spec?: Capabil const selected = value?.[definition.name] as Scalar | undefined; return (
-
{definition.label}
留空时由创作端或线路默认值决定
-
{ - const defaults = { ...(value || {}) } as Record; - if (next === undefined) delete defaults[definition.name]; - else defaults[definition.name] = next; - onChange?.(defaults); - }} />
+
+
{definition.label}
+
留空时由创作端或线路默认值决定
+
+
+ { + const defaults = { ...(value || {}) } as Record; + if (next === undefined) delete defaults[definition.name]; + else defaults[definition.name] = next; + onChange?.(defaults); + }} + /> +
); })} @@ -287,7 +349,13 @@ export function DefaultOptionsEditor({ spec, value, onChange }: { spec?: Capabil ); } -export function CapabilityRequestEditor({ spec, inputs, options, onInputsChange, onOptionsChange }: { +export function CapabilityRequestEditor({ + spec, + inputs, + options, + onInputsChange, + onOptionsChange, +}: { spec: CapabilitySpec; inputs: Record; options: Record; @@ -325,7 +393,10 @@ export function CapabilityRequestEditor({ spec, inputs, options, onInputsChange, const constraint = spec.options![definition.name]; return (