package cmd import ( "encoding/json" "fmt" "os" "strings" "time" "aigcpanel-cli/internal" "github.com/spf13/cobra" ) var taskCmd = &cobra.Command{ Use: "task", Short: "Submit a task and wait for result", Long: `Submit a task to AigcPanel and poll until completion. Usage: aigcpanel task --biz [--key value ...] aigcpanel task --biz --task-id --stage [--key value ...] Flags: --biz Required. Task type (e.g. VideoCompress, SoundGenerate) --task-id Task ID of a paused task to continue (used with --stage) --stage Stage name to continue (e.g. Config, Confirm) For long parameter values, use --key-json /path/to/file.json to read from a JSON file. Examples: aigcpanel task --biz VideoCompress --file /path/to/video.mp4 aigcpanel task --biz SoundGenerate --text "Hello world" aigcpanel task --biz VideoZoom --video /path/to/video.mp4 aigcpanel task --biz VideoZoom --task-id 123 --stage Config --times-json ./_temp/times.json`, DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { biz, taskId, stage, stageData, modelConfig, helpRequested, err := parseTaskArgs(args) if err != nil { return err } if helpRequested { return cmd.Help() } if biz == "" { return fmt.Errorf("--biz is required") } cfg, err := internal.LoadAuthConfig() if err != nil { return err } // Continue a paused task if taskId != "" && stage != "" { continueResult, err := internal.DoRequest(cfg, "/api/task/continue", map[string]any{ "taskId": taskId, "stage": stage, "data": stageData, }) if err != nil { return err } cCode, _ := continueResult["code"].(float64) if cCode != 0 { return internal.PrintJSON(continueResult) } // Poll for result after continue return pollTask(cfg, taskId) } // Submit new task submitResult, err := internal.DoRequest(cfg, "/api/task/submit", map[string]any{ "biz": biz, "modelConfig": modelConfig, }) if err != nil { return err } code, _ := submitResult["code"].(float64) if code != 0 { return internal.PrintJSON(submitResult) } dataMap, _ := submitResult["data"].(map[string]any) newTaskId, _ := dataMap["taskId"].(string) if newTaskId == "" { return fmt.Errorf("no taskId returned from task submit") } return pollTask(cfg, newTaskId) }, } // pollTask polls /api/model/query until the task reaches a terminal state. func pollTask(cfg *internal.AuthConfig, taskId string) error { deadline := time.Now().Add(120 * time.Second) for time.Now().Before(deadline) { queryResult, err := internal.DoRequest(cfg, "/api/model/query", map[string]any{ "taskId": taskId, }) if err != nil { return err } qCode, _ := queryResult["code"].(float64) if qCode != 0 { return internal.PrintJSON(queryResult) } qData, _ := queryResult["data"].(map[string]any) status, _ := qData["status"].(string) switch status { case "success", "pause": return internal.PrintJSON(queryResult) case "error", "fail": return internal.PrintJSON(queryResult) default: time.Sleep(500 * time.Millisecond) continue } } return internal.PrintJSON(map[string]any{ "code": -1, "msg": "timeout waiting for task result", }) } // parseTaskArgs manually parses --key value style arguments. // Reserved flags: --biz, --task-id, --stage. // Flags ending in -json are treated as JSON file paths. // All other --key value pairs become either modelConfig or stageData entries // depending on whether --stage is provided. func parseTaskArgs(args []string) (biz, taskId, stage string, stageData map[string]any, modelConfig map[string]any, helpRequested bool, err error) { modelConfig = map[string]any{} stageData = map[string]any{} // First pass: extract reserved flags type kv struct{ key, value string } var pairs []kv i := 0 for i < len(args) { arg := args[i] if arg == "--help" || arg == "-h" { return "", "", "", nil, nil, true, nil } if !strings.HasPrefix(arg, "--") { i++ continue } key := strings.TrimPrefix(arg, "--") if i+1 >= len(args) || strings.HasPrefix(args[i+1], "--") { fmt.Fprintf(os.Stderr, "warning: flag --%s has no value, ignoring\n", key) i++ continue } value := args[i+1] i += 2 switch key { case "biz": biz = value case "task-id": taskId = value case "stage": stage = value default: pairs = append(pairs, kv{key, value}) } } // Second pass: distribute remaining flags to modelConfig or stageData for _, p := range pairs { key, value := p.key, p.value // JSON file flag: --key-json /path/to/file.json if strings.HasSuffix(key, "-json") { realKey := strings.TrimSuffix(key, "-json") fileBytes, readErr := os.ReadFile(value) if readErr != nil { err = fmt.Errorf("cannot read JSON file for --%s: %w", key, readErr) return } var parsed any if jsonErr := json.Unmarshal(fileBytes, &parsed); jsonErr != nil { err = fmt.Errorf("invalid JSON in file for --%s: %w", key, jsonErr) return } if stage != "" { stageData[realKey] = parsed } else { modelConfig[realKey] = parsed } continue } // Regular flag: auto-parse JSON arrays/objects trimmed := strings.TrimSpace(value) var parsedValue any if (strings.HasPrefix(trimmed, "[") && strings.HasSuffix(trimmed, "]")) || (strings.HasPrefix(trimmed, "{") && strings.HasSuffix(trimmed, "}")) { var parsed any if jsonErr := json.Unmarshal([]byte(trimmed), &parsed); jsonErr == nil { parsedValue = parsed } else { parsedValue = value } } else { parsedValue = value } if stage != "" { stageData[key] = parsedValue } else { modelConfig[key] = parsedValue } } return biz, taskId, stage, stageData, modelConfig, false, nil }