@pippit-dev/cli 1.0.31 → 1.0.33
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 +20 -9
- package/checksums.txt +6 -6
- package/cmd/generate_image/generate_image.go +4 -2
- package/cmd/generate_image_test.go +150 -1
- package/cmd/marketing.go +144 -0
- package/cmd/marketing_test.go +179 -0
- package/cmd/model.go +7 -10
- package/cmd/model_test.go +59 -1
- package/cmd/root.go +1 -0
- package/cmd/short_drama_test.go +2 -0
- package/cmd/source_test.go +6 -1
- package/dist/checksums.txt +6 -6
- package/internal/common/client.go +11 -1
- 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/xyq-marketing-skill/SKILL.md +8 -6
- package/skills/xyq-marketing-skill/examples/product-video.md +1 -1
- package/skills/xyq-marketing-skill/references/api.md +2 -2
- package/skills/xyq-marketing-skill/scripts/marketing.js +76 -38
- package/skills/xyq-nest-skill/SKILL.md +2 -3
- 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
|
@@ -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
|
+
}
|
|
@@ -7,6 +7,7 @@ import (
|
|
|
7
7
|
"fmt"
|
|
8
8
|
"os"
|
|
9
9
|
"path/filepath"
|
|
10
|
+
"sort"
|
|
10
11
|
"strings"
|
|
11
12
|
"time"
|
|
12
13
|
|
|
@@ -16,12 +17,14 @@ import (
|
|
|
16
17
|
)
|
|
17
18
|
|
|
18
19
|
const (
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
VideoScene = "web_turbo_video_generator"
|
|
21
|
+
ImageScene = "web_image_agent"
|
|
22
|
+
CacheTTL = 5 * time.Minute
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
// Catalog preserves the server's model configuration, including unknown fields
|
|
24
|
-
// and integer enums.
|
|
26
|
+
// and integer enums. Image names are resolved here before submission; admission
|
|
27
|
+
// and parameter validation remain the server's responsibility.
|
|
25
28
|
type Catalog struct {
|
|
26
29
|
Scene string `json:"scene"`
|
|
27
30
|
ConfigKey string `json:"config_key"`
|
|
@@ -31,7 +34,7 @@ type Catalog struct {
|
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
type Summary struct {
|
|
34
|
-
Key string `json:"key"`
|
|
37
|
+
Key string `json:"key,omitempty"`
|
|
35
38
|
Name string `json:"name"`
|
|
36
39
|
Kind string `json:"kind"`
|
|
37
40
|
}
|
|
@@ -62,7 +65,22 @@ func NewService(runner *common.Runner) *Service {
|
|
|
62
65
|
return &Service{runner: runner, cacheDir: dir, now: time.Now}
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
func (
|
|
68
|
+
func sceneForType(modelType string) (string, error) {
|
|
69
|
+
switch modelType {
|
|
70
|
+
case "video":
|
|
71
|
+
return VideoScene, nil
|
|
72
|
+
case "image":
|
|
73
|
+
return ImageScene, nil
|
|
74
|
+
default:
|
|
75
|
+
return "", fmt.Errorf("不支持的模型类型 %q;--type 可选 video 或 image", modelType)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func (s *Service) Get(ctx context.Context, modelType string, refresh bool) (*Result, error) {
|
|
80
|
+
scene, err := sceneForType(modelType)
|
|
81
|
+
if err != nil {
|
|
82
|
+
return nil, err
|
|
83
|
+
}
|
|
66
84
|
if err := ctx.Err(); err != nil {
|
|
67
85
|
return nil, err
|
|
68
86
|
}
|
|
@@ -79,9 +97,9 @@ func (s *Service) Get(ctx context.Context, refresh bool) (*Result, error) {
|
|
|
79
97
|
if s.runner.Config.Paths != nil && s.runner.Config.Paths.GetAvailableModelList != "" {
|
|
80
98
|
path = s.runner.Config.Paths.GetAvailableModelList
|
|
81
99
|
}
|
|
82
|
-
cachePath := s.cachePath(accessKey, path)
|
|
100
|
+
cachePath := s.cachePath(accessKey, path, scene)
|
|
83
101
|
if !refresh && cachePath != "" {
|
|
84
|
-
if entry := s.readCache(cachePath); entry != nil {
|
|
102
|
+
if entry := s.readCache(cachePath, scene); entry != nil {
|
|
85
103
|
return &Result{Catalog: entry.Catalog, Cached: true, FetchedAt: entry.FetchedAt}, nil
|
|
86
104
|
}
|
|
87
105
|
}
|
|
@@ -100,14 +118,14 @@ func (s *Service) Get(ctx context.Context, refresh bool) (*Result, error) {
|
|
|
100
118
|
err = s.runner.Client.SendRequest(queryCtx, path, struct {
|
|
101
119
|
Scene string `json:"scene"`
|
|
102
120
|
SourceModelKey string `json:"source_model_key"`
|
|
103
|
-
}{Scene:
|
|
121
|
+
}{Scene: scene}, &resp)
|
|
104
122
|
if err != nil {
|
|
105
123
|
return nil, fmt.Errorf("模型查询失败,请稍后重试(可加 --refresh): %w", err)
|
|
106
124
|
}
|
|
107
125
|
if resp.Ret != "0" {
|
|
108
126
|
return nil, common.NewLogIDError(fmt.Sprintf("模型查询失败,请稍后重试(可加 --refresh): ret=%s errmsg=%s", resp.Ret, resp.Errmsg), resp.LogID)
|
|
109
127
|
}
|
|
110
|
-
if err := validateCatalog(resp.Data); err != nil {
|
|
128
|
+
if err := validateCatalog(resp.Data, scene); err != nil {
|
|
111
129
|
return nil, common.NewLogIDError("模型配置无效,请稍后重试(可加 --refresh): "+err.Error(), resp.LogID)
|
|
112
130
|
}
|
|
113
131
|
currentKey, err := s.runner.Auth.ResolveAccessKey(ctx)
|
|
@@ -121,24 +139,24 @@ func (s *Service) Get(ctx context.Context, refresh bool) (*Result, error) {
|
|
|
121
139
|
return result, nil
|
|
122
140
|
}
|
|
123
141
|
|
|
124
|
-
func (s *Service) cachePath(accessKey, path string) string {
|
|
142
|
+
func (s *Service) cachePath(accessKey, path, scene string) string {
|
|
125
143
|
if s.cacheDir == "" {
|
|
126
144
|
return ""
|
|
127
145
|
}
|
|
128
146
|
scope, _ := json.Marshal([]string{
|
|
129
|
-
"v1", strings.TrimRight(s.runner.Config.BaseURL, "/"), path,
|
|
147
|
+
"v1", strings.TrimRight(s.runner.Config.BaseURL, "/"), path, scene,
|
|
130
148
|
version.Current(), accessKey,
|
|
131
149
|
})
|
|
132
150
|
return filepath.Join(s.cacheDir, fmt.Sprintf("%x.json", sha256.Sum256(scope)))
|
|
133
151
|
}
|
|
134
152
|
|
|
135
|
-
func (s *Service) readCache(path string) *cacheEntry {
|
|
153
|
+
func (s *Service) readCache(path, scene string) *cacheEntry {
|
|
136
154
|
raw, err := os.ReadFile(path)
|
|
137
155
|
if err != nil {
|
|
138
156
|
return nil
|
|
139
157
|
}
|
|
140
158
|
var entry cacheEntry
|
|
141
|
-
if json.Unmarshal(raw, &entry) != nil || validateCatalog(entry.Catalog) != nil {
|
|
159
|
+
if json.Unmarshal(raw, &entry) != nil || validateCatalog(entry.Catalog, scene) != nil {
|
|
142
160
|
return nil
|
|
143
161
|
}
|
|
144
162
|
age := s.now().Sub(entry.FetchedAt)
|
|
@@ -172,20 +190,37 @@ func (s *Service) writeCache(path string, result *Result) error {
|
|
|
172
190
|
return os.Rename(f.Name(), path)
|
|
173
191
|
}
|
|
174
192
|
|
|
175
|
-
func validateCatalog(catalog *Catalog) error {
|
|
176
|
-
|
|
193
|
+
func validateCatalog(catalog *Catalog, scene string) error {
|
|
194
|
+
kind := "video"
|
|
195
|
+
if scene == ImageScene {
|
|
196
|
+
kind = "image"
|
|
197
|
+
} else if scene != VideoScene {
|
|
198
|
+
return fmt.Errorf("不支持的模型场景 %q", scene)
|
|
199
|
+
}
|
|
200
|
+
if catalog == nil || catalog.Config == nil || catalog.Scene != scene || catalog.ConfigKey == "" {
|
|
177
201
|
return fmt.Errorf("缺少配置或场景不匹配")
|
|
178
202
|
}
|
|
179
203
|
seen := make(map[string]bool)
|
|
204
|
+
seenNames := make(map[string]bool)
|
|
180
205
|
for _, raw := range catalog.Config.Models {
|
|
181
206
|
var model Summary
|
|
182
|
-
if json.Unmarshal(raw, &model) != nil || strings.TrimSpace(model.Key) == "" || model.Kind !=
|
|
207
|
+
if json.Unmarshal(raw, &model) != nil || strings.TrimSpace(model.Key) == "" || model.Kind != kind {
|
|
183
208
|
return fmt.Errorf("模型条目缺少有效 key 或 kind")
|
|
184
209
|
}
|
|
185
210
|
if seen[model.Key] {
|
|
211
|
+
if kind == "image" {
|
|
212
|
+
return fmt.Errorf("图片模型配置包含重复条目")
|
|
213
|
+
}
|
|
186
214
|
return fmt.Errorf("模型 key 重复: %s", model.Key)
|
|
187
215
|
}
|
|
188
216
|
seen[model.Key] = true
|
|
217
|
+
if kind == "image" {
|
|
218
|
+
name := strings.TrimSpace(model.Name)
|
|
219
|
+
if name == "" || strings.EqualFold(name, strings.TrimSpace(model.Key)) || seenNames[name] {
|
|
220
|
+
return fmt.Errorf("图片模型展示名称缺失、与模型标识相同或重复,请刷新模型列表")
|
|
221
|
+
}
|
|
222
|
+
seenNames[name] = true
|
|
223
|
+
}
|
|
189
224
|
}
|
|
190
225
|
return nil
|
|
191
226
|
}
|
|
@@ -196,7 +231,10 @@ func (c *Catalog) Search(query string) []Summary {
|
|
|
196
231
|
for _, raw := range c.Config.Models {
|
|
197
232
|
var model Summary
|
|
198
233
|
_ = json.Unmarshal(raw, &model) // validated at the network/cache boundary
|
|
199
|
-
if
|
|
234
|
+
if c.Scene == ImageScene {
|
|
235
|
+
model.Key = "" // Wire identifiers stay in the raw catalog only.
|
|
236
|
+
model.Name = strings.TrimSpace(model.Name)
|
|
237
|
+
} else if model.Key == query {
|
|
200
238
|
return []Summary{model}
|
|
201
239
|
}
|
|
202
240
|
if query == "" || strings.Contains(strings.ToLower(model.Name), strings.ToLower(query)) || strings.Contains(strings.ToLower(model.Key), strings.ToLower(query)) {
|
|
@@ -206,14 +244,78 @@ func (c *Catalog) Search(query string) []Summary {
|
|
|
206
244
|
return matches
|
|
207
245
|
}
|
|
208
246
|
|
|
209
|
-
func (c *Catalog)
|
|
210
|
-
|
|
247
|
+
func (c *Catalog) findModel(selector string) (json.RawMessage, error) {
|
|
248
|
+
selector = strings.TrimSpace(selector)
|
|
249
|
+
var found json.RawMessage
|
|
211
250
|
for _, raw := range c.Config.Models {
|
|
212
251
|
var model Summary
|
|
213
252
|
_ = json.Unmarshal(raw, &model)
|
|
214
|
-
|
|
215
|
-
|
|
253
|
+
value := model.Key
|
|
254
|
+
if c.Scene == ImageScene {
|
|
255
|
+
value = strings.TrimSpace(model.Name)
|
|
216
256
|
}
|
|
257
|
+
if value != "" && value == selector {
|
|
258
|
+
if found != nil {
|
|
259
|
+
return nil, fmt.Errorf("模型名称重复,请执行 model list --type image --refresh 刷新后重试")
|
|
260
|
+
}
|
|
261
|
+
found = raw
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if found != nil {
|
|
265
|
+
return found, nil
|
|
266
|
+
}
|
|
267
|
+
if c.Scene == ImageScene {
|
|
268
|
+
return nil, fmt.Errorf("未找到该图片模型名称;请执行 model list --type image --refresh,并使用列表中的完整名称")
|
|
269
|
+
}
|
|
270
|
+
return nil, fmt.Errorf("未找到可用模型 %q;请执行 model list --type video --refresh 查看当前模型", selector)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
func (c *Catalog) Describe(selector string) (json.RawMessage, error) {
|
|
274
|
+
raw, err := c.findModel(selector)
|
|
275
|
+
if err != nil {
|
|
276
|
+
return nil, err
|
|
277
|
+
}
|
|
278
|
+
return describeModel(raw)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ImageModelKey translates the user's exact display name into a wire value.
|
|
282
|
+
// Never guess keys from names or send an unresolved name to the server.
|
|
283
|
+
func (c *Catalog) ImageModelKey(name string) (string, error) {
|
|
284
|
+
if c.Scene != ImageScene {
|
|
285
|
+
return "", fmt.Errorf("图片生成需要图片模型配置")
|
|
286
|
+
}
|
|
287
|
+
raw, err := c.findModel(name)
|
|
288
|
+
if err != nil {
|
|
289
|
+
return "", err
|
|
290
|
+
}
|
|
291
|
+
var model Summary
|
|
292
|
+
_ = json.Unmarshal(raw, &model)
|
|
293
|
+
return model.Key, nil
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ImageDisplayMessage keeps known wire identifiers out of server error messages.
|
|
297
|
+
func (c *Catalog) ImageDisplayMessage(message string) string {
|
|
298
|
+
var models []Summary
|
|
299
|
+
for _, raw := range c.Config.Models {
|
|
300
|
+
var model struct {
|
|
301
|
+
Summary
|
|
302
|
+
ReportName string `json:"report_name"`
|
|
303
|
+
}
|
|
304
|
+
_ = json.Unmarshal(raw, &model)
|
|
305
|
+
name := strings.TrimSpace(model.Name)
|
|
306
|
+
if model.Kind == "image" && name != "" {
|
|
307
|
+
for _, value := range []string{model.Key, model.ReportName} {
|
|
308
|
+
if value != "" && value != name {
|
|
309
|
+
models = append(models, Summary{Key: value, Name: name})
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// A key can be a prefix of another key. Replace the longer one first.
|
|
315
|
+
sort.Slice(models, func(i, j int) bool { return len(models[i].Key) > len(models[j].Key) })
|
|
316
|
+
pairs := make([]string, 0, len(models)*2)
|
|
317
|
+
for _, model := range models {
|
|
318
|
+
pairs = append(pairs, model.Key, strings.TrimSpace(model.Name))
|
|
217
319
|
}
|
|
218
|
-
return
|
|
320
|
+
return strings.NewReplacer(pairs...).Replace(message)
|
|
219
321
|
}
|