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