@pippit-dev/cli 0.0.29 → 1.0.1
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 +9 -7
- package/checksums.txt +6 -6
- package/cmd/download_result.go +57 -0
- package/cmd/get_thread.go +58 -0
- package/cmd/json.go +17 -0
- package/cmd/list_thread_file.go +51 -0
- package/cmd/root.go +4 -0
- package/cmd/short_drama/short_drama.go +0 -164
- package/cmd/short_drama_test.go +76 -49
- package/cmd/upload_file.go +50 -0
- package/internal/common/download_results.go +1 -1
- package/internal/common/error_log_test.go +3 -3
- package/internal/common/get_thread.go +1 -1
- package/internal/common/list_thread_file.go +1 -1
- package/internal/common/upload_file.go +1 -1
- package/package.json +1 -1
- package/skills/short-drama/SKILL.md +41 -41
package/README.md
CHANGED
|
@@ -196,15 +196,17 @@ npx @pippit-dev/cli@latest install
|
|
|
196
196
|
export XYQ_ACCESS_KEY="<access-key>"
|
|
197
197
|
pippit-tool-cli --version
|
|
198
198
|
pippit-tool-cli short-drama +submit-run --message "写一个赛博朋克短剧开头"
|
|
199
|
-
pippit-tool-cli
|
|
200
|
-
pippit-tool-cli
|
|
201
|
-
pippit-tool-cli
|
|
199
|
+
pippit-tool-cli upload-file --path ./reference.doc
|
|
200
|
+
pippit-tool-cli get-thread --thread-id thread_123 --run-id run_456
|
|
201
|
+
pippit-tool-cli list-thread-file --thread-id thread_123 --page-num 1 --page-size 200
|
|
202
|
+
pippit-tool-cli download-result --output-path ./thread_123/results/result.mp4 --url URL --updated-at 1779716734
|
|
202
203
|
```
|
|
203
204
|
|
|
204
205
|
`+submit-run`: 输出 `thread_id`、`run_id` 和 `web_thread_link`;其中 `--message` 为必填参数。
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
`get-thread`: 请求中带 `version=v2`,并输出 `readable_text`。
|
|
207
|
+
`list-thread-file`: 输出会话文件列表、分页提示和可直接传给下载命令的 `file_path`。
|
|
208
|
+
`upload-file`: 输出返回的 `asset_id`。 当前仅支持 `.doc`、`.docx` 和 `.txt` 文件。
|
|
209
|
+
`download-result`: 会把结果 URL 下载到 `--output-path` 指定的文件路径;传入 `--updated-at` 后,如果本地文件早于该时间戳会覆盖更新,否则跳过。
|
|
208
210
|
|
|
209
211
|
短剧命令的错误日志会追加写入本地每日日志文件:`~/.pippit_tool_cli/logs/yyyy-mm-dd.log`。日志路径会基于当前用户主目录和系统路径分隔符生成,因此可在 macOS、Linux 和 Windows 上使用。
|
|
210
212
|
|
|
@@ -214,4 +216,4 @@ pippit-tool-cli short-drama +download-result --output-path ./thread_123/results/
|
|
|
214
216
|
|
|
215
217
|
## 鉴权
|
|
216
218
|
|
|
217
|
-
`short-drama +submit-run`、`
|
|
219
|
+
`short-drama +submit-run`、`get-thread`、`list-thread-file`、`upload-file` 以及 `xyq-skill` Python 脚本都使用 `Authorization: Bearer <XYQ_ACCESS_KEY>` 鉴权。OAuth 命令代码仍保留在仓库中,但短剧运行时请求不使用 OAuth。
|
package/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
95c7bf8bb745dc6e390a8e71ebf9c464769b29f819e3dd9d77d3539453d320b4 pippit-tool-cli-1.0.1-darwin-amd64.tar.gz
|
|
2
|
+
2163023d193b8886518d9b0481ae6306ddfde01de1f6fb61bbbd40a140b75b67 pippit-tool-cli-1.0.1-darwin-arm64.tar.gz
|
|
3
|
+
15962cc45394ee0b204efb88753929b0842c8ad8d74333fe84ba91fe3f3e1596 pippit-tool-cli-1.0.1-linux-amd64.tar.gz
|
|
4
|
+
dbad696c1c212c6d14ef3e9788597d1751386ab7685111832b3ec8b82964bd57 pippit-tool-cli-1.0.1-linux-arm64.tar.gz
|
|
5
|
+
8c298e5200d83d72ddb6c044d63183aa0104ef2b62fb1b0492d3652bfd103f8a pippit-tool-cli-1.0.1-windows-amd64.zip
|
|
6
|
+
eebeac5fccccbf63e4949efbf92ae2e3518e34b0758a1379cd0f4cc7328e5997 pippit-tool-cli-1.0.1-windows-arm64.zip
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"io"
|
|
6
|
+
"strconv"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
10
|
+
"github.com/spf13/cobra"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func newDownloadResultCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
14
|
+
var opts common.DownloadResultOptions
|
|
15
|
+
|
|
16
|
+
cmd := &cobra.Command{
|
|
17
|
+
Use: "download-result",
|
|
18
|
+
Short: "Download a generated result URL",
|
|
19
|
+
Args: cobra.NoArgs,
|
|
20
|
+
RunE: withErrorLog("download-result", func() map[string]string {
|
|
21
|
+
fields := map[string]string{
|
|
22
|
+
"output_path": opts.OutputPath,
|
|
23
|
+
"has_url": strconv.FormatBool(strings.TrimSpace(opts.URL) != ""),
|
|
24
|
+
"workers": strconv.Itoa(opts.Workers),
|
|
25
|
+
}
|
|
26
|
+
if opts.UpdatedAt > 0 {
|
|
27
|
+
fields["updated_at"] = strconv.FormatInt(opts.UpdatedAt, 10)
|
|
28
|
+
}
|
|
29
|
+
return fields
|
|
30
|
+
}, func(cmd *cobra.Command, _ []string) error {
|
|
31
|
+
opts.OutputPath = strings.TrimSpace(opts.OutputPath)
|
|
32
|
+
if opts.OutputPath == "" {
|
|
33
|
+
return fmt.Errorf("--output-path is required")
|
|
34
|
+
}
|
|
35
|
+
opts.URL = strings.TrimSpace(opts.URL)
|
|
36
|
+
if opts.URL == "" {
|
|
37
|
+
return fmt.Errorf("--url is required")
|
|
38
|
+
}
|
|
39
|
+
if opts.Workers <= 0 {
|
|
40
|
+
return fmt.Errorf("--workers must be greater than 0")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
result, err := common.DownloadResult(cmd.Context(), opts, runner)
|
|
44
|
+
if err != nil {
|
|
45
|
+
return err
|
|
46
|
+
}
|
|
47
|
+
return writeJSON(stdout, result)
|
|
48
|
+
}),
|
|
49
|
+
}
|
|
50
|
+
cmd.SetOut(stdout)
|
|
51
|
+
cmd.SetErr(stderr)
|
|
52
|
+
cmd.Flags().StringVar(&opts.URL, "url", "", "URL to download")
|
|
53
|
+
cmd.Flags().StringVar(&opts.OutputPath, "output-path", "", "local output file path")
|
|
54
|
+
cmd.Flags().Int64Var(&opts.UpdatedAt, "updated-at", 0, "remote file update time as a Unix timestamp")
|
|
55
|
+
cmd.Flags().IntVar(&opts.Workers, "workers", 5, "parallel download workers")
|
|
56
|
+
return cmd
|
|
57
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"io"
|
|
6
|
+
"strings"
|
|
7
|
+
|
|
8
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
9
|
+
"github.com/spf13/cobra"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func newGetThreadCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
13
|
+
var opts common.GetThreadOptions
|
|
14
|
+
|
|
15
|
+
cmd := &cobra.Command{
|
|
16
|
+
Use: "get-thread",
|
|
17
|
+
Short: "Get a thread detail",
|
|
18
|
+
Args: cobra.NoArgs,
|
|
19
|
+
RunE: withErrorLog("get-thread", func() map[string]string {
|
|
20
|
+
return map[string]string{
|
|
21
|
+
"thread_id": opts.ThreadID,
|
|
22
|
+
"run_id": opts.RunID,
|
|
23
|
+
}
|
|
24
|
+
}, func(cmd *cobra.Command, _ []string) error {
|
|
25
|
+
opts.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
26
|
+
if opts.ThreadID == "" {
|
|
27
|
+
return fmt.Errorf("--thread-id is required")
|
|
28
|
+
}
|
|
29
|
+
opts.RunID = strings.TrimSpace(opts.RunID)
|
|
30
|
+
|
|
31
|
+
result, err := common.GetThread(cmd.Context(), &opts, runner)
|
|
32
|
+
if err != nil {
|
|
33
|
+
return err
|
|
34
|
+
}
|
|
35
|
+
_, err = fmt.Fprintln(stdout, result.ReadableText)
|
|
36
|
+
return err
|
|
37
|
+
}),
|
|
38
|
+
}
|
|
39
|
+
cmd.SetOut(stdout)
|
|
40
|
+
cmd.SetErr(stderr)
|
|
41
|
+
cmd.Flags().StringVar(&opts.ThreadID, "thread-id", "", "thread ID to fetch")
|
|
42
|
+
cmd.Flags().StringVar(&opts.RunID, "run-id", "", "run ID to fetch")
|
|
43
|
+
return cmd
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func withErrorLog(command string, fields func() map[string]string, run func(*cobra.Command, []string) error) func(*cobra.Command, []string) error {
|
|
47
|
+
return func(cmd *cobra.Command, args []string) error {
|
|
48
|
+
err := run(cmd, args)
|
|
49
|
+
if err != nil {
|
|
50
|
+
logFields := map[string]string(nil)
|
|
51
|
+
if fields != nil {
|
|
52
|
+
logFields = fields()
|
|
53
|
+
}
|
|
54
|
+
_ = common.AppendDailyErrorLog(command, err, logFields)
|
|
55
|
+
}
|
|
56
|
+
return err
|
|
57
|
+
}
|
|
58
|
+
}
|
package/cmd/json.go
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"io"
|
|
6
|
+
|
|
7
|
+
"github.com/bytedance/sonic"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func writeJSON(w io.Writer, v any) error {
|
|
11
|
+
data, err := sonic.Marshal(v)
|
|
12
|
+
if err != nil {
|
|
13
|
+
return err
|
|
14
|
+
}
|
|
15
|
+
_, err = fmt.Fprintln(w, string(data))
|
|
16
|
+
return err
|
|
17
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"io"
|
|
6
|
+
"strconv"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
10
|
+
"github.com/spf13/cobra"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func newListThreadFileCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
14
|
+
var opts common.ListThreadFileOptions
|
|
15
|
+
|
|
16
|
+
cmd := &cobra.Command{
|
|
17
|
+
Use: "list-thread-file",
|
|
18
|
+
Short: "List files in a thread",
|
|
19
|
+
Args: cobra.NoArgs,
|
|
20
|
+
RunE: withErrorLog("list-thread-file", func() map[string]string {
|
|
21
|
+
return map[string]string{
|
|
22
|
+
"thread_id": opts.ThreadID,
|
|
23
|
+
"page_num": strconv.Itoa(opts.PageNum),
|
|
24
|
+
"page_size": strconv.Itoa(opts.PageSize),
|
|
25
|
+
}
|
|
26
|
+
}, func(cmd *cobra.Command, _ []string) error {
|
|
27
|
+
opts.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
28
|
+
if opts.ThreadID == "" {
|
|
29
|
+
return fmt.Errorf("--thread-id is required")
|
|
30
|
+
}
|
|
31
|
+
if opts.PageSize <= 0 || opts.PageSize > common.MaxListThreadFilePageSize {
|
|
32
|
+
return fmt.Errorf("--page-size must be between 1 and %d", common.MaxListThreadFilePageSize)
|
|
33
|
+
}
|
|
34
|
+
if opts.PageNum <= 0 {
|
|
35
|
+
return fmt.Errorf("--page-num must be greater than 0")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
result, err := common.ListThreadFile(cmd.Context(), &opts, runner)
|
|
39
|
+
if err != nil {
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
return writeJSON(stdout, result)
|
|
43
|
+
}),
|
|
44
|
+
}
|
|
45
|
+
cmd.SetOut(stdout)
|
|
46
|
+
cmd.SetErr(stderr)
|
|
47
|
+
cmd.Flags().StringVar(&opts.ThreadID, "thread-id", "", "thread ID to list files for")
|
|
48
|
+
cmd.Flags().IntVar(&opts.PageNum, "page-num", 1, "page number (1-based)")
|
|
49
|
+
cmd.Flags().IntVar(&opts.PageSize, "page-size", common.MaxListThreadFilePageSize, fmt.Sprintf("number of files per page (between 1 and %d)", common.MaxListThreadFilePageSize))
|
|
50
|
+
return cmd
|
|
51
|
+
}
|
package/cmd/root.go
CHANGED
|
@@ -39,6 +39,10 @@ func newRootCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Comm
|
|
|
39
39
|
root.SetOut(stdout)
|
|
40
40
|
root.SetErr(stderr)
|
|
41
41
|
// root.AddCommand(authcmd.NewCommand(stdout, stderr, runner)) // temporarily disabled; auth is via access key injection
|
|
42
|
+
root.AddCommand(newDownloadResultCommand(stdout, stderr, runner))
|
|
43
|
+
root.AddCommand(newGetThreadCommand(stdout, stderr, runner))
|
|
44
|
+
root.AddCommand(newListThreadFileCommand(stdout, stderr, runner))
|
|
45
|
+
root.AddCommand(newUploadFileCommand(stdout, stderr, runner))
|
|
42
46
|
root.AddCommand(short_drama.NewCommand(stdout, stderr, runner))
|
|
43
47
|
root.AddCommand(updatecmd.NewCommand(stdout, stderr))
|
|
44
48
|
return root
|
|
@@ -3,7 +3,6 @@ package short_drama
|
|
|
3
3
|
import (
|
|
4
4
|
"fmt"
|
|
5
5
|
"io"
|
|
6
|
-
"path/filepath"
|
|
7
6
|
"strconv"
|
|
8
7
|
"strings"
|
|
9
8
|
|
|
@@ -23,10 +22,6 @@ func NewCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command
|
|
|
23
22
|
cmd.SetOut(stdout)
|
|
24
23
|
cmd.SetErr(stderr)
|
|
25
24
|
cmd.AddCommand(newShortDramaSubmitRunCommand(stdout, stderr, runner))
|
|
26
|
-
cmd.AddCommand(newShortDramaUploadFileCommand(stdout, stderr, runner))
|
|
27
|
-
cmd.AddCommand(newShortDramaDownloadResultCommand(stdout, stderr, runner))
|
|
28
|
-
cmd.AddCommand(newShortDramaGetThreadCommand(stdout, stderr, runner))
|
|
29
|
-
cmd.AddCommand(newShortDramaListThreadFileCommand(stdout, stderr, runner))
|
|
30
25
|
return cmd
|
|
31
26
|
}
|
|
32
27
|
|
|
@@ -65,157 +60,6 @@ func newShortDramaSubmitRunCommand(stdout, stderr io.Writer, runner *common.Runn
|
|
|
65
60
|
return cmd
|
|
66
61
|
}
|
|
67
62
|
|
|
68
|
-
func newShortDramaUploadFileCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
69
|
-
var opts common.UploadFileOptions
|
|
70
|
-
|
|
71
|
-
cmd := &cobra.Command{
|
|
72
|
-
Use: "+upload-file",
|
|
73
|
-
Short: "Upload a file for the short drama scene",
|
|
74
|
-
Args: cobra.NoArgs,
|
|
75
|
-
RunE: withErrorLog("short-drama +upload-file", func() map[string]string {
|
|
76
|
-
return map[string]string{
|
|
77
|
-
"file_name": fileNameForLog(opts.Path),
|
|
78
|
-
}
|
|
79
|
-
}, func(cmd *cobra.Command, _ []string) error {
|
|
80
|
-
opts.Path = strings.TrimSpace(opts.Path)
|
|
81
|
-
|
|
82
|
-
if opts.Path == "" {
|
|
83
|
-
return fmt.Errorf("--path is required")
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
result, err := common.UploadFile(cmd.Context(), opts, runner)
|
|
87
|
-
if err != nil {
|
|
88
|
-
return err
|
|
89
|
-
}
|
|
90
|
-
return writeJSON(stdout, result)
|
|
91
|
-
}),
|
|
92
|
-
}
|
|
93
|
-
cmd.SetOut(stdout)
|
|
94
|
-
cmd.SetErr(stderr)
|
|
95
|
-
cmd.Flags().StringVar(&opts.Path, "path", "", "local file path to upload")
|
|
96
|
-
return cmd
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
func newShortDramaDownloadResultCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
100
|
-
var opts common.DownloadResultOptions
|
|
101
|
-
|
|
102
|
-
cmd := &cobra.Command{
|
|
103
|
-
Use: "+download-result",
|
|
104
|
-
Short: "Download a generated result URL",
|
|
105
|
-
Args: cobra.NoArgs,
|
|
106
|
-
RunE: withErrorLog("short-drama +download-result", func() map[string]string {
|
|
107
|
-
fields := map[string]string{
|
|
108
|
-
"output_path": opts.OutputPath,
|
|
109
|
-
"has_url": strconv.FormatBool(strings.TrimSpace(opts.URL) != ""),
|
|
110
|
-
"workers": strconv.Itoa(opts.Workers),
|
|
111
|
-
}
|
|
112
|
-
if opts.UpdatedAt > 0 {
|
|
113
|
-
fields["updated_at"] = strconv.FormatInt(opts.UpdatedAt, 10)
|
|
114
|
-
}
|
|
115
|
-
return fields
|
|
116
|
-
}, func(cmd *cobra.Command, _ []string) error {
|
|
117
|
-
opts.OutputPath = strings.TrimSpace(opts.OutputPath)
|
|
118
|
-
if opts.OutputPath == "" {
|
|
119
|
-
return fmt.Errorf("--output-path is required")
|
|
120
|
-
}
|
|
121
|
-
opts.URL = strings.TrimSpace(opts.URL)
|
|
122
|
-
if opts.URL == "" {
|
|
123
|
-
return fmt.Errorf("--url is required")
|
|
124
|
-
}
|
|
125
|
-
if opts.Workers <= 0 {
|
|
126
|
-
return fmt.Errorf("--workers must be greater than 0")
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
result, err := common.DownloadResult(cmd.Context(), opts, runner)
|
|
130
|
-
if err != nil {
|
|
131
|
-
return err
|
|
132
|
-
}
|
|
133
|
-
return writeJSON(stdout, result)
|
|
134
|
-
}),
|
|
135
|
-
}
|
|
136
|
-
cmd.SetOut(stdout)
|
|
137
|
-
cmd.SetErr(stderr)
|
|
138
|
-
cmd.Flags().StringVar(&opts.URL, "url", "", "URL to download")
|
|
139
|
-
cmd.Flags().StringVar(&opts.OutputPath, "output-path", "", "local output file path")
|
|
140
|
-
cmd.Flags().Int64Var(&opts.UpdatedAt, "updated-at", 0, "remote file update time as a Unix timestamp")
|
|
141
|
-
cmd.Flags().IntVar(&opts.Workers, "workers", 5, "parallel download workers")
|
|
142
|
-
return cmd
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
func newShortDramaGetThreadCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
146
|
-
var opts common.GetThreadOptions
|
|
147
|
-
|
|
148
|
-
cmd := &cobra.Command{
|
|
149
|
-
Use: "+get-thread",
|
|
150
|
-
Short: "Get a short drama thread detail",
|
|
151
|
-
Args: cobra.NoArgs,
|
|
152
|
-
RunE: withErrorLog("short-drama +get-thread", func() map[string]string {
|
|
153
|
-
return map[string]string{
|
|
154
|
-
"thread_id": opts.ThreadID,
|
|
155
|
-
"run_id": opts.RunID,
|
|
156
|
-
}
|
|
157
|
-
}, func(cmd *cobra.Command, _ []string) error {
|
|
158
|
-
opts.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
159
|
-
if opts.ThreadID == "" {
|
|
160
|
-
return fmt.Errorf("--thread-id is required")
|
|
161
|
-
}
|
|
162
|
-
opts.RunID = strings.TrimSpace(opts.RunID)
|
|
163
|
-
|
|
164
|
-
result, err := common.GetThread(cmd.Context(), &opts, runner)
|
|
165
|
-
if err != nil {
|
|
166
|
-
return err
|
|
167
|
-
}
|
|
168
|
-
_, err = fmt.Fprintln(stdout, result.ReadableText)
|
|
169
|
-
return err
|
|
170
|
-
}),
|
|
171
|
-
}
|
|
172
|
-
cmd.SetOut(stdout)
|
|
173
|
-
cmd.SetErr(stderr)
|
|
174
|
-
cmd.Flags().StringVar(&opts.ThreadID, "thread-id", "", "thread ID to fetch")
|
|
175
|
-
cmd.Flags().StringVar(&opts.RunID, "run-id", "", "run ID to fetch")
|
|
176
|
-
return cmd
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
func newShortDramaListThreadFileCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
180
|
-
var opts common.ListThreadFileOptions
|
|
181
|
-
|
|
182
|
-
cmd := &cobra.Command{
|
|
183
|
-
Use: "+list-thread-file",
|
|
184
|
-
Short: "List files in a short drama thread",
|
|
185
|
-
Args: cobra.NoArgs,
|
|
186
|
-
RunE: withErrorLog("short-drama +list-thread-file", func() map[string]string {
|
|
187
|
-
return map[string]string{
|
|
188
|
-
"thread_id": opts.ThreadID,
|
|
189
|
-
"page_num": strconv.Itoa(opts.PageNum),
|
|
190
|
-
"page_size": strconv.Itoa(opts.PageSize),
|
|
191
|
-
}
|
|
192
|
-
}, func(cmd *cobra.Command, _ []string) error {
|
|
193
|
-
opts.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
194
|
-
if opts.ThreadID == "" {
|
|
195
|
-
return fmt.Errorf("--thread-id is required")
|
|
196
|
-
}
|
|
197
|
-
if opts.PageSize <= 0 || opts.PageSize > common.MaxListThreadFilePageSize {
|
|
198
|
-
return fmt.Errorf("--page-size must be between 1 and %d", common.MaxListThreadFilePageSize)
|
|
199
|
-
}
|
|
200
|
-
if opts.PageNum <= 0 {
|
|
201
|
-
return fmt.Errorf("--page-num must be greater than 0")
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
result, err := common.ListThreadFile(cmd.Context(), &opts, runner)
|
|
205
|
-
if err != nil {
|
|
206
|
-
return err
|
|
207
|
-
}
|
|
208
|
-
return writeJSON(stdout, result)
|
|
209
|
-
}),
|
|
210
|
-
}
|
|
211
|
-
cmd.SetOut(stdout)
|
|
212
|
-
cmd.SetErr(stderr)
|
|
213
|
-
cmd.Flags().StringVar(&opts.ThreadID, "thread-id", "", "thread ID to list files for")
|
|
214
|
-
cmd.Flags().IntVar(&opts.PageNum, "page-num", 1, "page number (1-based)")
|
|
215
|
-
cmd.Flags().IntVar(&opts.PageSize, "page-size", common.MaxListThreadFilePageSize, fmt.Sprintf("number of files per page (between 1 and %d)", common.MaxListThreadFilePageSize))
|
|
216
|
-
return cmd
|
|
217
|
-
}
|
|
218
|
-
|
|
219
63
|
func withErrorLog(command string, fields func() map[string]string, run func(*cobra.Command, []string) error) func(*cobra.Command, []string) error {
|
|
220
64
|
return func(cmd *cobra.Command, args []string) error {
|
|
221
65
|
err := run(cmd, args)
|
|
@@ -230,14 +74,6 @@ func withErrorLog(command string, fields func() map[string]string, run func(*cob
|
|
|
230
74
|
}
|
|
231
75
|
}
|
|
232
76
|
|
|
233
|
-
func fileNameForLog(path string) string {
|
|
234
|
-
path = strings.TrimSpace(path)
|
|
235
|
-
if path == "" {
|
|
236
|
-
return ""
|
|
237
|
-
}
|
|
238
|
-
return filepath.Base(path)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
77
|
func writeJSON(w io.Writer, v any) error {
|
|
242
78
|
data, err := sonic.Marshal(v)
|
|
243
79
|
if err != nil {
|
package/cmd/short_drama_test.go
CHANGED
|
@@ -125,7 +125,11 @@ func TestRootHelpListsSupportedCommands(t *testing.T) {
|
|
|
125
125
|
got := stdout.String()
|
|
126
126
|
for _, want := range []string{
|
|
127
127
|
"Pippit CLI submits short-drama workflows",
|
|
128
|
+
"download-result",
|
|
129
|
+
"get-thread",
|
|
130
|
+
"list-thread-file",
|
|
128
131
|
"short-drama",
|
|
132
|
+
"upload-file",
|
|
129
133
|
"update",
|
|
130
134
|
"--version",
|
|
131
135
|
} {
|
|
@@ -140,6 +144,29 @@ func TestRootHelpListsSupportedCommands(t *testing.T) {
|
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
146
|
|
|
147
|
+
func TestShortDramaDoesNotIncludeCommonThreadCommands(t *testing.T) {
|
|
148
|
+
var stdout, stderr bytes.Buffer
|
|
149
|
+
root := NewRootCommand(&stdout, &stderr)
|
|
150
|
+
cmd, _, err := root.Find([]string{"short-drama"})
|
|
151
|
+
if err != nil {
|
|
152
|
+
t.Fatalf("Find(short-drama) error = %v", err)
|
|
153
|
+
}
|
|
154
|
+
if cmd.CommandPath() != "pippit-tool-cli short-drama" {
|
|
155
|
+
t.Fatalf("CommandPath() = %q, want short-drama command", cmd.CommandPath())
|
|
156
|
+
}
|
|
157
|
+
removedCommands := map[string]bool{
|
|
158
|
+
"+download-result": true,
|
|
159
|
+
"+get-thread": true,
|
|
160
|
+
"+list-thread-file": true,
|
|
161
|
+
"+upload-file": true,
|
|
162
|
+
}
|
|
163
|
+
for _, child := range cmd.Commands() {
|
|
164
|
+
if removedCommands[child.Name()] {
|
|
165
|
+
t.Fatalf("short-drama child %s = %#v, want nil", child.Name(), child)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
143
170
|
func TestVersionFlagPrintsVersion(t *testing.T) {
|
|
144
171
|
var stdout, stderr bytes.Buffer
|
|
145
172
|
root := NewRootCommand(&stdout, &stderr)
|
|
@@ -187,7 +214,7 @@ func TestShortDramaSubmitRunRequiresAccessKey(t *testing.T) {
|
|
|
187
214
|
assertAccessKeyGuidance(t, err)
|
|
188
215
|
}
|
|
189
216
|
|
|
190
|
-
func
|
|
217
|
+
func TestUploadFile(t *testing.T) {
|
|
191
218
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
192
219
|
if r.Method != http.MethodPost {
|
|
193
220
|
t.Fatalf("method = %s, want POST", r.Method)
|
|
@@ -238,7 +265,7 @@ func TestShortDramaUploadFile(t *testing.T) {
|
|
|
238
265
|
|
|
239
266
|
var stdout, stderr bytes.Buffer
|
|
240
267
|
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
241
|
-
root.SetArgs([]string{"
|
|
268
|
+
root.SetArgs([]string{"upload-file", "--path", path})
|
|
242
269
|
|
|
243
270
|
if err := root.Execute(); err != nil {
|
|
244
271
|
t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
|
|
@@ -250,10 +277,10 @@ func TestShortDramaUploadFile(t *testing.T) {
|
|
|
250
277
|
}
|
|
251
278
|
}
|
|
252
279
|
|
|
253
|
-
func
|
|
280
|
+
func TestUploadFileRequiresPath(t *testing.T) {
|
|
254
281
|
var stdout, stderr bytes.Buffer
|
|
255
282
|
root := NewRootCommand(&stdout, &stderr)
|
|
256
|
-
root.SetArgs([]string{"
|
|
283
|
+
root.SetArgs([]string{"upload-file"})
|
|
257
284
|
|
|
258
285
|
err := root.Execute()
|
|
259
286
|
if err == nil {
|
|
@@ -264,7 +291,7 @@ func TestShortDramaUploadFileRequiresPath(t *testing.T) {
|
|
|
264
291
|
}
|
|
265
292
|
}
|
|
266
293
|
|
|
267
|
-
func
|
|
294
|
+
func TestUploadFileRequiresAccessKey(t *testing.T) {
|
|
268
295
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
269
296
|
t.Fatal("server should not receive request without access key")
|
|
270
297
|
}))
|
|
@@ -278,7 +305,7 @@ func TestShortDramaUploadFileRequiresAccessKey(t *testing.T) {
|
|
|
278
305
|
|
|
279
306
|
var stdout, stderr bytes.Buffer
|
|
280
307
|
root := newTestRootCommandWithAccessKey(t, &stdout, &stderr, server.URL, "")
|
|
281
|
-
root.SetArgs([]string{"
|
|
308
|
+
root.SetArgs([]string{"upload-file", "--path", path})
|
|
282
309
|
|
|
283
310
|
err := root.Execute()
|
|
284
311
|
if err == nil {
|
|
@@ -287,7 +314,7 @@ func TestShortDramaUploadFileRequiresAccessKey(t *testing.T) {
|
|
|
287
314
|
assertAccessKeyGuidance(t, err)
|
|
288
315
|
}
|
|
289
316
|
|
|
290
|
-
func
|
|
317
|
+
func TestUploadFileRejectsUnsupportedFileType(t *testing.T) {
|
|
291
318
|
cwd := chdirTemp(t)
|
|
292
319
|
path := filepath.Join(cwd, "story.png")
|
|
293
320
|
if err := os.WriteFile(path, []byte("png-data"), 0o644); err != nil {
|
|
@@ -296,7 +323,7 @@ func TestShortDramaUploadFileRejectsUnsupportedFileType(t *testing.T) {
|
|
|
296
323
|
|
|
297
324
|
var stdout, stderr bytes.Buffer
|
|
298
325
|
root := NewRootCommand(&stdout, &stderr)
|
|
299
|
-
root.SetArgs([]string{"
|
|
326
|
+
root.SetArgs([]string{"upload-file", "--path", path})
|
|
300
327
|
|
|
301
328
|
err := root.Execute()
|
|
302
329
|
if err == nil {
|
|
@@ -307,7 +334,7 @@ func TestShortDramaUploadFileRejectsUnsupportedFileType(t *testing.T) {
|
|
|
307
334
|
}
|
|
308
335
|
}
|
|
309
336
|
|
|
310
|
-
func
|
|
337
|
+
func TestDownloadResult(t *testing.T) {
|
|
311
338
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
312
339
|
if r.Header.Get("User-Agent") != "Pippit-CLI/1.0" {
|
|
313
340
|
t.Fatalf("User-Agent = %q, want Pippit-CLI/1.0", r.Header.Get("User-Agent"))
|
|
@@ -326,7 +353,7 @@ func TestShortDramaDownloadResult(t *testing.T) {
|
|
|
326
353
|
root := NewRootCommand(&stdout, &stderr)
|
|
327
354
|
outputPath := filepath.Join("results", "cover.jpeg")
|
|
328
355
|
root.SetArgs([]string{
|
|
329
|
-
"
|
|
356
|
+
"download-result",
|
|
330
357
|
"--output-path", outputPath,
|
|
331
358
|
"--workers", "2",
|
|
332
359
|
"--url", server.URL + "/image?filename=ignored.jpeg",
|
|
@@ -358,7 +385,7 @@ func TestShortDramaDownloadResult(t *testing.T) {
|
|
|
358
385
|
assertFileContent(t, wantFiles[0], "image-data")
|
|
359
386
|
}
|
|
360
387
|
|
|
361
|
-
func
|
|
388
|
+
func TestDownloadResultSkipsExistingFile(t *testing.T) {
|
|
362
389
|
serverCalled := false
|
|
363
390
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
364
391
|
serverCalled = true
|
|
@@ -378,7 +405,7 @@ func TestShortDramaDownloadResultSkipsExistingFile(t *testing.T) {
|
|
|
378
405
|
var stdout, stderr bytes.Buffer
|
|
379
406
|
root := NewRootCommand(&stdout, &stderr)
|
|
380
407
|
root.SetArgs([]string{
|
|
381
|
-
"
|
|
408
|
+
"download-result",
|
|
382
409
|
"--output-path", outputPath,
|
|
383
410
|
"--url", server.URL + "/image",
|
|
384
411
|
})
|
|
@@ -404,7 +431,7 @@ func TestShortDramaDownloadResultSkipsExistingFile(t *testing.T) {
|
|
|
404
431
|
assertFileContent(t, outputPath, "existing-data")
|
|
405
432
|
}
|
|
406
433
|
|
|
407
|
-
func
|
|
434
|
+
func TestDownloadResultSkipsExistingFileWhenLocalIsFresh(t *testing.T) {
|
|
408
435
|
serverCalled := false
|
|
409
436
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
410
437
|
serverCalled = true
|
|
@@ -429,7 +456,7 @@ func TestShortDramaDownloadResultSkipsExistingFileWhenLocalIsFresh(t *testing.T)
|
|
|
429
456
|
var stdout, stderr bytes.Buffer
|
|
430
457
|
root := NewRootCommand(&stdout, &stderr)
|
|
431
458
|
root.SetArgs([]string{
|
|
432
|
-
"
|
|
459
|
+
"download-result",
|
|
433
460
|
"--output-path", outputPath,
|
|
434
461
|
"--updated-at", "1779716734",
|
|
435
462
|
"--url", server.URL + "/image",
|
|
@@ -450,7 +477,7 @@ func TestShortDramaDownloadResultSkipsExistingFileWhenLocalIsFresh(t *testing.T)
|
|
|
450
477
|
assertFileContent(t, outputPath, "existing-data")
|
|
451
478
|
}
|
|
452
479
|
|
|
453
|
-
func
|
|
480
|
+
func TestDownloadResultOverwritesStaleExistingFile(t *testing.T) {
|
|
454
481
|
serverCalled := false
|
|
455
482
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
456
483
|
serverCalled = true
|
|
@@ -478,7 +505,7 @@ func TestShortDramaDownloadResultOverwritesStaleExistingFile(t *testing.T) {
|
|
|
478
505
|
var stdout, stderr bytes.Buffer
|
|
479
506
|
root := NewRootCommand(&stdout, &stderr)
|
|
480
507
|
root.SetArgs([]string{
|
|
481
|
-
"
|
|
508
|
+
"download-result",
|
|
482
509
|
"--output-path", outputPath,
|
|
483
510
|
"--updated-at", "1779716734",
|
|
484
511
|
"--url", server.URL + "/image",
|
|
@@ -509,7 +536,7 @@ func TestShortDramaDownloadResultOverwritesStaleExistingFile(t *testing.T) {
|
|
|
509
536
|
}
|
|
510
537
|
}
|
|
511
538
|
|
|
512
|
-
func
|
|
539
|
+
func TestDownloadResultDownloadsMetaJSON(t *testing.T) {
|
|
513
540
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
514
541
|
if r.URL.Path != "/meta.json" {
|
|
515
542
|
t.Fatalf("path = %s, want meta.json", r.URL.Path)
|
|
@@ -524,7 +551,7 @@ func TestShortDramaDownloadResultDownloadsMetaJSON(t *testing.T) {
|
|
|
524
551
|
var stdout, stderr bytes.Buffer
|
|
525
552
|
root := NewRootCommand(&stdout, &stderr)
|
|
526
553
|
root.SetArgs([]string{
|
|
527
|
-
"
|
|
554
|
+
"download-result",
|
|
528
555
|
"--output-path", outputPath,
|
|
529
556
|
"--url", server.URL + "/meta.json",
|
|
530
557
|
})
|
|
@@ -547,10 +574,10 @@ func TestShortDramaDownloadResultDownloadsMetaJSON(t *testing.T) {
|
|
|
547
574
|
assertFileContent(t, outputPath, `{"ok":true}`)
|
|
548
575
|
}
|
|
549
576
|
|
|
550
|
-
func
|
|
577
|
+
func TestDownloadResultRequiresOutputPath(t *testing.T) {
|
|
551
578
|
var stdout, stderr bytes.Buffer
|
|
552
579
|
root := NewRootCommand(&stdout, &stderr)
|
|
553
|
-
root.SetArgs([]string{"
|
|
580
|
+
root.SetArgs([]string{"download-result", "--url", "https://example.com/image.png"})
|
|
554
581
|
|
|
555
582
|
err := root.Execute()
|
|
556
583
|
if err == nil {
|
|
@@ -561,11 +588,11 @@ func TestShortDramaDownloadResultRequiresOutputPath(t *testing.T) {
|
|
|
561
588
|
}
|
|
562
589
|
}
|
|
563
590
|
|
|
564
|
-
func
|
|
591
|
+
func TestDownloadResultRejectsOutputDirFlag(t *testing.T) {
|
|
565
592
|
var stdout, stderr bytes.Buffer
|
|
566
593
|
root := NewRootCommand(&stdout, &stderr)
|
|
567
594
|
root.SetArgs([]string{
|
|
568
|
-
"
|
|
595
|
+
"download-result",
|
|
569
596
|
"--output-dir", filepath.Join("results", "image.png"),
|
|
570
597
|
"--url", "https://example.com/image.png",
|
|
571
598
|
})
|
|
@@ -579,10 +606,10 @@ func TestShortDramaDownloadResultRejectsOutputDirFlag(t *testing.T) {
|
|
|
579
606
|
}
|
|
580
607
|
}
|
|
581
608
|
|
|
582
|
-
func
|
|
609
|
+
func TestDownloadResultRequiresURL(t *testing.T) {
|
|
583
610
|
var stdout, stderr bytes.Buffer
|
|
584
611
|
root := NewRootCommand(&stdout, &stderr)
|
|
585
|
-
root.SetArgs([]string{"
|
|
612
|
+
root.SetArgs([]string{"download-result", "--output-path", filepath.Join("results", "image.png")})
|
|
586
613
|
|
|
587
614
|
err := root.Execute()
|
|
588
615
|
if err == nil {
|
|
@@ -593,10 +620,10 @@ func TestShortDramaDownloadResultRequiresURL(t *testing.T) {
|
|
|
593
620
|
}
|
|
594
621
|
}
|
|
595
622
|
|
|
596
|
-
func
|
|
623
|
+
func TestDownloadResultRejectsInvalidScheme(t *testing.T) {
|
|
597
624
|
var stdout, stderr bytes.Buffer
|
|
598
625
|
root := NewRootCommand(&stdout, &stderr)
|
|
599
|
-
root.SetArgs([]string{"
|
|
626
|
+
root.SetArgs([]string{"download-result", "--output-path", filepath.Join("results", "image.png"), "--url", "file:///etc/passwd"})
|
|
600
627
|
|
|
601
628
|
err := root.Execute()
|
|
602
629
|
if err == nil {
|
|
@@ -607,7 +634,7 @@ func TestShortDramaDownloadResultRejectsInvalidScheme(t *testing.T) {
|
|
|
607
634
|
}
|
|
608
635
|
}
|
|
609
636
|
|
|
610
|
-
func
|
|
637
|
+
func TestDownloadResultAllFailed(t *testing.T) {
|
|
611
638
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
612
639
|
w.WriteHeader(http.StatusNotFound)
|
|
613
640
|
}))
|
|
@@ -618,7 +645,7 @@ func TestShortDramaDownloadResultAllFailed(t *testing.T) {
|
|
|
618
645
|
var stdout, stderr bytes.Buffer
|
|
619
646
|
root := NewRootCommand(&stdout, &stderr)
|
|
620
647
|
root.SetArgs([]string{
|
|
621
|
-
"
|
|
648
|
+
"download-result",
|
|
622
649
|
"--output-path", filepath.Join("results", "missing.png"),
|
|
623
650
|
"--url", server.URL + "/notfound",
|
|
624
651
|
})
|
|
@@ -643,7 +670,7 @@ func TestShortDramaDownloadResultAllFailed(t *testing.T) {
|
|
|
643
670
|
}
|
|
644
671
|
}
|
|
645
672
|
|
|
646
|
-
func
|
|
673
|
+
func TestDownloadResultOutputPath(t *testing.T) {
|
|
647
674
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
648
675
|
_, _ = w.Write([]byte("image-data"))
|
|
649
676
|
}))
|
|
@@ -655,7 +682,7 @@ func TestShortDramaDownloadResultOutputPath(t *testing.T) {
|
|
|
655
682
|
root := NewRootCommand(&stdout, &stderr)
|
|
656
683
|
outputPath := filepath.Join("custom", "nested", "cover.png")
|
|
657
684
|
root.SetArgs([]string{
|
|
658
|
-
"
|
|
685
|
+
"download-result",
|
|
659
686
|
"--output-path", outputPath,
|
|
660
687
|
"--url", server.URL + "/image.png",
|
|
661
688
|
})
|
|
@@ -671,7 +698,7 @@ func TestShortDramaDownloadResultOutputPath(t *testing.T) {
|
|
|
671
698
|
assertFileContent(t, outputPath, "image-data")
|
|
672
699
|
}
|
|
673
700
|
|
|
674
|
-
func
|
|
701
|
+
func TestGetThread(t *testing.T) {
|
|
675
702
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
676
703
|
if r.Method != http.MethodPost {
|
|
677
704
|
t.Fatalf("method = %s, want POST", r.Method)
|
|
@@ -709,7 +736,7 @@ func TestShortDramaGetThread(t *testing.T) {
|
|
|
709
736
|
var stdout, stderr bytes.Buffer
|
|
710
737
|
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
711
738
|
root.SetArgs([]string{
|
|
712
|
-
"
|
|
739
|
+
"get-thread",
|
|
713
740
|
"--thread-id", "thread_123",
|
|
714
741
|
"--run-id", "run_456",
|
|
715
742
|
})
|
|
@@ -723,12 +750,12 @@ func TestShortDramaGetThread(t *testing.T) {
|
|
|
723
750
|
}
|
|
724
751
|
}
|
|
725
752
|
|
|
726
|
-
func
|
|
753
|
+
func TestGetThreadRequiresThreadID(t *testing.T) {
|
|
727
754
|
clearDailyErrorLog(t)
|
|
728
755
|
|
|
729
756
|
var stdout, stderr bytes.Buffer
|
|
730
757
|
root := NewRootCommand(&stdout, &stderr)
|
|
731
|
-
root.SetArgs([]string{"
|
|
758
|
+
root.SetArgs([]string{"get-thread"})
|
|
732
759
|
|
|
733
760
|
err := root.Execute()
|
|
734
761
|
if err == nil {
|
|
@@ -742,7 +769,7 @@ func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
|
|
|
742
769
|
if len(entries) != 1 {
|
|
743
770
|
t.Fatalf("log entries = %d, want 1: %#v", len(entries), entries)
|
|
744
771
|
}
|
|
745
|
-
if entries[0]["command"] != "
|
|
772
|
+
if entries[0]["command"] != "get-thread" {
|
|
746
773
|
t.Fatalf("command = %v, want get-thread", entries[0]["command"])
|
|
747
774
|
}
|
|
748
775
|
if entries[0]["error"] != "--thread-id is required" {
|
|
@@ -750,7 +777,7 @@ func TestShortDramaGetThreadRequiresThreadID(t *testing.T) {
|
|
|
750
777
|
}
|
|
751
778
|
}
|
|
752
779
|
|
|
753
|
-
func
|
|
780
|
+
func TestGetThreadRequiresAccessKey(t *testing.T) {
|
|
754
781
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
755
782
|
t.Fatal("server should not receive request without access key")
|
|
756
783
|
}))
|
|
@@ -758,7 +785,7 @@ func TestShortDramaGetThreadRequiresAccessKey(t *testing.T) {
|
|
|
758
785
|
|
|
759
786
|
var stdout, stderr bytes.Buffer
|
|
760
787
|
root := newTestRootCommandWithAccessKey(t, &stdout, &stderr, server.URL, "")
|
|
761
|
-
root.SetArgs([]string{"
|
|
788
|
+
root.SetArgs([]string{"get-thread", "--thread-id", "thread_123"})
|
|
762
789
|
|
|
763
790
|
err := root.Execute()
|
|
764
791
|
if err == nil {
|
|
@@ -767,7 +794,7 @@ func TestShortDramaGetThreadRequiresAccessKey(t *testing.T) {
|
|
|
767
794
|
assertAccessKeyGuidance(t, err)
|
|
768
795
|
}
|
|
769
796
|
|
|
770
|
-
func
|
|
797
|
+
func TestListThreadFile(t *testing.T) {
|
|
771
798
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
772
799
|
if r.Method != http.MethodPost {
|
|
773
800
|
t.Fatalf("method = %s, want POST", r.Method)
|
|
@@ -802,7 +829,7 @@ func TestShortDramaListThreadFile(t *testing.T) {
|
|
|
802
829
|
var stdout, stderr bytes.Buffer
|
|
803
830
|
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
804
831
|
root.SetArgs([]string{
|
|
805
|
-
"
|
|
832
|
+
"list-thread-file",
|
|
806
833
|
"--thread-id", "thread_123",
|
|
807
834
|
"--page-num", "2",
|
|
808
835
|
"--page-size", "10",
|
|
@@ -843,7 +870,7 @@ func TestShortDramaListThreadFile(t *testing.T) {
|
|
|
843
870
|
}
|
|
844
871
|
}
|
|
845
872
|
|
|
846
|
-
func
|
|
873
|
+
func TestListThreadFileMessageWhenPageFull(t *testing.T) {
|
|
847
874
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
848
875
|
if !strings.Contains(r.URL.Path, "list_thread_file") {
|
|
849
876
|
t.Fatalf("path = %s, want list_thread_file path", r.URL.Path)
|
|
@@ -855,7 +882,7 @@ func TestShortDramaListThreadFileMessageWhenPageFull(t *testing.T) {
|
|
|
855
882
|
var stdout, stderr bytes.Buffer
|
|
856
883
|
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
857
884
|
root.SetArgs([]string{
|
|
858
|
-
"
|
|
885
|
+
"list-thread-file",
|
|
859
886
|
"--thread-id", "thread_123",
|
|
860
887
|
"--page-num", "2",
|
|
861
888
|
"--page-size", "200",
|
|
@@ -872,10 +899,10 @@ func TestShortDramaListThreadFileMessageWhenPageFull(t *testing.T) {
|
|
|
872
899
|
}
|
|
873
900
|
}
|
|
874
901
|
|
|
875
|
-
func
|
|
902
|
+
func TestListThreadFileRequiresThreadID(t *testing.T) {
|
|
876
903
|
var stdout, stderr bytes.Buffer
|
|
877
904
|
root := NewRootCommand(&stdout, &stderr)
|
|
878
|
-
root.SetArgs([]string{"
|
|
905
|
+
root.SetArgs([]string{"list-thread-file"})
|
|
879
906
|
|
|
880
907
|
err := root.Execute()
|
|
881
908
|
if err == nil {
|
|
@@ -886,7 +913,7 @@ func TestShortDramaListThreadFileRequiresThreadID(t *testing.T) {
|
|
|
886
913
|
}
|
|
887
914
|
}
|
|
888
915
|
|
|
889
|
-
func
|
|
916
|
+
func TestListThreadFileRequiresAccessKey(t *testing.T) {
|
|
890
917
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
891
918
|
t.Fatal("server should not receive request without access key")
|
|
892
919
|
}))
|
|
@@ -894,7 +921,7 @@ func TestShortDramaListThreadFileRequiresAccessKey(t *testing.T) {
|
|
|
894
921
|
|
|
895
922
|
var stdout, stderr bytes.Buffer
|
|
896
923
|
root := newTestRootCommandWithAccessKey(t, &stdout, &stderr, server.URL, "")
|
|
897
|
-
root.SetArgs([]string{"
|
|
924
|
+
root.SetArgs([]string{"list-thread-file", "--thread-id", "thread_123"})
|
|
898
925
|
|
|
899
926
|
err := root.Execute()
|
|
900
927
|
if err == nil {
|
|
@@ -903,11 +930,11 @@ func TestShortDramaListThreadFileRequiresAccessKey(t *testing.T) {
|
|
|
903
930
|
assertAccessKeyGuidance(t, err)
|
|
904
931
|
}
|
|
905
932
|
|
|
906
|
-
func
|
|
933
|
+
func TestListThreadFileRejectsPageSizeAboveMax(t *testing.T) {
|
|
907
934
|
var stdout, stderr bytes.Buffer
|
|
908
935
|
root := NewRootCommand(&stdout, &stderr)
|
|
909
936
|
root.SetArgs([]string{
|
|
910
|
-
"
|
|
937
|
+
"list-thread-file",
|
|
911
938
|
"--thread-id", "thread_123",
|
|
912
939
|
"--page-size", "201",
|
|
913
940
|
})
|
|
@@ -942,13 +969,13 @@ func assertAccessKeyGuidance(t *testing.T, err error) {
|
|
|
942
969
|
t.Fatal("error = nil, want access key guidance")
|
|
943
970
|
}
|
|
944
971
|
msg := err.Error()
|
|
945
|
-
if !strings.Contains(msg, "XYQ_ACCESS_KEY
|
|
972
|
+
if !strings.Contains(msg, "XYQ_ACCESS_KEY") {
|
|
946
973
|
t.Fatalf("error = %q, want access key guidance", err)
|
|
947
974
|
}
|
|
948
975
|
if !strings.Contains(msg, "https://xyq.jianying.com/home?tab_name=home") {
|
|
949
976
|
t.Fatalf("error = %q, want access key settings URL", err)
|
|
950
977
|
}
|
|
951
|
-
if !strings.Contains(msg, `export XYQ_ACCESS_KEY="<access-key>"`) {
|
|
978
|
+
if !strings.Contains(msg, `export XYQ_ACCESS_KEY="<your-access-key>"`) {
|
|
952
979
|
t.Fatalf("error = %q, want setup command guidance", err)
|
|
953
980
|
}
|
|
954
981
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"io"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
10
|
+
"github.com/spf13/cobra"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func newUploadFileCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
14
|
+
var opts common.UploadFileOptions
|
|
15
|
+
|
|
16
|
+
cmd := &cobra.Command{
|
|
17
|
+
Use: "upload-file",
|
|
18
|
+
Short: "Upload a file",
|
|
19
|
+
Args: cobra.NoArgs,
|
|
20
|
+
RunE: withErrorLog("upload-file", func() map[string]string {
|
|
21
|
+
return map[string]string{
|
|
22
|
+
"file_name": fileNameForLog(opts.Path),
|
|
23
|
+
}
|
|
24
|
+
}, func(cmd *cobra.Command, _ []string) error {
|
|
25
|
+
opts.Path = strings.TrimSpace(opts.Path)
|
|
26
|
+
|
|
27
|
+
if opts.Path == "" {
|
|
28
|
+
return fmt.Errorf("--path is required")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
result, err := common.UploadFile(cmd.Context(), opts, runner)
|
|
32
|
+
if err != nil {
|
|
33
|
+
return err
|
|
34
|
+
}
|
|
35
|
+
return writeJSON(stdout, result)
|
|
36
|
+
}),
|
|
37
|
+
}
|
|
38
|
+
cmd.SetOut(stdout)
|
|
39
|
+
cmd.SetErr(stderr)
|
|
40
|
+
cmd.Flags().StringVar(&opts.Path, "path", "", "local file path to upload")
|
|
41
|
+
return cmd
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func fileNameForLog(path string) string {
|
|
45
|
+
path = strings.TrimSpace(path)
|
|
46
|
+
if path == "" {
|
|
47
|
+
return ""
|
|
48
|
+
}
|
|
49
|
+
return filepath.Base(path)
|
|
50
|
+
}
|
|
@@ -32,7 +32,7 @@ type DownloadResultError struct {
|
|
|
32
32
|
Error string `json:"error"`
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// DownloadResultResponse is the JSON envelope printed by `pippit-tool-cli
|
|
35
|
+
// DownloadResultResponse is the JSON envelope printed by `pippit-tool-cli download-result`.
|
|
36
36
|
type DownloadResultResponse struct {
|
|
37
37
|
OutputPath string `json:"output_path"`
|
|
38
38
|
Downloaded []string `json:"downloaded"`
|
|
@@ -16,7 +16,7 @@ func TestAppendDailyErrorLog(t *testing.T) {
|
|
|
16
16
|
t.Setenv("HOME", home)
|
|
17
17
|
t.Setenv("USERPROFILE", home)
|
|
18
18
|
|
|
19
|
-
err := AppendDailyErrorLog("
|
|
19
|
+
err := AppendDailyErrorLog("get-thread", fmt.Errorf("request failed"), map[string]string{
|
|
20
20
|
"thread_id": "thread_123",
|
|
21
21
|
"access_key": "secret",
|
|
22
22
|
"empty": "",
|
|
@@ -24,7 +24,7 @@ func TestAppendDailyErrorLog(t *testing.T) {
|
|
|
24
24
|
if err != nil {
|
|
25
25
|
t.Fatalf("AppendDailyErrorLog(): %v", err)
|
|
26
26
|
}
|
|
27
|
-
if err := AppendDailyErrorLog("
|
|
27
|
+
if err := AppendDailyErrorLog("get-thread", fmt.Errorf("second failure"), nil); err != nil {
|
|
28
28
|
t.Fatalf("AppendDailyErrorLog() second call: %v", err)
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -42,7 +42,7 @@ func TestAppendDailyErrorLog(t *testing.T) {
|
|
|
42
42
|
if err := sonic.Unmarshal([]byte(lines[0]), &first); err != nil {
|
|
43
43
|
t.Fatalf("decode first log line: %v\n%s", err, lines[0])
|
|
44
44
|
}
|
|
45
|
-
if first["command"] != "
|
|
45
|
+
if first["command"] != "get-thread" {
|
|
46
46
|
t.Fatalf("command = %v, want get-thread command", first["command"])
|
|
47
47
|
}
|
|
48
48
|
if first["error"] != "request failed" {
|
|
@@ -15,7 +15,7 @@ type GetThreadOptions struct {
|
|
|
15
15
|
RunID string `json:"run_id,omitempty"`
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// GetThreadResult is the parsed get_thread response used by `pippit-tool-cli
|
|
18
|
+
// GetThreadResult is the parsed get_thread response used by `pippit-tool-cli get-thread`.
|
|
19
19
|
type GetThreadResult struct {
|
|
20
20
|
ReadableText string `json:"readable_text"`
|
|
21
21
|
}
|
|
@@ -25,7 +25,7 @@ type ThreadFile struct {
|
|
|
25
25
|
UpdatedAt *int64 `json:"updated_at,omitempty"`
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// ListThreadFileResult is the JSON envelope printed by `pippit-tool-cli
|
|
28
|
+
// ListThreadFileResult is the JSON envelope printed by `pippit-tool-cli list-thread-file`.
|
|
29
29
|
type ListThreadFileResult struct {
|
|
30
30
|
Files []*ThreadFile `json:"files"`
|
|
31
31
|
Total int64 `json:"total"`
|
|
@@ -17,7 +17,7 @@ type UploadFileOptions struct {
|
|
|
17
17
|
FileName string `json:"file_name"`
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// UploadFileResult is the JSON envelope printed by `pippit-tool-cli
|
|
20
|
+
// UploadFileResult is the JSON envelope printed by `pippit-tool-cli upload-file`.
|
|
21
21
|
type UploadFileResult struct {
|
|
22
22
|
AssetID string `json:"asset_id"`
|
|
23
23
|
}
|
package/package.json
CHANGED
|
@@ -29,7 +29,7 @@ metadata:
|
|
|
29
29
|
4. **获取会话文件** - 根据 `thread_id` 拉取会话文件列表,得到 `file_path`、`download_url`。这和查询会话进展同等重要。
|
|
30
30
|
5. **下载重要资产** - 使用文件列表中的 `download_url` 下载资源,并按 `file_path` 写入用户本地目标文件路径。
|
|
31
31
|
|
|
32
|
-
重要资产包括但不限于:剧本设计、场景设计、场景图、人物角色设计、人物图、分集草稿、故事板、最终视频产物。只要
|
|
32
|
+
重要资产包括但不限于:剧本设计、场景设计、场景图、人物角色设计、人物图、分集草稿、故事板、最终视频产物。只要 `list-thread-file` 返回了这些资产的 `download_url`,就要及时调用下载工具落盘,不要只展示文件元信息。
|
|
33
33
|
|
|
34
34
|
## 短剧主流程顺序
|
|
35
35
|
|
|
@@ -94,17 +94,17 @@ pippit-tool-cli short-drama +submit-run --message "参考这个大纲写第一
|
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
96
|
# 查询会话可读进展
|
|
97
|
-
pippit-tool-cli
|
|
97
|
+
pippit-tool-cli get-thread --thread-id THREAD_ID --run-id RUN_ID
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
> `thread_id` 和 `run_id` 由 `+submit-run` 返回。`run_id` 可省略,省略时返回当前 `thread_id` 下的所有 Run;传入时只看指定 Run。
|
|
101
101
|
|
|
102
102
|
### 3. 上传文件
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
当用户提供短剧大纲、人物设定、世界观设定、已有分集或剧本等本地参考文件时,可先上传文件。`upload-file` 当前只接收本地文件路径,并且只支持 `.doc`、`.docx` 和 `.txt` 后缀;不要把 `.md`、`.pdf`、图片、视频或 URL 传给该命令。
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
|
-
pippit-tool-cli
|
|
107
|
+
pippit-tool-cli upload-file --path /path/to/outline.txt
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
上传成功后命令只返回 `asset_id`:
|
|
@@ -121,10 +121,10 @@ pippit-tool-cli short-drama +upload-file --path /path/to/outline.txt
|
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
123
|
# 获取会话文件列表
|
|
124
|
-
pippit-tool-cli
|
|
124
|
+
pippit-tool-cli list-thread-file --thread-id THREAD_ID --page-num 1 --page-size 200
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
`list-thread-file` 返回的每个文件对象包含:
|
|
128
128
|
|
|
129
129
|
```json
|
|
130
130
|
{
|
|
@@ -134,16 +134,16 @@ pippit-tool-cli short-drama +list-thread-file --thread-id THREAD_ID --page-num 1
|
|
|
134
134
|
}
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
`list-thread-file` 只负责获取会话文件列表,不负责下载文件,也不需要判断本地文件是否已存在。
|
|
138
138
|
|
|
139
139
|
### 5. 下载文件资源
|
|
140
140
|
|
|
141
141
|
```bash
|
|
142
142
|
# 下载文件资源到指定文件路径
|
|
143
|
-
pippit-tool-cli
|
|
143
|
+
pippit-tool-cli download-result --url DOWNLOAD_URL --output-path FILE_PATH --updated-at UPDATED_AT
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
`FILE_PATH` 必须直接使用
|
|
146
|
+
`FILE_PATH` 必须直接使用 `list-thread-file` 返回的完整 `file_path`,包含文件名,不要取父目录。`UPDATED_AT` 使用同一文件对象返回的 `updated_at`;如果没有 `updated_at`,可省略 `--updated-at`。`download-result` 负责把会话产生的文件通过 URL 下载到该目标文件路径;如果目标文件已存在且本地修改时间不早于 `updated_at`,跳过下载;如果本地文件早于 `updated_at`,覆盖更新。
|
|
147
147
|
|
|
148
148
|
## 典型工作流
|
|
149
149
|
|
|
@@ -154,8 +154,8 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
|
|
|
154
154
|
→ 拿到 thread_id、run_id 和 web_thread_link
|
|
155
155
|
2. 立即将 web_thread_link 展示给用户
|
|
156
156
|
3. 并行发起,二者同等重要:
|
|
157
|
-
a. pippit-tool-cli
|
|
158
|
-
b. pippit-tool-cli
|
|
157
|
+
a. pippit-tool-cli get-thread --thread-id THREAD_ID --run-id RUN_ID
|
|
158
|
+
b. pippit-tool-cli list-thread-file --thread-id THREAD_ID --page-num PAGE_NUM --page-size 200
|
|
159
159
|
4. 检查 `get-thread` 返回的 readable_text:
|
|
160
160
|
- 如果任务仍在进行中:展示可读进展,继续查询
|
|
161
161
|
- 如果后端 Agent 提出问题:从 readable_text 中提取问题并展示,等待用户回复
|
|
@@ -163,9 +163,9 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
|
|
|
163
163
|
- 对每个文件取 file_path、download_url、updated_at
|
|
164
164
|
- 将 file_path 作为本地目标文件路径,包含文件名
|
|
165
165
|
- 有 download_url 的重要资产:加入本轮下载队列
|
|
166
|
-
- 不判断 file_path 在本地是否已存在,是否跳过由
|
|
166
|
+
- 不判断 file_path 在本地是否已存在,是否跳过由 download-result 内部处理
|
|
167
167
|
- 如果本轮 total 达到 200:下一轮将 PAGE_NUM 加 1,继续查询新一页文件
|
|
168
|
-
6. 对重要资产,立即调用
|
|
168
|
+
6. 对重要资产,立即调用 download-result 并行下载资源:
|
|
169
169
|
- 使用第 5 步获取的 download_url 作为 --url
|
|
170
170
|
- 使用第 5 步获取的完整 file_path 作为 --output-path
|
|
171
171
|
- 如果第 5 步返回 updated_at,作为 --updated-at 传入
|
|
@@ -179,7 +179,7 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
|
|
|
179
179
|
|
|
180
180
|
```
|
|
181
181
|
1. 检查用户提供的是一个本地 `.doc`、`.docx` 或 `.txt` 剧本文件路径;如果不是,告知当前上传命令只支持这三类文件,不要擅自转换或改写文件。
|
|
182
|
-
2. pippit-tool-cli
|
|
182
|
+
2. pippit-tool-cli upload-file --path /path/to/file.txt
|
|
183
183
|
→ 拿到 asset_id
|
|
184
184
|
3. pippit-tool-cli short-drama +submit-run --message "用户的原始短剧需求" --asset-ids asset_id
|
|
185
185
|
→ 拿到 thread_id、run_id 和 web_thread_link
|
|
@@ -199,23 +199,23 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
|
|
|
199
199
|
## 轮询策略
|
|
200
200
|
|
|
201
201
|
- **间隔**:每 10 秒查询一次。
|
|
202
|
-
- **进展查询**:每轮调用
|
|
203
|
-
- **并行查询**:每次 `+submit-run` 返回 `thread_id` 后,同时发起
|
|
204
|
-
-
|
|
205
|
-
- **重要资产识别**:每轮都检查
|
|
206
|
-
- **文件下载**:解析
|
|
202
|
+
- **进展查询**:每轮调用 `get-thread` 查看 `readable_text`。优先带上本轮 `run_id` 聚焦当前任务;需要查看整个会话时可省略 `--run-id`。
|
|
203
|
+
- **并行查询**:每次 `+submit-run` 返回 `thread_id` 后,同时发起 `get-thread` 和 `list-thread-file`;二者同等重要,不能只查询会话进展而忽略会话文件。
|
|
204
|
+
- **文件分页**:`list-thread-file` 使用 `--page-size 200`。如果本轮返回的 `total` 达到 200,下一轮使用 `--page-num` 加 1 查询新一页结果;如果未达到 200,保持当前页继续轮询新增产物。
|
|
205
|
+
- **重要资产识别**:每轮都检查 `list-thread-file` 返回的文件。剧本设计、场景设计、场景图、人物角色设计、人物图、分集草稿、故事板、最终视频产物都是重要资产。
|
|
206
|
+
- **文件下载**:解析 `list-thread-file` 的结果后,对带 `download_url` 的重要资产立即调用 `download-result` 下载资源;不要在 `list-thread-file` 阶段检查文件是否已存在,存在性检查由下载工具内部处理。
|
|
207
207
|
- **下载完成标准**:不要把文件元信息展示当成下载完成;必须拿到本地 `file_path`,或明确记录该文件在重试后仍下载失败。
|
|
208
208
|
- **用户确认**:如果消息中出现需要用户确认、补充设定或回答问题的内容,先判断是否包含表单、问卷、选项或按钮;包含时按“短剧主流程顺序”和“表单与问卷选项处理原则”清洗选项,再展示给用户并等待回复。
|
|
209
209
|
- **超时**:如果长时间无结果,告知用户任务仍在生成中,可稍后通过 `web_thread_link` 查看。
|
|
210
|
-
-
|
|
210
|
+
- **错误处理**:`get-thread`、`list-thread-file` 或 `download-result` 任一调用失败时,记录失败原因和参数,在后续轮询中主动重试;重试期间继续处理其他成功返回的消息和文件。连续多轮失败后再向用户说明仍未完成的查询或下载项。
|
|
211
211
|
|
|
212
212
|
## 完成标准
|
|
213
213
|
|
|
214
|
-
一次短剧任务不能只以
|
|
214
|
+
一次短剧任务不能只以 `get-thread` 返回的 `readable_text` 作为结束条件。完成前必须同时检查:
|
|
215
215
|
|
|
216
|
-
1. 已处理
|
|
217
|
-
2. 已用 `--page-size 200` 调用
|
|
218
|
-
3. 对所有带 `download_url` 的重要资产,已调用
|
|
216
|
+
1. 已处理 `get-thread` 返回的最新 `readable_text`、用户确认问题和最终消息。
|
|
217
|
+
2. 已用 `--page-size 200` 调用 `list-thread-file` 获取会话文件列表;如果本轮 `total` 达到 200,已在后续轮询中递增 `page-num` 查询新一页。
|
|
218
|
+
3. 对所有带 `download_url` 的重要资产,已调用 `download-result` 下载到本地 `file_path`。
|
|
219
219
|
4. 已按短剧主流程顺序检查服务端表单、问卷和选项,没有把跳过必要阶段的选项直接呈现给用户;如果跳过 `剧本标准化`,已明确这是可选阶段。
|
|
220
220
|
5. 对查询失败或下载失败的资产,已在后续轮询中主动重试,并在最终回复中列出仍失败的文件或命令。
|
|
221
221
|
|
|
@@ -231,7 +231,7 @@ pippit-tool-cli short-drama +download-result --url DOWNLOAD_URL --output-path FI
|
|
|
231
231
|
}
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
**get-thread** 返回:
|
|
235
235
|
|
|
236
236
|
```text
|
|
237
237
|
Thread: thread_...
|
|
@@ -242,7 +242,7 @@ Thread: thread_...
|
|
|
242
242
|
[assistant] ...
|
|
243
243
|
```
|
|
244
244
|
|
|
245
|
-
|
|
245
|
+
**upload-file** 返回:
|
|
246
246
|
|
|
247
247
|
```json
|
|
248
248
|
{
|
|
@@ -250,9 +250,9 @@ Thread: thread_...
|
|
|
250
250
|
}
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
`upload-file` 通过 `multipart/form-data` 上传文件,表单文件字段名为 `file`。本地文件必须存在、不能是目录,后缀必须是 `.doc`、`.docx` 或 `.txt`;不支持的后缀会直接报错。返回的 `asset_id` 来自服务端 `pippit_asset_id`,如果没有该字段才回退到 `asset_id`。
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
**list-thread-file** 返回:
|
|
256
256
|
|
|
257
257
|
```json
|
|
258
258
|
{
|
|
@@ -270,7 +270,7 @@ Thread: thread_...
|
|
|
270
270
|
|
|
271
271
|
当 `total` 达到 200 时,`message` 会用 `<system-remind>` 提示下一轮将 `page-num` 加 1 查询新一页。
|
|
272
272
|
|
|
273
|
-
|
|
273
|
+
**download-result** 返回:
|
|
274
274
|
|
|
275
275
|
```json
|
|
276
276
|
{
|
|
@@ -281,16 +281,16 @@ Thread: thread_...
|
|
|
281
281
|
|
|
282
282
|
## 会话文件与资源下载
|
|
283
283
|
|
|
284
|
-
先用
|
|
284
|
+
先用 `list-thread-file` 获取会话文件列表,再用 `download-result` 并行下载重要资产。获取文件元信息不是最终目标,重要资产落盘才是核心目标。文件是否已存在由下载工具内部检查,`list-thread-file` 阶段不要做本地存在性判断。
|
|
285
285
|
|
|
286
286
|
### 获取会话文件
|
|
287
287
|
|
|
288
|
-
从
|
|
288
|
+
从 `list-thread-file` 的 `files` 中逐个读取文件元信息:`file_path`、`file_name`、`download_url`、`updated_at`。重点识别剧本设计、场景设计、场景图、人物角色设计、人物图、分集草稿、故事板、最终视频产物等重要资产。
|
|
289
289
|
|
|
290
290
|
```
|
|
291
291
|
1. 有download_url的重要资产
|
|
292
292
|
→ 记录该file_path、URL和updated_at
|
|
293
|
-
→ 使用
|
|
293
|
+
→ 使用 download-result 将URL资源下载到该file_path;有updated_at时传入--updated-at
|
|
294
294
|
2. 本轮total达到200
|
|
295
295
|
→ 下一轮page-num加1,继续查询新一页结果
|
|
296
296
|
3. 本轮total未达到200
|
|
@@ -304,7 +304,7 @@ Thread: thread_...
|
|
|
304
304
|
|
|
305
305
|
对带 `download_url` 的重要资产调用下载工具,可并行。重要资产必须主动下载,不要等用户再次要求,也不要在调用下载工具前先检查本地文件是否存在。
|
|
306
306
|
|
|
307
|
-
1. 调用 `pippit-tool-cli
|
|
307
|
+
1. 调用 `pippit-tool-cli download-result --url DOWNLOAD_URL --output-path FILE_PATH --updated-at UPDATED_AT`;如果文件对象没有 `updated_at`,省略 `--updated-at`。
|
|
308
308
|
2. 下载完成后,向用户展示本地文件路径;如果某个文件下载失败,记录失败项并在后续轮询中重试,不阻塞已成功落盘的文件展示。
|
|
309
309
|
|
|
310
310
|
## 向用户展示内容
|
|
@@ -323,9 +323,9 @@ Thread: thread_...
|
|
|
323
323
|
|
|
324
324
|
你要做的只有三件事:
|
|
325
325
|
|
|
326
|
-
1. **上传**:如果用户给了本地 `.doc` / `.docx` / `.txt` 参考文件,先调用
|
|
326
|
+
1. **上传**:如果用户给了本地 `.doc` / `.docx` / `.txt` 参考文件,先调用 `upload-file`。
|
|
327
327
|
2. **提交任务**:首次创作时把用户原始短剧需求和唯一剧本 `asset_id` 通过 `+submit-run --asset-ids` 发给后端;同一 `thread_id` 后续续写或修改不再追加新的剧本文件。
|
|
328
|
-
3. **传话、取文件、下载资源**:根据
|
|
328
|
+
3. **传话、取文件、下载资源**:根据 `get-thread` 返回的 `readable_text` 展示进展、问题和结果;遇到表单、问卷、选项或按钮时,只做流程合理性清洗,不替用户决定创作内容;根据 `list-thread-file` 获取文件列表;再根据 `download_url` 调用 `download-result` 把缺失资源下载到用户本地。
|
|
329
329
|
|
|
330
330
|
**不要做的事:**
|
|
331
331
|
|
|
@@ -340,10 +340,10 @@ Thread: thread_...
|
|
|
340
340
|
|
|
341
341
|
- `--message` 是用户的原始短剧需求,不能为空。
|
|
342
342
|
- 查询进展时优先使用 `+submit-run` 返回的 `thread_id` 和 `run_id`;如果需要查看整个会话,可以省略 `--run-id`。
|
|
343
|
-
-
|
|
344
|
-
-
|
|
345
|
-
-
|
|
343
|
+
- `get-thread` 当前固定走服务端 v2 响应,输出字段是 `readable_text`;不要解析旧版 `messages` 数组。
|
|
344
|
+
- `upload-file` 当前用于短剧场景文件上传链路,只支持本地 `.doc` / `.docx` / `.txt` 文件;`--path` 不能为空,路径必须指向真实文件,不能是目录。
|
|
345
|
+
- `upload-file` 上传成功后只返回 `asset_id`;把该值原样作为 `+submit-run --asset-ids` 的参数。
|
|
346
346
|
- 单次创作会话中(相同 `thread_id`),`+submit-run` 只支持绑定一个剧本文件。不要在同一 `thread_id` 下重复上传并追加第二个剧本 `asset_id`;用户给多个剧本时,先让用户选择一个,或分别开启新的创作会话。
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
-
-
|
|
347
|
+
- `list-thread-file` 只需要 `thread_id`;分页参数使用 `--page-num 1 --page-size 200` 起步,`total` 达到 200 时下一轮递增 `page-num`。
|
|
348
|
+
- `list-thread-file` 和 `download-result` 是两个不同的 CLI 指令:前者获取会话文件元信息,后者下载 URL 资源并写入到本地目标文件路径。
|
|
349
|
+
- `download-result` 接收 `--url`、`--output-path`、`--updated-at`、`--workers`;`--output-path` 必须是包含文件名的目标文件路径。
|