Skip to main content

quantum_sdk/
image.rs

1use serde::{Deserialize, Deserializer, Serialize};
2
3use crate::client::Client;
4use crate::error::Result;
5
6/// Deserialize null as Default::default() (e.g. null → empty Vec).
7fn 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/// Request body for image generation.
16#[derive(Debug, Clone, Serialize, Default)]
17pub struct ImageRequest {
18    /// Image generation model (e.g. "grok-imagine-image", "gpt-image-1", "dall-e-3").
19    pub model: String,
20
21    /// Describes the image to generate.
22    pub prompt: String,
23
24    /// Number of images to generate (default 1).
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub count: Option<i32>,
27
28    /// Output dimensions (e.g. "1024x1024", "1536x1024").
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub size: Option<String>,
31
32    /// Aspect ratio (e.g. "16:9", "1:1").
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub aspect_ratio: Option<String>,
35
36    /// Quality level (e.g. "standard", "hd").
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub quality: Option<String>,
39
40    /// Image format (e.g. "png", "jpeg", "webp").
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub output_format: Option<String>,
43
44    /// Style preset (e.g. "vivid", "natural"). DALL-E 3 specific.
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub style: Option<String>,
47
48    /// Background mode (e.g. "auto", "transparent", "opaque"). GPT-Image specific.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub background: Option<String>,
51
52    /// Image URL or data URI for image-to-3D conversion (Meshy).
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub image_url: Option<String>,
55
56    // ── Meshy 3D generation options ──
57
58    /// Mesh topology: "triangle" or "quad".
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub topology: Option<String>,
61
62    /// Target polygon count (100-300,000).
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub target_polycount: Option<i32>,
65
66    /// Symmetry mode: "auto", "on", or "off".
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub symmetry_mode: Option<String>,
69
70    /// Pose mode: "", "a-pose", or "t-pose".
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub pose_mode: Option<String>,
73
74    /// Generate PBR texture maps (base_color, metallic, roughness, normal).
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub enable_pbr: Option<bool>,
77}
78
79/// Response from image generation.
80#[derive(Debug, Clone, Deserialize)]
81pub struct ImageResponse {
82    /// Generated images.
83    #[serde(default, deserialize_with = "deserialize_null_as_default")]
84    pub images: Vec<GeneratedImage>,
85
86    /// Model that generated the images.
87    #[serde(default)]
88    pub model: String,
89
90    /// Total cost in ticks.
91    #[serde(default)]
92    pub cost_ticks: i64,
93
94    /// Unique request identifier.
95    #[serde(default)]
96    pub request_id: String,
97}
98
99/// A single generated image.
100#[derive(Debug, Clone, Deserialize)]
101pub struct GeneratedImage {
102    /// Base64-encoded image data.
103    pub base64: String,
104
105    /// Image format (e.g. "png", "jpeg").
106    pub format: String,
107
108    /// Image index within the batch.
109    pub index: i32,
110}
111
112/// Request body for image editing.
113#[derive(Debug, Clone, Serialize, Default)]
114pub struct ImageEditRequest {
115    /// Editing model (e.g. "gpt-image-1", "grok-imagine-image").
116    pub model: String,
117
118    /// Describes the desired edit.
119    pub prompt: String,
120
121    /// Base64-encoded input images.
122    pub input_images: Vec<String>,
123
124    /// Number of edited images to generate (default 1).
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub count: Option<i32>,
127
128    /// Output dimensions.
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub size: Option<String>,
131}
132
133/// Response from image editing (same shape as generation).
134pub type ImageEditResponse = ImageResponse;
135
136impl Client {
137    /// Generates images from a text prompt.
138    pub async fn generate_image(&self, req: &ImageRequest) -> Result<ImageResponse> {
139        let (mut resp, meta) = self
140            .post_json::<ImageRequest, ImageResponse>("/qai/v1/images/generate", req)
141            .await?;
142        if resp.cost_ticks == 0 {
143            resp.cost_ticks = meta.cost_ticks;
144        }
145        if resp.request_id.is_empty() {
146            resp.request_id = meta.request_id;
147        }
148        Ok(resp)
149    }
150
151    /// Edits images using an AI model.
152    pub async fn edit_image(&self, req: &ImageEditRequest) -> Result<ImageEditResponse> {
153        let (mut resp, meta) = self
154            .post_json::<ImageEditRequest, ImageEditResponse>("/qai/v1/images/edit", req)
155            .await?;
156        if resp.cost_ticks == 0 {
157            resp.cost_ticks = meta.cost_ticks;
158        }
159        if resp.request_id.is_empty() {
160            resp.request_id = meta.request_id;
161        }
162        Ok(resp)
163    }
164}