use serde::{Deserialize, Serialize};
use crate::chat::ChatRequest;
use crate::client::Client;
use crate::error::Result;
#[derive(Debug, Clone, Serialize)]
pub struct JobCreateRequest {
#[serde(rename = "type")]
pub job_type: String,
pub params: serde_json::Value,
}
#[derive(Debug, Clone, Deserialize)]
pub struct JobCreateResponse {
pub job_id: String,
#[serde(default)]
pub status: String,
#[serde(rename = "type", default)]
pub job_type: Option<String>,
#[serde(default)]
pub request_id: Option<String>,
#[serde(default)]
pub created_at: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct JobStatusResponse {
pub job_id: String,
pub status: String,
#[serde(rename = "type", default)]
pub job_type: Option<String>,
#[serde(default)]
pub result: Option<serde_json::Value>,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub cost_ticks: i64,
#[serde(default)]
pub request_id: Option<String>,
#[serde(default)]
pub created_at: Option<String>,
#[serde(default)]
pub started_at: Option<String>,
#[serde(default)]
pub completed_at: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct JobSummary {
pub job_id: String,
pub status: String,
#[serde(rename = "type", default)]
pub job_type: Option<String>,
#[serde(default)]
pub created_at: Option<String>,
#[serde(default)]
pub completed_at: Option<String>,
#[serde(default)]
pub cost_ticks: i64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ListJobsResponse {
pub jobs: Vec<JobSummary>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct JobStreamEvent {
#[serde(rename = "type", default)]
pub event_type: String,
#[serde(default)]
pub job_id: Option<String>,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub result: Option<serde_json::Value>,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub cost_ticks: i64,
#[serde(default)]
pub completed_at: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct JobAcceptedResponse {
pub job_id: String,
#[serde(default)]
pub status: String,
#[serde(rename = "type", default)]
pub job_type: Option<String>,
#[serde(default)]
pub request_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct JobListEntry {
pub job_id: String,
#[serde(rename = "type", default)]
pub job_type: Option<String>,
pub status: String,
#[serde(default)]
pub result: Option<serde_json::Value>,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub cost_ticks: i64,
#[serde(default)]
pub created_at: Option<String>,
#[serde(default)]
pub started_at: Option<String>,
#[serde(default)]
pub completed_at: Option<String>,
#[serde(default)]
pub request_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct JobListResponse {
pub jobs: Vec<JobListEntry>,
#[serde(default)]
pub request_id: Option<String>,
}
impl Client {
pub async fn create_job(&self, req: &JobCreateRequest) -> Result<JobCreateResponse> {
let (resp, _meta) = self
.post_json::<JobCreateRequest, JobCreateResponse>("/qai/v1/jobs", req)
.await?;
Ok(resp)
}
pub async fn get_job(&self, job_id: &str) -> Result<JobStatusResponse> {
let path = format!("/qai/v1/jobs/{job_id}");
let (resp, _meta) = self.get_json::<JobStatusResponse>(&path).await?;
Ok(resp)
}
pub async fn list_jobs(&self) -> Result<ListJobsResponse> {
let (resp, _meta) = self
.get_json::<ListJobsResponse>("/qai/v1/jobs")
.await?;
Ok(resp)
}
pub async fn stream_job(
&self,
job_id: &str,
) -> Result<reqwest::Response> {
let path = format!("/qai/v1/jobs/{job_id}/stream");
let (resp, _meta) = self.get_stream_raw(&path).await?;
Ok(resp)
}
pub async fn poll_job(
&self,
job_id: &str,
poll_interval: std::time::Duration,
max_attempts: usize,
) -> Result<JobStatusResponse> {
for _ in 0..max_attempts {
tokio::time::sleep(poll_interval).await;
let status = self.get_job(job_id).await?;
match status.status.as_str() {
"completed" | "failed" => return Ok(status),
_ => continue,
}
}
Ok(JobStatusResponse {
job_id: job_id.to_string(),
status: "timeout".to_string(),
job_type: None,
result: None,
error: Some(format!("Job polling timed out after {max_attempts} attempts")),
cost_ticks: 0,
request_id: None,
created_at: None,
started_at: None,
completed_at: None,
})
}
pub async fn generate_3d(
&self,
model: &str,
prompt: Option<&str>,
image_url: Option<&str>,
) -> Result<JobCreateResponse> {
let mut params = serde_json::json!({ "model": model });
if let Some(p) = prompt {
params["prompt"] = serde_json::Value::String(p.to_string());
}
if let Some(u) = image_url {
params["image_url"] = serde_json::Value::String(u.to_string());
}
let req = JobCreateRequest {
job_type: "3d/generate".to_string(),
params,
};
self.create_job(&req).await
}
pub async fn chat_job(&self, req: &ChatRequest) -> Result<JobCreateResponse> {
let params = serde_json::to_value(req)?;
let job_req = JobCreateRequest {
job_type: "chat".to_string(),
params,
};
self.create_job(&job_req).await
}
}