@pippit-dev/cli 1.0.32 → 1.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -8
- package/checksums.txt +6 -6
- package/cmd/generate_image/generate_image.go +4 -2
- package/cmd/generate_image_test.go +150 -1
- package/cmd/model.go +7 -10
- package/cmd/model_test.go +59 -1
- package/cmd/short_drama_test.go +2 -0
- package/cmd/source_test.go +6 -1
- package/dist/checksums.txt +6 -6
- package/internal/common/ratio.go +12 -0
- package/internal/generate_image/generate_image.go +19 -7
- package/internal/generate_image/generate_image_test.go +57 -14
- package/internal/models/describe.go +79 -19
- package/internal/models/describe_test.go +131 -5
- package/internal/models/models.go +124 -22
- package/internal/models/models_test.go +158 -16
- package/package.json +1 -1
- package/skills/short-drama/agents/openai.yaml +3 -0
- package/skills/xyq-marketing-skill/agents/openai.yaml +3 -0
- package/skills/xyq-nest-skill/SKILL.md +2 -3
- package/skills/xyq-nest-skill/agents/openai.yaml +3 -0
- package/skills/xyq-nest-skill/commands/generate-image.md +12 -7
- package/skills/xyq-nest-skill/commands/model.md +49 -7
- package/skills/xyq-nest-skill/examples/generate-and-deliver.md +4 -2
- package/skills/xyq-nest-skill/examples/image-edit.md +5 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package generate_image
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"encoding/json"
|
|
4
5
|
"strings"
|
|
5
6
|
"testing"
|
|
6
7
|
)
|
|
@@ -19,10 +20,10 @@ func TestValidateOptionsRequiresModel(t *testing.T) {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
func
|
|
23
|
+
func TestValidateOptionsAllowsDynamicModelName(t *testing.T) {
|
|
23
24
|
opts := &Options{
|
|
24
25
|
Prompt: "x",
|
|
25
|
-
Model: "
|
|
26
|
+
Model: "图片测试模型",
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
if err := ValidateOptions(opts); err != nil {
|
|
@@ -33,7 +34,7 @@ func TestValidateOptionsAllowsServerDecidedModel(t *testing.T) {
|
|
|
33
34
|
func TestValidateOptionsAllowsServerDecidedRatio(t *testing.T) {
|
|
34
35
|
opts := &Options{
|
|
35
36
|
Prompt: "x",
|
|
36
|
-
Model: "
|
|
37
|
+
Model: "图片测试模型",
|
|
37
38
|
Ratio: "99",
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -45,7 +46,7 @@ func TestValidateOptionsAllowsServerDecidedRatio(t *testing.T) {
|
|
|
45
46
|
func TestValidateOptionsAllowsServerDecidedResolution(t *testing.T) {
|
|
46
47
|
opts := &Options{
|
|
47
48
|
Prompt: "x",
|
|
48
|
-
Model: "
|
|
49
|
+
Model: "图片测试模型",
|
|
49
50
|
Resolution: "8K",
|
|
50
51
|
}
|
|
51
52
|
|
|
@@ -58,7 +59,7 @@ func TestValidateOptionsRejectsNegativeGenerateImageCount(t *testing.T) {
|
|
|
58
59
|
count := -1
|
|
59
60
|
opts := &Options{
|
|
60
61
|
Prompt: "x",
|
|
61
|
-
Model: "
|
|
62
|
+
Model: "图片测试模型",
|
|
62
63
|
GenerateImageCount: &count,
|
|
63
64
|
}
|
|
64
65
|
|
|
@@ -83,6 +84,13 @@ func TestParseRatioSupportsVisibleEnumValues(t *testing.T) {
|
|
|
83
84
|
{ratio: "4", want: 4},
|
|
84
85
|
{ratio: "5", want: 5},
|
|
85
86
|
{ratio: "6", want: 6},
|
|
87
|
+
{ratio: "7", want: 7},
|
|
88
|
+
{ratio: "8", want: 8},
|
|
89
|
+
{ratio: "9", want: 9},
|
|
90
|
+
{ratio: "10", want: 10},
|
|
91
|
+
{ratio: "11", want: 11},
|
|
92
|
+
{ratio: "12", want: 12},
|
|
93
|
+
{ratio: "99", want: 99},
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
for _, tt := range cases {
|
|
@@ -98,20 +106,55 @@ func TestParseRatioSupportsVisibleEnumValues(t *testing.T) {
|
|
|
98
106
|
}
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
func
|
|
102
|
-
_,
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
func TestImageOptionalParametersAndEffortPassThrough(t *testing.T) {
|
|
110
|
+
for _, effort := range []string{"", " HIGH ", "future-effort"} {
|
|
111
|
+
opts := &Options{Prompt: "image", Model: "未来图片模型", Effort: effort}
|
|
112
|
+
if err := ValidateOptions(opts); err != nil {
|
|
113
|
+
t.Fatal(err)
|
|
114
|
+
}
|
|
115
|
+
raw, err := json.Marshal(buildSubmitRunBody(opts, "future-image-model", nil))
|
|
116
|
+
if err != nil {
|
|
117
|
+
t.Fatal(err)
|
|
118
|
+
}
|
|
119
|
+
var body struct {
|
|
120
|
+
Settings map[string]json.RawMessage `json:"general_agent_settings"`
|
|
121
|
+
}
|
|
122
|
+
if err := json.Unmarshal(raw, &body); err != nil {
|
|
123
|
+
t.Fatal(err)
|
|
124
|
+
}
|
|
125
|
+
for _, key := range []string{"ratio", "resolution", "generate_image_count"} {
|
|
126
|
+
if _, exists := body.Settings[key]; exists {
|
|
127
|
+
t.Fatalf("unspecified %s was filled: %s", key, raw)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if effort == "" {
|
|
131
|
+
if _, exists := body.Settings["image_effort"]; exists {
|
|
132
|
+
t.Fatalf("unspecified effort was filled: %s", raw)
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
var got string
|
|
136
|
+
if err := json.Unmarshal(body.Settings["image_effort"], &got); err != nil || got != strings.ToLower(strings.TrimSpace(effort)) {
|
|
137
|
+
t.Fatalf("effort not forwarded: %s, %v", raw, err)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
105
140
|
}
|
|
106
|
-
|
|
107
|
-
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func TestParseRatioRejectsNonInteger(t *testing.T) {
|
|
144
|
+
for _, ratio := range []string{"9:16", "adaptive", "17:11", "3.0"} {
|
|
145
|
+
t.Run(ratio, func(t *testing.T) {
|
|
146
|
+
err := ValidateOptions(&Options{Prompt: "image", Model: "未来图片模型", Ratio: ratio})
|
|
147
|
+
if err == nil || !strings.Contains(err.Error(), "必须是整数枚举值") {
|
|
148
|
+
t.Fatalf("ratio %q: error = %v, want integer enum validation", ratio, err)
|
|
149
|
+
}
|
|
150
|
+
})
|
|
108
151
|
}
|
|
109
152
|
}
|
|
110
153
|
|
|
111
154
|
func TestValidateOptionsRejectsUnsupportedImageExtension(t *testing.T) {
|
|
112
155
|
opts := &Options{
|
|
113
156
|
Prompt: "x",
|
|
114
|
-
Model: "
|
|
157
|
+
Model: "图片测试模型",
|
|
115
158
|
ImagePaths: []string{"ref.tiff"},
|
|
116
159
|
}
|
|
117
160
|
|
|
@@ -128,13 +171,13 @@ func TestBuildSubmitRunBodyWithGeneralAgentSettings(t *testing.T) {
|
|
|
128
171
|
count := 2
|
|
129
172
|
opts := &Options{
|
|
130
173
|
Prompt: " 生成小猫海报 ",
|
|
131
|
-
Model: "
|
|
174
|
+
Model: " Seedream 5.0 Pro ",
|
|
132
175
|
Ratio: "6",
|
|
133
176
|
Resolution: " 4k ",
|
|
134
177
|
GenerateImageCount: &count,
|
|
135
178
|
}
|
|
136
179
|
|
|
137
|
-
body := buildSubmitRunBody(opts, []string{"asset_1"})
|
|
180
|
+
body := buildSubmitRunBody(opts, "seedream_5.0_pro", []string{"asset_1"})
|
|
138
181
|
if body["agent_name"] != agentNameNest {
|
|
139
182
|
t.Fatalf("agent_name = %v, want nest agent", body["agent_name"])
|
|
140
183
|
}
|
|
@@ -6,17 +6,15 @@ import (
|
|
|
6
6
|
"slices"
|
|
7
7
|
"strconv"
|
|
8
8
|
"strings"
|
|
9
|
-
)
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
var ratioValues = map[int64]string{
|
|
14
|
-
0: "adaptive", 1: "", 2: "16:9", 3: "9:16", 4: "4:3", 5: "3:4", 6: "1:1",
|
|
15
|
-
7: "2:1", 8: "2.35:1", 9: "1.85:1", 10: "1.125:2.436", 11: "3:2", 12: "2:3", 13: "21:9",
|
|
16
|
-
}
|
|
10
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
11
|
+
)
|
|
17
12
|
|
|
18
13
|
type dimensionConfig struct {
|
|
19
14
|
Key string `json:"key"`
|
|
15
|
+
Label string `json:"label"`
|
|
16
|
+
Description string `json:"description"`
|
|
17
|
+
Required *bool `json:"required_field"`
|
|
20
18
|
DefaultValue *string `json:"default_value"`
|
|
21
19
|
OptionList []struct {
|
|
22
20
|
Value string `json:"value"`
|
|
@@ -36,6 +34,8 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
|
|
|
36
34
|
var fields map[string]json.RawMessage
|
|
37
35
|
var source struct {
|
|
38
36
|
Key string `json:"key"`
|
|
37
|
+
Name string `json:"name"`
|
|
38
|
+
Kind string `json:"kind"`
|
|
39
39
|
Ratios []int64 `json:"supported_ratio_list"`
|
|
40
40
|
Default *int64 `json:"default_ratio"`
|
|
41
41
|
Parameter struct {
|
|
@@ -57,18 +57,27 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
|
|
|
57
57
|
}
|
|
58
58
|
delete(out, "config_key")
|
|
59
59
|
delete(out, "is_default") // A server default does not authorize model selection.
|
|
60
|
+
if source.Kind == "image" {
|
|
61
|
+
// Billing maps and other Web-only metadata can contain wire model names.
|
|
62
|
+
// Keep them in the raw catalog, not in host-facing command output.
|
|
63
|
+
out = map[string]any{"name": strings.TrimSpace(source.Name), "kind": source.Kind}
|
|
64
|
+
for _, key := range []string{"description", "parameter_config", "creation_mode_config"} {
|
|
65
|
+
if value, exists := fields[key]; exists {
|
|
66
|
+
out[key] = value
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
60
70
|
warnings := []string{}
|
|
61
71
|
warn := func(message string) { warnings = append(warnings, message) }
|
|
62
|
-
ratios := make([]
|
|
72
|
+
ratios := make([]any, 0, len(source.Ratios))
|
|
63
73
|
for _, value := range source.Ratios {
|
|
64
|
-
|
|
65
|
-
if value := ratioValues[value]; value != "" && !slices.Contains(ratios, value) {
|
|
74
|
+
if value, ok := cliRatioValue(value, source.Kind); ok && !slices.Contains(ratios, value) {
|
|
66
75
|
ratios = append(ratios, value)
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
ratio := map[string]any{"options": ratios}
|
|
70
79
|
if source.Default != nil {
|
|
71
|
-
if value :=
|
|
80
|
+
if value, ok := cliRatioValue(*source.Default, source.Kind); ok {
|
|
72
81
|
if slices.Contains(ratios, value) {
|
|
73
82
|
ratio["default"] = value
|
|
74
83
|
} else {
|
|
@@ -84,17 +93,37 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
|
|
|
84
93
|
|
|
85
94
|
seen := make(map[string]bool)
|
|
86
95
|
for _, dimension := range source.Parameter.Dimensions {
|
|
87
|
-
if dimension == nil
|
|
96
|
+
if dimension == nil {
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
if source.Kind == "image" {
|
|
100
|
+
if dimension.Key != "resolution" && dimension.Key != "ratio" && dimension.Key != "effort" {
|
|
101
|
+
continue
|
|
102
|
+
}
|
|
103
|
+
} else if dimension.Key != "resolution" && dimension.Key != "duration" {
|
|
88
104
|
continue
|
|
89
105
|
}
|
|
90
106
|
if seen[dimension.Key] {
|
|
91
107
|
return nil, fmt.Errorf("模型配置包含重复 %s 维度,请 --refresh 重试", dimension.Key)
|
|
92
108
|
}
|
|
93
109
|
seen[dimension.Key] = true
|
|
94
|
-
out[dimension.Key] = describeDimension(dimension, warn)
|
|
110
|
+
out[dimension.Key] = describeDimension(dimension, source.Kind, warn)
|
|
111
|
+
}
|
|
112
|
+
if source.Kind == "image" {
|
|
113
|
+
if imageRatio, ok := out["ratio"].(map[string]any); ok {
|
|
114
|
+
if options, ok := imageRatio["options"].([]any); ok {
|
|
115
|
+
labels := make(map[string]string, len(options))
|
|
116
|
+
for _, option := range options {
|
|
117
|
+
value := option.(int64)
|
|
118
|
+
labels[strconv.FormatInt(value, 10)] = common.RatioValue(value)
|
|
119
|
+
}
|
|
120
|
+
imageRatio["option_labels"] = labels
|
|
121
|
+
}
|
|
122
|
+
}
|
|
95
123
|
}
|
|
96
|
-
//
|
|
97
|
-
|
|
124
|
+
// Image dimensions retain their complete source metadata and combination rules.
|
|
125
|
+
// Video descriptions keep the existing compact representation.
|
|
126
|
+
if parameterRaw, ok := fields["parameter_config"]; source.Kind != "image" && ok && string(parameterRaw) != "null" {
|
|
98
127
|
var parameter map[string]json.RawMessage
|
|
99
128
|
if err := json.Unmarshal(parameterRaw, ¶meter); err != nil {
|
|
100
129
|
return nil, err
|
|
@@ -146,7 +175,7 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
|
|
|
146
175
|
if len(limits) > 0 {
|
|
147
176
|
out["material_limits"] = limits
|
|
148
177
|
}
|
|
149
|
-
if source.Creation != nil {
|
|
178
|
+
if source.Creation != nil && source.Kind != "image" {
|
|
150
179
|
modes := make([]map[string]any, 0, len(source.Creation.Modes))
|
|
151
180
|
for _, rawMode := range source.Creation.Modes {
|
|
152
181
|
if string(rawMode) == "null" {
|
|
@@ -181,7 +210,7 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
|
|
|
181
210
|
case "", "inherit":
|
|
182
211
|
entry["ratio"] = ratio
|
|
183
212
|
if mode.Key == "text_to_video" && (source.Key == "MiniMax-H3" || source.Key == "MiniMax-H3-Max") {
|
|
184
|
-
fixed := make([]
|
|
213
|
+
fixed := make([]any, 0, len(ratios))
|
|
185
214
|
for _, value := range ratios {
|
|
186
215
|
if value != "adaptive" {
|
|
187
216
|
fixed = append(fixed, value)
|
|
@@ -210,7 +239,7 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
|
|
|
210
239
|
}
|
|
211
240
|
out["creation_modes"] = modes
|
|
212
241
|
}
|
|
213
|
-
if (source.Key == "MiniMax-H3" || source.Key == "MiniMax-H3-Max") && slices.Contains(ratios, "adaptive") {
|
|
242
|
+
if source.Kind != "image" && (source.Key == "MiniMax-H3" || source.Key == "MiniMax-H3-Max") && slices.Contains(ratios, "adaptive") {
|
|
214
243
|
out["notes"] = []string{"MiniMax 使用 adaptive 需要参考图片或视频;纯文生视频请选择固定比例。创作模式的比例策略优先于模型级选项。"}
|
|
215
244
|
}
|
|
216
245
|
if len(warnings) > 0 {
|
|
@@ -219,8 +248,29 @@ func describeModel(raw json.RawMessage) (json.RawMessage, error) {
|
|
|
219
248
|
return json.Marshal(out)
|
|
220
249
|
}
|
|
221
250
|
|
|
222
|
-
|
|
251
|
+
// Image generation accepts wire enums; video generation accepts ratio strings.
|
|
252
|
+
func cliRatioValue(value int64, kind string) (any, bool) {
|
|
253
|
+
label := common.RatioValue(value)
|
|
254
|
+
if label == "" {
|
|
255
|
+
return nil, false
|
|
256
|
+
}
|
|
257
|
+
if kind == "image" {
|
|
258
|
+
return value, true
|
|
259
|
+
}
|
|
260
|
+
return label, true
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
func describeDimension(d *dimensionConfig, kind string, warn func(string)) map[string]any {
|
|
223
264
|
out := map[string]any{}
|
|
265
|
+
if d.Label != "" {
|
|
266
|
+
out["label"] = d.Label
|
|
267
|
+
}
|
|
268
|
+
if d.Description != "" {
|
|
269
|
+
out["description"] = d.Description
|
|
270
|
+
}
|
|
271
|
+
if d.Required != nil {
|
|
272
|
+
out["required_field"] = *d.Required
|
|
273
|
+
}
|
|
224
274
|
if len(d.ActiveWhenAny) > 0 {
|
|
225
275
|
out["active_when_any"] = d.ActiveWhenAny
|
|
226
276
|
}
|
|
@@ -230,6 +280,16 @@ func describeDimension(d *dimensionConfig, warn func(string)) map[string]any {
|
|
|
230
280
|
number, err := strconv.ParseInt(value, 10, 32)
|
|
231
281
|
return number, err == nil && number > 0
|
|
232
282
|
}
|
|
283
|
+
if d.Key == "ratio" {
|
|
284
|
+
number, err := strconv.ParseInt(value, 10, 64)
|
|
285
|
+
if err != nil {
|
|
286
|
+
return nil, false
|
|
287
|
+
}
|
|
288
|
+
return cliRatioValue(number, kind)
|
|
289
|
+
}
|
|
290
|
+
if kind == "image" && d.Key == "resolution" {
|
|
291
|
+
return strings.ToUpper(value), value != ""
|
|
292
|
+
}
|
|
233
293
|
return strings.ToLower(value), value != ""
|
|
234
294
|
}
|
|
235
295
|
if d.Key == "duration" {
|
|
@@ -21,12 +21,12 @@ func description(t *testing.T, raw string) map[string]json.RawMessage {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
|
|
24
|
-
raw := `{"key":"MiniMax-H3","is_default":true,"supported_ratio_list":[0,2,13,3,4,5,6],"default_ratio":3,"config_key":"internal",
|
|
24
|
+
raw := `{"key":"MiniMax-H3","kind":"video","is_default":true,"supported_ratio_list":[0,2,13,3,4,5,6],"default_ratio":3,"config_key":"internal",
|
|
25
25
|
"future_field":9007199254740993,"audio_total_limit":0,"max_image_size":31457280,"min_video_duration":2000,
|
|
26
26
|
"supported_duration_list":[{"value":999}],"default_duration_value":999,
|
|
27
27
|
"parameter_config":{"dimensions":[
|
|
28
|
-
{"key":"duration","default_value":"10","range_config":{"min_value":4,"max_value":15,"step":1}},
|
|
29
|
-
{"key":"resolution","default_value":" 768P ","option_list":[{"value":"768p"},{"value":"2k"},{"value":"4k","disabled":true}]},
|
|
28
|
+
{"key":"duration","label":"视频时长","description":"生成的视频时长","required_field":true,"default_value":"10","range_config":{"min_value":4,"max_value":15,"step":1}},
|
|
29
|
+
{"key":"resolution","label":"视频分辨率","description":"输出清晰度","required_field":false,"default_value":" 768P ","option_list":[{"value":"768p"},{"value":"2k"},{"value":"4k","disabled":true}]},
|
|
30
30
|
{"key":"seed","default_value":"random"}],"need_available_combinations":true}}
|
|
31
31
|
`
|
|
32
32
|
out := description(t, raw)
|
|
@@ -46,8 +46,10 @@ func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
|
|
|
46
46
|
t.Fatalf("ratio=%+v", ratio)
|
|
47
47
|
}
|
|
48
48
|
var resolution struct {
|
|
49
|
-
Options
|
|
50
|
-
Default
|
|
49
|
+
Options []string
|
|
50
|
+
Default string
|
|
51
|
+
Label, Description string
|
|
52
|
+
Required *bool `json:"required_field"`
|
|
51
53
|
}
|
|
52
54
|
if err := json.Unmarshal(out["resolution"], &resolution); err != nil {
|
|
53
55
|
t.Fatal(err)
|
|
@@ -55,9 +57,14 @@ func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
|
|
|
55
57
|
if !reflect.DeepEqual(resolution.Options, []string{"768p", "2k"}) || resolution.Default != "768p" {
|
|
56
58
|
t.Fatalf("resolution=%+v", resolution)
|
|
57
59
|
}
|
|
60
|
+
if resolution.Label != "视频分辨率" || resolution.Description != "输出清晰度" || resolution.Required == nil || *resolution.Required {
|
|
61
|
+
t.Fatalf("video resolution metadata lost: %+v", resolution)
|
|
62
|
+
}
|
|
58
63
|
var duration struct {
|
|
59
64
|
Min, Max, Step, Default int
|
|
60
65
|
Unit string
|
|
66
|
+
Label, Description string
|
|
67
|
+
Required *bool `json:"required_field"`
|
|
61
68
|
}
|
|
62
69
|
if err := json.Unmarshal(out["duration"], &duration); err != nil {
|
|
63
70
|
t.Fatal(err)
|
|
@@ -65,6 +72,9 @@ func TestDescriptionCLIParametersAndRawPreservation(t *testing.T) {
|
|
|
65
72
|
if duration.Min != 4 || duration.Max != 15 || duration.Step != 1 || duration.Default != 10 || duration.Unit != "seconds" {
|
|
66
73
|
t.Fatalf("duration=%+v", duration)
|
|
67
74
|
}
|
|
75
|
+
if duration.Label != "视频时长" || duration.Description != "生成的视频时长" || duration.Required == nil || !*duration.Required {
|
|
76
|
+
t.Fatalf("video duration metadata lost: %+v", duration)
|
|
77
|
+
}
|
|
68
78
|
if string(out["future_field"]) != "9007199254740993" {
|
|
69
79
|
t.Fatal("integer precision lost")
|
|
70
80
|
}
|
|
@@ -197,3 +207,119 @@ func TestDescriptionCreationModesAndCacheNotMutated(t *testing.T) {
|
|
|
197
207
|
t.Fatal("description mutated cached raw config")
|
|
198
208
|
}
|
|
199
209
|
}
|
|
210
|
+
|
|
211
|
+
func TestImageDescriptionParametersAndConstraints(t *testing.T) {
|
|
212
|
+
raw := `{"key":"future-image","name":"未来图片模型","report_name":"internal-report-model","kind":"image","is_default":true,
|
|
213
|
+
"supported_ratio_list":[0,2,6,13,1,999],"default_ratio":6,
|
|
214
|
+
"parameter_config":{"dimensions":[
|
|
215
|
+
{"key":"resolution","label":"图片分辨率","required_field":false,"default_value":"2k","option_list":[{"value":"2k","label":"高清"},{"value":"4K","disabled":true}]},
|
|
216
|
+
{"key":"effort","label":"推理强度","description":"计算档位","required_field":true,"default_value":"HIGH","active_when_any":[{"resolution":["2k"]}],"option_list":[{"value":"low"},{"value":"HIGH","label":"高","description":"更多计算"},{"value":"max","disabled":true}]},
|
|
217
|
+
{"key":"future-dimension","option_list":[{"value":"future-choice"}]}],
|
|
218
|
+
"need_available_combinations":true,"combination_dimension_keys":["resolution","effort"],
|
|
219
|
+
"default_combination":{"resolution":"2k","effort":"HIGH"},
|
|
220
|
+
"available_combinations":[{"option_values":{"resolution":"2k","effort":"HIGH"}}]},
|
|
221
|
+
"creation_mode_config":{"modes":[{"key":"reference_generation","enabled":true}]}}
|
|
222
|
+
`
|
|
223
|
+
out := description(t, raw)
|
|
224
|
+
if _, exists := out["key"]; exists || string(out["name"]) != `"未来图片模型"` {
|
|
225
|
+
t.Fatalf("image identity must contain only the display name: %s", out)
|
|
226
|
+
}
|
|
227
|
+
if _, exists := out["report_name"]; exists {
|
|
228
|
+
t.Fatal("internal reporting model name exposed")
|
|
229
|
+
}
|
|
230
|
+
var ratio struct {
|
|
231
|
+
Options []int64
|
|
232
|
+
Default int64
|
|
233
|
+
Labels map[string]string `json:"option_labels"`
|
|
234
|
+
}
|
|
235
|
+
var resolution, effort struct {
|
|
236
|
+
Options []string
|
|
237
|
+
Default string
|
|
238
|
+
Required *bool `json:"required_field"`
|
|
239
|
+
Active json.RawMessage `json:"active_when_any"`
|
|
240
|
+
Label string
|
|
241
|
+
Description string
|
|
242
|
+
}
|
|
243
|
+
for key, target := range map[string]any{"ratio": &ratio, "resolution": &resolution, "effort": &effort} {
|
|
244
|
+
if err := json.Unmarshal(out[key], target); err != nil {
|
|
245
|
+
t.Fatal(err)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if !reflect.DeepEqual(ratio.Options, []int64{0, 2, 6, 13}) || ratio.Default != 6 || !reflect.DeepEqual(ratio.Labels, map[string]string{"0": "adaptive", "2": "16:9", "6": "1:1", "13": "21:9"}) {
|
|
249
|
+
t.Fatalf("ratio=%+v", ratio)
|
|
250
|
+
}
|
|
251
|
+
if !reflect.DeepEqual(resolution.Options, []string{"2K"}) || resolution.Default != "2K" || resolution.Required == nil || *resolution.Required {
|
|
252
|
+
t.Fatalf("resolution=%+v", resolution)
|
|
253
|
+
}
|
|
254
|
+
if !reflect.DeepEqual(effort.Options, []string{"low", "high"}) || effort.Default != "high" || effort.Required == nil || !*effort.Required || effort.Label != "推理强度" || effort.Description != "计算档位" || string(effort.Active) != `[{"resolution":["2k"]}]` {
|
|
255
|
+
t.Fatalf("effort=%+v", effort)
|
|
256
|
+
}
|
|
257
|
+
var source map[string]json.RawMessage
|
|
258
|
+
if err := json.Unmarshal([]byte(raw), &source); err != nil {
|
|
259
|
+
t.Fatal(err)
|
|
260
|
+
}
|
|
261
|
+
var compact bytes.Buffer
|
|
262
|
+
if err := json.Compact(&compact, source["parameter_config"]); err != nil {
|
|
263
|
+
t.Fatal(err)
|
|
264
|
+
}
|
|
265
|
+
if !bytes.Equal(out["parameter_config"], compact.Bytes()) {
|
|
266
|
+
t.Fatalf("raw dimension metadata or combination rules changed: %s", out["parameter_config"])
|
|
267
|
+
}
|
|
268
|
+
if bytes.Contains(out["creation_mode_config"], []byte("generate_type")) || len(out["creation_modes"]) != 0 {
|
|
269
|
+
t.Fatal("image modes must not acquire video generation selectors")
|
|
270
|
+
}
|
|
271
|
+
if _, exists := out["is_default"]; exists {
|
|
272
|
+
t.Fatal("model default marker exposed")
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
func TestImageDescriptionOptionalEffortAndRatioDimension(t *testing.T) {
|
|
277
|
+
out := description(t, `{"kind":"image","supported_ratio_list":[2],"parameter_config":{"dimensions":[{"key":"ratio","default_value":"13","option_list":[{"value":"13"},{"value":"6"},{"value":"2","disabled":true},{"value":"999"},{"value":"1"}]}]}}`)
|
|
278
|
+
if len(out["effort"]) != 0 || len(out["resolution"]) != 0 {
|
|
279
|
+
t.Fatal("absent capabilities must not be invented")
|
|
280
|
+
}
|
|
281
|
+
var ratio struct {
|
|
282
|
+
Options []int64
|
|
283
|
+
Default int64
|
|
284
|
+
Labels map[string]string `json:"option_labels"`
|
|
285
|
+
}
|
|
286
|
+
if err := json.Unmarshal(out["ratio"], &ratio); err != nil {
|
|
287
|
+
t.Fatal(err)
|
|
288
|
+
}
|
|
289
|
+
if !reflect.DeepEqual(ratio.Options, []int64{13, 6}) || ratio.Default != 13 || !reflect.DeepEqual(ratio.Labels, map[string]string{"13": "21:9", "6": "1:1"}) {
|
|
290
|
+
t.Fatalf("ratio dimension did not override legacy list: %+v", ratio)
|
|
291
|
+
}
|
|
292
|
+
for _, raw := range []string{
|
|
293
|
+
`{"kind":"image","parameter_config":{"dimensions":[{"key":"effort","default_value":"high","option_list":[{"value":"low"},{"value":"high","disabled":true}]}]}}`,
|
|
294
|
+
`{"kind":"image","parameter_config":{"dimensions":[{"key":"effort","option_list":[]}]}}`,
|
|
295
|
+
} {
|
|
296
|
+
out := description(t, raw)
|
|
297
|
+
if len(out["warnings"]) == 0 || bytes.Contains(out["effort"], []byte(`"default"`)) {
|
|
298
|
+
t.Fatalf("invalid effort configuration accepted: %s", out["effort"])
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
_, err := describeModel(json.RawMessage(`{"kind":"image","parameter_config":{"dimensions":[{"key":"effort"},{"key":"effort"}]}}`))
|
|
302
|
+
if err == nil {
|
|
303
|
+
t.Fatal("duplicate effort dimension accepted")
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
func TestImageRatioZeroDefaultAndUnavailableValues(t *testing.T) {
|
|
308
|
+
for _, raw := range []string{
|
|
309
|
+
`{"kind":"image","supported_ratio_list":[0,0,3,1,999],"default_ratio":0}`,
|
|
310
|
+
`{"kind":"image","parameter_config":{"dimensions":[{"key":"ratio","default_value":"0","option_list":[{"value":"0"},{"value":"0"},{"value":"3"},{"value":"6","disabled":true},{"value":"1"},{"value":"999"}]}]}}`,
|
|
311
|
+
} {
|
|
312
|
+
out := description(t, raw)
|
|
313
|
+
var ratio struct {
|
|
314
|
+
Options []int64
|
|
315
|
+
Default *int64
|
|
316
|
+
Labels map[string]string `json:"option_labels"`
|
|
317
|
+
}
|
|
318
|
+
if err := json.Unmarshal(out["ratio"], &ratio); err != nil {
|
|
319
|
+
t.Fatal(err)
|
|
320
|
+
}
|
|
321
|
+
if !reflect.DeepEqual(ratio.Options, []int64{0, 3}) || ratio.Default == nil || *ratio.Default != 0 || !reflect.DeepEqual(ratio.Labels, map[string]string{"0": "adaptive", "3": "9:16"}) {
|
|
322
|
+
t.Fatalf("numeric zero default or available ratio values changed: %s", out["ratio"])
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|