mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-29 04:52:51 -04:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61f1d15422 | ||
|
|
ea47bab950 | ||
|
|
3861a9bc87 |
@@ -5,6 +5,14 @@ use serde_derive::{Deserialize, Serialize};
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CommonImageModel {
|
||||
#[serde(rename = "flux_1_dev")]
|
||||
Flux1Dev,
|
||||
#[serde(rename = "flux_1_schnell")]
|
||||
Flux1Schnell,
|
||||
#[serde(rename = "flux_pro_1p1")]
|
||||
FluxPro11,
|
||||
#[serde(rename = "flux_pro_1p1_ultra")]
|
||||
FluxPro11Ultra,
|
||||
#[serde(rename = "gpt_image_1p5")]
|
||||
GptImage1p5,
|
||||
#[serde(rename = "nano_banana")]
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
|
||||
/// Strategy for handling requests where the model only supports text-to-image
|
||||
/// but the request includes image inputs (image editing mode).
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum GenerationModeMismatchStrategy {
|
||||
/// Abort the generation: return an error if image inputs are provided
|
||||
/// for a text-to-image-only model.
|
||||
AbortGeneration,
|
||||
|
||||
/// Proceed anyway, ignoring the image inputs for the generation.
|
||||
GenerateAnyway,
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod generation_mode_mismatch_strategy;
|
||||
pub mod multi_router_client;
|
||||
pub mod multi_router_client_builder;
|
||||
pub mod request_mismatch_mitigation_strategy;
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageNumImages;
|
||||
|
||||
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_dev::PlanArtcraftFlux1Dev;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_flux_1_dev(
|
||||
plan: &PlanArtcraftFlux1Dev<'_>,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.02/image (2 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 2;
|
||||
|
||||
let num_images: u64 = match plan.num_images {
|
||||
GenerateFlux1DevTextToImageNumImages::One => 1,
|
||||
GenerateFlux1DevTextToImageNumImages::Two => 2,
|
||||
GenerateFlux1DevTextToImageNumImages::Three => 3,
|
||||
GenerateFlux1DevTextToImageNumImages::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::provider::Provider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
|
||||
fn estimate_usd_cents(image_batch_count: u16) -> u64 {
|
||||
let request = GenerateImageRequest {
|
||||
model: CommonImageModel::Flux1Dev,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
image_inputs: None,
|
||||
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,
|
||||
};
|
||||
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.02/image = 2 cents each
|
||||
assert_eq!(estimate_usd_cents(1), 2);
|
||||
assert_eq!(estimate_usd_cents(2), 4);
|
||||
assert_eq!(estimate_usd_cents(3), 6);
|
||||
assert_eq!(estimate_usd_cents(4), 8);
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_schnell::PlanArtcraftFlux1Schnell;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_flux_1_schnell(
|
||||
_plan: &PlanArtcraftFlux1Schnell<'_>,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: not available
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: None,
|
||||
cost_in_usd_cents: None,
|
||||
is_free: true,
|
||||
is_unlimited: true,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::common_image_model::CommonImageModel;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
|
||||
#[test]
|
||||
fn test_estimate_cost_is_none() {
|
||||
let request = GenerateImageRequest {
|
||||
model: CommonImageModel::Flux1Schnell,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
image_batch_count: Some(1),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
let estimate = request.build()
|
||||
.expect("build should succeed")
|
||||
.estimate_costs();
|
||||
assert!(estimate.cost_in_usd_cents.is_none());
|
||||
assert!(estimate.cost_in_credits.is_none());
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_text_to_image::GenerateFluxPro11TextToImageNumImages;
|
||||
|
||||
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1::PlanArtcraftFluxPro11;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_flux_pro_1p1(
|
||||
plan: &PlanArtcraftFluxPro11<'_>,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// NB(bt): Slightly more than 3 cents, weird megapixel maths. We can eat that cost for now.
|
||||
let cost_per_image: u64 = 3;
|
||||
|
||||
let num_images: u64 = match plan.num_images {
|
||||
GenerateFluxPro11TextToImageNumImages::One => 1,
|
||||
GenerateFluxPro11TextToImageNumImages::Two => 2,
|
||||
GenerateFluxPro11TextToImageNumImages::Three => 3,
|
||||
GenerateFluxPro11TextToImageNumImages::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::provider::Provider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
|
||||
fn estimate_usd_cents(image_batch_count: u16) -> u64 {
|
||||
let request = GenerateImageRequest {
|
||||
model: CommonImageModel::FluxPro11,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
image_inputs: None,
|
||||
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,
|
||||
};
|
||||
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.03/image = 3 cents each (slightly more than 3¢ actual, rounded up)
|
||||
assert_eq!(estimate_usd_cents(1), 3);
|
||||
assert_eq!(estimate_usd_cents(2), 6);
|
||||
assert_eq!(estimate_usd_cents(3), 9);
|
||||
assert_eq!(estimate_usd_cents(4), 12);
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageNumImages;
|
||||
|
||||
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1_ultra::PlanArtcraftFluxPro11Ultra;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_flux_pro_1p1_ultra(
|
||||
plan: &PlanArtcraftFluxPro11Ultra<'_>,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.06/image (6 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 6;
|
||||
|
||||
let num_images: u64 = match plan.num_images {
|
||||
GenerateFluxPro11UltraTextToImageNumImages::One => 1,
|
||||
GenerateFluxPro11UltraTextToImageNumImages::Two => 2,
|
||||
GenerateFluxPro11UltraTextToImageNumImages::Three => 3,
|
||||
GenerateFluxPro11UltraTextToImageNumImages::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::provider::Provider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
|
||||
fn estimate_usd_cents(image_batch_count: u16) -> u64 {
|
||||
let request = GenerateImageRequest {
|
||||
model: CommonImageModel::FluxPro11Ultra,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
image_inputs: None,
|
||||
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,
|
||||
};
|
||||
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.06/image = 6 cents each
|
||||
assert_eq!(estimate_usd_cents(1), 6);
|
||||
assert_eq!(estimate_usd_cents(2), 12);
|
||||
assert_eq!(estimate_usd_cents(3), 18);
|
||||
assert_eq!(estimate_usd_cents(4), 24);
|
||||
}
|
||||
}
|
||||
+1
@@ -84,6 +84,7 @@ mod tests {
|
||||
aspect_ratio,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
request.build()
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
request.build()
|
||||
|
||||
+1
@@ -52,6 +52,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
request.build()
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
request.build()
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
request.build()
|
||||
|
||||
+4
@@ -1,3 +1,7 @@
|
||||
pub mod estimate_image_cost_artcraft_flux_1_dev;
|
||||
pub mod estimate_image_cost_artcraft_flux_1_schnell;
|
||||
pub mod estimate_image_cost_artcraft_flux_pro_1p1;
|
||||
pub mod estimate_image_cost_artcraft_flux_pro_1p1_ultra;
|
||||
pub mod estimate_image_cost_artcraft_gpt_image_1p5;
|
||||
pub mod estimate_image_cost_artcraft_nano_banana;
|
||||
pub mod estimate_image_cost_artcraft_nano_banana_pro;
|
||||
|
||||
+1
@@ -50,6 +50,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
request.build()
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
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_flux_1_dev::PlanArtcraftFlux1Dev;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageRequest;
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
|
||||
|
||||
pub async fn execute_artcraft_flux_1_dev(
|
||||
plan: &PlanArtcraftFlux1Dev<'_>,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = GenerateFlux1DevTextToImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: Some(plan.num_images),
|
||||
};
|
||||
|
||||
let response = generate_flux_1_dev_text_to_image(
|
||||
&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::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::{base_flux_1_dev_image_request, get_artcraft_client};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_flux_1_dev() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_flux_1_dev_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.
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_flux_1_dev_batch_four() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
..base_flux_1_dev_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.
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires one real API request per aspect ratio variant (expensive)
|
||||
async fn test_text_to_image_flux_1_dev_all_aspect_ratios() {
|
||||
let client = get_artcraft_client();
|
||||
|
||||
for ar in CommonAspectRatio::iter() {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a bear walking through an abandoned desert city at night"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
match request.build() {
|
||||
Err(e) => println!(" plan error: {:?}", e),
|
||||
Ok(plan) => match plan.generate_image(&client).await {
|
||||
Ok(response) => {
|
||||
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
|
||||
println!(" job token: {:?}", payload.inference_job_token);
|
||||
}
|
||||
Err(e) => println!(" generate error: {:?}", e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(1, 2); // NB: Intentional failure to inspect the responses above.
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
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_flux_1_schnell::PlanArtcraftFlux1Schnell;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageRequest;
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
|
||||
|
||||
pub async fn execute_artcraft_flux_1_schnell(
|
||||
plan: &PlanArtcraftFlux1Schnell<'_>,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = GenerateFlux1SchnellTextToImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: Some(plan.num_images),
|
||||
};
|
||||
|
||||
let response = generate_flux_1_schnell_text_to_image(
|
||||
&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::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::{base_flux_1_schnell_image_request, get_artcraft_client};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_flux_1_schnell() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a man and woman walking through a cyberpunk city at night"),
|
||||
..base_flux_1_schnell_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.
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_flux_1_schnell_batch_four() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
..base_flux_1_schnell_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.
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires one real API request per aspect ratio variant (expensive)
|
||||
async fn test_text_to_image_flux_1_schnell_all_aspect_ratios() {
|
||||
let client = get_artcraft_client();
|
||||
|
||||
for ar in CommonAspectRatio::iter() {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a man and woman walking through a futuristic city at evening"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
match request.build() {
|
||||
Err(e) => println!(" plan error: {:?}", e),
|
||||
Ok(plan) => match plan.generate_image(&client).await {
|
||||
Ok(response) => {
|
||||
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
|
||||
println!(" job token: {:?}", payload.inference_job_token);
|
||||
}
|
||||
Err(e) => println!(" generate error: {:?}", e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(1, 2); // NB: Intentional failure to inspect the responses above.
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
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_flux_pro_1p1::PlanArtcraftFluxPro11;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_text_to_image::GenerateFluxPro11TextToImageRequest;
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
|
||||
|
||||
pub async fn execute_artcraft_flux_pro_1p1(
|
||||
plan: &PlanArtcraftFluxPro11<'_>,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = GenerateFluxPro11TextToImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: Some(plan.num_images),
|
||||
};
|
||||
|
||||
let response = generate_flux_pro_11_text_to_image(
|
||||
&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::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::{base_flux_pro_1p1_image_request, get_artcraft_client};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_flux_pro_1p1() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_flux_pro_1p1_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.
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_flux_pro_1p1_batch_four() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
..base_flux_pro_1p1_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.
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires one real API request per aspect ratio variant (expensive)
|
||||
async fn test_text_to_image_flux_pro_1p1_all_aspect_ratios() {
|
||||
let client = get_artcraft_client();
|
||||
|
||||
for ar in CommonAspectRatio::iter() {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a bear walking through an abandoned desert city at night"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
match request.build() {
|
||||
Err(e) => println!(" plan error: {:?}", e),
|
||||
Ok(plan) => match plan.generate_image(&client).await {
|
||||
Ok(response) => {
|
||||
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
|
||||
println!(" job token: {:?}", payload.inference_job_token);
|
||||
}
|
||||
Err(e) => println!(" generate error: {:?}", e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(1, 2); // NB: Intentional failure to inspect the responses above.
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
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_flux_pro_1p1_ultra::PlanArtcraftFluxPro11Ultra;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
|
||||
|
||||
pub async fn execute_artcraft_flux_pro_1p1_ultra(
|
||||
plan: &PlanArtcraftFluxPro11Ultra<'_>,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = GenerateFluxPro11UltraTextToImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: Some(plan.num_images),
|
||||
};
|
||||
|
||||
let response = generate_flux_pro_11_ultra_text_to_image(
|
||||
&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::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::{base_flux_pro_1p1_ultra_image_request, get_artcraft_client};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_flux_pro_1p1_ultra() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_flux_pro_1p1_ultra_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.
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_flux_pro_1p1_ultra_batch_four() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
..base_flux_pro_1p1_ultra_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.
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires one real API request per aspect ratio variant (expensive)
|
||||
async fn test_text_to_image_flux_pro_1p1_ultra_all_aspect_ratios() {
|
||||
let client = get_artcraft_client();
|
||||
|
||||
for ar in CommonAspectRatio::iter() {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a t-rex walking through an abandoned desert city at night"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
match request.build() {
|
||||
Err(e) => println!(" plan error: {:?}", e),
|
||||
Ok(plan) => match plan.generate_image(&client).await {
|
||||
Ok(response) => {
|
||||
let payload = response.get_artcraft_payload().expect("expected Artcraft payload");
|
||||
println!(" job token: {:?}", payload.inference_job_token);
|
||||
}
|
||||
Err(e) => println!(" generate error: {:?}", e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(1, 2); // NB: Intentional failure to inspect the responses above.
|
||||
}
|
||||
}
|
||||
+4
@@ -1,3 +1,7 @@
|
||||
pub mod generate_image_artcraft_flux_1_dev;
|
||||
pub mod generate_image_artcraft_flux_1_schnell;
|
||||
pub mod generate_image_artcraft_flux_pro_1p1;
|
||||
pub mod generate_image_artcraft_flux_pro_1p1_ultra;
|
||||
pub mod generate_image_artcraft_gpt_image_1p5;
|
||||
pub mod generate_image_artcraft_nano_banana;
|
||||
pub mod generate_image_artcraft_nano_banana_pro;
|
||||
|
||||
+27
-1
@@ -3,10 +3,15 @@ use crate::api::common_image_model::CommonImageModel;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
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::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_dev::plan_generate_image_artcraft_flux_1_dev;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_schnell::plan_generate_image_artcraft_flux_1_schnell;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1::plan_generate_image_artcraft_flux_pro_1p1;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1_ultra::plan_generate_image_artcraft_flux_pro_1p1_ultra;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_gpt_image_1p5::plan_generate_image_artcraft_gpt_image_1p5;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana::plan_generate_image_artcraft_nano_banana;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_pro::plan_generate_image_artcraft_nano_banana_pro;
|
||||
@@ -41,6 +46,11 @@ pub struct GenerateImageRequest<'a> {
|
||||
/// If the request is a mismatch with the (model/provider), how to mitigate it.
|
||||
pub request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy,
|
||||
|
||||
/// Controls behavior when `image_inputs` are supplied to a text-to-image-only model.
|
||||
/// `None` is equivalent to `GenerateAnyway` — image inputs are silently ignored.
|
||||
/// Set `AbortGeneration` to return an error instead.
|
||||
pub generation_mode_mismatch_strategy: Option<GenerationModeMismatchStrategy>,
|
||||
|
||||
/// Some providers support idempotency.
|
||||
/// If not supplied, we'll generate one for the required providers.
|
||||
pub idempotency_token: Option<&'a str>,
|
||||
@@ -51,6 +61,18 @@ impl<'a> GenerateImageRequest<'a> {
|
||||
pub fn build(&self) -> Result<ImageGenerationPlan<'_>, ArtcraftRouterError> {
|
||||
match self.provider {
|
||||
Provider::Artcraft => match self.model {
|
||||
CommonImageModel::Flux1Dev => {
|
||||
plan_generate_image_artcraft_flux_1_dev(self).map(ImageGenerationPlan::ArtcraftFlux1Dev)
|
||||
}
|
||||
CommonImageModel::Flux1Schnell => {
|
||||
plan_generate_image_artcraft_flux_1_schnell(self).map(ImageGenerationPlan::ArtcraftFlux1Schnell)
|
||||
}
|
||||
CommonImageModel::FluxPro11 => {
|
||||
plan_generate_image_artcraft_flux_pro_1p1(self).map(ImageGenerationPlan::ArtcraftFluxPro11)
|
||||
}
|
||||
CommonImageModel::FluxPro11Ultra => {
|
||||
plan_generate_image_artcraft_flux_pro_1p1_ultra(self).map(ImageGenerationPlan::ArtcraftFluxPro11Ultra)
|
||||
}
|
||||
CommonImageModel::GptImage1p5 => {
|
||||
plan_generate_image_artcraft_gpt_image_1p5(self).map(ImageGenerationPlan::ArtcraftGptImage1p5)
|
||||
}
|
||||
@@ -71,7 +93,11 @@ impl<'a> GenerateImageRequest<'a> {
|
||||
CommonImageModel::NanaBananaPro => {
|
||||
plan_generate_image_fal_nano_banana_pro(self).map(ImageGenerationPlan::FalNanaBananaPro)
|
||||
}
|
||||
CommonImageModel::GptImage1p5
|
||||
CommonImageModel::Flux1Dev
|
||||
| CommonImageModel::Flux1Schnell
|
||||
| CommonImageModel::FluxPro11
|
||||
| CommonImageModel::FluxPro11Ultra
|
||||
| CommonImageModel::GptImage1p5
|
||||
| CommonImageModel::NanaBanana
|
||||
| CommonImageModel::Seedream4
|
||||
| CommonImageModel::Seedream4p5 => {
|
||||
|
||||
+44
@@ -1,11 +1,19 @@
|
||||
use crate::client::router_client::RouterClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_flux_1_dev::estimate_image_cost_artcraft_flux_1_dev;
|
||||
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_flux_1_schnell::estimate_image_cost_artcraft_flux_1_schnell;
|
||||
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_flux_pro_1p1::estimate_image_cost_artcraft_flux_pro_1p1;
|
||||
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_flux_pro_1p1_ultra::estimate_image_cost_artcraft_flux_pro_1p1_ultra;
|
||||
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_gpt_image_1p5::estimate_image_cost_artcraft_gpt_image_1p5;
|
||||
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_nano_banana::estimate_image_cost_artcraft_nano_banana;
|
||||
use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraft_nano_banana_pro::estimate_image_cost_artcraft_nano_banana_pro;
|
||||
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::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;
|
||||
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_flux_pro_1p1::execute_artcraft_flux_pro_1p1;
|
||||
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_flux_pro_1p1_ultra::execute_artcraft_flux_pro_1p1_ultra;
|
||||
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_gpt_image_1p5::execute_artcraft_gpt_image_1p5;
|
||||
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_nano_banana::execute_artcraft_nano_banana;
|
||||
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_nano_banana_pro::execute_artcraft_nano_banana_pro;
|
||||
@@ -14,6 +22,10 @@ use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_
|
||||
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;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_dev::PlanArtcraftFlux1Dev;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_schnell::PlanArtcraftFlux1Schnell;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1::PlanArtcraftFluxPro11;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1_ultra::PlanArtcraftFluxPro11Ultra;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_gpt_image_1p5::PlanArtcraftGptImage1p5;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana::PlanArtcraftNanaBanana;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_pro::PlanArtcraftNanaBananaPro;
|
||||
@@ -23,6 +35,10 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ImageGenerationPlan<'a> {
|
||||
ArtcraftFlux1Dev(PlanArtcraftFlux1Dev<'a>),
|
||||
ArtcraftFlux1Schnell(PlanArtcraftFlux1Schnell<'a>),
|
||||
ArtcraftFluxPro11(PlanArtcraftFluxPro11<'a>),
|
||||
ArtcraftFluxPro11Ultra(PlanArtcraftFluxPro11Ultra<'a>),
|
||||
ArtcraftGptImage1p5(PlanArtcraftGptImage1p5<'a>),
|
||||
ArtcraftNanaBanana(PlanArtcraftNanaBanana<'a>),
|
||||
ArtcraftNanaBananaPro(PlanArtcraftNanaBananaPro<'a>),
|
||||
@@ -37,6 +53,22 @@ impl<'a> ImageGenerationPlan<'a> {
|
||||
client: &RouterClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
match self {
|
||||
ImageGenerationPlan::ArtcraftFlux1Dev(plan) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_flux_1_dev(plan, artcraft_client).await
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftFlux1Schnell(plan) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_flux_1_schnell(plan, artcraft_client).await
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftFluxPro11(plan) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_flux_pro_1p1(plan, artcraft_client).await
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftFluxPro11Ultra(plan) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_flux_pro_1p1_ultra(plan, artcraft_client).await
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftGptImage1p5(plan) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_gpt_image_1p5(plan, artcraft_client).await
|
||||
@@ -66,6 +98,18 @@ impl<'a> ImageGenerationPlan<'a> {
|
||||
|
||||
pub fn estimate_costs(&self) -> ImageGenerationCostEstimate {
|
||||
match self {
|
||||
ImageGenerationPlan::ArtcraftFlux1Dev(plan) => {
|
||||
estimate_image_cost_artcraft_flux_1_dev(plan)
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftFlux1Schnell(plan) => {
|
||||
estimate_image_cost_artcraft_flux_1_schnell(plan)
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftFluxPro11(plan) => {
|
||||
estimate_image_cost_artcraft_flux_pro_1p1(plan)
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftFluxPro11Ultra(plan) => {
|
||||
estimate_image_cost_artcraft_flux_pro_1p1_ultra(plan)
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftGptImage1p5(plan) => {
|
||||
estimate_image_cost_artcraft_gpt_image_1p5(plan)
|
||||
}
|
||||
|
||||
+4
@@ -1,3 +1,7 @@
|
||||
pub mod plan_generate_image_artcraft_flux_1_dev;
|
||||
pub mod plan_generate_image_artcraft_flux_1_schnell;
|
||||
pub mod plan_generate_image_artcraft_flux_pro_1p1;
|
||||
pub mod plan_generate_image_artcraft_flux_pro_1p1_ultra;
|
||||
pub mod plan_generate_image_artcraft_gpt_image_1p5;
|
||||
pub mod plan_generate_image_artcraft_nano_banana;
|
||||
pub mod plan_generate_image_artcraft_nano_banana_pro;
|
||||
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
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::text::generate_flux_1_dev_text_to_image::{
|
||||
GenerateFlux1DevTextToImageAspectRatio, GenerateFlux1DevTextToImageNumImages,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlanArtcraftFlux1Dev<'a> {
|
||||
pub prompt: Option<&'a str>,
|
||||
pub aspect_ratio: Option<GenerateFlux1DevTextToImageAspectRatio>,
|
||||
pub num_images: GenerateFlux1DevTextToImageNumImages,
|
||||
pub idempotency_token: String,
|
||||
}
|
||||
|
||||
pub fn plan_generate_image_artcraft_flux_1_dev<'a>(
|
||||
request: &'a GenerateImageRequest<'a>,
|
||||
) -> Result<PlanArtcraftFlux1Dev<'a>, ArtcraftRouterError> {
|
||||
// Flux 1 Dev is text-to-image only. Abort if image inputs are provided and
|
||||
// the caller has opted into strict mode.
|
||||
if request.image_inputs.is_some() {
|
||||
if let Some(GenerationModeMismatchStrategy::AbortGeneration) = request.generation_mode_mismatch_strategy {
|
||||
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_inputs",
|
||||
value: "Flux 1 Dev is a text-to-image model and does not support image editing".to_string(),
|
||||
}));
|
||||
}
|
||||
// None or GenerateAnyway: silently ignore image inputs
|
||||
}
|
||||
|
||||
let strategy = request.request_mismatch_mitigation_strategy;
|
||||
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
|
||||
let num_images = plan_num_images(request.image_batch_count, strategy)?;
|
||||
|
||||
Ok(PlanArtcraftFlux1Dev {
|
||||
prompt: request.prompt,
|
||||
aspect_ratio,
|
||||
num_images,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
})
|
||||
}
|
||||
|
||||
// Flux 1 Dev supported aspect ratios:
|
||||
// Square, SquareHd, LandscapeFourByThree, LandscapeSixteenByNine,
|
||||
// PortraitThreeByFour, PortraitNineBySixteen
|
||||
fn plan_aspect_ratio(
|
||||
aspect_ratio: Option<CommonAspectRatio>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<GenerateFlux1DevTextToImageAspectRatio>, ArtcraftRouterError> {
|
||||
use GenerateFlux1DevTextToImageAspectRatio as FlAr;
|
||||
match aspect_ratio {
|
||||
None => Ok(None),
|
||||
|
||||
// Auto: not valid for text-to-image; fall back to square
|
||||
Some(CommonAspectRatio::Auto)
|
||||
| Some(CommonAspectRatio::Auto2k)
|
||||
| Some(CommonAspectRatio::Auto4k) => Ok(Some(FlAr::Square)),
|
||||
|
||||
// Direct mappings
|
||||
Some(CommonAspectRatio::Square) => Ok(Some(FlAr::Square)),
|
||||
Some(CommonAspectRatio::SquareHd) => Ok(Some(FlAr::SquareHd)),
|
||||
Some(CommonAspectRatio::WideFourByThree) | Some(CommonAspectRatio::WideThreeByTwo) => {
|
||||
Ok(Some(FlAr::LandscapeFourByThree))
|
||||
}
|
||||
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideTwentyOneByNine) => {
|
||||
Ok(Some(FlAr::LandscapeSixteenByNine))
|
||||
}
|
||||
Some(CommonAspectRatio::TallThreeByFour)
|
||||
| Some(CommonAspectRatio::TallTwoByThree)
|
||||
| Some(CommonAspectRatio::WideFiveByFour) => Ok(Some(FlAr::PortraitThreeByFour)),
|
||||
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineByTwentyOne) => {
|
||||
Ok(Some(FlAr::PortraitNineBySixteen))
|
||||
}
|
||||
|
||||
// Mismatch — TallFourByFive maps to nearest (LandscapeFourByThree per existing desktop behavior)
|
||||
Some(unsupported) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "aspect_ratio",
|
||||
value: format!("{:?}", unsupported),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
Ok(Some(nearest_aspect_ratio(unsupported)))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> GenerateFlux1DevTextToImageAspectRatio {
|
||||
use GenerateFlux1DevTextToImageAspectRatio as FlAr;
|
||||
match aspect_ratio {
|
||||
// TallFourByFive (0.8) — nearest is LandscapeFourByThree per existing desktop behavior
|
||||
CommonAspectRatio::TallFourByFive => FlAr::LandscapeFourByThree,
|
||||
_ => FlAr::Square,
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_num_images(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<GenerateFlux1DevTextToImageNumImages, ArtcraftRouterError> {
|
||||
use GenerateFlux1DevTextToImageNumImages as FlN;
|
||||
let count = image_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 => Ok(FlN::One),
|
||||
2 => Ok(FlN::Two),
|
||||
3 => Ok(FlN::Three),
|
||||
4 => Ok(FlN::Four),
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(FlN::Four),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::base_flux_1_dev_image_request;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_dev_text_to_image::{
|
||||
GenerateFlux1DevTextToImageAspectRatio as FlAr,
|
||||
GenerateFlux1DevTextToImageNumImages as FlN,
|
||||
};
|
||||
|
||||
// ── Generation mode mismatch ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_abort_returns_error() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::AbortGeneration),
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
let result = request.build();
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { field: "image_inputs", .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_generate_anyway_succeeds() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
assert!(request.build().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_none_strategy_succeeds() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: None,
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
assert!(request.build().is_ok());
|
||||
}
|
||||
|
||||
// ── Aspect ratio ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_none_is_none() {
|
||||
let request = GenerateImageRequest { aspect_ratio: None, ..base_flux_1_dev_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFlux1Dev(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Dev") };
|
||||
assert!(plan.aspect_ratio.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_auto_falls_back_to_square() {
|
||||
for ar in [CommonAspectRatio::Auto, CommonAspectRatio::Auto2k, CommonAspectRatio::Auto4k] {
|
||||
let request = GenerateImageRequest { aspect_ratio: Some(ar), ..base_flux_1_dev_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFlux1Dev(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Dev") };
|
||||
assert!(matches!(plan.aspect_ratio, Some(FlAr::Square)), "expected Square for {:?}", ar);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_direct_mappings() {
|
||||
let cases = [
|
||||
(CommonAspectRatio::Square, FlAr::Square),
|
||||
(CommonAspectRatio::SquareHd, FlAr::SquareHd),
|
||||
(CommonAspectRatio::WideFourByThree, FlAr::LandscapeFourByThree),
|
||||
(CommonAspectRatio::WideThreeByTwo, FlAr::LandscapeFourByThree),
|
||||
(CommonAspectRatio::WideSixteenByNine, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::Wide, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::WideTwentyOneByNine, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::TallThreeByFour, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::TallTwoByThree, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::WideFiveByFour, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::TallNineBySixteen, FlAr::PortraitNineBySixteen),
|
||||
(CommonAspectRatio::Tall, FlAr::PortraitNineBySixteen),
|
||||
(CommonAspectRatio::TallNineByTwentyOne, FlAr::PortraitNineBySixteen),
|
||||
];
|
||||
for (common, expected) in cases {
|
||||
let request = GenerateImageRequest { aspect_ratio: Some(common), ..base_flux_1_dev_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFlux1Dev(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Dev") };
|
||||
assert!(
|
||||
matches!(plan.aspect_ratio, Some(ar) if std::mem::discriminant(&ar) == std::mem::discriminant(&expected)),
|
||||
"expected {:?} for {:?}", expected, common,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_unsupported_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::TallFourByFive),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
let result = request.build();
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_unsupported_nearest_match() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::TallFourByFive),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftFlux1Dev(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Dev") };
|
||||
assert!(
|
||||
matches!(plan.aspect_ratio, Some(FlAr::LandscapeFourByThree)),
|
||||
"expected LandscapeFourByThree nearest match with {:?}", strategy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Num images ───────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn num_images_zero_is_always_error() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::ErrorOut,
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_direct_mapping() {
|
||||
let cases = [(1, FlN::One), (2, FlN::Two), (3, FlN::Three), (4, FlN::Four)];
|
||||
for (count, expected) in cases {
|
||||
let request = GenerateImageRequest { image_batch_count: Some(count), ..base_flux_1_dev_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFlux1Dev(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Dev") };
|
||||
assert!(
|
||||
std::mem::discriminant(&plan.num_images) == std::mem::discriminant(&expected),
|
||||
"expected {:?} for count {}", expected, count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_out_of_range_clamps_to_four() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftFlux1Dev(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Dev") };
|
||||
assert!(matches!(plan.num_images, FlN::Four));
|
||||
}
|
||||
}
|
||||
}
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
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::text::generate_flux_1_schnell_text_to_image::{
|
||||
GenerateFlux1SchnellTextToImageAspectRatio, GenerateFlux1SchnellTextToImageNumImages,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlanArtcraftFlux1Schnell<'a> {
|
||||
pub prompt: Option<&'a str>,
|
||||
pub aspect_ratio: Option<GenerateFlux1SchnellTextToImageAspectRatio>,
|
||||
pub num_images: GenerateFlux1SchnellTextToImageNumImages,
|
||||
pub idempotency_token: String,
|
||||
}
|
||||
|
||||
pub fn plan_generate_image_artcraft_flux_1_schnell<'a>(
|
||||
request: &'a GenerateImageRequest<'a>,
|
||||
) -> Result<PlanArtcraftFlux1Schnell<'a>, ArtcraftRouterError> {
|
||||
// Flux 1 Schnell is text-to-image only. Abort if image inputs are provided and
|
||||
// the caller has opted into strict mode.
|
||||
if request.image_inputs.is_some() {
|
||||
if let Some(GenerationModeMismatchStrategy::AbortGeneration) = request.generation_mode_mismatch_strategy {
|
||||
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_inputs",
|
||||
value: "Flux 1 Schnell is a text-to-image model and does not support image editing".to_string(),
|
||||
}));
|
||||
}
|
||||
// None or GenerateAnyway: silently ignore image inputs
|
||||
}
|
||||
|
||||
let strategy = request.request_mismatch_mitigation_strategy;
|
||||
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
|
||||
let num_images = plan_num_images(request.image_batch_count, strategy)?;
|
||||
|
||||
Ok(PlanArtcraftFlux1Schnell {
|
||||
prompt: request.prompt,
|
||||
aspect_ratio,
|
||||
num_images,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
})
|
||||
}
|
||||
|
||||
// Flux 1 Schnell supported aspect ratios (identical to Flux 1 Dev):
|
||||
// Square, SquareHd, LandscapeFourByThree, LandscapeSixteenByNine,
|
||||
// PortraitThreeByFour, PortraitNineBySixteen
|
||||
fn plan_aspect_ratio(
|
||||
aspect_ratio: Option<CommonAspectRatio>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<GenerateFlux1SchnellTextToImageAspectRatio>, ArtcraftRouterError> {
|
||||
use GenerateFlux1SchnellTextToImageAspectRatio as FlAr;
|
||||
match aspect_ratio {
|
||||
None => Ok(None),
|
||||
|
||||
// Auto: not valid for text-to-image; fall back to square
|
||||
Some(CommonAspectRatio::Auto)
|
||||
| Some(CommonAspectRatio::Auto2k)
|
||||
| Some(CommonAspectRatio::Auto4k) => Ok(Some(FlAr::Square)),
|
||||
|
||||
// Direct mappings
|
||||
Some(CommonAspectRatio::Square) => Ok(Some(FlAr::Square)),
|
||||
Some(CommonAspectRatio::SquareHd) => Ok(Some(FlAr::SquareHd)),
|
||||
Some(CommonAspectRatio::WideFourByThree) | Some(CommonAspectRatio::WideThreeByTwo) => {
|
||||
Ok(Some(FlAr::LandscapeFourByThree))
|
||||
}
|
||||
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideTwentyOneByNine) => {
|
||||
Ok(Some(FlAr::LandscapeSixteenByNine))
|
||||
}
|
||||
Some(CommonAspectRatio::TallThreeByFour)
|
||||
| Some(CommonAspectRatio::TallTwoByThree)
|
||||
| Some(CommonAspectRatio::WideFiveByFour) => Ok(Some(FlAr::PortraitThreeByFour)),
|
||||
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineByTwentyOne) => {
|
||||
Ok(Some(FlAr::PortraitNineBySixteen))
|
||||
}
|
||||
|
||||
// Mismatch — TallFourByFive maps to nearest (LandscapeFourByThree per existing desktop behavior)
|
||||
Some(unsupported) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "aspect_ratio",
|
||||
value: format!("{:?}", unsupported),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
Ok(Some(nearest_aspect_ratio(unsupported)))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> GenerateFlux1SchnellTextToImageAspectRatio {
|
||||
use GenerateFlux1SchnellTextToImageAspectRatio as FlAr;
|
||||
match aspect_ratio {
|
||||
// TallFourByFive (0.8) — nearest is LandscapeFourByThree per existing desktop behavior
|
||||
CommonAspectRatio::TallFourByFive => FlAr::LandscapeFourByThree,
|
||||
_ => FlAr::Square,
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_num_images(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<GenerateFlux1SchnellTextToImageNumImages, ArtcraftRouterError> {
|
||||
use GenerateFlux1SchnellTextToImageNumImages as FlN;
|
||||
let count = image_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 => Ok(FlN::One),
|
||||
2 => Ok(FlN::Two),
|
||||
3 => Ok(FlN::Three),
|
||||
4 => Ok(FlN::Four),
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(FlN::Four),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::base_flux_1_schnell_image_request;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_schnell_text_to_image::{
|
||||
GenerateFlux1SchnellTextToImageAspectRatio as FlAr,
|
||||
GenerateFlux1SchnellTextToImageNumImages as FlN,
|
||||
};
|
||||
|
||||
// ── Generation mode mismatch ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_abort_returns_error() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::AbortGeneration),
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
let result = request.build();
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { field: "image_inputs", .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_generate_anyway_succeeds() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
assert!(request.build().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_none_strategy_succeeds() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: None,
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
assert!(request.build().is_ok());
|
||||
}
|
||||
|
||||
// ── Aspect ratio ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_none_is_none() {
|
||||
let request = GenerateImageRequest { aspect_ratio: None, ..base_flux_1_schnell_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFlux1Schnell(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Schnell") };
|
||||
assert!(plan.aspect_ratio.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_auto_falls_back_to_square() {
|
||||
for ar in [CommonAspectRatio::Auto, CommonAspectRatio::Auto2k, CommonAspectRatio::Auto4k] {
|
||||
let request = GenerateImageRequest { aspect_ratio: Some(ar), ..base_flux_1_schnell_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFlux1Schnell(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Schnell") };
|
||||
assert!(matches!(plan.aspect_ratio, Some(FlAr::Square)), "expected Square for {:?}", ar);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_direct_mappings() {
|
||||
let cases = [
|
||||
(CommonAspectRatio::Square, FlAr::Square),
|
||||
(CommonAspectRatio::SquareHd, FlAr::SquareHd),
|
||||
(CommonAspectRatio::WideFourByThree, FlAr::LandscapeFourByThree),
|
||||
(CommonAspectRatio::WideThreeByTwo, FlAr::LandscapeFourByThree),
|
||||
(CommonAspectRatio::WideSixteenByNine, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::Wide, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::WideTwentyOneByNine, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::TallThreeByFour, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::TallTwoByThree, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::WideFiveByFour, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::TallNineBySixteen, FlAr::PortraitNineBySixteen),
|
||||
(CommonAspectRatio::Tall, FlAr::PortraitNineBySixteen),
|
||||
(CommonAspectRatio::TallNineByTwentyOne, FlAr::PortraitNineBySixteen),
|
||||
];
|
||||
for (common, expected) in cases {
|
||||
let request = GenerateImageRequest { aspect_ratio: Some(common), ..base_flux_1_schnell_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFlux1Schnell(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Schnell") };
|
||||
assert!(
|
||||
matches!(plan.aspect_ratio, Some(ar) if std::mem::discriminant(&ar) == std::mem::discriminant(&expected)),
|
||||
"expected {:?} for {:?}", expected, common,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_unsupported_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::TallFourByFive),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_unsupported_nearest_match() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::TallFourByFive),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftFlux1Schnell(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Schnell") };
|
||||
assert!(matches!(plan.aspect_ratio, Some(FlAr::LandscapeFourByThree)));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Num images ───────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn num_images_zero_is_always_error() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::ErrorOut,
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_direct_mapping() {
|
||||
let cases = [(1, FlN::One), (2, FlN::Two), (3, FlN::Three), (4, FlN::Four)];
|
||||
for (count, expected) in cases {
|
||||
let request = GenerateImageRequest { image_batch_count: Some(count), ..base_flux_1_schnell_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFlux1Schnell(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Schnell") };
|
||||
assert!(
|
||||
std::mem::discriminant(&plan.num_images) == std::mem::discriminant(&expected),
|
||||
"expected {:?} for count {}", expected, count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_out_of_range_clamps_to_four() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftFlux1Schnell(plan) = request.build().unwrap() else { panic!("expected ArtcraftFlux1Schnell") };
|
||||
assert!(matches!(plan.num_images, FlN::Four));
|
||||
}
|
||||
}
|
||||
}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
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::text::generate_flux_pro_11_text_to_image::{
|
||||
GenerateFluxPro11TextToImageAspectRatio, GenerateFluxPro11TextToImageNumImages,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlanArtcraftFluxPro11<'a> {
|
||||
pub prompt: Option<&'a str>,
|
||||
pub aspect_ratio: Option<GenerateFluxPro11TextToImageAspectRatio>,
|
||||
pub num_images: GenerateFluxPro11TextToImageNumImages,
|
||||
pub idempotency_token: String,
|
||||
}
|
||||
|
||||
pub fn plan_generate_image_artcraft_flux_pro_1p1<'a>(
|
||||
request: &'a GenerateImageRequest<'a>,
|
||||
) -> Result<PlanArtcraftFluxPro11<'a>, ArtcraftRouterError> {
|
||||
// Flux Pro 1.1 is text-to-image only. Abort if image inputs are provided and
|
||||
// the caller has opted into strict mode.
|
||||
if request.image_inputs.is_some() {
|
||||
if let Some(GenerationModeMismatchStrategy::AbortGeneration) = request.generation_mode_mismatch_strategy {
|
||||
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_inputs",
|
||||
value: "Flux Pro 1.1 is a text-to-image model and does not support image editing".to_string(),
|
||||
}));
|
||||
}
|
||||
// None or GenerateAnyway: silently ignore image inputs
|
||||
}
|
||||
|
||||
let strategy = request.request_mismatch_mitigation_strategy;
|
||||
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
|
||||
let num_images = plan_num_images(request.image_batch_count, strategy)?;
|
||||
|
||||
Ok(PlanArtcraftFluxPro11 {
|
||||
prompt: request.prompt,
|
||||
aspect_ratio,
|
||||
num_images,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
})
|
||||
}
|
||||
|
||||
// Flux Pro 1.1 supported aspect ratios (no SquareHd):
|
||||
// Square, LandscapeFourByThree, LandscapeSixteenByNine,
|
||||
// PortraitThreeByFour, PortraitNineBySixteen
|
||||
fn plan_aspect_ratio(
|
||||
aspect_ratio: Option<CommonAspectRatio>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<GenerateFluxPro11TextToImageAspectRatio>, ArtcraftRouterError> {
|
||||
use GenerateFluxPro11TextToImageAspectRatio as FlAr;
|
||||
match aspect_ratio {
|
||||
None => Ok(None),
|
||||
|
||||
// Auto and SquareHd: no direct equivalent; fall back to square
|
||||
Some(CommonAspectRatio::Auto)
|
||||
| Some(CommonAspectRatio::Auto2k)
|
||||
| Some(CommonAspectRatio::Auto4k)
|
||||
| Some(CommonAspectRatio::SquareHd) => Ok(Some(FlAr::Square)),
|
||||
|
||||
// Direct mappings
|
||||
Some(CommonAspectRatio::Square) => Ok(Some(FlAr::Square)),
|
||||
Some(CommonAspectRatio::WideFourByThree) | Some(CommonAspectRatio::WideThreeByTwo) => {
|
||||
Ok(Some(FlAr::LandscapeFourByThree))
|
||||
}
|
||||
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideTwentyOneByNine) => {
|
||||
Ok(Some(FlAr::LandscapeSixteenByNine))
|
||||
}
|
||||
Some(CommonAspectRatio::TallThreeByFour)
|
||||
| Some(CommonAspectRatio::TallTwoByThree)
|
||||
| Some(CommonAspectRatio::WideFiveByFour) => Ok(Some(FlAr::PortraitThreeByFour)),
|
||||
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineByTwentyOne) => {
|
||||
Ok(Some(FlAr::PortraitNineBySixteen))
|
||||
}
|
||||
|
||||
// Mismatch — TallFourByFive maps to nearest (LandscapeFourByThree per existing desktop behavior)
|
||||
Some(unsupported) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "aspect_ratio",
|
||||
value: format!("{:?}", unsupported),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
Ok(Some(nearest_aspect_ratio(unsupported)))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> GenerateFluxPro11TextToImageAspectRatio {
|
||||
use GenerateFluxPro11TextToImageAspectRatio as FlAr;
|
||||
match aspect_ratio {
|
||||
// TallFourByFive (0.8) — nearest is LandscapeFourByThree per existing desktop behavior
|
||||
CommonAspectRatio::TallFourByFive => FlAr::LandscapeFourByThree,
|
||||
_ => FlAr::Square,
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_num_images(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<GenerateFluxPro11TextToImageNumImages, ArtcraftRouterError> {
|
||||
use GenerateFluxPro11TextToImageNumImages as FlN;
|
||||
let count = image_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 => Ok(FlN::One),
|
||||
2 => Ok(FlN::Two),
|
||||
3 => Ok(FlN::Three),
|
||||
4 => Ok(FlN::Four),
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(FlN::Four),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::base_flux_pro_1p1_image_request;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_text_to_image::{
|
||||
GenerateFluxPro11TextToImageAspectRatio as FlAr,
|
||||
GenerateFluxPro11TextToImageNumImages as FlN,
|
||||
};
|
||||
|
||||
// ── Generation mode mismatch ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_abort_returns_error() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::AbortGeneration),
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
let result = request.build();
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { field: "image_inputs", .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_generate_anyway_succeeds() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
assert!(request.build().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_none_strategy_succeeds() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: None,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
assert!(request.build().is_ok());
|
||||
}
|
||||
|
||||
// ── Aspect ratio ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_none_is_none() {
|
||||
let request = GenerateImageRequest { aspect_ratio: None, ..base_flux_pro_1p1_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11") };
|
||||
assert!(plan.aspect_ratio.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_auto_and_square_hd_fall_back_to_square() {
|
||||
for ar in [
|
||||
CommonAspectRatio::Auto,
|
||||
CommonAspectRatio::Auto2k,
|
||||
CommonAspectRatio::Auto4k,
|
||||
CommonAspectRatio::SquareHd,
|
||||
] {
|
||||
let request = GenerateImageRequest { aspect_ratio: Some(ar), ..base_flux_pro_1p1_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11") };
|
||||
assert!(matches!(plan.aspect_ratio, Some(FlAr::Square)), "expected Square for {:?}", ar);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_direct_mappings() {
|
||||
let cases = [
|
||||
(CommonAspectRatio::Square, FlAr::Square),
|
||||
(CommonAspectRatio::WideFourByThree, FlAr::LandscapeFourByThree),
|
||||
(CommonAspectRatio::WideThreeByTwo, FlAr::LandscapeFourByThree),
|
||||
(CommonAspectRatio::WideSixteenByNine, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::Wide, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::WideTwentyOneByNine, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::TallThreeByFour, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::TallTwoByThree, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::WideFiveByFour, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::TallNineBySixteen, FlAr::PortraitNineBySixteen),
|
||||
(CommonAspectRatio::Tall, FlAr::PortraitNineBySixteen),
|
||||
(CommonAspectRatio::TallNineByTwentyOne, FlAr::PortraitNineBySixteen),
|
||||
];
|
||||
for (common, expected) in cases {
|
||||
let request = GenerateImageRequest { aspect_ratio: Some(common), ..base_flux_pro_1p1_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11") };
|
||||
assert!(
|
||||
matches!(plan.aspect_ratio, Some(ar) if std::mem::discriminant(&ar) == std::mem::discriminant(&expected)),
|
||||
"expected {:?} for {:?}", expected, common,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_unsupported_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::TallFourByFive),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_unsupported_nearest_match() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::TallFourByFive),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11") };
|
||||
assert!(matches!(plan.aspect_ratio, Some(FlAr::LandscapeFourByThree)));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Num images ───────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn num_images_zero_is_always_error() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::ErrorOut,
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_direct_mapping() {
|
||||
let cases = [(1, FlN::One), (2, FlN::Two), (3, FlN::Three), (4, FlN::Four)];
|
||||
for (count, expected) in cases {
|
||||
let request = GenerateImageRequest { image_batch_count: Some(count), ..base_flux_pro_1p1_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11") };
|
||||
assert!(
|
||||
std::mem::discriminant(&plan.num_images) == std::mem::discriminant(&expected),
|
||||
"expected {:?} for count {}", expected, count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_out_of_range_clamps_to_four() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11") };
|
||||
assert!(matches!(plan.num_images, FlN::Four));
|
||||
}
|
||||
}
|
||||
}
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
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::text::generate_flux_pro_11_ultra_text_to_image::{
|
||||
GenerateFluxPro11UltraTextToImageAspectRatio, GenerateFluxPro11UltraTextToImageNumImages,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlanArtcraftFluxPro11Ultra<'a> {
|
||||
pub prompt: Option<&'a str>,
|
||||
pub aspect_ratio: Option<GenerateFluxPro11UltraTextToImageAspectRatio>,
|
||||
pub num_images: GenerateFluxPro11UltraTextToImageNumImages,
|
||||
pub idempotency_token: String,
|
||||
}
|
||||
|
||||
pub fn plan_generate_image_artcraft_flux_pro_1p1_ultra<'a>(
|
||||
request: &'a GenerateImageRequest<'a>,
|
||||
) -> Result<PlanArtcraftFluxPro11Ultra<'a>, ArtcraftRouterError> {
|
||||
// Flux Pro 1.1 Ultra is text-to-image only. Abort if image inputs are provided and
|
||||
// the caller has opted into strict mode.
|
||||
if request.image_inputs.is_some() {
|
||||
if let Some(GenerationModeMismatchStrategy::AbortGeneration) = request.generation_mode_mismatch_strategy {
|
||||
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_inputs",
|
||||
value: "Flux Pro 1.1 Ultra is a text-to-image model and does not support image editing".to_string(),
|
||||
}));
|
||||
}
|
||||
// None or GenerateAnyway: silently ignore image inputs
|
||||
}
|
||||
|
||||
let strategy = request.request_mismatch_mitigation_strategy;
|
||||
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, strategy)?;
|
||||
let num_images = plan_num_images(request.image_batch_count, strategy)?;
|
||||
|
||||
Ok(PlanArtcraftFluxPro11Ultra {
|
||||
prompt: request.prompt,
|
||||
aspect_ratio,
|
||||
num_images,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
})
|
||||
}
|
||||
|
||||
// Flux Pro 1.1 Ultra supported aspect ratios:
|
||||
// Square, LandscapeThreeByTwo, LandscapeFourByThree, LandscapeSixteenByNine,
|
||||
// LandscapeTwentyOneByNine, PortraitTwoByThree, PortraitThreeByFour,
|
||||
// PortraitNineBySixteen, PortraitNineByTwentyOne
|
||||
fn plan_aspect_ratio(
|
||||
aspect_ratio: Option<CommonAspectRatio>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<GenerateFluxPro11UltraTextToImageAspectRatio>, ArtcraftRouterError> {
|
||||
use GenerateFluxPro11UltraTextToImageAspectRatio as FlAr;
|
||||
match aspect_ratio {
|
||||
None => Ok(None),
|
||||
|
||||
// Auto and SquareHd: no direct equivalent; fall back to square
|
||||
Some(CommonAspectRatio::Auto)
|
||||
| Some(CommonAspectRatio::Auto2k)
|
||||
| Some(CommonAspectRatio::Auto4k)
|
||||
| Some(CommonAspectRatio::SquareHd) => Ok(Some(FlAr::Square)),
|
||||
|
||||
// Direct mappings
|
||||
Some(CommonAspectRatio::Square) => Ok(Some(FlAr::Square)),
|
||||
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Some(FlAr::LandscapeThreeByTwo)),
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(FlAr::LandscapeFourByThree)),
|
||||
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => {
|
||||
Ok(Some(FlAr::LandscapeSixteenByNine))
|
||||
}
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(FlAr::LandscapeTwentyOneByNine)),
|
||||
Some(CommonAspectRatio::TallTwoByThree) => Ok(Some(FlAr::PortraitTwoByThree)),
|
||||
Some(CommonAspectRatio::TallThreeByFour) | Some(CommonAspectRatio::WideFiveByFour) => {
|
||||
Ok(Some(FlAr::PortraitThreeByFour))
|
||||
}
|
||||
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => {
|
||||
Ok(Some(FlAr::PortraitNineBySixteen))
|
||||
}
|
||||
Some(CommonAspectRatio::TallNineByTwentyOne) => Ok(Some(FlAr::PortraitNineByTwentyOne)),
|
||||
|
||||
// Mismatch — TallFourByFive maps to nearest (LandscapeFourByThree per existing desktop behavior)
|
||||
Some(unsupported) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "aspect_ratio",
|
||||
value: format!("{:?}", unsupported),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => {
|
||||
Ok(Some(nearest_aspect_ratio(unsupported)))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> GenerateFluxPro11UltraTextToImageAspectRatio {
|
||||
use GenerateFluxPro11UltraTextToImageAspectRatio as FlAr;
|
||||
match aspect_ratio {
|
||||
// TallFourByFive (0.8) — nearest is LandscapeFourByThree per existing desktop behavior
|
||||
CommonAspectRatio::TallFourByFive => FlAr::LandscapeFourByThree,
|
||||
_ => FlAr::Square,
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_num_images(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<GenerateFluxPro11UltraTextToImageNumImages, ArtcraftRouterError> {
|
||||
use GenerateFluxPro11UltraTextToImageNumImages as FlN;
|
||||
let count = image_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 => Ok(FlN::One),
|
||||
2 => Ok(FlN::Two),
|
||||
3 => Ok(FlN::Three),
|
||||
4 => Ok(FlN::Four),
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(FlN::Four),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::base_flux_pro_1p1_ultra_image_request;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_ultra_text_to_image::{
|
||||
GenerateFluxPro11UltraTextToImageAspectRatio as FlAr,
|
||||
GenerateFluxPro11UltraTextToImageNumImages as FlN,
|
||||
};
|
||||
|
||||
// ── Generation mode mismatch ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_abort_returns_error() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::AbortGeneration),
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
let result = request.build();
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { field: "image_inputs", .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_generate_anyway_succeeds() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
assert!(request.build().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_inputs_with_none_strategy_succeeds() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
generation_mode_mismatch_strategy: None,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
assert!(request.build().is_ok());
|
||||
}
|
||||
|
||||
// ── Aspect ratio ─────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_none_is_none() {
|
||||
let request = GenerateImageRequest { aspect_ratio: None, ..base_flux_pro_1p1_ultra_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11Ultra(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11Ultra") };
|
||||
assert!(plan.aspect_ratio.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_auto_and_square_hd_fall_back_to_square() {
|
||||
for ar in [
|
||||
CommonAspectRatio::Auto,
|
||||
CommonAspectRatio::Auto2k,
|
||||
CommonAspectRatio::Auto4k,
|
||||
CommonAspectRatio::SquareHd,
|
||||
] {
|
||||
let request = GenerateImageRequest { aspect_ratio: Some(ar), ..base_flux_pro_1p1_ultra_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11Ultra(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11Ultra") };
|
||||
assert!(matches!(plan.aspect_ratio, Some(FlAr::Square)), "expected Square for {:?}", ar);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_direct_mappings() {
|
||||
let cases = [
|
||||
(CommonAspectRatio::Square, FlAr::Square),
|
||||
(CommonAspectRatio::WideThreeByTwo, FlAr::LandscapeThreeByTwo),
|
||||
(CommonAspectRatio::WideFourByThree, FlAr::LandscapeFourByThree),
|
||||
(CommonAspectRatio::WideSixteenByNine, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::Wide, FlAr::LandscapeSixteenByNine),
|
||||
(CommonAspectRatio::WideTwentyOneByNine, FlAr::LandscapeTwentyOneByNine),
|
||||
(CommonAspectRatio::TallTwoByThree, FlAr::PortraitTwoByThree),
|
||||
(CommonAspectRatio::TallThreeByFour, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::WideFiveByFour, FlAr::PortraitThreeByFour),
|
||||
(CommonAspectRatio::TallNineBySixteen, FlAr::PortraitNineBySixteen),
|
||||
(CommonAspectRatio::Tall, FlAr::PortraitNineBySixteen),
|
||||
(CommonAspectRatio::TallNineByTwentyOne, FlAr::PortraitNineByTwentyOne),
|
||||
];
|
||||
for (common, expected) in cases {
|
||||
let request = GenerateImageRequest { aspect_ratio: Some(common), ..base_flux_pro_1p1_ultra_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11Ultra(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11Ultra") };
|
||||
assert!(
|
||||
matches!(plan.aspect_ratio, Some(ar) if std::mem::discriminant(&ar) == std::mem::discriminant(&expected)),
|
||||
"expected {:?} for {:?}", expected, common,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_unsupported_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::TallFourByFive),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_unsupported_nearest_match() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::TallFourByFive),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11Ultra(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11Ultra") };
|
||||
assert!(matches!(plan.aspect_ratio, Some(FlAr::LandscapeFourByThree)));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Num images ───────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn num_images_zero_is_always_error() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::ErrorOut,
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_direct_mapping() {
|
||||
let cases = [(1, FlN::One), (2, FlN::Two), (3, FlN::Three), (4, FlN::Four)];
|
||||
for (count, expected) in cases {
|
||||
let request = GenerateImageRequest { image_batch_count: Some(count), ..base_flux_pro_1p1_ultra_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11Ultra(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11Ultra") };
|
||||
assert!(
|
||||
std::mem::discriminant(&plan.num_images) == std::mem::discriminant(&expected),
|
||||
"expected {:?} for count {}", expected, count,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
assert!(matches!(
|
||||
request.build(),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_out_of_range_clamps_to_four() {
|
||||
for strategy in [
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftFluxPro11Ultra(plan) = request.build().unwrap() else { panic!("expected ArtcraftFluxPro11Ultra") };
|
||||
assert!(matches!(plan.num_images, FlN::Four));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -252,6 +252,7 @@ mod tests {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub fn base_image_request() -> GenerateImageRequest<'static> {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
@@ -53,6 +54,7 @@ pub fn base_fal_image_request() -> GenerateImageRequest<'static> {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
@@ -67,6 +69,7 @@ pub fn base_seedream_4_image_request() -> GenerateImageRequest<'static> {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
@@ -81,6 +84,7 @@ pub fn base_seedream_4p5_image_request() -> GenerateImageRequest<'static> {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
@@ -95,6 +99,7 @@ pub fn base_nano_banana_image_request() -> GenerateImageRequest<'static> {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
@@ -109,6 +114,67 @@ pub fn base_gpt_image_1p5_image_request() -> GenerateImageRequest<'static> {
|
||||
aspect_ratio: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_flux_1_dev_image_request() -> GenerateImageRequest<'static> {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::Flux1Dev,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: Some("a cat in space"),
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_flux_1_schnell_image_request() -> GenerateImageRequest<'static> {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::Flux1Schnell,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: Some("a cat in space"),
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_flux_pro_1p1_image_request() -> GenerateImageRequest<'static> {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::FluxPro11,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: Some("a cat in space"),
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_flux_pro_1p1_ultra_image_request() -> GenerateImageRequest<'static> {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::FluxPro11Ultra,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: Some("a cat in space"),
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -74,6 +74,7 @@ pub(super) async fn handle_image_edit_artcraft_via_router(
|
||||
aspect_ratio,
|
||||
image_batch_count: request.image_count.map(|n| n as u16),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_artcraft_flux_1_dev::aspect_ratio_to_artcraft_flux_1_dev;
|
||||
use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_artcraft_nano_banana_pro::aspect_ratio_to_artcraft_nano_banana_pro;
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_api_defs::generate::image::multi_function::nano_banana_pro_multi_function_image_gen::NanoBananaProMultiFunctionImageGenAspectRatio;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_dev_text_to_image::{GenerateFlux1DevTextToImageAspectRatio, GenerateFlux1DevTextToImageNumImages, GenerateFlux1DevTextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
|
||||
|
||||
pub async fn handle_artcraft_flux_1_dev_text_to_image(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
info!("enqueue Flux 1 Dev");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let request = GenerateFlux1DevTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt.clone(),
|
||||
aspect_ratio: get_aspect_ratio(request),
|
||||
num_images: request.number_images
|
||||
.and_then(|num| match num {
|
||||
1 => Some(GenerateFlux1DevTextToImageNumImages::One),
|
||||
2 => Some(GenerateFlux1DevTextToImageNumImages::Two),
|
||||
3 => Some(GenerateFlux1DevTextToImageNumImages::Three),
|
||||
4 => Some(GenerateFlux1DevTextToImageNumImages::Four),
|
||||
_ => None, // NB: use service defaults
|
||||
}),
|
||||
};
|
||||
|
||||
let result = generate_flux_1_dev_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_token = match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux 1 Dev text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux 1 Dev text to image generation: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(GenerationModel::Flux1Dev),
|
||||
provider_job_id: Some(job_token.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option<GenerateFlux1DevTextToImageAspectRatio> {
|
||||
if let Some(common_aspect_ratio) = request.common_aspect_ratio {
|
||||
// Handle modern aspect ratio
|
||||
let aspect = aspect_ratio_to_artcraft_flux_1_dev(common_aspect_ratio);
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
if let Some(aspect_ratio) = request.aspect_ratio {
|
||||
// Handle deprecated aspect ratio
|
||||
let aspect = match aspect_ratio {
|
||||
TextToImageSize::Tall => GenerateFlux1DevTextToImageAspectRatio::PortraitNineBySixteen,
|
||||
TextToImageSize::Wide => GenerateFlux1DevTextToImageAspectRatio::LandscapeSixteenByNine,
|
||||
TextToImageSize::Auto => GenerateFlux1DevTextToImageAspectRatio::SquareHd,
|
||||
TextToImageSize::Square => GenerateFlux1DevTextToImageAspectRatio::SquareHd,
|
||||
};
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_artcraft_flux_1_dev::aspect_ratio_to_artcraft_flux_1_dev;
|
||||
use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_artcraft_flux_1_schnell::aspect_ratio_to_artcraft_flux_1_schnell;
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageAspectRatio, GenerateFlux1SchnellTextToImageNumImages, GenerateFlux1SchnellTextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
|
||||
|
||||
pub async fn handle_artcraft_flux_1_schnell_text_to_image(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
info!("enqueue Flux 1 Schnell");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let request = GenerateFlux1SchnellTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt.clone(),
|
||||
aspect_ratio: get_aspect_ratio(request),
|
||||
num_images: request.number_images
|
||||
.and_then(|num| match num {
|
||||
1 => Some(GenerateFlux1SchnellTextToImageNumImages::One),
|
||||
2 => Some(GenerateFlux1SchnellTextToImageNumImages::Two),
|
||||
3 => Some(GenerateFlux1SchnellTextToImageNumImages::Three),
|
||||
4 => Some(GenerateFlux1SchnellTextToImageNumImages::Four),
|
||||
_ => None, // NB: use service defaults
|
||||
}),
|
||||
};
|
||||
|
||||
let result = generate_flux_1_schnell_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_token = match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux 1 Schnell text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux 1 Schnell text to image generation: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(GenerationModel::Flux1Schnell),
|
||||
provider_job_id: Some(job_token.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option<GenerateFlux1SchnellTextToImageAspectRatio> {
|
||||
if let Some(common_aspect_ratio) = request.common_aspect_ratio {
|
||||
// Handle modern aspect ratio
|
||||
let aspect = aspect_ratio_to_artcraft_flux_1_schnell(common_aspect_ratio);
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
if let Some(aspect_ratio) = request.aspect_ratio {
|
||||
// Handle deprecated aspect ratio
|
||||
let aspect = match aspect_ratio {
|
||||
TextToImageSize::Tall => GenerateFlux1SchnellTextToImageAspectRatio::PortraitNineBySixteen,
|
||||
TextToImageSize::Wide => GenerateFlux1SchnellTextToImageAspectRatio::LandscapeSixteenByNine,
|
||||
TextToImageSize::Auto => GenerateFlux1SchnellTextToImageAspectRatio::SquareHd,
|
||||
TextToImageSize::Square => GenerateFlux1SchnellTextToImageAspectRatio::SquareHd,
|
||||
};
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_artcraft_flux_1_schnell::aspect_ratio_to_artcraft_flux_1_schnell;
|
||||
use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_artcraft_flux_pro_1p1::aspect_ratio_to_artcraft_flux_pro_1p1;
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_text_to_image::{GenerateFluxPro11TextToImageAspectRatio, GenerateFluxPro11TextToImageNumImages, GenerateFluxPro11TextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
|
||||
|
||||
pub async fn handle_artcraft_flux_pro_1p1_text_to_image(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
info!("enqueue Flux Pro 1.1");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let request = GenerateFluxPro11TextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt.clone(),
|
||||
aspect_ratio: get_aspect_ratio(request),
|
||||
num_images: request.number_images
|
||||
.and_then(|num| match num {
|
||||
1 => Some(GenerateFluxPro11TextToImageNumImages::One),
|
||||
2 => Some(GenerateFluxPro11TextToImageNumImages::Two),
|
||||
3 => Some(GenerateFluxPro11TextToImageNumImages::Three),
|
||||
4 => Some(GenerateFluxPro11TextToImageNumImages::Four),
|
||||
_ => None, // NB: use service defaults
|
||||
}),
|
||||
};
|
||||
|
||||
let result = generate_flux_pro_11_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_token = match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux Pro 1.1 text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux Pro 1.1 text to image generation: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(GenerationModel::FluxPro11),
|
||||
provider_job_id: Some(job_token.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option<GenerateFluxPro11TextToImageAspectRatio> {
|
||||
if let Some(common_aspect_ratio) = request.common_aspect_ratio {
|
||||
// Handle modern aspect ratio
|
||||
let aspect = aspect_ratio_to_artcraft_flux_pro_1p1(common_aspect_ratio);
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
if let Some(aspect_ratio) = request.aspect_ratio {
|
||||
// Handle deprecated aspect ratio
|
||||
let aspect = match aspect_ratio {
|
||||
TextToImageSize::Tall => GenerateFluxPro11TextToImageAspectRatio::PortraitNineBySixteen,
|
||||
TextToImageSize::Wide => GenerateFluxPro11TextToImageAspectRatio::LandscapeSixteenByNine,
|
||||
TextToImageSize::Square => GenerateFluxPro11TextToImageAspectRatio::Square,
|
||||
TextToImageSize::Auto => GenerateFluxPro11TextToImageAspectRatio::Square,
|
||||
};
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_artcraft_flux_pro_1p1::aspect_ratio_to_artcraft_flux_pro_1p1;
|
||||
use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_artcraft_flux_pro_1p1_ultra::aspect_ratio_to_artcraft_flux_pro_1p1_ultra;
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_ultra_text_to_image::{GenerateFluxPro11UltraTextToImageAspectRatio, GenerateFluxPro11UltraTextToImageNumImages, GenerateFluxPro11UltraTextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
|
||||
|
||||
pub async fn handle_artcraft_flux_pro_1p1_ultra_text_to_image(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
info!("enqueue Flux Pro 1.1 Ultra");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let request = GenerateFluxPro11UltraTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt.clone(),
|
||||
aspect_ratio: get_aspect_ratio(request),
|
||||
num_images: request.number_images
|
||||
.and_then(|num| match num {
|
||||
1 => Some(GenerateFluxPro11UltraTextToImageNumImages::One),
|
||||
2 => Some(GenerateFluxPro11UltraTextToImageNumImages::Two),
|
||||
3 => Some(GenerateFluxPro11UltraTextToImageNumImages::Three),
|
||||
4 => Some(GenerateFluxPro11UltraTextToImageNumImages::Four),
|
||||
_ => None, // NB: use service defaults
|
||||
}),
|
||||
};
|
||||
|
||||
let result = generate_flux_pro_11_ultra_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_token = match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux Pro 1.1 Ultra text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux Pro 1.1 Ultra text to image generation: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(GenerationModel::FluxPro11Ultra),
|
||||
provider_job_id: Some(job_token.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option<GenerateFluxPro11UltraTextToImageAspectRatio> {
|
||||
if let Some(common_aspect_ratio) = request.common_aspect_ratio {
|
||||
// Handle modern aspect ratio
|
||||
let aspect = aspect_ratio_to_artcraft_flux_pro_1p1_ultra(common_aspect_ratio);
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
if let Some(aspect_ratio) = request.aspect_ratio {
|
||||
// Handle deprecated aspect ratio
|
||||
let aspect = match aspect_ratio {
|
||||
TextToImageSize::Tall => GenerateFluxPro11UltraTextToImageAspectRatio::PortraitNineBySixteen,
|
||||
TextToImageSize::Wide => GenerateFluxPro11UltraTextToImageAspectRatio::LandscapeSixteenByNine,
|
||||
TextToImageSize::Square => GenerateFluxPro11UltraTextToImageAspectRatio::Square,
|
||||
TextToImageSize::Auto => GenerateFluxPro11UltraTextToImageAspectRatio::Square,
|
||||
};
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
+5
-9
@@ -1,9 +1,5 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_flux_1_dev_text_to_image::handle_artcraft_flux_1_dev_text_to_image;
|
||||
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_flux_1_schnell_text_to_image::handle_artcraft_flux_1_schnell_text_to_image;
|
||||
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_flux_pro_1p1_text_to_image::handle_artcraft_flux_pro_1p1_text_to_image;
|
||||
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_flux_pro_1p1_ultra_text_to_image::handle_artcraft_flux_pro_1p1_ultra_text_to_image;
|
||||
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_gpt_image_1_text_to_image::handle_artcraft_gpt_image_1_text_to_image;
|
||||
use crate::core::commands::enqueue::text_to_image::artcraft::handle_text_to_image_artcraft_via_router::handle_text_to_image_artcraft_via_router;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageModel};
|
||||
@@ -24,7 +20,7 @@ pub async fn handle_text_to_image_artcraft(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
|
||||
match model {
|
||||
TextToImageModel::Midjourney | TextToImageModel::GrokImage => {
|
||||
Err(GenerateError::BadProviderForModel {
|
||||
@@ -35,10 +31,10 @@ pub async fn handle_text_to_image_artcraft(
|
||||
TextToImageModel::Recraft3 => {
|
||||
Err(GenerateError::NotYetImplemented(format!("not yet implemented in Artcraft")))
|
||||
}
|
||||
TextToImageModel::Flux1Dev => handle_artcraft_flux_1_dev_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
|
||||
TextToImageModel::Flux1Schnell => handle_artcraft_flux_1_schnell_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
|
||||
TextToImageModel::FluxPro11 =>handle_artcraft_flux_pro_1p1_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
|
||||
TextToImageModel::FluxPro11Ultra => handle_artcraft_flux_pro_1p1_ultra_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
|
||||
TextToImageModel::Flux1Dev => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Flux1Dev, GenerationModel::Flux1Dev).await,
|
||||
TextToImageModel::Flux1Schnell => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Flux1Schnell, GenerationModel::Flux1Schnell).await,
|
||||
TextToImageModel::FluxPro11 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::FluxPro11, GenerationModel::FluxPro11).await,
|
||||
TextToImageModel::FluxPro11Ultra => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::FluxPro11Ultra, GenerationModel::FluxPro11Ultra).await,
|
||||
TextToImageModel::GptImage1 => handle_artcraft_gpt_image_1_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
|
||||
TextToImageModel::GptImage1p5 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::GptImage1p5, GenerationModel::GptImage1p5).await,
|
||||
TextToImageModel::Gemini25Flash | TextToImageModel::NanoBanana => {
|
||||
|
||||
+1
@@ -52,6 +52,7 @@ pub(super) async fn handle_text_to_image_artcraft_via_router(
|
||||
aspect_ratio,
|
||||
image_batch_count: request.number_images.map(|n| n as u16),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
pub (super) mod handle_text_to_image_artcraft;
|
||||
|
||||
mod handle_text_to_image_artcraft_via_router;
|
||||
mod handle_artcraft_flux_1_dev_text_to_image;
|
||||
mod handle_artcraft_flux_1_schnell_text_to_image;
|
||||
mod handle_artcraft_flux_pro_1p1_text_to_image;
|
||||
mod handle_artcraft_flux_pro_1p1_ultra_text_to_image;
|
||||
mod handle_artcraft_gpt_image_1_text_to_image;
|
||||
mod handle_artcraft_gpt_image_1_text_to_image;
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ pub async fn estimate_image_cost_handler(
|
||||
aspect_ratio: router_aspect_ratio,
|
||||
image_batch_count: request.image_batch_count,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user