From bf1bc6234622364e59f4733df90c48dde9fff607 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 9 Mar 2026 19:28:25 -0400 Subject: [PATCH] artcraft router - qwen angle --- .../src/endpoints/generate/image/angle/mod.rs | 1 + .../angle/qwen_edit_2511_edit_image_angle.rs | 21 ++ .../src/endpoints/generate/image/mod.rs | 1 + .../src/api/common_image_model.rs | 2 + ...estimate_image_cost_artcraft_flux_1_dev.rs | 3 + ...mate_image_cost_artcraft_flux_1_schnell.rs | 3 + ...timate_image_cost_artcraft_flux_pro_1p1.rs | 3 + ..._image_cost_artcraft_flux_pro_1p1_ultra.rs | 3 + ...imate_image_cost_artcraft_gpt_image_1p5.rs | 3 + ...stimate_image_cost_artcraft_nano_banana.rs | 3 + ...imate_image_cost_artcraft_nano_banana_2.rs | 3 + ...ate_image_cost_artcraft_nano_banana_pro.rs | 3 + ...age_cost_artcraft_qwen_edit_2511_angles.rs | 72 +++++ ...estimate_image_cost_artcraft_seedream_4.rs | 3 + ...timate_image_cost_artcraft_seedream_4p5.rs | 3 + ...ate_image_cost_artcraft_seedream_5_lite.rs | 3 + .../generate_image/cost/artcraft/mod.rs | 1 + ...estimate_image_cost_fal_nano_banana_pro.rs | 3 + ...te_image_artcraft_qwen_edit_2511_angles.rs | 74 +++++ .../generate_image/execute/artcraft/mod.rs | 1 + .../generate_image/generate_image_request.rs | 16 +- .../generate_image/image_generation_plan.rs | 11 + .../generate_image/plan/artcraft/mod.rs | 1 + ...te_image_artcraft_qwen_edit_2511_angles.rs | 256 ++++++++++++++++++ ...plan_generate_image_fal_nano_banana_pro.rs | 3 + .../artcraft_router/src/test_helpers.rs | 54 ++++ .../handle_image_edit_artcraft_via_router.rs | 3 + ...andle_text_to_image_artcraft_via_router.rs | 3 + .../image/estimate_image_cost_handler.rs | 3 + 29 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/angle/mod.rs create mode 100644 crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/angle/qwen_edit_2511_edit_image_angle.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_qwen_edit_2511_angles.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/artcraft/generate_image_artcraft_qwen_edit_2511_angles.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/artcraft/plan_generate_image_artcraft_qwen_edit_2511_angles.rs diff --git a/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/angle/mod.rs b/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/angle/mod.rs new file mode 100644 index 0000000000..5f14688361 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/angle/mod.rs @@ -0,0 +1 @@ +pub mod qwen_edit_2511_edit_image_angle; diff --git a/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/angle/qwen_edit_2511_edit_image_angle.rs b/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/angle/qwen_edit_2511_edit_image_angle.rs new file mode 100644 index 0000000000..aec899c9fe --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/angle/qwen_edit_2511_edit_image_angle.rs @@ -0,0 +1,21 @@ +use crate::credentials::storyteller_credential_set::StorytellerCredentialSet; +use crate::error::storyteller_error::StorytellerError; +use crate::utils::api_host::ApiHost; +use crate::utils::basic_json_post_request::basic_json_post_request; +use artcraft_api_defs::generate::image::angle::qwen_edit_2511_edit_image_angle::{ + QwenEdit2511EditImageAngleRequest, QwenEdit2511EditImageAngleResponse, + QWEN_EDIT_2511_EDIT_IMAGE_ANGLE_PATH, +}; + +pub async fn qwen_edit_2511_edit_image_angle( + api_host: &ApiHost, + maybe_creds: Option<&StorytellerCredentialSet>, + request: QwenEdit2511EditImageAngleRequest, +) -> Result { + Ok(basic_json_post_request( + api_host, + QWEN_EDIT_2511_EDIT_IMAGE_ANGLE_PATH, + maybe_creds, + request, + ).await?) +} diff --git a/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/mod.rs b/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/mod.rs index 271a82aead..068f2ef5f9 100644 --- a/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/mod.rs +++ b/crates/api_clients/artcraft/artcraft_client/src/endpoints/generate/image/mod.rs @@ -1,3 +1,4 @@ +pub mod angle; pub mod bg_removal; pub mod edit; pub mod inpaint; diff --git a/crates/api_clients/artcraft/artcraft_router/src/api/common_image_model.rs b/crates/api_clients/artcraft/artcraft_router/src/api/common_image_model.rs index ca4977099b..6b57eae6d2 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/api/common_image_model.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/api/common_image_model.rs @@ -27,4 +27,6 @@ pub enum CommonImageModel { Seedream4p5, #[serde(rename = "seedream_5_lite")] Seedream5Lite, + #[serde(rename = "qwen_edit_2511_angles")] + QwenEdit2511Angles, } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_1_dev.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_1_dev.rs index e5f0376b32..d139f4080c 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_1_dev.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_1_dev.rs @@ -47,6 +47,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_1_schnell.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_1_schnell.rs index c2a5d1f1d8..eb31099d13 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_1_schnell.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_1_schnell.rs @@ -35,6 +35,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; let estimate = request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_pro_1p1.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_pro_1p1.rs index 19ccfb2734..ad5fd28241 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_pro_1p1.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_pro_1p1.rs @@ -47,6 +47,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_pro_1p1_ultra.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_pro_1p1_ultra.rs index 407dff5ffc..ce1c080e27 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_pro_1p1_ultra.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_flux_pro_1p1_ultra.rs @@ -47,6 +47,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_gpt_image_1p5.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_gpt_image_1p5.rs index eb89a0454a..5ff0af3d2e 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_gpt_image_1p5.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_gpt_image_1p5.rs @@ -86,6 +86,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana.rs index a4b68e0682..9d0ea6de3b 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana.rs @@ -47,6 +47,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana_2.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana_2.rs index d9ce31944a..e6c64e0d1b 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana_2.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana_2.rs @@ -55,6 +55,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana_pro.rs index fbe50cd426..50f78da1d4 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana_pro.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_nano_banana_pro.rs @@ -54,6 +54,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_qwen_edit_2511_angles.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_qwen_edit_2511_angles.rs new file mode 100644 index 0000000000..4933a4132b --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_qwen_edit_2511_angles.rs @@ -0,0 +1,72 @@ +use artcraft_api_defs::generate::image::angle::qwen_edit_2511_edit_image_angle::QwenEdit2511EditImageAngleNumImages; + +use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate; +use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_qwen_edit_2511_angles::PlanArtcraftQwenEdit2511Angles; + +pub(crate) fn estimate_image_cost_artcraft_qwen_edit_2511_angles( + plan: &PlanArtcraftQwenEdit2511Angles<'_>, +) -> ImageGenerationCostEstimate { + // Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent. + let cost_per_image: u64 = 4; + + let num_images: u64 = match plan.num_images { + QwenEdit2511EditImageAngleNumImages::One => 1, + QwenEdit2511EditImageAngleNumImages::Two => 2, + QwenEdit2511EditImageAngleNumImages::Three => 3, + QwenEdit2511EditImageAngleNumImages::Four => 4, + }; + + let cost_in_usd_cents = cost_per_image * num_images; + + ImageGenerationCostEstimate { + cost_in_credits: Some(cost_in_usd_cents), + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } +} + +#[cfg(test)] +mod tests { + use crate::api::common_image_model::CommonImageModel; + use crate::api::image_list_ref::ImageListRef; + use crate::api::provider::Provider; + use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; + use crate::generate::generate_image::generate_image_request::GenerateImageRequest; + use tokens::tokens::media_files::MediaFileToken; + + fn estimate_usd_cents(image_batch_count: u16) -> u64 { + let tokens = vec![MediaFileToken::new_from_str("test_token")]; + let request = GenerateImageRequest { + model: CommonImageModel::QwenEdit2511Angles, + provider: Provider::Artcraft, + prompt: None, + image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)), + resolution: None, + aspect_ratio: None, + image_batch_count: Some(image_batch_count), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + generation_mode_mismatch_strategy: None, + idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, + }; + request.build() + .expect("build should succeed") + .estimate_costs() + .cost_in_usd_cents + .expect("cost_in_usd_cents should be present") + } + + #[test] + fn test_estimate_cost_usd_cents() { + // $0.04/image = 4 cents each + assert_eq!(estimate_usd_cents(1), 4); + assert_eq!(estimate_usd_cents(2), 8); + assert_eq!(estimate_usd_cents(3), 12); + assert_eq!(estimate_usd_cents(4), 16); + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_4.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_4.rs index b7fe5f2a0e..b65a8ddc65 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_4.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_4.rs @@ -47,6 +47,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_4p5.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_4p5.rs index a255aae2a8..111e5f8509 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_4p5.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_4p5.rs @@ -47,6 +47,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_5_lite.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_5_lite.rs index a746fa320e..08baf8b08b 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_5_lite.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/estimate_image_cost_artcraft_seedream_5_lite.rs @@ -47,6 +47,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/mod.rs index 6edfe32f8c..47d67036ed 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/artcraft/mod.rs @@ -9,3 +9,4 @@ pub mod estimate_image_cost_artcraft_nano_banana_pro; pub mod estimate_image_cost_artcraft_seedream_4; pub mod estimate_image_cost_artcraft_seedream_4p5; pub mod estimate_image_cost_artcraft_seedream_5_lite; +pub mod estimate_image_cost_artcraft_qwen_edit_2511_angles; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_nano_banana_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_nano_banana_pro.rs index b72a798a2d..9466a2a160 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_nano_banana_pro.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/cost/fal/estimate_image_cost_fal_nano_banana_pro.rs @@ -52,6 +52,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; request.build() .expect("build should succeed") diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/artcraft/generate_image_artcraft_qwen_edit_2511_angles.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/artcraft/generate_image_artcraft_qwen_edit_2511_angles.rs new file mode 100644 index 0000000000..d48d06833c --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/artcraft/generate_image_artcraft_qwen_edit_2511_angles.rs @@ -0,0 +1,74 @@ +use crate::client::router_artcraft_client::RouterArtcraftClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_image::generate_image_response::{ + ArtcraftImageResponsePayload, GenerateImageResponse, +}; +use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_qwen_edit_2511_angles::PlanArtcraftQwenEdit2511Angles; +use artcraft_api_defs::generate::image::angle::qwen_edit_2511_edit_image_angle::QwenEdit2511EditImageAngleRequest; +use artcraft_client::endpoints::generate::image::angle::qwen_edit_2511_edit_image_angle::qwen_edit_2511_edit_image_angle; + +pub async fn execute_artcraft_qwen_edit_2511_angles( + plan: &PlanArtcraftQwenEdit2511Angles<'_>, + artcraft_client: &RouterArtcraftClient, +) -> Result { + let request = QwenEdit2511EditImageAngleRequest { + uuid_idempotency_token: plan.idempotency_token.clone(), + image_media_token: plan.image_input.clone(), + horizontal_angle: plan.horizontal_angle, + vertical_angle: plan.vertical_angle, + zoom: plan.zoom, + additional_prompt: plan.prompt.map(|p| p.to_string()), + num_images: Some(plan.num_images), + image_size: plan.image_size, + }; + + let response = qwen_edit_2511_edit_image_angle( + &artcraft_client.api_host, + Some(&artcraft_client.credentials), + request, + ) + .await + .map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?; + + Ok(GenerateImageResponse::Artcraft(ArtcraftImageResponsePayload { + inference_job_token: response.inference_job_token, + })) +} + +#[cfg(test)] +mod tests { + use crate::api::common_aspect_ratio::CommonAspectRatio; + use crate::api::image_list_ref::ImageListRef; + use crate::generate::generate_image::generate_image_request::GenerateImageRequest; + use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan; + use crate::test_helpers::{base_qwen_edit_2511_angles_image_request, get_artcraft_client}; + use tokens::tokens::media_files::MediaFileToken; + + #[tokio::test] + #[ignore] // manually run — fires a real API request and incurs cost + async fn test_angle_edit_qwen_edit_2511() { + let client = get_artcraft_client(); + let tokens = vec![MediaFileToken::new_from_str("m_r45apt2swza6wp8j2z10237t92kkr8")]; + let request = GenerateImageRequest { + aspect_ratio: Some(CommonAspectRatio::SquareHd), + image_batch_count: Some(1), + prompt: None, + image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)), + horizontal_angle: Some(30.0), + vertical_angle: Some(0.0), + zoom: Some(1.0), + ..base_qwen_edit_2511_angles_image_request() + }; + + let plan = request.build().unwrap(); + let result = plan.generate_image(&client).await; + + println!("Result: {:?}", result); + let response = result.expect("generate_image request failed"); + let payload = response.get_artcraft_payload().expect("expected Artcraft payload"); + println!("Job token: {:?}", payload.inference_job_token); + + assert_eq!(1, 2); // NB: Intentional failure to inspect the response above. + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/artcraft/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/artcraft/mod.rs index 679dd8da8b..50a8ff4e26 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/artcraft/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/execute/artcraft/mod.rs @@ -9,3 +9,4 @@ pub mod generate_image_artcraft_nano_banana_pro; pub mod generate_image_artcraft_seedream_4; pub mod generate_image_artcraft_seedream_4p5; pub mod generate_image_artcraft_seedream_5_lite; +pub mod generate_image_artcraft_qwen_edit_2511_angles; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/generate_image_request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/generate_image_request.rs index b5564e38d9..095c113e86 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/generate_image_request.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/generate_image_request.rs @@ -19,6 +19,7 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4::plan_generate_image_artcraft_seedream_4; use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4p5::plan_generate_image_artcraft_seedream_4p5; use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::plan_generate_image_artcraft_seedream_5_lite; +use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_qwen_edit_2511_angles::plan_generate_image_artcraft_qwen_edit_2511_angles; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::plan_generate_image_fal_nano_banana_pro; pub struct GenerateImageRequest<'a> { @@ -45,6 +46,15 @@ pub struct GenerateImageRequest<'a> { /// How many images to generate. pub image_batch_count: Option, + /// Only for angle manipulation models. + pub horizontal_angle: Option, + + /// Only for angle manipulation models. + pub vertical_angle: Option, + + /// Only for angle manipulation models. + pub zoom: Option, + /// If the request is a mismatch with the (model/provider), how to mitigate it. pub request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy, @@ -96,6 +106,9 @@ impl<'a> GenerateImageRequest<'a> { CommonImageModel::Seedream5Lite => { plan_generate_image_artcraft_seedream_5_lite(self).map(ImageGenerationPlan::ArtcraftSeedream5Lite) } + CommonImageModel::QwenEdit2511Angles => { + plan_generate_image_artcraft_qwen_edit_2511_angles(self).map(ImageGenerationPlan::ArtcraftQwenEdit2511Angles) + } }, Provider::Fal => match self.model { CommonImageModel::NanaBananaPro => { @@ -110,7 +123,8 @@ impl<'a> GenerateImageRequest<'a> { | CommonImageModel::NanaBanana2 | CommonImageModel::Seedream4 | CommonImageModel::Seedream4p5 - | CommonImageModel::Seedream5Lite => { + | CommonImageModel::Seedream5Lite + | CommonImageModel::QwenEdit2511Angles => { Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { field: "provider", value: format!("{:?} is only available on the Artcraft provider", self.model), diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/image_generation_plan.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/image_generation_plan.rs index 78165afbc1..4a3ea71774 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/image_generation_plan.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/image_generation_plan.rs @@ -11,6 +11,7 @@ use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraf use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_seedream_4::estimate_image_cost_artcraft_seedream_4; use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_seedream_4p5::estimate_image_cost_artcraft_seedream_4p5; use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_seedream_5_lite::estimate_image_cost_artcraft_seedream_5_lite; +use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_qwen_edit_2511_angles::estimate_image_cost_artcraft_qwen_edit_2511_angles; use crate::generate::generate_image::cost::fal::estimate_image_cost_fal_nano_banana_pro::estimate_image_cost_fal_nano_banana_pro; use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_flux_1_dev::execute_artcraft_flux_1_dev; use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_flux_1_schnell::execute_artcraft_flux_1_schnell; @@ -23,6 +24,7 @@ use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_ use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_seedream_4::execute_artcraft_seedream_4; use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_seedream_4p5::execute_artcraft_seedream_4p5; use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_seedream_5_lite::execute_artcraft_seedream_5_lite; +use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_qwen_edit_2511_angles::execute_artcraft_qwen_edit_2511_angles; use crate::generate::generate_image::execute::fal::generate_image_fal_nano_banana_pro::execute_fal_nano_banana_pro; use crate::generate::generate_image::generate_image_response::GenerateImageResponse; use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate; @@ -37,6 +39,7 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4::PlanArtcraftSeedream4; use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4p5::PlanArtcraftSeedream4p5; use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::PlanArtcraftSeedream5Lite; +use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_qwen_edit_2511_angles::PlanArtcraftQwenEdit2511Angles; use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::PlanFalNanaBananaPro; #[derive(Debug)] @@ -52,6 +55,7 @@ pub enum ImageGenerationPlan<'a> { ArtcraftSeedream4(PlanArtcraftSeedream4<'a>), ArtcraftSeedream4p5(PlanArtcraftSeedream4p5<'a>), ArtcraftSeedream5Lite(PlanArtcraftSeedream5Lite<'a>), + ArtcraftQwenEdit2511Angles(PlanArtcraftQwenEdit2511Angles<'a>), FalNanaBananaPro(PlanFalNanaBananaPro<'a>), } @@ -105,6 +109,10 @@ impl<'a> ImageGenerationPlan<'a> { let artcraft_client = client.get_artcraft_client_ref()?; execute_artcraft_seedream_5_lite(plan, artcraft_client).await } + ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) => { + let artcraft_client = client.get_artcraft_client_ref()?; + execute_artcraft_qwen_edit_2511_angles(plan, artcraft_client).await + } ImageGenerationPlan::FalNanaBananaPro(plan) => { let fal_client = client.get_fal_client_ref()?; execute_fal_nano_banana_pro(plan, fal_client).await @@ -147,6 +155,9 @@ impl<'a> ImageGenerationPlan<'a> { ImageGenerationPlan::ArtcraftSeedream5Lite(plan) => { estimate_image_cost_artcraft_seedream_5_lite(plan) } + ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) => { + estimate_image_cost_artcraft_qwen_edit_2511_angles(plan) + } ImageGenerationPlan::FalNanaBananaPro(plan) => { estimate_image_cost_fal_nano_banana_pro(plan) } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/artcraft/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/artcraft/mod.rs index d344957719..40ab194e3d 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/artcraft/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/artcraft/mod.rs @@ -9,3 +9,4 @@ pub mod plan_generate_image_artcraft_nano_banana_pro; pub mod plan_generate_image_artcraft_seedream_4; pub mod plan_generate_image_artcraft_seedream_4p5; pub mod plan_generate_image_artcraft_seedream_5_lite; +pub mod plan_generate_image_artcraft_qwen_edit_2511_angles; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/artcraft/plan_generate_image_artcraft_qwen_edit_2511_angles.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/artcraft/plan_generate_image_artcraft_qwen_edit_2511_angles.rs new file mode 100644 index 0000000000..ca4642e1b8 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/artcraft/plan_generate_image_artcraft_qwen_edit_2511_angles.rs @@ -0,0 +1,256 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::image_list_ref::ImageListRef; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_image::generate_image_request::GenerateImageRequest; +use artcraft_api_defs::generate::image::angle::qwen_edit_2511_edit_image_angle::{ + QwenEdit2511EditImageAngleImageSize, + QwenEdit2511EditImageAngleNumImages, +}; +use tokens::tokens::media_files::MediaFileToken; + +#[derive(Debug, Clone)] +pub struct PlanArtcraftQwenEdit2511Angles<'a> { + pub prompt: Option<&'a str>, + pub image_input: &'a MediaFileToken, + pub horizontal_angle: Option, + pub vertical_angle: Option, + pub zoom: Option, + pub image_size: Option, + pub num_images: QwenEdit2511EditImageAngleNumImages, + pub idempotency_token: String, +} + +pub fn plan_generate_image_artcraft_qwen_edit_2511_angles<'a>( + request: &'a GenerateImageRequest<'a>, +) -> Result, ArtcraftRouterError> { + let strategy = request.request_mismatch_mitigation_strategy; + + // Angle models require exactly one input image. + let image_input = resolve_single_image_input(request.image_inputs)?; + let image_size = plan_image_size(request.aspect_ratio, strategy)?; + let num_images = plan_num_images(request.image_batch_count, strategy)?; + + Ok(PlanArtcraftQwenEdit2511Angles { + prompt: request.prompt, + image_input, + horizontal_angle: request.horizontal_angle, + vertical_angle: request.vertical_angle, + zoom: request.zoom, + image_size, + num_images, + idempotency_token: request.get_or_generate_idempotency_token(), + }) +} + +fn resolve_single_image_input<'a>( + image_list_ref: Option>, +) -> Result<&'a MediaFileToken, ArtcraftRouterError> { + match image_list_ref { + None => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_inputs", + value: "Angle models require exactly one input image".to_string(), + })) + } + Some(ImageListRef::MediaFileTokens(tokens)) => { + if tokens.len() != 1 { + return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_inputs", + value: format!("Angle models require exactly one input image, got {}", tokens.len()), + })); + } + Ok(&tokens[0]) + } + Some(ImageListRef::Urls(_)) => { + Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens)) + } + } +} + +// Qwen Edit 2511 supported image sizes: +// Square, SquareHd +// PortraitFourThree, PortraitSixteenNine +// LandscapeFourThree, LandscapeSixteenNine +fn plan_image_size( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + use QwenEdit2511EditImageAngleImageSize as S; + match aspect_ratio { + None => Ok(None), + + Some(CommonAspectRatio::Auto) | Some(CommonAspectRatio::Auto2k) | Some(CommonAspectRatio::Auto4k) => { + Ok(Some(S::SquareHd)) + } + + Some(CommonAspectRatio::Square) => Ok(Some(S::Square)), + Some(CommonAspectRatio::SquareHd) => Ok(Some(S::SquareHd)), + + Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideSixteenByNine) => Ok(Some(S::LandscapeSixteenNine)), + Some(CommonAspectRatio::WideFourByThree) => Ok(Some(S::LandscapeFourThree)), + + Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineBySixteen) => Ok(Some(S::PortraitSixteenNine)), + Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(S::PortraitFourThree)), + + Some(unsupported @ CommonAspectRatio::WideFiveByFour) + | Some(unsupported @ CommonAspectRatio::WideThreeByTwo) + | Some(unsupported @ CommonAspectRatio::WideTwentyOneByNine) + | Some(unsupported @ CommonAspectRatio::TallFourByFive) + | Some(unsupported @ CommonAspectRatio::TallTwoByThree) + | Some(unsupported @ CommonAspectRatio::TallNineByTwentyOne) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade + | RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Some(S::SquareHd)), + }, + } +} + +fn plan_num_images( + image_batch_count: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + use QwenEdit2511EditImageAngleNumImages as N; + let count = image_batch_count.unwrap_or(1); + match count { + 0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)), + 1 => Ok(N::One), + 2 => Ok(N::Two), + 3 => Ok(N::Three), + 4 => Ok(N::Four), + _ => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "image_batch_count", + value: format!("{}", count), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade + | RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(N::Four), + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::api::image_list_ref::ImageListRef; + use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan; + use crate::test_helpers::base_qwen_edit_2511_angles_image_request; + use artcraft_api_defs::generate::image::angle::qwen_edit_2511_edit_image_angle::{ + QwenEdit2511EditImageAngleImageSize as S, + QwenEdit2511EditImageAngleNumImages as N, + }; + use tokens::tokens::media_files::MediaFileToken; + + fn make_token() -> MediaFileToken { + MediaFileToken::new_from_str("test_token_123") + } + + #[test] + fn requires_image_input() { + let request = GenerateImageRequest { + image_inputs: None, + ..base_qwen_edit_2511_angles_image_request() + }; + let result = request.build(); + assert!(result.is_err()); + } + + #[test] + fn rejects_urls() { + let urls = vec!["https://example.com/img.png".to_string()]; + let request = GenerateImageRequest { + image_inputs: Some(ImageListRef::Urls(&urls)), + ..base_qwen_edit_2511_angles_image_request() + }; + let result = request.build(); + assert!(result.is_err()); + } + + #[test] + fn accepts_single_media_token() { + let tokens = vec![make_token()]; + let request = GenerateImageRequest { + image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)), + ..base_qwen_edit_2511_angles_image_request() + }; + let result = request.build(); + assert!(result.is_ok()); + } + + #[test] + fn rejects_multiple_media_tokens() { + let tokens = vec![make_token(), make_token()]; + let request = GenerateImageRequest { + image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)), + ..base_qwen_edit_2511_angles_image_request() + }; + let result = request.build(); + assert!(result.is_err()); + } + + #[test] + fn image_size_none_is_none() { + let tokens = vec![make_token()]; + let request = GenerateImageRequest { + aspect_ratio: None, + image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)), + ..base_qwen_edit_2511_angles_image_request() + }; + let ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) = request.build().unwrap() else { panic!("expected ArtcraftQwenEdit2511Angles") }; + assert!(plan.image_size.is_none()); + } + + #[test] + fn image_size_square_maps_directly() { + let tokens = vec![make_token()]; + let request = GenerateImageRequest { + aspect_ratio: Some(CommonAspectRatio::Square), + image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)), + ..base_qwen_edit_2511_angles_image_request() + }; + let ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) = request.build().unwrap() else { panic!("expected ArtcraftQwenEdit2511Angles") }; + assert!(matches!(plan.image_size, Some(S::Square))); + } + + #[test] + fn num_images_direct_mapping() { + let tokens = vec![make_token()]; + let cases = [(1, N::One), (2, N::Two), (3, N::Three), (4, N::Four)]; + for (count, expected) in cases { + let request = GenerateImageRequest { + image_batch_count: Some(count), + image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)), + ..base_qwen_edit_2511_angles_image_request() + }; + let ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) = request.build().unwrap() else { panic!("expected ArtcraftQwenEdit2511Angles") }; + assert!( + std::mem::discriminant(&plan.num_images) == std::mem::discriminant(&expected), + "expected {:?} for count {}", expected, count, + ); + } + } + + #[test] + fn angle_fields_are_passed_through() { + let tokens = vec![make_token()]; + let request = GenerateImageRequest { + horizontal_angle: Some(45.0), + vertical_angle: Some(-30.0), + zoom: Some(1.5), + image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)), + ..base_qwen_edit_2511_angles_image_request() + }; + let ImageGenerationPlan::ArtcraftQwenEdit2511Angles(plan) = request.build().unwrap() else { panic!("expected ArtcraftQwenEdit2511Angles") }; + assert_eq!(plan.horizontal_angle, Some(45.0)); + assert_eq!(plan.vertical_angle, Some(-30.0)); + assert_eq!(plan.zoom, Some(1.5)); + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_nano_banana_pro.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_nano_banana_pro.rs index 9f2d8f79c3..568d5136ff 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_nano_banana_pro.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/plan/fal/plan_generate_image_fal_nano_banana_pro.rs @@ -254,6 +254,9 @@ mod tests { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } diff --git a/crates/api_clients/artcraft/artcraft_router/src/test_helpers.rs b/crates/api_clients/artcraft/artcraft_router/src/test_helpers.rs index 7768143a4b..e7fd81f65d 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/test_helpers.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/test_helpers.rs @@ -33,6 +33,9 @@ pub fn base_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -56,6 +59,9 @@ pub fn base_fal_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -71,6 +77,9 @@ pub fn base_seedream_4_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -86,6 +95,9 @@ pub fn base_seedream_4p5_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -101,6 +113,9 @@ pub fn base_seedream_5_lite_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -116,6 +131,9 @@ pub fn base_nano_banana_2_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -131,6 +149,9 @@ pub fn base_nano_banana_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -146,6 +167,9 @@ pub fn base_gpt_image_1p5_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -161,6 +185,9 @@ pub fn base_flux_1_dev_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -176,6 +203,9 @@ pub fn base_flux_1_schnell_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -191,6 +221,27 @@ pub fn base_flux_pro_1p1_image_request() -> GenerateImageRequest<'static> { request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, + } +} + +pub fn base_qwen_edit_2511_angles_image_request() -> GenerateImageRequest<'static> { + GenerateImageRequest { + model: CommonImageModel::QwenEdit2511Angles, + provider: Provider::Artcraft, + prompt: None, + image_inputs: None, + resolution: None, + aspect_ratio: None, + image_batch_count: None, + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + generation_mode_mismatch_strategy: None, + idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } @@ -206,6 +257,9 @@ pub fn base_flux_pro_1p1_ultra_image_request() -> GenerateImageRequest<'static> request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, } } diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_edit/artcraft/handle_image_edit_artcraft_via_router.rs b/crates/desktop/artcraft/src/core/commands/enqueue/image_edit/artcraft/handle_image_edit_artcraft_via_router.rs index 505abc226b..b97a14fa99 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_edit/artcraft/handle_image_edit_artcraft_via_router.rs +++ b/crates/desktop/artcraft/src/core/commands/enqueue/image_edit/artcraft/handle_image_edit_artcraft_via_router.rs @@ -76,6 +76,9 @@ pub(super) async fn handle_image_edit_artcraft_via_router( request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; let plan = router_request.build()?; diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/text_to_image/artcraft/handle_text_to_image_artcraft_via_router.rs b/crates/desktop/artcraft/src/core/commands/enqueue/text_to_image/artcraft/handle_text_to_image_artcraft_via_router.rs index 6063ff48e8..0a375585d1 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/text_to_image/artcraft/handle_text_to_image_artcraft_via_router.rs +++ b/crates/desktop/artcraft/src/core/commands/enqueue/text_to_image/artcraft/handle_text_to_image_artcraft_via_router.rs @@ -54,6 +54,9 @@ pub(super) async fn handle_text_to_image_artcraft_via_router( request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, generation_mode_mismatch_strategy: None, idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; let plan = router_request.build()?; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/image/estimate_image_cost_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/image/estimate_image_cost_handler.rs index 5b7ef7e003..24e3871f63 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/image/estimate_image_cost_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/image/estimate_image_cost_handler.rs @@ -49,6 +49,9 @@ pub async fn estimate_image_cost_handler( request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade, generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway), idempotency_token: None, + horizontal_angle: None, + vertical_angle: None, + zoom: None, }; let plan = router_request.build()