diff --git a/crates/api_clients/seedance2pro_client/src/requests/generate_image/generate_image.rs b/crates/api_clients/seedance2pro_client/src/requests/generate_image/generate_image.rs index de9ed82b47..bb00ae4f5d 100644 --- a/crates/api_clients/seedance2pro_client/src/requests/generate_image/generate_image.rs +++ b/crates/api_clients/seedance2pro_client/src/requests/generate_image/generate_image.rs @@ -53,13 +53,16 @@ pub struct KinoviGenerateImageRequest { /// Optional negative prompt. Sent as the `"no"` field on the wire. pub negative_prompt: Option, + /// "0 = literal, 1000 = artistic" /// Midjourney "stylize" parameter. Valid range 0–1000. `None` omits the /// field and lets Kinovi pick its default. pub stylize: Option, + /// "Unconventional aesthetics" /// Midjourney "weird" parameter. Valid range 0–3000. pub weird: Option, + /// "Variation between images" /// Midjourney "chaos" parameter. Valid range 0–100. pub chaos: Option, @@ -71,6 +74,10 @@ pub struct KinoviGenerateImageRequest { /// Number of distinct generations to enqueue in a single request. pub batch_count: KinoviMidjourneyBatchCount, + + /// Optional reference image URLs. Sent on the wire as `uploadedUrls`. + /// Midjourney uses these as visual inspiration alongside the prompt. + pub reference_image_urls: Option>, } impl std::fmt::Debug for KinoviGenerateImageRequest { @@ -86,6 +93,7 @@ impl std::fmt::Debug for KinoviGenerateImageRequest { .field("quality", &self.quality) .field("raw_mode", &self.raw_mode) .field("batch_count", &self.batch_count) + .field("reference_image_urls", &self.reference_image_urls) .finish() } } @@ -245,6 +253,10 @@ pub async fn generate_image(args: GenerateImageArgs<'_>) -> Result) -> Result 1 { Some(batch_count_value) } else { None }; let style = if req.raw_mode { Some("raw") } else { None }; + let uploaded_urls = req.reference_image_urls.filter(|urls| !urls.is_empty()); let request_body = BatchRequest { zero: BatchRequestInner { @@ -493,6 +508,7 @@ mod tests { style, negative_prompt: req.negative_prompt, batch_count, + uploaded_urls, }, }, }, @@ -516,6 +532,7 @@ mod tests { quality: None, raw_mode: false, batch_count: KinoviMidjourneyBatchCount::One, + reference_image_urls: None, } } @@ -588,7 +605,7 @@ mod tests { fn optionals_omitted_when_none() { let p = api_params(minimal_request(KinoviMidjourneyModel::V7, "p")); let obj = p.as_object().unwrap(); - for key in ["stylize", "chaos", "weird", "quality", "style", "no", "batchCount"] { + for key in ["stylize", "chaos", "weird", "quality", "style", "no", "batchCount", "uploadedUrls"] { assert!(!obj.contains_key(key), "expected `{}` to be omitted in minimal request", key); } } @@ -728,6 +745,7 @@ mod tests { quality: Some(KinoviMidjourneyQuality::Half), raw_mode: true, batch_count: KinoviMidjourneyBatchCount::One, + reference_image_urls: None, }; let got = api_params(req); let expected: Value = serde_json::from_str( @@ -750,6 +768,7 @@ mod tests { quality: Some(KinoviMidjourneyQuality::Full), raw_mode: false, batch_count: KinoviMidjourneyBatchCount::Four, + reference_image_urls: None, }; let got = api_params(req); let expected: Value = serde_json::from_str( @@ -793,6 +812,7 @@ mod tests { quality: None, raw_mode: false, batch_count: KinoviMidjourneyBatchCount::One, + reference_image_urls: None, }, }; let result = generate_image(args).await?; @@ -822,6 +842,7 @@ mod tests { quality: None, raw_mode: false, batch_count: KinoviMidjourneyBatchCount::One, + reference_image_urls: None, }, }; let result = generate_image(args).await?; @@ -841,7 +862,7 @@ mod tests { host_override: None, request: KinoviGenerateImageRequest { model: KinoviMidjourneyModel::V8, - prompt: "abandoned k-mart, overgrown vines, retrofuturist signage".to_string(), + prompt: "A magical shiba inu sorcerer casting spells in a crystal cave".to_string(), aspect_ratio: KinoviMidjourneyAspectRatio::Square1x1, negative_prompt: Some("dark, gloomy".to_string()), stylize: Some(730), @@ -850,6 +871,7 @@ mod tests { quality: Some(KinoviMidjourneyQuality::Half), raw_mode: true, batch_count: KinoviMidjourneyBatchCount::One, + reference_image_urls: None, }, }; let result = generate_image(args).await?; @@ -878,6 +900,7 @@ mod tests { quality: Some(KinoviMidjourneyQuality::Full), raw_mode: false, batch_count: KinoviMidjourneyBatchCount::Four, + reference_image_urls: None, }, }; let result = generate_image(args).await?; diff --git a/crates/api_clients/seedance2pro_client/src/requests/generate_image/request_types.rs b/crates/api_clients/seedance2pro_client/src/requests/generate_image/request_types.rs index 701c007d5a..86bf4943d5 100644 --- a/crates/api_clients/seedance2pro_client/src/requests/generate_image/request_types.rs +++ b/crates/api_clients/seedance2pro_client/src/requests/generate_image/request_types.rs @@ -50,6 +50,11 @@ pub (super) struct ApiParams { /// (Midjourney returns four images per task regardless). #[serde(rename = "batchCount", skip_serializing_if = "Option::is_none")] pub batch_count: Option, + + /// Reference image URLs. When present, Midjourney uses them as visual + /// inspiration alongside the prompt. + #[serde(rename = "uploadedUrls", skip_serializing_if = "Option::is_none")] + pub uploaded_urls: Option>, } #[derive(Deserialize, Debug)]