package cli import ( "encoding/json" "fmt" "net/url" "time" "github.com/spf13/cobra" "gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi" ) // newMeshJobsCmd is the 3D models clients asked for, as jobs: every owner's, // what each is doing, and a way to stop one. (ctl jobs is the control // plane's own work, serving and updating models.) func newMeshJobsCmd(client func() *ctlClient, asJSON *bool) *cobra.Command { cmd := &cobra.Command{ Use: "3d", Short: "3D models clients asked for: the jobs, how far along each is, and stopping one", } var status string var limit int list := &cobra.Command{ Use: "list", Short: "Every owner's 3D jobs, newest first", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { q := url.Values{} if status != "" { q.Set("status", status) } if limit > 0 { q.Set("limit", fmt.Sprint(limit)) } var jobs []adminapi.JobView if err := client().get(cmd.Context(), "/api/v1/client-jobs?"+q.Encode(), &jobs); err != nil { return err } if *asJSON { return emitJSON(jobs) } if len(jobs) == 0 { fmt.Fprintln(cmd.OutOrStdout(), "no 3D jobs") return nil } w := tabTo(cmd.OutOrStdout()) fmt.Fprintln(w, "JOB\tMODEL\tSTATUS\tPROGRESS\tOWNER\tASKED") for _, j := range jobs { fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s ago\n", j.ID, j.Model, j.Status, progressText(j), j.Owner, time.Since(j.CreatedAt).Round(time.Second)) } return w.Flush() }, } list.Flags().StringVar(&status, "status", "", "only jobs in this status: queued, running, completed, failed…") list.Flags().IntVar(&limit, "limit", 0, "the most jobs to show (50)") get := &cobra.Command{ Use: "get JOB", Short: "One 3D job: its status, progress, result or error", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { var j adminapi.JobView if err := client().get(cmd.Context(), "/api/v1/client-jobs/"+url.PathEscape(args[0]), &j); err != nil { return err } if *asJSON { return emitJSON(j) } out := cmd.OutOrStdout() fmt.Fprintf(out, "job %s\nmodel %s\nstatus %s\n", j.ID, j.Model, j.Status) if p := progressText(j); p != "" { fmt.Fprintf(out, "progress %s\n", p) } fmt.Fprintf(out, "owner %s (key %s)\nasked %s\n", j.Owner, j.KeyID, j.CreatedAt.UTC().Format(time.RFC3339)) if j.FinishedAt != nil && j.StartedAt != nil { fmt.Fprintf(out, "took %s\n", j.FinishedAt.Sub(*j.StartedAt).Round(time.Second)) } if j.OutputFileID != "" { fmt.Fprintf(out, "result %s (%s)\n", j.OutputFileID, j.ContentType) } if j.Error != "" { fmt.Fprintf(out, "error %s\n", j.Error) } return nil }, } cancel := &cobra.Command{ Use: "cancel JOB", Short: "Stop a 3D job, in the engine's queue too", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { var j adminapi.JobView if err := client().post(cmd.Context(), "/api/v1/client-jobs/"+url.PathEscape(args[0])+"/cancel", nil, &j); err != nil { return err } fmt.Fprintf(cmd.OutOrStdout(), "%s is %s\n", j.ID, j.Status) return nil }, } cmd.AddCommand(list, get, cancel) return cmd } // progressText is a job's progress as a person reads it. func progressText(j adminapi.JobView) string { if len(j.Progress) == 0 { return "" } var p struct { State string `json:"state"` Ahead int `json:"ahead"` NodeClass string `json:"node_class"` Step int `json:"step"` Steps int `json:"steps"` Percent int `json:"percent"` } if json.Unmarshal(j.Progress, &p) != nil { return "" } switch { case p.State == "queued" && p.Ahead > 0: return fmt.Sprintf("queued, %d ahead", p.Ahead) case p.State == "running" && p.Steps > 0: return fmt.Sprintf("running %d%%, %s step %d/%d", p.Percent, p.NodeClass, p.Step, p.Steps) case p.State == "running" && p.NodeClass != "": return "running " + p.NodeClass } return p.State }