@pippit-dev/cli 1.0.2 → 1.0.4
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 +1 -1
- package/checksums.txt +6 -6
- package/cmd/generate_video/query_result.go +17 -21
- package/cmd/generate_video_test.go +136 -29
- package/internal/generate_video/query_result.go +114 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -238,7 +238,7 @@ pippit-tool-cli query-result \
|
|
|
238
238
|
--download-dir "./output"
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
-
`query-result` 会查询指定 Run
|
|
241
|
+
`query-result` 会查询指定 Run 并输出 JSON。Run 成功完成后下载视频产物,`completed=true`,`videos` 中只包含 `download_url` 和 `output_path`;Run 失败也视为终态,`completed=true` 且填充 `error_message`;Run 未到终态时 `completed=false`。
|
|
242
242
|
|
|
243
243
|
## HTTP 客户端
|
|
244
244
|
|
package/checksums.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
889fabfbe3bd94cfeb34f9e3ee5fd9c2b39d5ab57f13e8b2c257dfca142bb2ce pippit-tool-cli-1.0.4-darwin-amd64.tar.gz
|
|
2
|
+
8c54acc247b9f7050697fd2c52f3df84558fcfe01db3c7e186b4741294738236 pippit-tool-cli-1.0.4-darwin-arm64.tar.gz
|
|
3
|
+
45de9614ed9b4241775f0451e603ab833484810def2eed31e4b3673a9cfe283d pippit-tool-cli-1.0.4-linux-amd64.tar.gz
|
|
4
|
+
44852e1542b44085d543f2f2672ae9793e6e6bf7457ec90f7efcf46e3e252820 pippit-tool-cli-1.0.4-linux-arm64.tar.gz
|
|
5
|
+
4b921069137fbe129973dbceae7fdbaa8a8f5cc2803ff3347b1e21452c3b9946 pippit-tool-cli-1.0.4-windows-amd64.zip
|
|
6
|
+
adf357fab99845e47933669d0fef8395189e023f69546454f61e4aca14392195 pippit-tool-cli-1.0.4-windows-arm64.zip
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package generate_video
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"
|
|
4
|
+
"encoding/json"
|
|
5
5
|
"io"
|
|
6
6
|
"strings"
|
|
7
7
|
|
|
@@ -26,27 +26,11 @@ func NewQueryResultCommand(stdout, stderr io.Writer, runner *common.Runner) *cob
|
|
|
26
26
|
"run_id": strings.TrimSpace(opts.RunID),
|
|
27
27
|
"download_dir": strings.TrimSpace(opts.DownloadDir),
|
|
28
28
|
})
|
|
29
|
-
|
|
29
|
+
result = queryResultFromError(err, opts)
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
_, err = fmt.Fprintln(stdout, "Run 已完成,产物已下载:")
|
|
36
|
-
if err != nil {
|
|
37
|
-
return err
|
|
38
|
-
}
|
|
39
|
-
for i, path := range result.OutputPaths {
|
|
40
|
-
if _, err := fmt.Fprintln(stdout, path); err != nil {
|
|
41
|
-
return err
|
|
42
|
-
}
|
|
43
|
-
if i < len(result.DownloadURLs) && strings.TrimSpace(result.DownloadURLs[i]) != "" {
|
|
44
|
-
if _, err := fmt.Fprintf(stdout, "download_url: %s\n", result.DownloadURLs[i]); err != nil {
|
|
45
|
-
return err
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return nil
|
|
31
|
+
encoder := json.NewEncoder(stdout)
|
|
32
|
+
encoder.SetIndent("", " ")
|
|
33
|
+
return encoder.Encode(result)
|
|
50
34
|
},
|
|
51
35
|
}
|
|
52
36
|
cmd.SetOut(stdout)
|
|
@@ -56,3 +40,15 @@ func NewQueryResultCommand(stdout, stderr io.Writer, runner *common.Runner) *cob
|
|
|
56
40
|
cmd.Flags().StringVar(&opts.DownloadDir, "download-dir", "", "directory to download completed videos into")
|
|
57
41
|
return cmd
|
|
58
42
|
}
|
|
43
|
+
|
|
44
|
+
func queryResultFromError(err error, opts *internalgen.QueryResultOptions) *internalgen.QueryResultResult {
|
|
45
|
+
result := &internalgen.QueryResultResult{
|
|
46
|
+
ErrorMessage: err.Error(),
|
|
47
|
+
Videos: []internalgen.QueryResultVideo{},
|
|
48
|
+
}
|
|
49
|
+
if opts != nil {
|
|
50
|
+
result.ThreadID = strings.TrimSpace(opts.ThreadID)
|
|
51
|
+
result.RunID = strings.TrimSpace(opts.RunID)
|
|
52
|
+
}
|
|
53
|
+
return result
|
|
54
|
+
}
|
|
@@ -332,10 +332,34 @@ func TestQueryResultDownloadsCompletedVideo(t *testing.T) {
|
|
|
332
332
|
if !requestedDownload {
|
|
333
333
|
t.Fatal("download endpoint was not requested")
|
|
334
334
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
335
|
+
got := decodeJSON(t, stdout.Bytes())
|
|
336
|
+
if got["completed"] != true {
|
|
337
|
+
t.Fatalf("completed = %v, want true", got["completed"])
|
|
338
|
+
}
|
|
339
|
+
if got["thread_id"] != "thread_123" || got["run_id"] != "run_456" {
|
|
340
|
+
t.Fatalf("ids = (%v, %v), want thread/run ids", got["thread_id"], got["run_id"])
|
|
341
|
+
}
|
|
342
|
+
if _, ok := got["state"]; ok {
|
|
343
|
+
t.Fatalf("state should not be returned: %#v", got)
|
|
344
|
+
}
|
|
345
|
+
if got["error_message"] != "" {
|
|
346
|
+
t.Fatalf("error_message = %v, want empty", got["error_message"])
|
|
347
|
+
}
|
|
348
|
+
videos, ok := got["videos"].([]any)
|
|
349
|
+
if !ok || len(videos) != 1 {
|
|
350
|
+
t.Fatalf("videos = %#v, want one video", got["videos"])
|
|
351
|
+
}
|
|
352
|
+
video, ok := videos[0].(map[string]any)
|
|
353
|
+
if !ok {
|
|
354
|
+
t.Fatalf("video = %#v, want object", videos[0])
|
|
355
|
+
}
|
|
356
|
+
if video["download_url"] != downloadURL || video["output_path"] != outputPath {
|
|
357
|
+
t.Fatalf("video = %#v, want download_url/output_path", video)
|
|
358
|
+
}
|
|
359
|
+
for _, unwanted := range []string{"vid", "asset_id", "title"} {
|
|
360
|
+
if _, ok := video[unwanted]; ok {
|
|
361
|
+
t.Fatalf("video = %#v, should not contain %s", video, unwanted)
|
|
362
|
+
}
|
|
339
363
|
}
|
|
340
364
|
assertFileContent(t, outputPath, "video-data")
|
|
341
365
|
}
|
|
@@ -360,30 +384,64 @@ func TestQueryResultIgnoresVideoDataWithoutVideoSubType(t *testing.T) {
|
|
|
360
384
|
"--download-dir", t.TempDir(),
|
|
361
385
|
})
|
|
362
386
|
|
|
363
|
-
err := root.Execute()
|
|
364
|
-
|
|
365
|
-
|
|
387
|
+
if err := root.Execute(); err != nil {
|
|
388
|
+
t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
|
|
389
|
+
}
|
|
390
|
+
got := decodeJSON(t, stdout.Bytes())
|
|
391
|
+
if got["completed"] != false {
|
|
392
|
+
t.Fatalf("completed = %v, want false", got["completed"])
|
|
393
|
+
}
|
|
394
|
+
if got["thread_id"] != "thread_123" || got["run_id"] != "run_456" {
|
|
395
|
+
t.Fatalf("ids = (%v, %v), want thread/run ids", got["thread_id"], got["run_id"])
|
|
366
396
|
}
|
|
367
|
-
if
|
|
368
|
-
t.Fatalf("
|
|
397
|
+
if got["error_message"] != "下载失败:未找到可下载的视频产物" {
|
|
398
|
+
t.Fatalf("error_message = %v, want no downloadable video error", got["error_message"])
|
|
369
399
|
}
|
|
370
|
-
|
|
371
|
-
|
|
400
|
+
videos, ok := got["videos"].([]any)
|
|
401
|
+
if !ok || len(videos) != 0 {
|
|
402
|
+
t.Fatalf("videos = %#v, want empty", got["videos"])
|
|
372
403
|
}
|
|
373
404
|
}
|
|
374
405
|
|
|
375
|
-
func
|
|
406
|
+
func TestQueryResultValidationErrorReturnsJSON(t *testing.T) {
|
|
407
|
+
var stdout, stderr bytes.Buffer
|
|
408
|
+
root := newTestRootCommand(t, &stdout, &stderr, "http://127.0.0.1")
|
|
409
|
+
root.SetArgs([]string{
|
|
410
|
+
"query-result",
|
|
411
|
+
"--run-id", "run_456",
|
|
412
|
+
"--download-dir", t.TempDir(),
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
if err := root.Execute(); err != nil {
|
|
416
|
+
t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
|
|
417
|
+
}
|
|
418
|
+
got := decodeJSON(t, stdout.Bytes())
|
|
419
|
+
if got["completed"] != false {
|
|
420
|
+
t.Fatalf("completed = %v, want false", got["completed"])
|
|
421
|
+
}
|
|
422
|
+
if got["thread_id"] != "" || got["run_id"] != "run_456" {
|
|
423
|
+
t.Fatalf("ids = (%v, %v), want empty thread_id and run_id", got["thread_id"], got["run_id"])
|
|
424
|
+
}
|
|
425
|
+
if got["error_message"] != "查询失败:缺少必填参数 --thread-id" {
|
|
426
|
+
t.Fatalf("error_message = %v, want validation error", got["error_message"])
|
|
427
|
+
}
|
|
428
|
+
videos, ok := got["videos"].([]any)
|
|
429
|
+
if !ok || len(videos) != 0 {
|
|
430
|
+
t.Fatalf("videos = %#v, want empty", got["videos"])
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
func TestQueryResultGetThreadBusinessErrorReturnsErrorMessage(t *testing.T) {
|
|
376
435
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
377
436
|
switch r.URL.Path {
|
|
378
437
|
case "/api/biz/v1/skill/get_thread":
|
|
379
|
-
_, _ = w.Write([]byte(`{"ret":"5","errmsg":"
|
|
438
|
+
_, _ = w.Write([]byte(`{"ret":"5","errmsg":"创作失败:暂时无法生成","log_id":"log_456"}`))
|
|
380
439
|
default:
|
|
381
440
|
t.Fatalf("unexpected path %s", r.URL.Path)
|
|
382
441
|
}
|
|
383
442
|
}))
|
|
384
443
|
defer server.Close()
|
|
385
444
|
|
|
386
|
-
clearDailyErrorLog(t)
|
|
387
445
|
var stdout, stderr bytes.Buffer
|
|
388
446
|
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
389
447
|
root.SetArgs([]string{
|
|
@@ -393,23 +451,61 @@ func TestQueryResultErrorLogIncludesLogID(t *testing.T) {
|
|
|
393
451
|
"--download-dir", t.TempDir(),
|
|
394
452
|
})
|
|
395
453
|
|
|
396
|
-
err := root.Execute()
|
|
397
|
-
|
|
398
|
-
t.Fatal("Execute() error = nil, want get_thread error")
|
|
454
|
+
if err := root.Execute(); err != nil {
|
|
455
|
+
t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
|
|
399
456
|
}
|
|
400
|
-
|
|
401
|
-
if
|
|
402
|
-
t.Fatalf("
|
|
457
|
+
got := decodeJSON(t, stdout.Bytes())
|
|
458
|
+
if got["completed"] != true {
|
|
459
|
+
t.Fatalf("completed = %v, want true", got["completed"])
|
|
403
460
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
461
|
+
if got["thread_id"] != "thread_123" || got["run_id"] != "run_456" {
|
|
462
|
+
t.Fatalf("ids = (%v, %v), want thread/run ids", got["thread_id"], got["run_id"])
|
|
463
|
+
}
|
|
464
|
+
if got["error_message"] != "创作失败:暂时无法生成 log_id=log_456" {
|
|
465
|
+
t.Fatalf("error_message = %v, want get_thread business error", got["error_message"])
|
|
466
|
+
}
|
|
467
|
+
videos, ok := got["videos"].([]any)
|
|
468
|
+
if !ok || len(videos) != 0 {
|
|
469
|
+
t.Fatalf("videos = %#v, want empty", got["videos"])
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
func TestQueryResultFailedReturnsErrorMessage(t *testing.T) {
|
|
474
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
475
|
+
switch r.URL.Path {
|
|
476
|
+
case "/api/biz/v1/skill/get_thread":
|
|
477
|
+
_, _ = w.Write([]byte(`{"ret":"0","errmsg":"","data":{"thread":{"thread_id":"thread_123","run_list":[{"run_id":"run_456","state":4,"entry_list":[{"artifact":{"content":[{"sub_type":"biz/x_data_video","data":"{\"error_message\":\"生成失败\",\"error_code\":\"11001\"}"}]}}]}]}}}`))
|
|
478
|
+
default:
|
|
479
|
+
t.Fatalf("unexpected path %s", r.URL.Path)
|
|
480
|
+
}
|
|
481
|
+
}))
|
|
482
|
+
defer server.Close()
|
|
483
|
+
|
|
484
|
+
var stdout, stderr bytes.Buffer
|
|
485
|
+
root := newTestRootCommand(t, &stdout, &stderr, server.URL)
|
|
486
|
+
root.SetArgs([]string{
|
|
487
|
+
"query-result",
|
|
488
|
+
"--thread-id", "thread_123",
|
|
489
|
+
"--run-id", "run_456",
|
|
490
|
+
"--download-dir", t.TempDir(),
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
if err := root.Execute(); err != nil {
|
|
494
|
+
t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
|
|
495
|
+
}
|
|
496
|
+
got := decodeJSON(t, stdout.Bytes())
|
|
497
|
+
if got["completed"] != true {
|
|
498
|
+
t.Fatalf("completed = %v, want true", got["completed"])
|
|
407
499
|
}
|
|
408
|
-
if
|
|
409
|
-
t.Fatalf("
|
|
500
|
+
if _, ok := got["state"]; ok {
|
|
501
|
+
t.Fatalf("state should not be returned: %#v", got)
|
|
410
502
|
}
|
|
411
|
-
if
|
|
412
|
-
t.Fatalf("
|
|
503
|
+
if got["error_message"] != "生成失败 (error_code=11001)" {
|
|
504
|
+
t.Fatalf("error_message = %v, want failure message", got["error_message"])
|
|
505
|
+
}
|
|
506
|
+
videos, ok := got["videos"].([]any)
|
|
507
|
+
if !ok || len(videos) != 0 {
|
|
508
|
+
t.Fatalf("videos = %#v, want empty", got["videos"])
|
|
413
509
|
}
|
|
414
510
|
}
|
|
415
511
|
|
|
@@ -436,8 +532,19 @@ func TestQueryResultPendingDoesNotDownload(t *testing.T) {
|
|
|
436
532
|
if err := root.Execute(); err != nil {
|
|
437
533
|
t.Fatalf("Execute() error = %v, stderr = %s", err, stderr.String())
|
|
438
534
|
}
|
|
439
|
-
|
|
440
|
-
|
|
535
|
+
got := decodeJSON(t, stdout.Bytes())
|
|
536
|
+
if got["completed"] != false {
|
|
537
|
+
t.Fatalf("completed = %v, want false", got["completed"])
|
|
538
|
+
}
|
|
539
|
+
if _, ok := got["state"]; ok {
|
|
540
|
+
t.Fatalf("state should not be returned: %#v", got)
|
|
541
|
+
}
|
|
542
|
+
if got["error_message"] != "" {
|
|
543
|
+
t.Fatalf("error_message = %v, want empty", got["error_message"])
|
|
544
|
+
}
|
|
545
|
+
videos, ok := got["videos"].([]any)
|
|
546
|
+
if !ok || len(videos) != 0 {
|
|
547
|
+
t.Fatalf("videos = %#v, want empty", got["videos"])
|
|
441
548
|
}
|
|
442
549
|
}
|
|
443
550
|
|
|
@@ -3,6 +3,7 @@ package generate_video
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
5
|
"encoding/json"
|
|
6
|
+
"errors"
|
|
6
7
|
"fmt"
|
|
7
8
|
"path/filepath"
|
|
8
9
|
"strconv"
|
|
@@ -12,7 +13,10 @@ import (
|
|
|
12
13
|
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
13
14
|
)
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
+
const (
|
|
17
|
+
successRunState = 3
|
|
18
|
+
failedRunState = 4
|
|
19
|
+
)
|
|
16
20
|
|
|
17
21
|
// QueryResultOptions is the command-facing request shape for query-result.
|
|
18
22
|
type QueryResultOptions struct {
|
|
@@ -23,10 +27,17 @@ type QueryResultOptions struct {
|
|
|
23
27
|
|
|
24
28
|
// QueryResultResult describes the user-facing query-result outcome.
|
|
25
29
|
type QueryResultResult struct {
|
|
26
|
-
Completed bool
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
Completed bool `json:"completed"`
|
|
31
|
+
ThreadID string `json:"thread_id"`
|
|
32
|
+
RunID string `json:"run_id"`
|
|
33
|
+
ErrorMessage string `json:"error_message"`
|
|
34
|
+
Videos []QueryResultVideo `json:"videos"`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// QueryResultVideo describes a downloaded video from query-result.
|
|
38
|
+
type QueryResultVideo struct {
|
|
39
|
+
DownloadURL string `json:"download_url"`
|
|
40
|
+
OutputPath string `json:"output_path"`
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
type queryThread struct {
|
|
@@ -35,9 +46,12 @@ type queryThread struct {
|
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
type queryRun struct {
|
|
38
|
-
RunID
|
|
39
|
-
State
|
|
40
|
-
|
|
49
|
+
RunID string `json:"run_id"`
|
|
50
|
+
State int `json:"state"`
|
|
51
|
+
ErrorMessage string `json:"error_message"`
|
|
52
|
+
ErrorMsg string `json:"error_msg"`
|
|
53
|
+
Errmsg string `json:"errmsg"`
|
|
54
|
+
EntryList []queryEntry `json:"entry_list"`
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
type queryEntry struct {
|
|
@@ -54,7 +68,9 @@ type queryContent struct {
|
|
|
54
68
|
}
|
|
55
69
|
|
|
56
70
|
type queryContentData struct {
|
|
57
|
-
Video
|
|
71
|
+
Video *queryVideo `json:"video"`
|
|
72
|
+
ErrorMessage string `json:"error_message"`
|
|
73
|
+
ErrorCode json.RawMessage `json:"error_code"`
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
type queryVideo struct {
|
|
@@ -74,6 +90,9 @@ func QueryResult(ctx context.Context, opts *QueryResultOptions, runner *common.R
|
|
|
74
90
|
RunID: opts.RunID,
|
|
75
91
|
}, runner)
|
|
76
92
|
if err != nil {
|
|
93
|
+
if result, ok := queryResultFromGetThreadBusinessError(err, opts); ok {
|
|
94
|
+
return result, nil
|
|
95
|
+
}
|
|
77
96
|
return nil, fmt.Errorf("查询失败:%w", err)
|
|
78
97
|
}
|
|
79
98
|
|
|
@@ -86,11 +105,17 @@ func QueryResult(ctx context.Context, opts *QueryResultOptions, runner *common.R
|
|
|
86
105
|
if !ok {
|
|
87
106
|
return nil, fmt.Errorf("查询失败:未找到 run_id=%s 对应的 Run", opts.RunID)
|
|
88
107
|
}
|
|
89
|
-
if run.State !=
|
|
90
|
-
|
|
91
|
-
Completed:
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
if run.State != successRunState {
|
|
109
|
+
result := &QueryResultResult{
|
|
110
|
+
Completed: run.State == failedRunState,
|
|
111
|
+
ThreadID: firstNonEmpty(thread.ThreadID, opts.ThreadID),
|
|
112
|
+
RunID: opts.RunID,
|
|
113
|
+
Videos: []QueryResultVideo{},
|
|
114
|
+
}
|
|
115
|
+
if run.State == failedRunState {
|
|
116
|
+
result.ErrorMessage = firstNonEmpty(extractQueryErrorMessage(run), "Run 失败")
|
|
117
|
+
}
|
|
118
|
+
return result, nil
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
videos := extractQueryVideos(run)
|
|
@@ -103,14 +128,12 @@ func QueryResult(ctx context.Context, opts *QueryResultOptions, runner *common.R
|
|
|
103
128
|
return nil, fmt.Errorf("下载失败:解析下载目录失败:%w", err)
|
|
104
129
|
}
|
|
105
130
|
|
|
106
|
-
|
|
107
|
-
downloadURLs := make([]string, 0, len(videos))
|
|
131
|
+
resultVideos := make([]QueryResultVideo, 0, len(videos))
|
|
108
132
|
usedNames := make(map[string]int, len(videos))
|
|
109
133
|
for i, video := range videos {
|
|
110
134
|
if strings.TrimSpace(video.DownloadURL) == "" {
|
|
111
135
|
return nil, fmt.Errorf("下载失败:第 %d 个视频产物 download_url 为空", i+1)
|
|
112
136
|
}
|
|
113
|
-
downloadURLs = append(downloadURLs, video.DownloadURL)
|
|
114
137
|
outputPath := filepath.Join(downloadDir, uniqueQueryResultFileName(videoFileName(video, i+1), usedNames))
|
|
115
138
|
download, err := common.DownloadResult(ctx, common.DownloadResultOptions{
|
|
116
139
|
URL: video.DownloadURL,
|
|
@@ -120,25 +143,55 @@ func QueryResult(ctx context.Context, opts *QueryResultOptions, runner *common.R
|
|
|
120
143
|
if err != nil {
|
|
121
144
|
return nil, fmt.Errorf("下载失败:%w", err)
|
|
122
145
|
}
|
|
146
|
+
actualOutputPath := outputPath
|
|
123
147
|
if len(download.Downloaded) > 0 {
|
|
124
|
-
|
|
125
|
-
|
|
148
|
+
actualOutputPath = download.Downloaded[0]
|
|
149
|
+
} else if len(download.AlreadyExist) > 0 {
|
|
150
|
+
actualOutputPath = download.AlreadyExist[0]
|
|
126
151
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
outputPaths = append(outputPaths, outputPath)
|
|
152
|
+
resultVideos = append(resultVideos, QueryResultVideo{
|
|
153
|
+
DownloadURL: video.DownloadURL,
|
|
154
|
+
OutputPath: actualOutputPath,
|
|
155
|
+
})
|
|
132
156
|
}
|
|
133
157
|
|
|
134
158
|
return &QueryResultResult{
|
|
135
|
-
Completed:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
159
|
+
Completed: true,
|
|
160
|
+
ThreadID: firstNonEmpty(thread.ThreadID, opts.ThreadID),
|
|
161
|
+
RunID: opts.RunID,
|
|
162
|
+
Videos: resultVideos,
|
|
139
163
|
}, nil
|
|
140
164
|
}
|
|
141
165
|
|
|
166
|
+
func queryResultFromGetThreadBusinessError(err error, opts *QueryResultOptions) (*QueryResultResult, bool) {
|
|
167
|
+
var logErr *common.LogIDError
|
|
168
|
+
if !errors.As(err, &logErr) {
|
|
169
|
+
return nil, false
|
|
170
|
+
}
|
|
171
|
+
message := getThreadBusinessErrorMessage(logErr.Message)
|
|
172
|
+
if message == "" {
|
|
173
|
+
message = "查询失败"
|
|
174
|
+
}
|
|
175
|
+
if logID := logErr.LogID(); logID != "" {
|
|
176
|
+
message = fmt.Sprintf("%s log_id=%s", message, logID)
|
|
177
|
+
}
|
|
178
|
+
return &QueryResultResult{
|
|
179
|
+
Completed: true,
|
|
180
|
+
ThreadID: opts.ThreadID,
|
|
181
|
+
RunID: opts.RunID,
|
|
182
|
+
ErrorMessage: message,
|
|
183
|
+
Videos: []QueryResultVideo{},
|
|
184
|
+
}, true
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
func getThreadBusinessErrorMessage(message string) string {
|
|
188
|
+
message = strings.TrimSpace(message)
|
|
189
|
+
if idx := strings.Index(message, "errmsg="); idx >= 0 {
|
|
190
|
+
return strings.TrimSpace(message[idx+len("errmsg="):])
|
|
191
|
+
}
|
|
192
|
+
return message
|
|
193
|
+
}
|
|
194
|
+
|
|
142
195
|
func validateQueryResultOptions(opts *QueryResultOptions) error {
|
|
143
196
|
if opts == nil {
|
|
144
197
|
return fmt.Errorf("查询失败:缺少必填参数 --thread-id")
|
|
@@ -235,6 +288,39 @@ func extractQueryVideos(run queryRun) []queryVideo {
|
|
|
235
288
|
return videos
|
|
236
289
|
}
|
|
237
290
|
|
|
291
|
+
func extractQueryErrorMessage(run queryRun) string {
|
|
292
|
+
if message := firstNonEmpty(run.ErrorMessage, run.ErrorMsg, run.Errmsg); message != "" {
|
|
293
|
+
return message
|
|
294
|
+
}
|
|
295
|
+
for _, entry := range run.EntryList {
|
|
296
|
+
for _, content := range entry.Artifact.Content {
|
|
297
|
+
data := content.Data
|
|
298
|
+
if message := firstNonEmpty(data.ErrorMessage); message != "" {
|
|
299
|
+
if code := rawMessageString(data.ErrorCode); code != "" {
|
|
300
|
+
return fmt.Sprintf("%s (error_code=%s)", message, code)
|
|
301
|
+
}
|
|
302
|
+
return message
|
|
303
|
+
}
|
|
304
|
+
if code := rawMessageString(data.ErrorCode); code != "" {
|
|
305
|
+
return "error_code=" + code
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return ""
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
func rawMessageString(raw json.RawMessage) string {
|
|
313
|
+
raw = json.RawMessage(strings.TrimSpace(string(raw)))
|
|
314
|
+
if len(raw) == 0 || string(raw) == "null" {
|
|
315
|
+
return ""
|
|
316
|
+
}
|
|
317
|
+
var value string
|
|
318
|
+
if err := json.Unmarshal(raw, &value); err == nil {
|
|
319
|
+
return strings.TrimSpace(value)
|
|
320
|
+
}
|
|
321
|
+
return strings.TrimSpace(string(raw))
|
|
322
|
+
}
|
|
323
|
+
|
|
238
324
|
func videoFileName(video queryVideo, index int) string {
|
|
239
325
|
name := firstNonEmpty(video.VID, video.Title, video.AssetID)
|
|
240
326
|
if name == "" {
|