From b52b7060541bbb60152cc4ba8ab5ec8cb8df296f Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 12 Jul 2026 21:30:31 -0400 Subject: [PATCH] client --- .../image/generate_seedream_5p0_pro.rs | 620 ++++++++++++++++++ .../src/generate/image/mod.rs | 1 + 2 files changed, 621 insertions(+) create mode 100644 crates/api_clients/seedance2pro_client/src/generate/image/generate_seedream_5p0_pro.rs diff --git a/crates/api_clients/seedance2pro_client/src/generate/image/generate_seedream_5p0_pro.rs b/crates/api_clients/seedance2pro_client/src/generate/image/generate_seedream_5p0_pro.rs new file mode 100644 index 0000000000..77ec2c67bb --- /dev/null +++ b/crates/api_clients/seedance2pro_client/src/generate/image/generate_seedream_5p0_pro.rs @@ -0,0 +1,620 @@ +use serde_derive::Serialize; + +use crate::cost::kinovi_generation_cost::KinoviGenerationCost; +use crate::creds::seedance2pro_session::Seedance2ProSession; +use crate::error::seedance2pro_client_error::Seedance2ProClientError; +use crate::error::seedance2pro_error::Seedance2ProError; +use crate::requests::kinovi_host::KinoviHost; +use crate::requests::workflow_run_task::workflow_run_task::{ + workflow_run_task_custom, WorkflowRunTaskCustomArgs, WorkflowRunTaskResponse, +}; + +const BUSINESS_TYPE: &str = "seedream-5-pro-image-generation"; +const MODEL: &str = "seedream-5.0-pro"; + +/// Kinovi allows at most this many reference images per request. +pub const MAX_REFERENCE_IMAGES: usize = 14; + +/// Credits per generated image at 1k resolution. +const CREDITS_PER_IMAGE_1K: u64 = 14; + +/// Credits per generated image at 2k resolution. +const CREDITS_PER_IMAGE_2K: u64 = 24; + +// ── Args ── + +pub struct GenerateSeedream5p0ProArgs<'a> { + pub request: GenerateSeedream5p0ProRequest, + pub session: &'a Seedance2ProSession, + pub host_override: Option, +} + +// ── Request ── + +/// Seedream 5.0 Pro: text-to-image (optionally with reference images). +#[derive(Clone, Debug)] +pub struct GenerateSeedream5p0ProRequest { + /// Reference images are cited in the prompt as @Image1, @Image2, etc. + pub prompt: String, + + pub aspect_ratio: KinoviSeedream5p0ProAspectRatio, + + pub resolution: KinoviSeedream5p0ProResolution, + + /// Number of images to generate in a single request. + pub batch_count: KinoviSeedream5p0ProBatchCount, + + /// Optional reference image URLs, up to [`MAX_REFERENCE_IMAGES`]. + /// Referenced in prompts as @Image1, @Image2, etc. + pub reference_image_urls: Option>, +} + +// ── Enums ── + +/// Aspect ratios supported by Seedream 5.0 Pro. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum KinoviSeedream5p0ProAspectRatio { + /// Let the model pick (useful with reference images). + Auto, + Square1x1, + Standard4x3, + Portrait3x4, + Landscape16x9, + Portrait9x16, + Wide3x2, + Tall2x3, + UltraWide21x9, +} + +/// Output resolution. Determines the per-image credit price. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum KinoviSeedream5p0ProResolution { + /// 1k (14 credits per image) + OneK, + /// 2k (24 credits per image) + TwoK, +} + +/// Number of images to generate in a single request. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum KinoviSeedream5p0ProBatchCount { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, +} + +// ── Pricing ── +// +// Seedream 5.0 Pro credit pricing (per image; batch count multiplies): +// +// | Resolution | Credits/image | +// |------------|---------------| +// | 1k | 14 | +// | 2k | 24 | +// +// Reference images do not affect cost. +// Credit package: 525,000 credits for $2,159.0909 (~243.16 credits/$1, rounded down to 243). + +impl GenerateSeedream5p0ProRequest { + /// Calculate the cost of this generation request, in Kinovi credits and + /// USD cents. + pub fn calculate_costs(&self) -> KinoviGenerationCost { + let credits_per_image = match self.resolution { + KinoviSeedream5p0ProResolution::OneK => CREDITS_PER_IMAGE_1K, + KinoviSeedream5p0ProResolution::TwoK => CREDITS_PER_IMAGE_2K, + }; + let total_credits = credits_per_image * u64::from(self.batch_count.as_u8()); + KinoviGenerationCost::from_kinovi_credits(total_credits) + } +} + +impl KinoviSeedream5p0ProBatchCount { + fn as_u8(&self) -> u8 { + match self { + Self::One => 1, + Self::Two => 2, + Self::Three => 3, + Self::Four => 4, + Self::Five => 5, + Self::Six => 6, + Self::Seven => 7, + Self::Eight => 8, + } + } +} + +impl KinoviSeedream5p0ProAspectRatio { + fn as_api_str(&self) -> &'static str { + match self { + Self::Auto => "auto", + Self::Square1x1 => "1:1", + Self::Standard4x3 => "4:3", + Self::Portrait3x4 => "3:4", + Self::Landscape16x9 => "16:9", + Self::Portrait9x16 => "9:16", + Self::Wide3x2 => "3:2", + Self::Tall2x3 => "2:3", + Self::UltraWide21x9 => "21:9", + } + } +} + +impl KinoviSeedream5p0ProResolution { + fn as_api_str(&self) -> &'static str { + match self { + Self::OneK => "1k", + Self::TwoK => "2k", + } + } +} + +// ── Response ── + +pub struct GenerateSeedream5p0ProResponse { + pub task_id: String, + pub order_id: String, + + /// Present when batch_count > 1. + pub task_ids: Option>, + + /// Present when batch_count > 1. + pub order_ids: Option>, +} + +// ── Entry point ── + +pub async fn generate_seedream_5p0_pro( + args: GenerateSeedream5p0ProArgs<'_>, +) -> Result { + let api_params = build_api_params(&args.request)?; + + let raw_response: WorkflowRunTaskResponse = workflow_run_task_custom(WorkflowRunTaskCustomArgs { + business_type: BUSINESS_TYPE, + api_params, + session: args.session, + host_override: args.host_override, + }).await?; + + Ok(GenerateSeedream5p0ProResponse { + task_id: raw_response.task_id, + order_id: raw_response.order_id, + task_ids: raw_response.task_ids, + order_ids: raw_response.order_ids, + }) +} + +// ── Wire payload ── + +/// The `apiParams` shape for `seedream-5-pro-image-generation`. Field order +/// matches the captured browser traffic. +#[derive(Serialize, Debug)] +struct Seedream5p0ProApiParams { + prompt: String, + #[serde(rename = "aspectRatio")] + aspect_ratio: &'static str, + resolution: &'static str, + model: &'static str, + /// Omitted when generating a single image. + #[serde(rename = "batchCount", skip_serializing_if = "Option::is_none")] + batch_count: Option, + /// Omitted when no reference images are attached. + #[serde(rename = "uploadedUrls", skip_serializing_if = "Option::is_none")] + uploaded_urls: Option>, +} + +fn build_api_params( + request: &GenerateSeedream5p0ProRequest, +) -> Result { + let uploaded_urls = match &request.reference_image_urls { + Some(urls) if urls.len() > MAX_REFERENCE_IMAGES => { + return Err(Seedance2ProClientError::InvalidRequestField { + field: "reference_image_urls", + raw_value: format!("{} urls", urls.len()), + reason: format!("At most {} reference images are allowed", MAX_REFERENCE_IMAGES), + }); + } + Some(urls) if !urls.is_empty() => Some(urls.clone()), + _ => None, + }; + + let batch_count_value = request.batch_count.as_u8(); + let batch_count = if batch_count_value > 1 { Some(batch_count_value) } else { None }; + + Ok(Seedream5p0ProApiParams { + prompt: request.prompt.clone(), + aspect_ratio: request.aspect_ratio.as_api_str(), + resolution: request.resolution.as_api_str(), + model: MODEL, + batch_count, + uploaded_urls, + }) +} + +// ── Tests ── + +#[cfg(test)] +mod tests { + use super::*; + + fn base_request() -> GenerateSeedream5p0ProRequest { + GenerateSeedream5p0ProRequest { + prompt: "An anime girl riding a dinosaur".to_string(), + aspect_ratio: KinoviSeedream5p0ProAspectRatio::Auto, + resolution: KinoviSeedream5p0ProResolution::TwoK, + batch_count: KinoviSeedream5p0ProBatchCount::One, + reference_image_urls: None, + } + } + + mod request_shape_tests { + use super::*; + + /// Mirrors capture 1_seedream_5.txt (auto aspect ratio, 2k, single image, + /// no references). + #[test] + fn auto_2k_single_image() { + let params = build_api_params(&base_request()).unwrap(); + assert_eq!( + serde_json::to_string(¶ms).unwrap(), + r#"{"prompt":"An anime girl riding a dinosaur","aspectRatio":"auto","resolution":"2k","model":"seedream-5.0-pro"}"#, + ); + } + + /// Mirrors capture 2_seedream_5_21x9.txt (21:9, 2k, single image). + #[test] + fn ultra_wide_21x9_2k() { + let params = build_api_params(&GenerateSeedream5p0ProRequest { + prompt: "a pirate sailing a pirate ship".to_string(), + aspect_ratio: KinoviSeedream5p0ProAspectRatio::UltraWide21x9, + resolution: KinoviSeedream5p0ProResolution::TwoK, + batch_count: KinoviSeedream5p0ProBatchCount::One, + reference_image_urls: None, + }).unwrap(); + assert_eq!( + serde_json::to_string(¶ms).unwrap(), + r#"{"prompt":"a pirate sailing a pirate ship","aspectRatio":"21:9","resolution":"2k","model":"seedream-5.0-pro"}"#, + ); + } + + /// Mirrors capture 3_seedream_5_9x16_image_refs.txt (9:16, 1k, single + /// image, two reference images). + #[test] + fn portrait_9x16_1k_with_image_references() { + let params = build_api_params(&GenerateSeedream5p0ProRequest { + prompt: "The man in @Image1 is staring out the door in @Image2 ".to_string(), + aspect_ratio: KinoviSeedream5p0ProAspectRatio::Portrait9x16, + resolution: KinoviSeedream5p0ProResolution::OneK, + batch_count: KinoviSeedream5p0ProBatchCount::One, + reference_image_urls: Some(vec![ + "https://static.seedance2-pro.com/materials/20260713/1783904468032-0a803390.jpg".to_string(), + "https://static.seedance2-pro.com/materials/20260713/1783904481112-4a3fa0d2.png".to_string(), + ]), + }).unwrap(); + assert_eq!( + serde_json::to_string(¶ms).unwrap(), + r#"{"prompt":"The man in @Image1 is staring out the door in @Image2 ","aspectRatio":"9:16","resolution":"1k","model":"seedream-5.0-pro","uploadedUrls":["https://static.seedance2-pro.com/materials/20260713/1783904468032-0a803390.jpg","https://static.seedance2-pro.com/materials/20260713/1783904481112-4a3fa0d2.png"]}"#, + ); + } + + /// Mirrors capture 4_seedream_5_misc.txt (2:3, 2k, batch of 4, two + /// reference images). + #[test] + fn tall_2x3_2k_batch_four_with_image_references() { + let params = build_api_params(&GenerateSeedream5p0ProRequest { + prompt: "An anime girl is standing in the door @Image1 Outside the door, there's the galaxy and windmill @Image2 ".to_string(), + aspect_ratio: KinoviSeedream5p0ProAspectRatio::Tall2x3, + resolution: KinoviSeedream5p0ProResolution::TwoK, + batch_count: KinoviSeedream5p0ProBatchCount::Four, + reference_image_urls: Some(vec![ + "https://static.seedance2-pro.com/materials/20260713/1783904481112-4a3fa0d2.png".to_string(), + "https://static.seedance2-pro.com/materials/20260713/1783904588294-5c713908.jpg".to_string(), + ]), + }).unwrap(); + assert_eq!( + serde_json::to_string(¶ms).unwrap(), + r#"{"prompt":"An anime girl is standing in the door @Image1 Outside the door, there's the galaxy and windmill @Image2 ","aspectRatio":"2:3","resolution":"2k","model":"seedream-5.0-pro","batchCount":4,"uploadedUrls":["https://static.seedance2-pro.com/materials/20260713/1783904481112-4a3fa0d2.png","https://static.seedance2-pro.com/materials/20260713/1783904588294-5c713908.jpg"]}"#, + ); + } + + /// Mirrors capture 5_seedream_5_misc2.txt (1:1, 1k, batch of 8, two + /// reference images). + #[test] + fn square_1x1_1k_batch_eight_with_image_references() { + let params = build_api_params(&GenerateSeedream5p0ProRequest { + prompt: "The mirror from @Image2 is in front of the windmill in @Image1 . The sky and galaxy are lit up at night. ".to_string(), + aspect_ratio: KinoviSeedream5p0ProAspectRatio::Square1x1, + resolution: KinoviSeedream5p0ProResolution::OneK, + batch_count: KinoviSeedream5p0ProBatchCount::Eight, + reference_image_urls: Some(vec![ + "https://static.seedance2-pro.com/materials/20260713/1783904715554-1a7ce415.jpg".to_string(), + "https://static.seedance2-pro.com/materials/20260713/1783904719453-1d10e81f.png".to_string(), + ]), + }).unwrap(); + assert_eq!( + serde_json::to_string(¶ms).unwrap(), + r#"{"prompt":"The mirror from @Image2 is in front of the windmill in @Image1 . The sky and galaxy are lit up at night. ","aspectRatio":"1:1","resolution":"1k","model":"seedream-5.0-pro","batchCount":8,"uploadedUrls":["https://static.seedance2-pro.com/materials/20260713/1783904715554-1a7ce415.jpg","https://static.seedance2-pro.com/materials/20260713/1783904719453-1d10e81f.png"]}"#, + ); + } + + #[test] + fn every_aspect_ratio_wire_value() { + let cases = [ + (KinoviSeedream5p0ProAspectRatio::Auto, "auto"), + (KinoviSeedream5p0ProAspectRatio::Square1x1, "1:1"), + (KinoviSeedream5p0ProAspectRatio::Standard4x3, "4:3"), + (KinoviSeedream5p0ProAspectRatio::Portrait3x4, "3:4"), + (KinoviSeedream5p0ProAspectRatio::Landscape16x9, "16:9"), + (KinoviSeedream5p0ProAspectRatio::Portrait9x16, "9:16"), + (KinoviSeedream5p0ProAspectRatio::Wide3x2, "3:2"), + (KinoviSeedream5p0ProAspectRatio::Tall2x3, "2:3"), + (KinoviSeedream5p0ProAspectRatio::UltraWide21x9, "21:9"), + ]; + for (variant, expected) in cases { + assert_eq!(variant.as_api_str(), expected, "wire value for {variant:?}"); + } + } + + #[test] + fn every_resolution_wire_value() { + assert_eq!(KinoviSeedream5p0ProResolution::OneK.as_api_str(), "1k"); + assert_eq!(KinoviSeedream5p0ProResolution::TwoK.as_api_str(), "2k"); + } + + #[test] + fn every_batch_count_value() { + let cases = [ + (KinoviSeedream5p0ProBatchCount::One, 1), + (KinoviSeedream5p0ProBatchCount::Two, 2), + (KinoviSeedream5p0ProBatchCount::Three, 3), + (KinoviSeedream5p0ProBatchCount::Four, 4), + (KinoviSeedream5p0ProBatchCount::Five, 5), + (KinoviSeedream5p0ProBatchCount::Six, 6), + (KinoviSeedream5p0ProBatchCount::Seven, 7), + (KinoviSeedream5p0ProBatchCount::Eight, 8), + ]; + for (variant, expected) in cases { + assert_eq!(variant.as_u8(), expected, "value for {variant:?}"); + } + } + + #[test] + fn batch_count_one_omits_field() { + let params = build_api_params(&base_request()).unwrap(); + assert_eq!(params.batch_count, None); + } + + #[test] + fn empty_reference_list_omits_uploaded_urls() { + let params = build_api_params(&GenerateSeedream5p0ProRequest { + reference_image_urls: Some(vec![]), + ..base_request() + }).unwrap(); + let json = serde_json::to_string(¶ms).unwrap(); + assert!(!json.contains("uploadedUrls"), "expected no uploadedUrls in {json}"); + } + + #[test] + fn business_type() { + assert_eq!(BUSINESS_TYPE, "seedream-5-pro-image-generation"); + } + } + + mod reference_image_limit_tests { + use super::*; + + #[test] + fn fourteen_references_is_allowed() { + let params = build_api_params(&request_with_n_references(MAX_REFERENCE_IMAGES)).unwrap(); + assert_eq!(params.uploaded_urls.map(|urls| urls.len()), Some(MAX_REFERENCE_IMAGES)); + } + + #[test] + fn fifteen_references_is_rejected() { + let result = build_api_params(&request_with_n_references(MAX_REFERENCE_IMAGES + 1)); + match result { + Err(Seedance2ProClientError::InvalidRequestField { field, .. }) => { + assert_eq!(field, "reference_image_urls"); + } + other => panic!("expected InvalidRequestField, got {other:?}"), + } + } + + fn request_with_n_references(count: usize) -> GenerateSeedream5p0ProRequest { + GenerateSeedream5p0ProRequest { + reference_image_urls: Some( + (0..count).map(|i| format!("https://example.com/ref{i}.jpg")).collect(), + ), + ..base_request() + } + } + } + + // ── Pricing ── + // + // 1k = 14 credits/image, 2k = 24 credits/image; batch count multiplies. + // Verified against captured traffic: 2k x 4 images = 96 credits + // (4_seedream_5_misc.txt), 1k x 8 images = 112 credits + // (5_seedream_5_misc2.txt). + + mod pricing_tests { + use super::*; + + #[test] + fn one_1k_image_is_fourteen_credits() { + let costs = request(KinoviSeedream5p0ProResolution::OneK, KinoviSeedream5p0ProBatchCount::One).calculate_costs(); + assert_eq!(costs.kinovi_credits, 14); + } + + #[test] + fn one_2k_image_is_twentyfour_credits() { + let costs = request(KinoviSeedream5p0ProResolution::TwoK, KinoviSeedream5p0ProBatchCount::One).calculate_costs(); + assert_eq!(costs.kinovi_credits, 24); + } + + /// Mirrors capture 4_seedream_5_misc.txt: 2k, batch of 4 → 96 credits. + #[test] + fn four_2k_images_is_ninetysix_credits() { + let costs = request(KinoviSeedream5p0ProResolution::TwoK, KinoviSeedream5p0ProBatchCount::Four).calculate_costs(); + assert_eq!(costs.kinovi_credits, 96); + } + + /// Mirrors capture 5_seedream_5_misc2.txt: 1k, batch of 8 → 112 credits. + #[test] + fn eight_1k_images_is_one_hundred_twelve_credits() { + let costs = request(KinoviSeedream5p0ProResolution::OneK, KinoviSeedream5p0ProBatchCount::Eight).calculate_costs(); + assert_eq!(costs.kinovi_credits, 112); + } + + #[test] + fn usd_cents_one_1k_image() { + // 14 credits; 1400/243 = 5.7613… → up 6¢, down 5¢. + let costs = request(KinoviSeedream5p0ProResolution::OneK, KinoviSeedream5p0ProBatchCount::One).calculate_costs(); + assert_eq!(costs.usd_cents_rounded_up, 6); + assert_eq!(costs.usd_cents_rounded_down, 5); + assert!((costs.usd_cents_fractional - (1400.0 / 243.0)).abs() < 1e-9); + } + + #[test] + fn usd_cents_one_2k_image() { + // 24 credits; 2400/243 = 9.8765… → up 10¢, down 9¢. + let costs = request(KinoviSeedream5p0ProResolution::TwoK, KinoviSeedream5p0ProBatchCount::One).calculate_costs(); + assert_eq!(costs.usd_cents_rounded_up, 10); + assert_eq!(costs.usd_cents_rounded_down, 9); + assert!((costs.usd_cents_fractional - (2400.0 / 243.0)).abs() < 1e-9); + } + + #[test] + fn usd_cents_eight_1k_images() { + // 112 credits; 11200/243 = 46.0905… → up 47¢, down 46¢. + let costs = request(KinoviSeedream5p0ProResolution::OneK, KinoviSeedream5p0ProBatchCount::Eight).calculate_costs(); + assert_eq!(costs.usd_cents_rounded_up, 47); + assert_eq!(costs.usd_cents_rounded_down, 46); + assert!((costs.usd_cents_fractional - (11200.0 / 243.0)).abs() < 1e-9); + } + + #[test] + fn reference_images_do_not_affect_cost() { + let without_refs = request(KinoviSeedream5p0ProResolution::TwoK, KinoviSeedream5p0ProBatchCount::Four).calculate_costs(); + let with_refs = GenerateSeedream5p0ProRequest { + reference_image_urls: Some(vec![ + "https://example.com/ref1.jpg".to_string(), + "https://example.com/ref2.jpg".to_string(), + ]), + ..request(KinoviSeedream5p0ProResolution::TwoK, KinoviSeedream5p0ProBatchCount::Four) + }.calculate_costs(); + assert_eq!(without_refs, with_refs); + } + + #[test] + fn aspect_ratio_does_not_affect_cost() { + let auto = request(KinoviSeedream5p0ProResolution::OneK, KinoviSeedream5p0ProBatchCount::One).calculate_costs(); + let ultra_wide = GenerateSeedream5p0ProRequest { + aspect_ratio: KinoviSeedream5p0ProAspectRatio::UltraWide21x9, + ..request(KinoviSeedream5p0ProResolution::OneK, KinoviSeedream5p0ProBatchCount::One) + }.calculate_costs(); + assert_eq!(auto, ultra_wide); + } + + fn request( + resolution: KinoviSeedream5p0ProResolution, + batch_count: KinoviSeedream5p0ProBatchCount, + ) -> GenerateSeedream5p0ProRequest { + GenerateSeedream5p0ProRequest { + resolution, + batch_count, + ..base_request() + } + } + } + + // ── Live usage tests ── + + mod live_tests { + use super::*; + use crate::test_utils::get_test_cookies::get_test_cookies; + use crate::test_utils::setup_test_logging::setup_test_logging; + use errors::AnyhowResult; + use log::LevelFilter; + + #[tokio::test] + #[ignore] // Sends a real generation to Kinovi; costs 24 credits. + async fn test_text_to_image_2k() -> AnyhowResult<()> { + setup_test_logging(LevelFilter::Trace); + let session = test_session()?; + let result = generate_seedream_5p0_pro(GenerateSeedream5p0ProArgs { + session: &session, + host_override: None, + request: GenerateSeedream5p0ProRequest { + prompt: "A corgi wearing a wizard hat, studio lighting".to_string(), + aspect_ratio: KinoviSeedream5p0ProAspectRatio::Auto, + resolution: KinoviSeedream5p0ProResolution::TwoK, + batch_count: KinoviSeedream5p0ProBatchCount::One, + reference_image_urls: None, + }, + }).await?; + println!("seedream 5.0 pro 2k — task_id={}, order_id={}", result.task_id, result.order_id); + assert!(!result.task_id.is_empty()); + assert!(!result.order_id.is_empty()); + assert_eq!(1, 2, "Inspect output above"); + Ok(()) + } + + #[tokio::test] + #[ignore] // Sends a real generation to Kinovi; costs 14 credits. + async fn test_text_to_image_1k_with_references() -> AnyhowResult<()> { + setup_test_logging(LevelFilter::Trace); + let session = test_session()?; + let result = generate_seedream_5p0_pro(GenerateSeedream5p0ProArgs { + session: &session, + host_override: None, + request: GenerateSeedream5p0ProRequest { + prompt: "The man in @Image1 is staring out the door in @Image2".to_string(), + aspect_ratio: KinoviSeedream5p0ProAspectRatio::Portrait9x16, + resolution: KinoviSeedream5p0ProResolution::OneK, + batch_count: KinoviSeedream5p0ProBatchCount::One, + reference_image_urls: Some(vec![ + "https://static.seedance2-pro.com/materials/20260713/1783904468032-0a803390.jpg".to_string(), + "https://static.seedance2-pro.com/materials/20260713/1783904481112-4a3fa0d2.png".to_string(), + ]), + }, + }).await?; + println!("seedream 5.0 pro 1k refs — task_id={}, order_id={}", result.task_id, result.order_id); + assert!(!result.task_id.is_empty()); + assert_eq!(1, 2, "Inspect output above"); + Ok(()) + } + + #[tokio::test] + #[ignore] // Sends a real generation to Kinovi; costs 56 credits. + async fn test_batch_four_1k() -> AnyhowResult<()> { + setup_test_logging(LevelFilter::Trace); + let session = test_session()?; + let result = generate_seedream_5p0_pro(GenerateSeedream5p0ProArgs { + session: &session, + host_override: None, + request: GenerateSeedream5p0ProRequest { + prompt: "A lighthouse on a cliff at golden hour".to_string(), + aspect_ratio: KinoviSeedream5p0ProAspectRatio::Landscape16x9, + resolution: KinoviSeedream5p0ProResolution::OneK, + batch_count: KinoviSeedream5p0ProBatchCount::Four, + reference_image_urls: None, + }, + }).await?; + println!("seedream 5.0 pro batch-4 — task_id={}, order_id={}", result.task_id, result.order_id); + assert_eq!(result.task_ids.as_ref().map(|ids| ids.len()), Some(4)); + assert_eq!(result.order_ids.as_ref().map(|ids| ids.len()), Some(4)); + assert_eq!(1, 2, "Inspect output above"); + Ok(()) + } + + fn test_session() -> AnyhowResult { + let cookies = get_test_cookies()?; + Ok(Seedance2ProSession::from_cookies_string(cookies)) + } + } +} diff --git a/crates/api_clients/seedance2pro_client/src/generate/image/mod.rs b/crates/api_clients/seedance2pro_client/src/generate/image/mod.rs index 05ab4c919d..0360d40366 100644 --- a/crates/api_clients/seedance2pro_client/src/generate/image/mod.rs +++ b/crates/api_clients/seedance2pro_client/src/generate/image/mod.rs @@ -1,3 +1,4 @@ pub mod generate_midjourney_v7; pub mod generate_midjourney_v7_niji; pub mod generate_midjourney_v8; +pub mod generate_seedream_5p0_pro;