1use serde::{Deserialize, Serialize};
2
3use crate::client::Client;
4use crate::error::Result;
5
6#[derive(Debug, Clone, Serialize)]
8pub struct JobCreateRequest {
9 #[serde(rename = "type")]
11 pub job_type: String,
12
13 pub params: serde_json::Value,
15}
16
17#[derive(Debug, Clone, Deserialize)]
19pub struct JobCreateResponse {
20 pub job_id: String,
21 #[serde(default)]
22 pub status: String,
23}
24
25#[derive(Debug, Clone, Deserialize)]
27pub struct JobStatusResponse {
28 pub job_id: String,
29 pub status: String,
30 #[serde(default)]
31 pub result: Option<serde_json::Value>,
32 #[serde(default)]
33 pub error: Option<String>,
34 #[serde(default)]
35 pub cost_ticks: i64,
36}
37
38#[derive(Debug, Clone, Deserialize)]
40pub struct JobSummary {
41 pub job_id: String,
42 pub status: String,
43 #[serde(rename = "type", default)]
44 pub job_type: Option<String>,
45 #[serde(default)]
46 pub created_at: Option<String>,
47 #[serde(default)]
48 pub completed_at: Option<String>,
49 #[serde(default)]
50 pub cost_ticks: i64,
51}
52
53#[derive(Debug, Clone, Deserialize)]
55pub struct ListJobsResponse {
56 pub jobs: Vec<JobSummary>,
57}
58
59impl Client {
60 pub async fn create_job(&self, req: &JobCreateRequest) -> Result<JobCreateResponse> {
62 let (resp, _meta) = self
63 .post_json::<JobCreateRequest, JobCreateResponse>("/qai/v1/jobs", req)
64 .await?;
65 Ok(resp)
66 }
67
68 pub async fn get_job(&self, job_id: &str) -> Result<JobStatusResponse> {
70 let path = format!("/qai/v1/jobs/{job_id}");
71 let (resp, _meta) = self.get_json::<JobStatusResponse>(&path).await?;
72 Ok(resp)
73 }
74
75 pub async fn list_jobs(&self) -> Result<ListJobsResponse> {
77 let (resp, _meta) = self
78 .get_json::<ListJobsResponse>("/qai/v1/jobs")
79 .await?;
80 Ok(resp)
81 }
82
83 pub async fn stream_job(
86 &self,
87 job_id: &str,
88 ) -> Result<reqwest::Response> {
89 let path = format!("/qai/v1/jobs/{job_id}/stream");
90 let (resp, _meta) = self.get_stream_raw(&path).await?;
91 Ok(resp)
92 }
93
94 pub async fn poll_job(
97 &self,
98 job_id: &str,
99 poll_interval: std::time::Duration,
100 max_attempts: usize,
101 ) -> Result<JobStatusResponse> {
102 for _ in 0..max_attempts {
103 tokio::time::sleep(poll_interval).await;
104 let status = self.get_job(job_id).await?;
105 match status.status.as_str() {
106 "completed" | "failed" => return Ok(status),
107 _ => continue,
108 }
109 }
110 Ok(JobStatusResponse {
111 job_id: job_id.to_string(),
112 status: "timeout".to_string(),
113 result: None,
114 error: Some(format!("Job polling timed out after {max_attempts} attempts")),
115 cost_ticks: 0,
116 })
117 }
118
119 pub async fn generate_3d(
124 &self,
125 model: &str,
126 prompt: Option<&str>,
127 image_url: Option<&str>,
128 ) -> Result<JobCreateResponse> {
129 let mut params = serde_json::json!({ "model": model });
130 if let Some(p) = prompt {
131 params["prompt"] = serde_json::Value::String(p.to_string());
132 }
133 if let Some(u) = image_url {
134 params["image_url"] = serde_json::Value::String(u.to_string());
135 }
136 let req = JobCreateRequest {
137 job_type: "3d/generate".to_string(),
138 params,
139 };
140 self.create_job(&req).await
141 }
142}