1use serde::{Deserialize, Deserializer, Serialize};
2
3use crate::client::Client;
4use crate::error::Result;
5
6fn deserialize_null_as_default<'de, D, T>(deserializer: D) -> std::result::Result<T, D::Error>
8where
9 D: Deserializer<'de>,
10 T: Default + Deserialize<'de>,
11{
12 Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
13}
14
15#[derive(Debug, Clone, Serialize, Default)]
17pub struct ImageRequest {
18 pub model: String,
20
21 pub prompt: String,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub count: Option<i32>,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub size: Option<String>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub aspect_ratio: Option<String>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub quality: Option<String>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub output_format: Option<String>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub style: Option<String>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub background: Option<String>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub image_url: Option<String>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
60 pub topology: Option<String>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub target_polycount: Option<i32>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub symmetry_mode: Option<String>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub pose_mode: Option<String>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub enable_pbr: Option<bool>,
77}
78
79#[derive(Debug, Clone, Deserialize)]
81pub struct ImageResponse {
82 #[serde(default, deserialize_with = "deserialize_null_as_default")]
84 pub images: Vec<GeneratedImage>,
85
86 #[serde(default)]
88 pub model: String,
89
90 #[serde(default)]
92 pub cost_ticks: i64,
93
94 #[serde(default)]
97 pub balance_after: i64,
98
99 #[serde(default)]
101 pub request_id: String,
102}
103
104#[derive(Debug, Clone, Deserialize)]
106pub struct GeneratedImage {
107 pub base64: String,
109
110 pub format: String,
112
113 pub index: i32,
115}
116
117#[derive(Debug, Clone, Serialize, Default)]
119pub struct ImageEditRequest {
120 pub model: String,
122
123 pub prompt: String,
125
126 pub input_images: Vec<String>,
128
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub count: Option<i32>,
132
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub size: Option<String>,
136}
137
138pub type ImageEditResponse = ImageResponse;
140
141impl Client {
142 pub async fn generate_image(&self, req: &ImageRequest) -> Result<ImageResponse> {
144 let (mut resp, meta) = self
145 .post_json::<ImageRequest, ImageResponse>("/qai/v1/images/generate", req)
146 .await?;
147 if resp.cost_ticks == 0 {
148 resp.cost_ticks = meta.cost_ticks;
149 }
150 if resp.balance_after == 0 {
151 resp.balance_after = meta.balance_after;
152 }
153 if resp.request_id.is_empty() {
154 resp.request_id = meta.request_id;
155 }
156 Ok(resp)
157 }
158
159 pub async fn edit_image(&self, req: &ImageEditRequest) -> Result<ImageEditResponse> {
161 let (mut resp, meta) = self
162 .post_json::<ImageEditRequest, ImageEditResponse>("/qai/v1/images/edit", req)
163 .await?;
164 if resp.cost_ticks == 0 {
165 resp.cost_ticks = meta.cost_ticks;
166 }
167 if resp.balance_after == 0 {
168 resp.balance_after = meta.balance_after;
169 }
170 if resp.request_id.is_empty() {
171 resp.request_id = meta.request_id;
172 }
173 Ok(resp)
174 }
175}