fix(platform): 模型能力 - 修复前台默认参数与后台空状态
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user