@pippit-dev/cli 1.0.45 → 1.0.47
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 +7 -1
- package/checksums.txt +6 -6
- package/cmd/generate_video_test.go +27 -17
- package/cmd/media_upload_test.go +132 -0
- package/dist/checksums.txt +6 -6
- package/dist/xyq-canvas-command-runtime.cjs +13 -13
- package/dist/xyq-canvas-command-runtime.cjs.sha256 +1 -1
- package/internal/common/upload_media.go +69 -0
- package/internal/common/upload_media_test.go +192 -0
- package/internal/generate_image/generate_image.go +4 -18
- package/internal/generate_video/generate_video.go +6 -26
- package/internal/space/session.go +27 -5
- package/package.json +2 -2
- package/skills/xyq-nest-skill/SKILL.md +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
8e14b9a2c496491c2ee44da3ed950ac8301b67981dfaba3ab1e042cf8b937298 xyq-canvas-command-runtime.cjs
|
|
2
2
|
3b97217c41dfb3c298185c80b1e70c2b469f552450e53d00fc1dcf7098740ffb xyq-canvas-command-runtime.cjs.LEGAL.txt
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"fmt"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"sync"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
const mediaUploadWorkers = 10
|
|
12
|
+
|
|
13
|
+
// UploadMediaGroup keeps the original order and type-local numbering of references.
|
|
14
|
+
type UploadMediaGroup struct {
|
|
15
|
+
Label string
|
|
16
|
+
Paths []string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// UploadMediaGroups waits for every upload, preserving group and path order even
|
|
20
|
+
// when requests finish out of order. Any failure prevents generation callers
|
|
21
|
+
// from receiving a partial set of asset IDs.
|
|
22
|
+
func UploadMediaGroups(ctx context.Context, runner *Runner, groups ...UploadMediaGroup) ([][]string, error) {
|
|
23
|
+
type task struct {
|
|
24
|
+
group int
|
|
25
|
+
index int
|
|
26
|
+
slot int
|
|
27
|
+
}
|
|
28
|
+
assetIDs := make([][]string, len(groups))
|
|
29
|
+
var tasks []task
|
|
30
|
+
for g, group := range groups {
|
|
31
|
+
assetIDs[g] = make([]string, len(group.Paths))
|
|
32
|
+
for i := range group.Paths {
|
|
33
|
+
tasks = append(tasks, task{group: g, index: i, slot: len(tasks)})
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
errs := make([]error, len(tasks))
|
|
37
|
+
taskCh := make(chan task)
|
|
38
|
+
var wg sync.WaitGroup
|
|
39
|
+
for worker := 0; worker < min(mediaUploadWorkers, len(tasks)); worker++ {
|
|
40
|
+
wg.Add(1)
|
|
41
|
+
go func() {
|
|
42
|
+
defer wg.Done()
|
|
43
|
+
for task := range taskCh {
|
|
44
|
+
group := groups[task.group]
|
|
45
|
+
path := group.Paths[task.index]
|
|
46
|
+
expanded, err := ExpandPath(path)
|
|
47
|
+
if err != nil {
|
|
48
|
+
errs[task.slot] = fmt.Errorf("第%d%s %q 路径解析失败: %w", task.index+1, group.Label, filepath.Base(path), err)
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
result, err := UploadFile(ctx, UploadFileOptions{Path: expanded}, runner)
|
|
52
|
+
if err != nil {
|
|
53
|
+
errs[task.slot] = fmt.Errorf("第%d%s %q 上传失败: %w", task.index+1, group.Label, filepath.Base(path), err)
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
assetIDs[task.group][task.index] = result.AssetID
|
|
57
|
+
}
|
|
58
|
+
}()
|
|
59
|
+
}
|
|
60
|
+
for _, task := range tasks {
|
|
61
|
+
taskCh <- task
|
|
62
|
+
}
|
|
63
|
+
close(taskCh)
|
|
64
|
+
wg.Wait()
|
|
65
|
+
if err := errors.Join(errs...); err != nil {
|
|
66
|
+
return nil, fmt.Errorf("参考素材上传失败,未提交生成:\n%w", err)
|
|
67
|
+
}
|
|
68
|
+
return assetIDs, nil
|
|
69
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
package common
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"context"
|
|
6
|
+
"fmt"
|
|
7
|
+
"net/http"
|
|
8
|
+
"net/http/httptest"
|
|
9
|
+
"os"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"reflect"
|
|
12
|
+
"strings"
|
|
13
|
+
"sync/atomic"
|
|
14
|
+
"testing"
|
|
15
|
+
"time"
|
|
16
|
+
|
|
17
|
+
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
18
|
+
"github.com/Pippit-dev/pippit-cli/internal/space"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
type mediaUploadClient struct {
|
|
22
|
+
Client
|
|
23
|
+
upload func(context.Context, MultipartFile, any) error
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func (c mediaUploadClient) SendMultipartRequest(ctx context.Context, _ string, _ map[string]string, file MultipartFile, out any) error {
|
|
27
|
+
return c.upload(ctx, file, out)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func TestUploadMediaGroupsConcurrentAndOrdered(t *testing.T) {
|
|
31
|
+
const wantConcurrency = 10
|
|
32
|
+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
33
|
+
defer cancel()
|
|
34
|
+
groups := []UploadMediaGroup{{Label: "张参考图片"}, {Label: "个参考视频"}, {Label: "段参考音频"}}
|
|
35
|
+
want := [][]string{
|
|
36
|
+
{"a.jpg", "b.jpg", "c.jpg", "d.jpg"},
|
|
37
|
+
{"a.mp4", "b.mp4", "c.mp4", "d.mp4"},
|
|
38
|
+
{"a.wav", "b.wav", "c.wav", "d.wav"},
|
|
39
|
+
}
|
|
40
|
+
release := make(map[string]chan struct{})
|
|
41
|
+
dir := t.TempDir()
|
|
42
|
+
for g, names := range want {
|
|
43
|
+
for _, name := range names {
|
|
44
|
+
path := filepath.Join(dir, name)
|
|
45
|
+
if err := os.WriteFile(path, []byte("media"), 0600); err != nil {
|
|
46
|
+
t.Fatal(err)
|
|
47
|
+
}
|
|
48
|
+
groups[g].Paths = append(groups[g].Paths, path)
|
|
49
|
+
release[name] = make(chan struct{})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
started := make(chan string, len(release))
|
|
53
|
+
var active, peak atomic.Int32
|
|
54
|
+
client := mediaUploadClient{upload: func(ctx context.Context, file MultipartFile, out any) error {
|
|
55
|
+
n := active.Add(1)
|
|
56
|
+
defer active.Add(-1)
|
|
57
|
+
for old := peak.Load(); n > old; old = peak.Load() {
|
|
58
|
+
if peak.CompareAndSwap(old, n) {
|
|
59
|
+
break
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
started <- file.FileName
|
|
63
|
+
select {
|
|
64
|
+
case <-release[file.FileName]:
|
|
65
|
+
case <-ctx.Done():
|
|
66
|
+
return ctx.Err()
|
|
67
|
+
}
|
|
68
|
+
response := out.(*uploadFileResponse)
|
|
69
|
+
response.Ret = "0"
|
|
70
|
+
response.Data.PippitAssetID = file.FileName
|
|
71
|
+
return nil
|
|
72
|
+
}}
|
|
73
|
+
type outcome struct {
|
|
74
|
+
ids [][]string
|
|
75
|
+
err error
|
|
76
|
+
}
|
|
77
|
+
done := make(chan outcome, 1)
|
|
78
|
+
go func() {
|
|
79
|
+
ids, err := UploadMediaGroups(ctx, &Runner{Client: client}, groups...)
|
|
80
|
+
done <- outcome{ids, err}
|
|
81
|
+
}()
|
|
82
|
+
var first []string
|
|
83
|
+
for i := 0; i < wantConcurrency; i++ {
|
|
84
|
+
select {
|
|
85
|
+
case name := <-started:
|
|
86
|
+
first = append(first, name)
|
|
87
|
+
case <-ctx.Done():
|
|
88
|
+
t.Fatal("uploads did not start concurrently")
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Release the first requests in reverse order; IDs must still match inputs.
|
|
92
|
+
for i := len(first) - 1; i >= 0; i-- {
|
|
93
|
+
close(release[first[i]])
|
|
94
|
+
}
|
|
95
|
+
for i := len(first); i < len(release); i++ {
|
|
96
|
+
select {
|
|
97
|
+
case name := <-started:
|
|
98
|
+
close(release[name])
|
|
99
|
+
case <-ctx.Done():
|
|
100
|
+
t.Fatal("remaining uploads did not start")
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
select {
|
|
104
|
+
case got := <-done:
|
|
105
|
+
if got.err != nil || !reflect.DeepEqual(got.ids, want) {
|
|
106
|
+
t.Fatalf("UploadMediaGroups() = %v, %v; want %v", got.ids, got.err, want)
|
|
107
|
+
}
|
|
108
|
+
case <-ctx.Done():
|
|
109
|
+
t.Fatal("uploads did not finish")
|
|
110
|
+
}
|
|
111
|
+
if got := peak.Load(); got != wantConcurrency {
|
|
112
|
+
t.Fatalf("peak uploads = %d, want %d", got, wantConcurrency)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func TestUploadMediaGroupsPreservesSpace(t *testing.T) {
|
|
117
|
+
for _, teamID := range []string{"0", "12"} {
|
|
118
|
+
t.Run(teamID, func(t *testing.T) {
|
|
119
|
+
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
120
|
+
wantHeader := teamID
|
|
121
|
+
if teamID == "0" {
|
|
122
|
+
wantHeader = ""
|
|
123
|
+
}
|
|
124
|
+
if r.Header.Get("X-Team-Id") != wantHeader || r.Header.Get("Authorization") != "Bearer test-key" {
|
|
125
|
+
t.Error("upload lost selected space or authentication")
|
|
126
|
+
}
|
|
127
|
+
if r.URL.Path != config.UploadFilePath {
|
|
128
|
+
t.Errorf("unexpected path %s", r.URL.Path)
|
|
129
|
+
}
|
|
130
|
+
file, header, err := r.FormFile("file")
|
|
131
|
+
if err != nil {
|
|
132
|
+
t.Errorf("FormFile(): %v", err)
|
|
133
|
+
http.Error(w, "invalid upload", http.StatusBadRequest)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
file.Close()
|
|
137
|
+
defer r.MultipartForm.RemoveAll()
|
|
138
|
+
fmt.Fprintf(w, `{"ret":"0","data":{"pippit_asset_id":%q}}`, header.Filename)
|
|
139
|
+
}))
|
|
140
|
+
defer api.Close()
|
|
141
|
+
store := &space.Store{Dir: t.TempDir(), Account: "account", Origin: api.URL}
|
|
142
|
+
var stderr bytes.Buffer
|
|
143
|
+
session := &space.Session{Store: store, Explicit: &teamID, Stderr: &stderr}
|
|
144
|
+
ctx := WithSpace(context.Background(), session)
|
|
145
|
+
var paths []string
|
|
146
|
+
dir := t.TempDir()
|
|
147
|
+
for i := 0; i < 7; i++ {
|
|
148
|
+
path := filepath.Join(dir, fmt.Sprintf("image-%d.jpg", i))
|
|
149
|
+
if err := os.WriteFile(path, []byte("image"), 0600); err != nil {
|
|
150
|
+
t.Fatal(err)
|
|
151
|
+
}
|
|
152
|
+
paths = append(paths, path)
|
|
153
|
+
}
|
|
154
|
+
runner := &Runner{Client: NewHTTPClient(api.URL, 5*time.Second, NewAccessKeyAuthorizer("test-key"))}
|
|
155
|
+
ids, err := UploadMediaGroups(ctx, runner, UploadMediaGroup{Label: "张参考图片", Paths: paths})
|
|
156
|
+
if err != nil {
|
|
157
|
+
t.Fatal(err)
|
|
158
|
+
}
|
|
159
|
+
if strings.Count(stderr.String(), "本次空间:") != 1 || session.TeamID() != teamID {
|
|
160
|
+
t.Fatalf("space selected more than once or changed: %s", stderr.String())
|
|
161
|
+
}
|
|
162
|
+
binding, err := space.DecodeBinding(session.Token())
|
|
163
|
+
if err != nil || binding.TeamID != teamID {
|
|
164
|
+
t.Fatalf("space token changed: %+v, %v", binding, err)
|
|
165
|
+
}
|
|
166
|
+
for _, id := range ids[0] {
|
|
167
|
+
ref := space.References{AssetIDs: []string{id}}
|
|
168
|
+
selected, err := (&space.Session{Store: store, Explicit: &teamID}).Resolve(ref)
|
|
169
|
+
if err != nil || selected.TeamID != teamID {
|
|
170
|
+
t.Fatalf("asset %s lost its space: %+v, %v", id, selected, err)
|
|
171
|
+
}
|
|
172
|
+
other := "34"
|
|
173
|
+
if _, err := (&space.Session{Store: store, Explicit: &other}).Resolve(ref); err == nil {
|
|
174
|
+
t.Fatalf("asset %s accepted in another team", id)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func TestUploadMediaGroupsCanceledBeforeUpload(t *testing.T) {
|
|
182
|
+
ctx, cancel := context.WithCancel(context.Background())
|
|
183
|
+
cancel()
|
|
184
|
+
client := mediaUploadClient{upload: func(context.Context, MultipartFile, any) error {
|
|
185
|
+
t.Error("canceled batch sent an upload request")
|
|
186
|
+
return nil
|
|
187
|
+
}}
|
|
188
|
+
ids, err := UploadMediaGroups(ctx, &Runner{Client: client}, UploadMediaGroup{Label: "张参考图片", Paths: []string{"a.jpg", "b.jpg"}})
|
|
189
|
+
if ids != nil || err == nil || strings.Count(err.Error(), "上传文件已取消") != 2 {
|
|
190
|
+
t.Fatalf("canceled batch = %v, %v", ids, err)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -58,12 +58,14 @@ func Run(ctx context.Context, opts *Options, runner *common.Runner) (*common.Sub
|
|
|
58
58
|
return nil, err
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
assetIDs, err := common.UploadMediaGroups(ctx, runner,
|
|
62
|
+
common.UploadMediaGroup{Label: "张参考图片", Paths: opts.ImagePaths},
|
|
63
|
+
)
|
|
62
64
|
if err != nil {
|
|
63
65
|
return nil, err
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
body := buildSubmitRunBody(opts, modelKey,
|
|
68
|
+
body := buildSubmitRunBody(opts, modelKey, assetIDs[0])
|
|
67
69
|
|
|
68
70
|
var resp common.SubmitRunResponse
|
|
69
71
|
if err := runner.Client.SendRequest(ctx, common.SubmitRunPath(runner), body, &resp); err != nil {
|
|
@@ -126,22 +128,6 @@ func SupportedRatioUsage() string {
|
|
|
126
128
|
return ratioUsage
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
func uploadImageList(ctx context.Context, paths []string, runner *common.Runner) ([]string, error) {
|
|
130
|
-
assetIDs := make([]string, 0, len(paths))
|
|
131
|
-
for i, path := range paths {
|
|
132
|
-
expanded, err := common.ExpandPath(path)
|
|
133
|
-
if err != nil {
|
|
134
|
-
return nil, fmt.Errorf("第%d张参考图片 %q 路径解析失败: %w", i+1, filepath.Base(path), err)
|
|
135
|
-
}
|
|
136
|
-
result, err := common.UploadFile(ctx, common.UploadFileOptions{Path: expanded}, runner)
|
|
137
|
-
if err != nil {
|
|
138
|
-
return nil, fmt.Errorf("第%d张参考图片 %q 上传失败: %w", i+1, filepath.Base(path), err)
|
|
139
|
-
}
|
|
140
|
-
assetIDs = append(assetIDs, result.AssetID)
|
|
141
|
-
}
|
|
142
|
-
return assetIDs, nil
|
|
143
|
-
}
|
|
144
|
-
|
|
145
131
|
func buildSubmitRunBody(opts *Options, modelKey string, imageAssetIDs []string) map[string]any {
|
|
146
132
|
ratio, _ := parseRatio(opts.Ratio)
|
|
147
133
|
body := map[string]any{
|
|
@@ -45,20 +45,16 @@ func Run(ctx context.Context, opts *Options, runner *common.Runner) (*Result, er
|
|
|
45
45
|
return nil, err
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if err != nil {
|
|
54
|
-
return nil, err
|
|
55
|
-
}
|
|
56
|
-
audioAssetIDs, err := uploadMediaList(ctx, "段参考音频", opts.AudioPaths, runner)
|
|
48
|
+
assetIDs, err := common.UploadMediaGroups(ctx, runner,
|
|
49
|
+
common.UploadMediaGroup{Label: "张参考图片", Paths: opts.ImagePaths},
|
|
50
|
+
common.UploadMediaGroup{Label: "个参考视频", Paths: opts.VideoPaths},
|
|
51
|
+
common.UploadMediaGroup{Label: "段参考音频", Paths: opts.AudioPaths},
|
|
52
|
+
)
|
|
57
53
|
if err != nil {
|
|
58
54
|
return nil, err
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
body := buildSubmitRunBody(opts,
|
|
57
|
+
body := buildSubmitRunBody(opts, assetIDs[0], assetIDs[1], assetIDs[2])
|
|
62
58
|
// Business attribution belongs to this command, regardless of the selected model.
|
|
63
59
|
return common.SubmitRunWithBabiParam(ctx, "generate-video", body, runner, map[string]string{
|
|
64
60
|
"scene_lv1": "ai_agent",
|
|
@@ -99,22 +95,6 @@ func validateMediaExtensions(kind string, paths []string, allowed map[string]str
|
|
|
99
95
|
return nil
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
func uploadMediaList(ctx context.Context, mediaLabel string, paths []string, runner *common.Runner) ([]string, error) {
|
|
103
|
-
assetIDs := make([]string, 0, len(paths))
|
|
104
|
-
for i, path := range paths {
|
|
105
|
-
expanded, err := common.ExpandPath(path)
|
|
106
|
-
if err != nil {
|
|
107
|
-
return nil, fmt.Errorf("第%d%s %q 路径解析失败: %w", i+1, mediaLabel, filepath.Base(path), err)
|
|
108
|
-
}
|
|
109
|
-
result, err := common.UploadFile(ctx, common.UploadFileOptions{Path: expanded}, runner)
|
|
110
|
-
if err != nil {
|
|
111
|
-
return nil, fmt.Errorf("第%d%s %q 上传失败: %w", i+1, mediaLabel, filepath.Base(path), err)
|
|
112
|
-
}
|
|
113
|
-
assetIDs = append(assetIDs, result.AssetID)
|
|
114
|
-
}
|
|
115
|
-
return assetIDs, nil
|
|
116
|
-
}
|
|
117
|
-
|
|
118
98
|
func buildSubmitRunBody(opts *Options, imageAssetIDs []string, videoAssetIDs []string, audioAssetIDs []string) map[string]any {
|
|
119
99
|
prompt := strings.TrimSpace(opts.Prompt)
|
|
120
100
|
param := common.VideoPartToolParam{
|
|
@@ -6,17 +6,21 @@ import (
|
|
|
6
6
|
"encoding/json"
|
|
7
7
|
"fmt"
|
|
8
8
|
"io"
|
|
9
|
+
"sync"
|
|
9
10
|
)
|
|
10
11
|
|
|
11
12
|
// Session is one command's immutable account/origin/team choice. It is not an
|
|
12
13
|
// authorization grant: every request still uses the current credential.
|
|
13
14
|
// Store is optional when a command explicitly selects its space.
|
|
15
|
+
// Concurrent requests share one selection; local records and diagnostics are
|
|
16
|
+
// serialized without holding the lock during network uploads.
|
|
14
17
|
type Session struct {
|
|
15
18
|
Store *Store
|
|
16
19
|
Explicit *string
|
|
17
20
|
Expected *Binding
|
|
18
21
|
Identity func(context.Context) (string, error)
|
|
19
22
|
Stderr io.Writer
|
|
23
|
+
mu sync.Mutex
|
|
20
24
|
selected *Selection
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -49,6 +53,9 @@ func (s *Session) CheckIdentity(ctx context.Context) error {
|
|
|
49
53
|
// Resolve selects and freezes the command's space, checking each request's
|
|
50
54
|
// records even after freezing. CheckIdentity belongs at the request boundary.
|
|
51
55
|
func (s *Session) Resolve(ref References) (Selection, error) {
|
|
56
|
+
s.mu.Lock()
|
|
57
|
+
defer s.mu.Unlock()
|
|
58
|
+
|
|
52
59
|
explicit := s.Explicit
|
|
53
60
|
fixed := s.selected
|
|
54
61
|
if s.Expected != nil {
|
|
@@ -129,25 +136,40 @@ func (s *Session) Resolve(ref References) (Selection, error) {
|
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
func (s *Session) TeamID() string {
|
|
132
|
-
if s == nil
|
|
139
|
+
if s == nil {
|
|
140
|
+
return "0"
|
|
141
|
+
}
|
|
142
|
+
s.mu.Lock()
|
|
143
|
+
defer s.mu.Unlock()
|
|
144
|
+
if s.selected == nil {
|
|
133
145
|
return "0"
|
|
134
146
|
}
|
|
135
147
|
return s.selected.TeamID
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
func (s *Session) Token() string {
|
|
139
|
-
if s == nil
|
|
151
|
+
if s == nil {
|
|
140
152
|
return ""
|
|
141
153
|
}
|
|
142
|
-
|
|
154
|
+
s.mu.Lock()
|
|
155
|
+
defer s.mu.Unlock()
|
|
156
|
+
if s.selected == nil || s.Store == nil {
|
|
157
|
+
return ""
|
|
158
|
+
}
|
|
159
|
+
data, _ := json.Marshal(Binding{Selection: Selection{TeamID: s.selected.TeamID}, Account: s.Store.Account, Origin: s.Store.Origin})
|
|
143
160
|
return base64.RawURLEncoding.EncodeToString(data)
|
|
144
161
|
}
|
|
145
162
|
|
|
146
163
|
func (s *Session) Remember(ref References) {
|
|
147
|
-
if s == nil
|
|
164
|
+
if s == nil {
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
s.mu.Lock()
|
|
168
|
+
defer s.mu.Unlock()
|
|
169
|
+
if s.selected == nil || s.Store == nil || s.Store.Account == "" {
|
|
148
170
|
return
|
|
149
171
|
}
|
|
150
172
|
if err := s.Store.Remember(ref, *s.selected); err != nil && s.Stderr != nil {
|
|
151
|
-
fmt.Fprintf(s.Stderr, "请求已成功,但空间记录保存失败:%v;保留 thread_id=%s run_id=%s asset_ids=%v,后续使用 --team-id %s 查询,不要重复提交。\n", err, ref.ThreadID, ref.RunID, ref.AssetIDs, s.TeamID
|
|
173
|
+
fmt.Fprintf(s.Stderr, "请求已成功,但空间记录保存失败:%v;保留 thread_id=%s run_id=%s asset_ids=%v,后续使用 --team-id %s 查询,不要重复提交。\n", err, ref.ThreadID, ref.RunID, ref.AssetIDs, s.selected.TeamID)
|
|
152
174
|
}
|
|
153
175
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pippit-dev/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.47",
|
|
4
4
|
"description": "Pippit CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pippit-tool-cli": "scripts/run.js"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"test:marketing-skill": "node scripts/marketing-skill.test.js",
|
|
11
11
|
"prepack": "node scripts/prepare-canvas-runtime.js",
|
|
12
12
|
"prepare:canvas-runtime": "node scripts/prepare-canvas-runtime.js",
|
|
13
|
-
"test": "node scripts/version-check.test.js && node scripts/skills.test.js && node scripts/marketing-skill.test.js && node scripts/install-wizard.test.js && node scripts/install-cli.test.js && node scripts/run-help.test.js && node scripts/canvas-command.test.js && node scripts/canvas-command-schema.test.js && node scripts/canvas-command-guides.test.js && node scripts/prepare-canvas-runtime.test.js && node scripts/canvas-command-runtime.test.js && node scripts/canvas-media-identity.test.js && npm run test:canvas-operations && go test ./... && go vet ./...",
|
|
13
|
+
"test": "node scripts/version-check.test.js && node scripts/skills.test.js && node scripts/marketing-skill.test.js && node scripts/install-wizard.test.js && node scripts/install-cli.test.js && node scripts/run-help.test.js && node scripts/canvas-command.test.js && node scripts/canvas-command-schema.test.js && node scripts/canvas-command-guides.test.js && node scripts/prepare-canvas-runtime.test.js && node scripts/canvas-command-runtime.test.js && node scripts/canvas-media-identity.test.js && node scripts/canvas-video-ratio.test.js && npm run test:canvas-operations && go test ./... && go vet ./...",
|
|
14
14
|
"verify:canvas-runtime": "node scripts/prepare-canvas-runtime.js --check-only",
|
|
15
15
|
"test:canvas-runtime": "node scripts/canvas-command-runtime.test.js",
|
|
16
16
|
"test:canvas-operations": "node scripts/canvas-command-canvas-operations.test.js && node scripts/canvas-command-scene3d-operations.test.js && node scripts/canvas-command-timeline-operations.test.js"
|
|
@@ -59,7 +59,7 @@ metadata:
|
|
|
59
59
|
- 提问优先使用宿主实际提供且当前模式允许的工具:Codex 的 `request_user_input` 或 `request_user_input_async`,WorkBuddy 的 `ask_user_question`;不可用时用普通聊天。需要答案时等待答复。
|
|
60
60
|
- 素材参数接收本地文件路径,CLI 内部上传。远程链接不能冒充本地路径;缺少可访问文件时先解决素材获取。单文件必须小于 500 MB(500000000 字节)。
|
|
61
61
|
- `generate-image` / `generate-video` 上传素材失败时,向用户说明 CLI 返回的素材类型、类型内序号、文件名、原始原因和可用的 LogID,并说明尚未提交生成任务。序号按同类型参数的传入顺序从 1 开始,不是所有附件的混合序号;只有服务端明确返回审核拒绝时才说明审核未通过,不能把网络、权限等失败都归为审核问题。
|
|
62
|
-
-
|
|
62
|
+
- `generate-image` / `generate-video` 将所有参考素材按最多 10 个并发上传,等待本批上传结束后统一返回全部失败项;某个素材失败不会跳过其他素材。错误按图片、视频、音频分组及各自输入顺序列出,生成引用也保留输入顺序,不按上传完成先后排序。任一素材失败均不提交生成;向用户一次性列全失败素材,不只转述第一项。网络失败、取消或超时不代表审核通过。根据失败原因等待用户替换对应素材或解决问题,不自动删减素材、重传或重新提交生成。旧 CLI 如首错即停或未返回文件定位,须如实说明其结果范围,不凭图片内容猜测未检查素材的审核结论。
|
|
63
63
|
- 提交成功后立即展示真实 `web_thread_link`;未返回链接时如实说明,保留任务 ID。后续查询和下载失败不能触发重复生成。
|
|
64
64
|
- 每个最终图片/视频都通过宿主文件交付或媒体渲染能力展示为真实附件或可预览媒体。URL、路径列表仅作补充;详细完成标准见共用交付流程。
|
|
65
65
|
|