mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-23 01:59:31 -04:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f573dce150 | ||
|
|
4cc5425b38 | ||
|
|
dcac0fb352 | ||
|
|
1f29cb1f50 | ||
|
|
0a6b7933f4 | ||
|
|
79ccdc3ac3 |
+68
@@ -0,0 +1,68 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const BYTEDANCE_SEEDREAM_5_LITE_MULTI_FUNCTION_IMAGE_GEN_PATH: &str = "/v1/generate/image/multi_function/bytedance_seedream_5_lite";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct BytedanceSeedream5LiteMultiFunctionImageGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Image media tokens to include in the editing context.
|
||||
/// If present, we're doing image editing (image-to-image / image-editing)
|
||||
/// If absent, we're doing image generation (text-to-image)
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<BytedanceSeedream5LiteMultiFunctionImageGenNumImages>,
|
||||
|
||||
pub max_images: Option<BytedanceSeedream5LiteMultiFunctionImageGenMaxImages>,
|
||||
|
||||
pub image_size: Option<BytedanceSeedream5LiteMultiFunctionImageGenImageSize>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BytedanceSeedream5LiteMultiFunctionImageGenNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BytedanceSeedream5LiteMultiFunctionImageGenMaxImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BytedanceSeedream5LiteMultiFunctionImageGenImageSize {
|
||||
// Square
|
||||
Square,
|
||||
SquareHd,
|
||||
// Tall
|
||||
PortraitFourThree,
|
||||
PortraitSixteenNine,
|
||||
// Wide
|
||||
LandscapeFourThree,
|
||||
LandscapeSixteenNine,
|
||||
// Auto (NB: 5 Lite max is auto_3K, not auto_4K like V4.5)
|
||||
Auto2k,
|
||||
Auto3k,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct BytedanceSeedream5LiteMultiFunctionImageGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod bytedance_seedream_v4_multi_function_image_gen;
|
||||
pub mod bytedance_seedream_v4p5_multi_function_image_gen;
|
||||
pub mod bytedance_seedream_5_lite_multi_function_image_gen;
|
||||
pub mod gpt_image_1p5_multi_function_image_gen;
|
||||
pub mod nano_banana_2_multi_function_image_gen;
|
||||
pub mod nano_banana_multi_function_image_gen;
|
||||
pub mod nano_banana_pro_multi_function_image_gen;
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const NANO_BANANA_2_MULTI_FUNCTION_IMAGE_GEN_PATH: &str = "/v1/generate/image/multi_function/nano_banana_2";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct NanaBanana2MultiFunctionImageGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Image media tokens to include in the editing context.
|
||||
/// If present, we're doing image editing (image-to-image / image-editing)
|
||||
/// If absent, we're doing image generation (text-to-image)
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<NanaBanana2MultiFunctionImageGenNumImages>,
|
||||
|
||||
/// Resolution of the image to generate. Default is OneK (1K).
|
||||
pub resolution: Option<NanaBanana2MultiFunctionImageGenImageResolution>,
|
||||
|
||||
/// Aspect ratio of the images to generate. Default is "1:1"
|
||||
pub aspect_ratio: Option<NanaBanana2MultiFunctionImageGenAspectRatio>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NanaBanana2MultiFunctionImageGenNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NanaBanana2MultiFunctionImageGenImageResolution {
|
||||
HalfK,
|
||||
OneK,
|
||||
TwoK,
|
||||
FourK,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NanaBanana2MultiFunctionImageGenAspectRatio {
|
||||
// Auto (only for image editing)
|
||||
Auto, // Default for image editing.
|
||||
// Square
|
||||
OneByOne, // Default for text-to-image
|
||||
// Wide
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
ThreeByTwo,
|
||||
SixteenByNine,
|
||||
TwentyOneByNine,
|
||||
// Tall
|
||||
FourByFive,
|
||||
ThreeByFour,
|
||||
TwoByThree,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct NanaBanana2MultiFunctionImageGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen::{BytedanceSeedream5LiteMultiFunctionImageGenRequest, BytedanceSeedream5LiteMultiFunctionImageGenResponse, BYTEDANCE_SEEDREAM_5_LITE_MULTI_FUNCTION_IMAGE_GEN_PATH};
|
||||
|
||||
pub async fn bytedance_seedream_5_lite_multi_function_image_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: BytedanceSeedream5LiteMultiFunctionImageGenRequest,
|
||||
) -> Result<BytedanceSeedream5LiteMultiFunctionImageGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
BYTEDANCE_SEEDREAM_5_LITE_MULTI_FUNCTION_IMAGE_GEN_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+2
@@ -1,5 +1,7 @@
|
||||
pub mod bytedance_seedream_v4_multi_function_image_gen_image;
|
||||
pub mod bytedance_seedream_v4p5_multi_function_image_gen_image;
|
||||
pub mod bytedance_seedream_5_lite_multi_function_image_gen_image;
|
||||
pub mod gpt_image_1p5_multi_function_image_gen_image;
|
||||
pub mod nano_banana_2_multi_function_image_gen_image;
|
||||
pub mod nano_banana_multi_function_image_gen_image;
|
||||
pub mod nano_banana_pro_multi_function_image_gen_image;
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::multi_function::nano_banana_2_multi_function_image_gen::{NanaBanana2MultiFunctionImageGenRequest, NanaBanana2MultiFunctionImageGenResponse, NANO_BANANA_2_MULTI_FUNCTION_IMAGE_GEN_PATH};
|
||||
|
||||
pub async fn nano_banana_2_multi_function_image_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: NanaBanana2MultiFunctionImageGenRequest,
|
||||
) -> Result<NanaBanana2MultiFunctionImageGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
NANO_BANANA_2_MULTI_FUNCTION_IMAGE_GEN_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -17,10 +17,14 @@ pub enum CommonImageModel {
|
||||
GptImage1p5,
|
||||
#[serde(rename = "nano_banana")]
|
||||
NanaBanana,
|
||||
#[serde(rename = "nano_banana_2")]
|
||||
NanaBanana2,
|
||||
#[serde(rename = "nano_banana_pro")]
|
||||
NanaBananaPro,
|
||||
#[serde(rename = "seedream_4")]
|
||||
Seedream4,
|
||||
#[serde(rename = "seedream_4p5")]
|
||||
Seedream4p5,
|
||||
#[serde(rename = "seedream_5_lite")]
|
||||
Seedream5Lite,
|
||||
}
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
use artcraft_api_defs::generate::image::multi_function::nano_banana_2_multi_function_image_gen::{
|
||||
NanaBanana2MultiFunctionImageGenImageResolution, NanaBanana2MultiFunctionImageGenNumImages,
|
||||
};
|
||||
|
||||
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_2::PlanArtcraftNanaBanana2;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_nano_banana_2(
|
||||
plan: &PlanArtcraftNanaBanana2<'_>,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.08/image at 0.5K, $0.15/image at 1K or 2K, $0.30/image at 4K.
|
||||
// (Stored as USD cents.)
|
||||
let cost_per_image: u64 = match plan.resolution {
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::HalfK) => 8,
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::FourK) => 30,
|
||||
_ => 15, // OneK, TwoK, or None (default 1K)
|
||||
};
|
||||
|
||||
let num_images: u64 = match plan.num_images {
|
||||
NanaBanana2MultiFunctionImageGenNumImages::One => 1,
|
||||
NanaBanana2MultiFunctionImageGenNumImages::Two => 2,
|
||||
NanaBanana2MultiFunctionImageGenNumImages::Three => 3,
|
||||
NanaBanana2MultiFunctionImageGenNumImages::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::common_resolution::CommonResolution;
|
||||
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(resolution: Option<CommonResolution>, image_batch_count: u16) -> u64 {
|
||||
let request = GenerateImageRequest {
|
||||
model: CommonImageModel::NanaBanana2,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
image_inputs: None,
|
||||
resolution,
|
||||
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() {
|
||||
// 1K/2K: $0.15/image = 15 cents
|
||||
assert_eq!(estimate_usd_cents(None, 1), 15);
|
||||
assert_eq!(estimate_usd_cents(Some(CommonResolution::OneK), 1), 15);
|
||||
assert_eq!(estimate_usd_cents(Some(CommonResolution::TwoK), 1), 15);
|
||||
assert_eq!(estimate_usd_cents(Some(CommonResolution::OneK), 2), 30);
|
||||
assert_eq!(estimate_usd_cents(Some(CommonResolution::OneK), 4), 60);
|
||||
|
||||
// 4K: $0.30/image = 30 cents
|
||||
assert_eq!(estimate_usd_cents(Some(CommonResolution::FourK), 1), 30);
|
||||
assert_eq!(estimate_usd_cents(Some(CommonResolution::FourK), 2), 60);
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen::BytedanceSeedream5LiteMultiFunctionImageGenNumImages;
|
||||
|
||||
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::PlanArtcraftSeedream5Lite;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_seedream_5_lite(
|
||||
plan: &PlanArtcraftSeedream5Lite<'_>,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 4;
|
||||
|
||||
let num_images: u64 = match plan.num_images {
|
||||
BytedanceSeedream5LiteMultiFunctionImageGenNumImages::One => 1,
|
||||
BytedanceSeedream5LiteMultiFunctionImageGenNumImages::Two => 2,
|
||||
BytedanceSeedream5LiteMultiFunctionImageGenNumImages::Three => 3,
|
||||
BytedanceSeedream5LiteMultiFunctionImageGenNumImages::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::Seedream5Lite,
|
||||
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.04/image = 4 cents each
|
||||
assert_eq!(estimate_usd_cents(1), 4);
|
||||
assert_eq!(estimate_usd_cents(2), 8);
|
||||
assert_eq!(estimate_usd_cents(3), 12);
|
||||
assert_eq!(estimate_usd_cents(4), 16);
|
||||
}
|
||||
}
|
||||
+2
@@ -4,6 +4,8 @@ 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_2;
|
||||
pub mod estimate_image_cost_artcraft_nano_banana_pro;
|
||||
pub mod estimate_image_cost_artcraft_seedream_4;
|
||||
pub mod estimate_image_cost_artcraft_seedream_4p5;
|
||||
pub mod estimate_image_cost_artcraft_seedream_5_lite;
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
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_nano_banana_2::PlanArtcraftNanaBanana2;
|
||||
use artcraft_api_defs::generate::image::multi_function::nano_banana_2_multi_function_image_gen::NanaBanana2MultiFunctionImageGenRequest;
|
||||
use artcraft_client::endpoints::generate::image::multi_function::nano_banana_2_multi_function_image_gen_image::nano_banana_2_multi_function_image_gen;
|
||||
|
||||
pub async fn execute_artcraft_nano_banana_2(
|
||||
plan: &PlanArtcraftNanaBanana2<'_>,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = NanaBanana2MultiFunctionImageGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
|
||||
num_images: Some(plan.num_images),
|
||||
resolution: plan.resolution,
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
};
|
||||
|
||||
let response = nano_banana_2_multi_function_image_gen(
|
||||
&artcraft_client.api_host,
|
||||
Some(&artcraft_client.credentials),
|
||||
request,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?;
|
||||
|
||||
Ok(GenerateImageResponse::Artcraft(ArtcraftImageResponsePayload {
|
||||
inference_job_token: response.inference_job_token,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::{base_nano_banana_2_image_request, get_artcraft_client};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_nano_banana_2() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_nano_banana_2_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.
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
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_seedream_5_lite::PlanArtcraftSeedream5Lite;
|
||||
use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen::BytedanceSeedream5LiteMultiFunctionImageGenRequest;
|
||||
use artcraft_client::endpoints::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen_image::bytedance_seedream_5_lite_multi_function_image_gen;
|
||||
|
||||
pub async fn execute_artcraft_seedream_5_lite(
|
||||
plan: &PlanArtcraftSeedream5Lite<'_>,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = BytedanceSeedream5LiteMultiFunctionImageGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
|
||||
num_images: Some(plan.num_images),
|
||||
image_size: plan.image_size,
|
||||
max_images: None,
|
||||
};
|
||||
|
||||
let response = bytedance_seedream_5_lite_multi_function_image_gen(
|
||||
&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::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::{base_seedream_5_lite_image_request, get_artcraft_client};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test_text_to_image_seedream_5_lite() {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night"),
|
||||
..base_seedream_5_lite_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.
|
||||
}
|
||||
}
|
||||
+2
@@ -4,6 +4,8 @@ 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_2;
|
||||
pub mod generate_image_artcraft_nano_banana_pro;
|
||||
pub mod generate_image_artcraft_seedream_4;
|
||||
pub mod generate_image_artcraft_seedream_4p5;
|
||||
pub mod generate_image_artcraft_seedream_5_lite;
|
||||
|
||||
+11
-1
@@ -14,9 +14,11 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf
|
||||
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_2::plan_generate_image_artcraft_nano_banana_2;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_pro::plan_generate_image_artcraft_nano_banana_pro;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4::plan_generate_image_artcraft_seedream_4;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4p5::plan_generate_image_artcraft_seedream_4p5;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::plan_generate_image_artcraft_seedream_5_lite;
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::plan_generate_image_fal_nano_banana_pro;
|
||||
|
||||
pub struct GenerateImageRequest<'a> {
|
||||
@@ -79,6 +81,9 @@ impl<'a> GenerateImageRequest<'a> {
|
||||
CommonImageModel::NanaBanana => {
|
||||
plan_generate_image_artcraft_nano_banana(self).map(ImageGenerationPlan::ArtcraftNanaBanana)
|
||||
}
|
||||
CommonImageModel::NanaBanana2 => {
|
||||
plan_generate_image_artcraft_nano_banana_2(self).map(ImageGenerationPlan::ArtcraftNanaBanana2)
|
||||
}
|
||||
CommonImageModel::NanaBananaPro => {
|
||||
plan_generate_image_artcraft_nano_banana_pro(self).map(ImageGenerationPlan::ArtcraftNanaBananaPro)
|
||||
}
|
||||
@@ -88,6 +93,9 @@ impl<'a> GenerateImageRequest<'a> {
|
||||
CommonImageModel::Seedream4p5 => {
|
||||
plan_generate_image_artcraft_seedream_4p5(self).map(ImageGenerationPlan::ArtcraftSeedream4p5)
|
||||
}
|
||||
CommonImageModel::Seedream5Lite => {
|
||||
plan_generate_image_artcraft_seedream_5_lite(self).map(ImageGenerationPlan::ArtcraftSeedream5Lite)
|
||||
}
|
||||
},
|
||||
Provider::Fal => match self.model {
|
||||
CommonImageModel::NanaBananaPro => {
|
||||
@@ -99,8 +107,10 @@ impl<'a> GenerateImageRequest<'a> {
|
||||
| CommonImageModel::FluxPro11Ultra
|
||||
| CommonImageModel::GptImage1p5
|
||||
| CommonImageModel::NanaBanana
|
||||
| CommonImageModel::NanaBanana2
|
||||
| CommonImageModel::Seedream4
|
||||
| CommonImageModel::Seedream4p5 => {
|
||||
| CommonImageModel::Seedream4p5
|
||||
| CommonImageModel::Seedream5Lite => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "provider",
|
||||
value: format!("{:?} is only available on the Artcraft provider", self.model),
|
||||
|
||||
+22
@@ -6,9 +6,11 @@ use crate::generate::generate_image::cost::artcraft::estimate_image_cost_artcraf
|
||||
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_2::estimate_image_cost_artcraft_nano_banana_2;
|
||||
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::artcraft::estimate_image_cost_artcraft_seedream_5_lite::estimate_image_cost_artcraft_seedream_5_lite;
|
||||
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;
|
||||
@@ -16,9 +18,11 @@ use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_
|
||||
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_2::execute_artcraft_nano_banana_2;
|
||||
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_nano_banana_pro::execute_artcraft_nano_banana_pro;
|
||||
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_seedream_4::execute_artcraft_seedream_4;
|
||||
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_seedream_4p5::execute_artcraft_seedream_4p5;
|
||||
use crate::generate::generate_image::execute::artcraft::generate_image_artcraft_seedream_5_lite::execute_artcraft_seedream_5_lite;
|
||||
use crate::generate::generate_image::execute::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;
|
||||
@@ -28,9 +32,11 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf
|
||||
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_2::PlanArtcraftNanaBanana2;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_pro::PlanArtcraftNanaBananaPro;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4::PlanArtcraftSeedream4;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4p5::PlanArtcraftSeedream4p5;
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::PlanArtcraftSeedream5Lite;
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_banana_pro::PlanFalNanaBananaPro;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -41,9 +47,11 @@ pub enum ImageGenerationPlan<'a> {
|
||||
ArtcraftFluxPro11Ultra(PlanArtcraftFluxPro11Ultra<'a>),
|
||||
ArtcraftGptImage1p5(PlanArtcraftGptImage1p5<'a>),
|
||||
ArtcraftNanaBanana(PlanArtcraftNanaBanana<'a>),
|
||||
ArtcraftNanaBanana2(PlanArtcraftNanaBanana2<'a>),
|
||||
ArtcraftNanaBananaPro(PlanArtcraftNanaBananaPro<'a>),
|
||||
ArtcraftSeedream4(PlanArtcraftSeedream4<'a>),
|
||||
ArtcraftSeedream4p5(PlanArtcraftSeedream4p5<'a>),
|
||||
ArtcraftSeedream5Lite(PlanArtcraftSeedream5Lite<'a>),
|
||||
FalNanaBananaPro(PlanFalNanaBananaPro<'a>),
|
||||
}
|
||||
|
||||
@@ -77,6 +85,10 @@ impl<'a> ImageGenerationPlan<'a> {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_nano_banana(plan, artcraft_client).await
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftNanaBanana2(plan) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_nano_banana_2(plan, artcraft_client).await
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftNanaBananaPro(plan) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_nano_banana_pro(plan, artcraft_client).await
|
||||
@@ -89,6 +101,10 @@ impl<'a> ImageGenerationPlan<'a> {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_seedream_4p5(plan, artcraft_client).await
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftSeedream5Lite(plan) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
execute_artcraft_seedream_5_lite(plan, artcraft_client).await
|
||||
}
|
||||
ImageGenerationPlan::FalNanaBananaPro(plan) => {
|
||||
let fal_client = client.get_fal_client_ref()?;
|
||||
execute_fal_nano_banana_pro(plan, fal_client).await
|
||||
@@ -116,6 +132,9 @@ impl<'a> ImageGenerationPlan<'a> {
|
||||
ImageGenerationPlan::ArtcraftNanaBanana(plan) => {
|
||||
estimate_image_cost_artcraft_nano_banana(plan)
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftNanaBanana2(plan) => {
|
||||
estimate_image_cost_artcraft_nano_banana_2(plan)
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftNanaBananaPro(plan) => {
|
||||
estimate_image_cost_artcraft_nano_banana_pro(plan)
|
||||
}
|
||||
@@ -125,6 +144,9 @@ impl<'a> ImageGenerationPlan<'a> {
|
||||
ImageGenerationPlan::ArtcraftSeedream4p5(plan) => {
|
||||
estimate_image_cost_artcraft_seedream_4p5(plan)
|
||||
}
|
||||
ImageGenerationPlan::ArtcraftSeedream5Lite(plan) => {
|
||||
estimate_image_cost_artcraft_seedream_5_lite(plan)
|
||||
}
|
||||
ImageGenerationPlan::FalNanaBananaPro(plan) => {
|
||||
estimate_image_cost_fal_nano_banana_pro(plan)
|
||||
}
|
||||
|
||||
+2
@@ -4,6 +4,8 @@ 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_2;
|
||||
pub mod plan_generate_image_artcraft_nano_banana_pro;
|
||||
pub mod plan_generate_image_artcraft_seedream_4;
|
||||
pub mod plan_generate_image_artcraft_seedream_4p5;
|
||||
pub mod plan_generate_image_artcraft_seedream_5_lite;
|
||||
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use artcraft_api_defs::generate::image::multi_function::nano_banana_2_multi_function_image_gen::{
|
||||
NanaBanana2MultiFunctionImageGenAspectRatio, NanaBanana2MultiFunctionImageGenImageResolution,
|
||||
NanaBanana2MultiFunctionImageGenNumImages,
|
||||
};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlanArtcraftNanaBanana2<'a> {
|
||||
pub prompt: Option<&'a str>,
|
||||
/// Input images for image editing. None means text-to-image mode.
|
||||
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
|
||||
pub aspect_ratio: Option<NanaBanana2MultiFunctionImageGenAspectRatio>,
|
||||
pub resolution: Option<NanaBanana2MultiFunctionImageGenImageResolution>,
|
||||
pub num_images: NanaBanana2MultiFunctionImageGenNumImages,
|
||||
pub idempotency_token: String,
|
||||
}
|
||||
|
||||
pub fn plan_generate_image_artcraft_nano_banana_2<'a>(
|
||||
request: &'a GenerateImageRequest<'a>,
|
||||
) -> Result<PlanArtcraftNanaBanana2<'a>, ArtcraftRouterError> {
|
||||
let strategy = request.request_mismatch_mitigation_strategy;
|
||||
|
||||
let is_edit_mode = request.image_inputs.is_some();
|
||||
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
|
||||
let aspect_ratio = plan_aspect_ratio(request.aspect_ratio, is_edit_mode, strategy)?;
|
||||
let resolution = plan_resolution(request.resolution, strategy)?;
|
||||
let num_images = plan_num_images(request.image_batch_count, strategy)?;
|
||||
|
||||
Ok(PlanArtcraftNanaBanana2 {
|
||||
prompt: request.prompt,
|
||||
image_inputs,
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
num_images,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_image_list_ref<'a>(
|
||||
image_list_ref: Option<ImageListRef<'a>>,
|
||||
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
|
||||
match image_list_ref {
|
||||
None => Ok(None),
|
||||
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
|
||||
Some(ImageListRef::Urls(_)) => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nano Banana 2 supported aspect ratios:
|
||||
// Text-to-image: OneByOne, FiveByFour, FourByThree, ThreeByTwo, SixteenByNine, TwentyOneByNine,
|
||||
// FourByFive, ThreeByFour, TwoByThree, NineBySixteen
|
||||
// Image editing: Auto (uses source image AR), plus all text-to-image ratios
|
||||
fn plan_aspect_ratio(
|
||||
aspect_ratio: Option<CommonAspectRatio>,
|
||||
is_edit_mode: bool,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<NanaBanana2MultiFunctionImageGenAspectRatio>, ArtcraftRouterError> {
|
||||
use NanaBanana2MultiFunctionImageGenAspectRatio as Nb2Ar;
|
||||
match aspect_ratio {
|
||||
// No preference — let the model use its default
|
||||
None => Ok(None),
|
||||
|
||||
// Auto: only valid in edit mode (use source image dimensions)
|
||||
Some(CommonAspectRatio::Auto)
|
||||
| Some(CommonAspectRatio::Auto2k)
|
||||
| Some(CommonAspectRatio::Auto4k) => {
|
||||
if is_edit_mode {
|
||||
Ok(Some(Nb2Ar::Auto))
|
||||
} else {
|
||||
// Auto not valid for text-to-image; fall back to square default
|
||||
Ok(Some(Nb2Ar::OneByOne))
|
||||
}
|
||||
}
|
||||
|
||||
// Direct mappings
|
||||
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(Nb2Ar::OneByOne)),
|
||||
Some(CommonAspectRatio::WideFiveByFour) => Ok(Some(Nb2Ar::FiveByFour)),
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(Nb2Ar::FourByThree)),
|
||||
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Some(Nb2Ar::ThreeByTwo)),
|
||||
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(Nb2Ar::SixteenByNine)),
|
||||
Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(Nb2Ar::TwentyOneByNine)),
|
||||
Some(CommonAspectRatio::TallFourByFive) => Ok(Some(Nb2Ar::FourByFive)),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(Nb2Ar::ThreeByFour)),
|
||||
Some(CommonAspectRatio::TallTwoByThree) => Ok(Some(Nb2Ar::TwoByThree)),
|
||||
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(Nb2Ar::NineBySixteen)),
|
||||
|
||||
// Mismatch — TallNineByTwentyOne has no direct equivalent
|
||||
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)))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Pick the nearest supported aspect ratio for unsupported inputs.
|
||||
fn nearest_aspect_ratio(aspect_ratio: CommonAspectRatio) -> NanaBanana2MultiFunctionImageGenAspectRatio {
|
||||
use NanaBanana2MultiFunctionImageGenAspectRatio as Nb2Ar;
|
||||
match aspect_ratio {
|
||||
// TallNineByTwentyOne (0.43) — nearest is NineBySixteen (0.5625)
|
||||
CommonAspectRatio::TallNineByTwentyOne => Nb2Ar::NineBySixteen,
|
||||
_ => Nb2Ar::OneByOne,
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_resolution(
|
||||
resolution: Option<CommonResolution>,
|
||||
_strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<NanaBanana2MultiFunctionImageGenImageResolution>, ArtcraftRouterError> {
|
||||
use CommonResolution as R;
|
||||
use NanaBanana2MultiFunctionImageGenImageResolution as Nb2Res;
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
Some(R::OneK) => Ok(Some(Nb2Res::OneK)),
|
||||
Some(R::TwoK) => Ok(Some(Nb2Res::TwoK)),
|
||||
Some(R::FourK) => Ok(Some(Nb2Res::FourK)),
|
||||
Some(R::ThreeK) => {
|
||||
// No 3K option; default to 2K
|
||||
Ok(Some(Nb2Res::TwoK))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nano Banana 2 supports 1, 2, 3, and 4 images.
|
||||
|
||||
fn plan_num_images(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<NanaBanana2MultiFunctionImageGenNumImages, ArtcraftRouterError> {
|
||||
use NanaBanana2MultiFunctionImageGenNumImages as Nb2N;
|
||||
let count = image_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 => Ok(Nb2N::One),
|
||||
2 => Ok(Nb2N::Two),
|
||||
3 => Ok(Nb2N::Three),
|
||||
4 => Ok(Nb2N::Four),
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade => Ok(Nb2N::Four),
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Nb2N::Four),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::base_nano_banana_2_image_request;
|
||||
use artcraft_api_defs::generate::image::multi_function::nano_banana_2_multi_function_image_gen::{
|
||||
NanaBanana2MultiFunctionImageGenAspectRatio as Nb2Ar,
|
||||
NanaBanana2MultiFunctionImageGenImageResolution as Nb2Res,
|
||||
NanaBanana2MultiFunctionImageGenNumImages as Nb2N,
|
||||
};
|
||||
|
||||
// ── Aspect ratio ────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_none_is_none() {
|
||||
let request = GenerateImageRequest { aspect_ratio: None, ..base_nano_banana_2_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftNanaBanana2(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana2") };
|
||||
assert!(plan.aspect_ratio.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_direct_square() {
|
||||
for ar in [CommonAspectRatio::Square, CommonAspectRatio::SquareHd] {
|
||||
let request = GenerateImageRequest { aspect_ratio: Some(ar), ..base_nano_banana_2_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftNanaBanana2(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana2") };
|
||||
assert!(matches!(plan.aspect_ratio, Some(Nb2Ar::OneByOne)), "expected OneByOne for {:?}", ar);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_auto_in_edit_mode_yields_auto() {
|
||||
for auto_ar in [CommonAspectRatio::Auto, CommonAspectRatio::Auto2k, CommonAspectRatio::Auto4k] {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(auto_ar),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
..base_nano_banana_2_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftNanaBanana2(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana2") };
|
||||
assert!(
|
||||
matches!(plan.aspect_ratio, Some(Nb2Ar::Auto)),
|
||||
"expected Auto in edit mode for {:?}", auto_ar,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_auto_in_text_to_image_falls_back_to_square() {
|
||||
for auto_ar in [CommonAspectRatio::Auto, CommonAspectRatio::Auto2k, CommonAspectRatio::Auto4k] {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(auto_ar),
|
||||
image_inputs: None,
|
||||
..base_nano_banana_2_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftNanaBanana2(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana2") };
|
||||
assert!(
|
||||
matches!(plan.aspect_ratio, Some(Nb2Ar::OneByOne)),
|
||||
"expected OneByOne fallback in text-to-image for {:?}", auto_ar,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Resolution ──────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn resolution_none_is_none() {
|
||||
let request = GenerateImageRequest { resolution: None, ..base_nano_banana_2_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftNanaBanana2(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana2") };
|
||||
assert!(plan.resolution.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_direct_mappings() {
|
||||
let cases = [
|
||||
(CommonResolution::OneK, Nb2Res::OneK),
|
||||
(CommonResolution::TwoK, Nb2Res::TwoK),
|
||||
(CommonResolution::FourK, Nb2Res::FourK),
|
||||
];
|
||||
for (common, expected) in cases {
|
||||
let request = GenerateImageRequest { resolution: Some(common), ..base_nano_banana_2_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftNanaBanana2(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana2") };
|
||||
assert!(
|
||||
matches!(plan.resolution, Some(r) if std::mem::discriminant(&r) == std::mem::discriminant(&expected)),
|
||||
"expected {:?} for {:?}", expected, common,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_three_k_falls_back_to_two_k() {
|
||||
let request = GenerateImageRequest {
|
||||
resolution: Some(CommonResolution::ThreeK),
|
||||
..base_nano_banana_2_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftNanaBanana2(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana2") };
|
||||
assert!(matches!(plan.resolution, Some(Nb2Res::TwoK)));
|
||||
}
|
||||
|
||||
// ── Num images ──────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn num_images_direct_mapping() {
|
||||
let cases = [
|
||||
(1, Nb2N::One),
|
||||
(2, Nb2N::Two),
|
||||
(3, Nb2N::Three),
|
||||
(4, Nb2N::Four),
|
||||
];
|
||||
for (count, expected) in cases {
|
||||
let request = GenerateImageRequest { image_batch_count: Some(count), ..base_nano_banana_2_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftNanaBanana2(plan) = request.build().unwrap() else { panic!("expected ArtcraftNanaBanana2") };
|
||||
assert!(
|
||||
std::mem::discriminant(&plan.num_images) == std::mem::discriminant(&expected),
|
||||
"expected {:?} for count {}", expected, count,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen::{
|
||||
BytedanceSeedream5LiteMultiFunctionImageGenImageSize,
|
||||
BytedanceSeedream5LiteMultiFunctionImageGenNumImages,
|
||||
};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlanArtcraftSeedream5Lite<'a> {
|
||||
pub prompt: Option<&'a str>,
|
||||
/// Input images for image editing. None means text-to-image mode.
|
||||
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
|
||||
pub image_size: Option<BytedanceSeedream5LiteMultiFunctionImageGenImageSize>,
|
||||
pub num_images: BytedanceSeedream5LiteMultiFunctionImageGenNumImages,
|
||||
pub idempotency_token: String,
|
||||
}
|
||||
|
||||
pub fn plan_generate_image_artcraft_seedream_5_lite<'a>(
|
||||
request: &'a GenerateImageRequest<'a>,
|
||||
) -> Result<PlanArtcraftSeedream5Lite<'a>, ArtcraftRouterError> {
|
||||
let strategy = request.request_mismatch_mitigation_strategy;
|
||||
|
||||
let is_edit_mode = request.image_inputs.is_some();
|
||||
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
|
||||
let image_size = plan_image_size(request.aspect_ratio, is_edit_mode, strategy)?;
|
||||
let num_images = plan_num_images(request.image_batch_count, strategy)?;
|
||||
|
||||
Ok(PlanArtcraftSeedream5Lite {
|
||||
prompt: request.prompt,
|
||||
image_inputs,
|
||||
image_size,
|
||||
num_images,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
})
|
||||
}
|
||||
|
||||
fn resolve_image_list_ref<'a>(
|
||||
image_list_ref: Option<ImageListRef<'a>>,
|
||||
) -> Result<Option<&'a Vec<MediaFileToken>>, ArtcraftRouterError> {
|
||||
match image_list_ref {
|
||||
None => Ok(None),
|
||||
Some(ImageListRef::MediaFileTokens(tokens)) => Ok(Some(tokens)),
|
||||
Some(ImageListRef::Urls(_)) => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ArtcraftOnlySupportsMediaTokens))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Seedream 5 Lite supported image sizes:
|
||||
// Square, SquareHd
|
||||
// PortraitFourThree, PortraitSixteenNine
|
||||
// LandscapeFourThree, LandscapeSixteenNine
|
||||
// Auto2k, Auto3k (NB: 5 Lite max is auto_3K, unlike V4.5's auto_4K)
|
||||
fn plan_image_size(
|
||||
aspect_ratio: Option<CommonAspectRatio>,
|
||||
is_edit_mode: bool,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<BytedanceSeedream5LiteMultiFunctionImageGenImageSize>, ArtcraftRouterError> {
|
||||
use BytedanceSeedream5LiteMultiFunctionImageGenImageSize as S;
|
||||
match aspect_ratio {
|
||||
None => Ok(None),
|
||||
|
||||
// Auto: V5 has no bare Auto; fall back to Auto2k in edit mode, Square for text-to-image
|
||||
Some(CommonAspectRatio::Auto) => {
|
||||
if is_edit_mode { Ok(Some(S::Auto2k)) } else { Ok(Some(S::Square)) }
|
||||
}
|
||||
|
||||
Some(CommonAspectRatio::Auto2k) => Ok(Some(S::Auto2k)),
|
||||
// 5 Lite max is auto_3K; map Auto4k down to Auto3k
|
||||
Some(CommonAspectRatio::Auto4k) => Ok(Some(S::Auto3k)),
|
||||
|
||||
// Square
|
||||
Some(CommonAspectRatio::Square) => Ok(Some(S::Square)),
|
||||
Some(CommonAspectRatio::SquareHd) => Ok(Some(S::SquareHd)),
|
||||
|
||||
// Wide — direct mappings
|
||||
Some(CommonAspectRatio::Wide) | Some(CommonAspectRatio::WideSixteenByNine) => Ok(Some(S::LandscapeSixteenNine)),
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(S::LandscapeFourThree)),
|
||||
|
||||
// Wide — no direct equivalent, nearest is LandscapeSixteenNine
|
||||
Some(unsupported @ CommonAspectRatio::WideFiveByFour)
|
||||
| Some(unsupported @ CommonAspectRatio::WideThreeByTwo)
|
||||
| Some(unsupported @ CommonAspectRatio::WideTwentyOneByNine) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "aspect_ratio",
|
||||
value: format!("{:?}", unsupported),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Some(S::LandscapeSixteenNine)),
|
||||
},
|
||||
|
||||
// Tall — direct mappings
|
||||
Some(CommonAspectRatio::Tall) | Some(CommonAspectRatio::TallNineBySixteen) => Ok(Some(S::PortraitSixteenNine)),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(S::PortraitFourThree)),
|
||||
|
||||
// Tall — no direct equivalent, nearest is PortraitSixteenNine
|
||||
Some(unsupported @ CommonAspectRatio::TallFourByFive)
|
||||
| Some(unsupported @ CommonAspectRatio::TallTwoByThree)
|
||||
| Some(unsupported @ CommonAspectRatio::TallNineByTwentyOne) => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "aspect_ratio",
|
||||
value: format!("{:?}", unsupported),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(Some(S::PortraitSixteenNine)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_num_images(
|
||||
image_batch_count: Option<u16>,
|
||||
strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<BytedanceSeedream5LiteMultiFunctionImageGenNumImages, ArtcraftRouterError> {
|
||||
use BytedanceSeedream5LiteMultiFunctionImageGenNumImages as N;
|
||||
let count = image_batch_count.unwrap_or(1);
|
||||
match count {
|
||||
0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)),
|
||||
1 => Ok(N::One),
|
||||
2 => Ok(N::Two),
|
||||
3 => Ok(N::Three),
|
||||
4 => Ok(N::Four),
|
||||
_ => match strategy {
|
||||
RequestMismatchMitigationStrategy::ErrorOut => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "image_batch_count",
|
||||
value: format!("{}", count),
|
||||
}))
|
||||
}
|
||||
RequestMismatchMitigationStrategy::PayMoreUpgrade
|
||||
| RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(N::Four),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::base_seedream_5_lite_image_request;
|
||||
use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen::{
|
||||
BytedanceSeedream5LiteMultiFunctionImageGenImageSize as S,
|
||||
BytedanceSeedream5LiteMultiFunctionImageGenNumImages as N,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn image_size_none_is_none() {
|
||||
let request = GenerateImageRequest { aspect_ratio: None, ..base_seedream_5_lite_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftSeedream5Lite(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream5Lite") };
|
||||
assert!(plan.image_size.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_size_auto_in_edit_mode_yields_auto2k() {
|
||||
let tokens = vec![];
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
..base_seedream_5_lite_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftSeedream5Lite(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream5Lite") };
|
||||
assert!(matches!(plan.image_size, Some(S::Auto2k)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_size_auto_in_text_to_image_yields_square() {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto),
|
||||
image_inputs: None,
|
||||
..base_seedream_5_lite_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftSeedream5Lite(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream5Lite") };
|
||||
assert!(matches!(plan.image_size, Some(S::Square)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto4k_is_mapped_to_auto3k() {
|
||||
// 5 Lite only supports up to auto_3K; auto_4K is downgraded
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto4k),
|
||||
..base_seedream_5_lite_image_request()
|
||||
};
|
||||
let ImageGenerationPlan::ArtcraftSeedream5Lite(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream5Lite") };
|
||||
assert!(matches!(plan.image_size, Some(S::Auto3k)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_direct_mapping() {
|
||||
let cases = [(1, N::One), (2, N::Two), (3, N::Three), (4, N::Four)];
|
||||
for (count, expected) in cases {
|
||||
let request = GenerateImageRequest { image_batch_count: Some(count), ..base_seedream_5_lite_image_request() };
|
||||
let ImageGenerationPlan::ArtcraftSeedream5Lite(plan) = request.build().unwrap() else { panic!("expected ArtcraftSeedream5Lite") };
|
||||
assert!(
|
||||
std::mem::discriminant(&plan.num_images) == std::mem::discriminant(&expected),
|
||||
"expected {:?} for count {}", expected, count,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,36 @@ pub fn base_seedream_4p5_image_request() -> GenerateImageRequest<'static> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_seedream_5_lite_image_request() -> GenerateImageRequest<'static> {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::Seedream5Lite,
|
||||
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_nano_banana_2_image_request() -> GenerateImageRequest<'static> {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::NanaBanana2,
|
||||
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_nano_banana_image_request() -> GenerateImageRequest<'static> {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::NanaBanana,
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
use fal::request::FalRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct NanoBanana2EditImageInput {
|
||||
pub prompt: String,
|
||||
|
||||
pub image_urls: Vec<String>,
|
||||
|
||||
/// Eg. "auto", "16:9", "1:1", etc.
|
||||
/// Options: auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
|
||||
/// Default: "auto"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub aspect_ratio: Option<String>,
|
||||
|
||||
/// Eg. "0.5K", "1K", "2K", "4K"
|
||||
/// Default: "1K"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolution: Option<String>,
|
||||
|
||||
/// 1 - 4
|
||||
/// Default: 1
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub num_images: Option<u8>,
|
||||
|
||||
/// "jpeg", "png", "webp"
|
||||
/// Default: "png"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub output_format: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub seed: Option<u64>,
|
||||
|
||||
/// The safety tolerance level for content moderation.
|
||||
/// 1 is the most strict (blocks most content), 6 is the least strict.
|
||||
/// Default value: "4"
|
||||
///
|
||||
/// Possible enum values: 1, 2, 3, 4, 5, 6
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub safety_tolerance: Option<String>,
|
||||
|
||||
/// Experimental parameter to limit the number of generations from each round
|
||||
/// of prompting to 1. Set to True to to disregard any instructions in the
|
||||
/// prompt regarding the number of images to generate and ignore any
|
||||
/// intermediate images generated by the model. This may affect generation
|
||||
/// quality.
|
||||
///
|
||||
/// Default value: true
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub limit_generations: Option<bool>,
|
||||
|
||||
/// Enable web search for the image generation task. This will allow
|
||||
/// the model to use the latest information from the web to generate
|
||||
/// the image.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enable_web_search: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NanoBanana2EditImageFile {
|
||||
pub url: String,
|
||||
//pub content_type: Option<String>,
|
||||
//pub file_name: Option<String>,
|
||||
//pub file_size: Option<u64>,
|
||||
//pub width: Option<u32>,
|
||||
//pub height: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NanoBanana2EditImageOutput {
|
||||
pub images: Vec<NanoBanana2EditImageFile>,
|
||||
//pub description: String,
|
||||
}
|
||||
|
||||
pub fn nano_banana_2_edit_image(
|
||||
params: NanoBanana2EditImageInput,
|
||||
) -> FalRequest<NanoBanana2EditImageInput, NanoBanana2EditImageOutput> {
|
||||
FalRequest::new("fal-ai/nano-banana-2/edit", params)
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
use fal::request::FalRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct NanoBanana2TextToImageInput {
|
||||
pub prompt: String,
|
||||
|
||||
/// Eg. "auto", "16:9", "1:1", etc.
|
||||
/// Options: auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
|
||||
/// Default: "auto"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub aspect_ratio: Option<String>,
|
||||
|
||||
/// Eg. "0.5K", "1K", "2K", "4K"
|
||||
/// Default: "1K"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolution: Option<String>,
|
||||
|
||||
/// 1 - 4
|
||||
/// Default: 1
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub num_images: Option<u8>,
|
||||
|
||||
/// The safety tolerance level for content moderation.
|
||||
/// 1 is the most strict (blocks most content), 6 is the least strict.
|
||||
/// Default value: "4"
|
||||
///
|
||||
/// Possible enum values: 1, 2, 3, 4, 5, 6
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub safety_tolerance: Option<String>,
|
||||
|
||||
/// "jpeg", "png", "webp"
|
||||
/// Default: "png"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub output_format: Option<String>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub seed: Option<u64>,
|
||||
|
||||
/// Experimental parameter to limit the number of generations from each round
|
||||
/// of prompting to 1. Set to True to to disregard any instructions in the
|
||||
/// prompt regarding the number of images to generate and ignore any
|
||||
/// intermediate images generated by the model. This may affect generation
|
||||
/// quality.
|
||||
///
|
||||
/// Default value: true
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub limit_generations: Option<bool>,
|
||||
|
||||
/// Enable web search for the image generation task. This will allow
|
||||
/// the model to use the latest information from the web to generate
|
||||
/// the image.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enable_web_search: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NanoBanana2ImageFile {
|
||||
pub url: String,
|
||||
//pub content_type: Option<String>,
|
||||
//pub file_name: Option<String>,
|
||||
//pub file_size: Option<u64>,
|
||||
//pub width: Option<u32>,
|
||||
//pub height: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct NanoBanana2TextToImageOutput {
|
||||
pub images: Vec<NanoBanana2ImageFile>,
|
||||
//pub description: String,
|
||||
}
|
||||
|
||||
pub fn nano_banana_2_text_to_image(
|
||||
params: NanoBanana2TextToImageInput,
|
||||
) -> FalRequest<NanoBanana2TextToImageInput, NanoBanana2TextToImageOutput> {
|
||||
FalRequest::new("fal-ai/nano-banana-2", params)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use fal::request::FalRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct SeedreamV5LiteEditImageInput {
|
||||
pub prompt: String,
|
||||
|
||||
/// Maximum 10 images. Excess images are ignored.
|
||||
pub image_urls: Vec<String>,
|
||||
|
||||
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9, auto_2K, auto_3K
|
||||
/// Default: "auto_2K"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image_size: Option<String>,
|
||||
|
||||
/// Default: 1
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub num_images: Option<u8>,
|
||||
|
||||
/// Default: 1
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_images: Option<u8>,
|
||||
|
||||
/// Default: true
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enable_safety_checker: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SeedreamV5LiteEditImageFile {
|
||||
pub url: String,
|
||||
//pub content_type: Option<String>,
|
||||
//pub file_name: Option<String>,
|
||||
//pub file_size: Option<u64>,
|
||||
//pub width: Option<u32>,
|
||||
//pub height: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SeedreamV5LiteEditImageOutput {
|
||||
pub images: Vec<SeedreamV5LiteEditImageFile>,
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
pub fn http_seedream_5_edit_image(
|
||||
params: SeedreamV5LiteEditImageInput,
|
||||
) -> FalRequest<SeedreamV5LiteEditImageInput, SeedreamV5LiteEditImageOutput> {
|
||||
FalRequest::new("fal-ai/bytedance/seedream/v5/lite/edit", params)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
use fal::request::FalRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct SeedreamV5LiteTextToImageInput {
|
||||
pub prompt: String,
|
||||
|
||||
/// Options: square_hd, square, portrait_4_3, portrait_16_9, landscape_4_3, landscape_16_9, auto_2K, auto_3K
|
||||
/// Default: "auto_2K"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub image_size: Option<String>,
|
||||
|
||||
/// Default: 1
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub num_images: Option<u8>,
|
||||
|
||||
/// Default: 1
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max_images: Option<u8>,
|
||||
|
||||
/// Default: true
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub enable_safety_checker: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SeedreamV5LiteImageFile {
|
||||
pub url: String,
|
||||
//pub content_type: Option<String>,
|
||||
//pub file_name: Option<String>,
|
||||
//pub file_size: Option<u64>,
|
||||
//pub width: Option<u32>,
|
||||
//pub height: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SeedreamV5LiteTextToImageOutput {
|
||||
pub images: Vec<SeedreamV5LiteImageFile>,
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
pub fn http_seedream_5_text_to_image(
|
||||
params: SeedreamV5LiteTextToImageInput,
|
||||
) -> FalRequest<SeedreamV5LiteTextToImageInput, SeedreamV5LiteTextToImageOutput> {
|
||||
FalRequest::new("fal-ai/bytedance/seedream/v5/lite/text-to-image", params)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod http_nano_banana_2_edit_image;
|
||||
pub mod http_nano_banana_2_text_to_image;
|
||||
pub mod http_seedream_5_edit_image;
|
||||
pub mod http_seedream_5_text_to_image;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod image;
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod http;
|
||||
pub mod queue;
|
||||
pub mod webhook;
|
||||
pub mod traits;
|
||||
pub mod traits;
|
||||
pub mod webhook;
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use crate::requests::http::image::http_seedream_5_edit_image::{http_seedream_5_edit_image, SeedreamV5LiteEditImageInput};
|
||||
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueBytedanceSeedreamV5LiteEditImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub image_urls: Vec<String>,
|
||||
|
||||
// Optional args
|
||||
pub num_images: Option<EnqueueBytedanceSeedreamV5LiteEditImageNumImages>,
|
||||
pub max_images: Option<EnqueueBytedanceSeedreamV5LiteEditImageMaxImages>,
|
||||
pub image_size: Option<EnqueueBytedanceSeedreamV5LiteEditImageSize>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV5LiteEditImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV5LiteEditImageMaxImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV5LiteEditImageSize {
|
||||
// Square
|
||||
Square,
|
||||
SquareHd,
|
||||
// Tall
|
||||
PortraitFourThree,
|
||||
PortraitSixteenNine,
|
||||
// Wide
|
||||
LandscapeFourThree,
|
||||
LandscapeSixteenNine,
|
||||
// Auto
|
||||
Auto2k,
|
||||
Auto3k, // NB: v5 uses auto_3K instead of v4's auto_4K
|
||||
}
|
||||
|
||||
|
||||
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV5LiteEditImageArgs<'_, U> {
|
||||
fn calculate_cost_in_cents(&self) -> UsdCents {
|
||||
// TODO(bt): Verify actual pricing for Seedream v5 Lite on fal.ai.
|
||||
let unit_cost = 4;
|
||||
let cost = match self.num_images {
|
||||
None => unit_cost,
|
||||
Some(EnqueueBytedanceSeedreamV5LiteEditImageNumImages::One) => unit_cost,
|
||||
Some(EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Two) => unit_cost * 2,
|
||||
Some(EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Three) => unit_cost * 3,
|
||||
Some(EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Four) => unit_cost * 4,
|
||||
};
|
||||
cost as UsdCents
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn enqueue_bytedance_seedream_v5_lite_edit_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueBytedanceSeedreamV5LiteEditImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = args.num_images
|
||||
.map(|n| match n {
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageNumImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Four => 4,
|
||||
});
|
||||
|
||||
let max_images = args.max_images
|
||||
.map(|n| match n {
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::Four => 4,
|
||||
});
|
||||
|
||||
let image_size = args.image_size
|
||||
.map(|s| match s {
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageSize::Square => "square",
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageSize::SquareHd => "square_hd",
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageSize::PortraitFourThree => "portrait_4_3",
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageSize::PortraitSixteenNine => "portrait_16_9",
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageSize::LandscapeFourThree => "landscape_4_3",
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageSize::LandscapeSixteenNine => "landscape_16_9",
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageSize::Auto2k => "auto_2K",
|
||||
EnqueueBytedanceSeedreamV5LiteEditImageSize::Auto3k => "auto_3K",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = SeedreamV5LiteEditImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
image_urls: args.image_urls,
|
||||
// Optionals
|
||||
num_images,
|
||||
max_images,
|
||||
image_size,
|
||||
// Constants
|
||||
enable_safety_checker: Some(false),
|
||||
};
|
||||
|
||||
let result = http_seedream_5_edit_image(request)
|
||||
.with_api_key(&args.api_key.0)
|
||||
.queue_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::image::edit::enqueue_bytedance_seedream_v5_lite_edit_image_webhook::{enqueue_bytedance_seedream_v5_lite_edit_image_webhook, EnqueueBytedanceSeedreamV5LiteEditImageArgs, EnqueueBytedanceSeedreamV5LiteEditImageMaxImages, EnqueueBytedanceSeedreamV5LiteEditImageNumImages, EnqueueBytedanceSeedreamV5LiteEditImageSize};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueBytedanceSeedreamV5LiteEditImageArgs {
|
||||
image_urls: vec![
|
||||
GHOST_IMAGE_URL.to_string(),
|
||||
TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
],
|
||||
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly",
|
||||
num_images: Some(EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Two),
|
||||
max_images: Some(EnqueueBytedanceSeedreamV5LiteEditImageMaxImages::Two),
|
||||
image_size: Some(EnqueueBytedanceSeedreamV5LiteEditImageSize::Auto2k),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use crate::requests::http::image::http_nano_banana_2_edit_image::{nano_banana_2_edit_image, NanoBanana2EditImageInput};
|
||||
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueNanoBanana2EditImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub image_urls: Vec<String>,
|
||||
pub num_images: EnqueueNanoBanana2EditImageNumImages,
|
||||
|
||||
// Optional args
|
||||
pub resolution: Option<EnqueueNanoBanana2EditImageResolution>,
|
||||
pub aspect_ratio: Option<EnqueueNanoBanana2EditImageAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBanana2EditImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBanana2EditImageResolution {
|
||||
HalfK, // "0.5K"
|
||||
OneK, // "1K" (default)
|
||||
TwoK, // "2K"
|
||||
FourK, // "4K"
|
||||
}
|
||||
|
||||
/// auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
|
||||
/// Default: "auto"
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBanana2EditImageAspectRatio {
|
||||
// Automatic (default)
|
||||
Auto,
|
||||
// Square
|
||||
OneByOne,
|
||||
// Wide
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
ThreeByTwo,
|
||||
SixteenByNine,
|
||||
TwentyOneByNine,
|
||||
// Tall
|
||||
FourByFive,
|
||||
ThreeByFour,
|
||||
TwoByThree,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
|
||||
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueNanoBanana2EditImageArgs<'_, U> {
|
||||
fn calculate_cost_in_cents(&self) -> UsdCents {
|
||||
// TODO(bt): Verify actual pricing for Nano Banana 2 on fal.ai.
|
||||
// 4K outputs may be charged at double the standard rate.
|
||||
let cost = match self.resolution {
|
||||
None => 15,
|
||||
Some(EnqueueNanoBanana2EditImageResolution::HalfK) => 8,
|
||||
Some(EnqueueNanoBanana2EditImageResolution::OneK) => 15,
|
||||
Some(EnqueueNanoBanana2EditImageResolution::TwoK) => 15,
|
||||
Some(EnqueueNanoBanana2EditImageResolution::FourK) => 30,
|
||||
};
|
||||
let cost = match self.num_images {
|
||||
EnqueueNanoBanana2EditImageNumImages::One => cost,
|
||||
EnqueueNanoBanana2EditImageNumImages::Two => cost * 2,
|
||||
EnqueueNanoBanana2EditImageNumImages::Three => cost * 3,
|
||||
EnqueueNanoBanana2EditImageNumImages::Four => cost * 4,
|
||||
};
|
||||
cost as UsdCents
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn enqueue_nano_banana_2_edit_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueNanoBanana2EditImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
EnqueueNanoBanana2EditImageNumImages::One => 1,
|
||||
EnqueueNanoBanana2EditImageNumImages::Two => 2,
|
||||
EnqueueNanoBanana2EditImageNumImages::Three => 3,
|
||||
EnqueueNanoBanana2EditImageNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueNanoBanana2EditImageResolution::HalfK => "0.5K",
|
||||
EnqueueNanoBanana2EditImageResolution::OneK => "1K",
|
||||
EnqueueNanoBanana2EditImageResolution::TwoK => "2K",
|
||||
EnqueueNanoBanana2EditImageResolution::FourK => "4K",
|
||||
})
|
||||
.map(|r| r.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|ar| match ar {
|
||||
// Auto
|
||||
EnqueueNanoBanana2EditImageAspectRatio::Auto => "auto",
|
||||
// Square
|
||||
EnqueueNanoBanana2EditImageAspectRatio::OneByOne => "1:1",
|
||||
// Wide
|
||||
EnqueueNanoBanana2EditImageAspectRatio::FiveByFour => "5:4",
|
||||
EnqueueNanoBanana2EditImageAspectRatio::FourByThree => "4:3",
|
||||
EnqueueNanoBanana2EditImageAspectRatio::ThreeByTwo => "3:2",
|
||||
EnqueueNanoBanana2EditImageAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueNanoBanana2EditImageAspectRatio::TwentyOneByNine => "21:9",
|
||||
// Tall
|
||||
EnqueueNanoBanana2EditImageAspectRatio::FourByFive => "4:5",
|
||||
EnqueueNanoBanana2EditImageAspectRatio::ThreeByFour => "3:4",
|
||||
EnqueueNanoBanana2EditImageAspectRatio::TwoByThree => "2:3",
|
||||
EnqueueNanoBanana2EditImageAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|ar| ar.to_string());
|
||||
|
||||
let request = NanoBanana2EditImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
image_urls: args.image_urls,
|
||||
num_images: Some(num_images),
|
||||
// Optionals
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
// Constants
|
||||
output_format: Some("png".to_string()),
|
||||
safety_tolerance: Some("6".to_string()),
|
||||
limit_generations: None,
|
||||
enable_web_search: None,
|
||||
seed: None,
|
||||
};
|
||||
|
||||
let result = nano_banana_2_edit_image(request)
|
||||
.with_api_key(&args.api_key.0)
|
||||
.queue_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::image::edit::enqueue_nano_banana_2_edit_image_webhook::{enqueue_nano_banana_2_edit_image_webhook, EnqueueNanoBanana2EditImageArgs, EnqueueNanoBanana2EditImageAspectRatio, EnqueueNanoBanana2EditImageNumImages, EnqueueNanoBanana2EditImageResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueNanoBanana2EditImageArgs {
|
||||
image_urls: vec![
|
||||
GHOST_IMAGE_URL.to_string(),
|
||||
TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
|
||||
],
|
||||
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly",
|
||||
num_images: EnqueueNanoBanana2EditImageNumImages::Two,
|
||||
aspect_ratio: Some(EnqueueNanoBanana2EditImageAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueNanoBanana2EditImageResolution::TwoK),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_nano_banana_2_edit_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod enqueue_bytedance_seedream_v4_edit_image_webhook;
|
||||
pub mod enqueue_bytedance_seedream_v4p5_edit_image_webhook;
|
||||
pub mod enqueue_bytedance_seedream_v5_lite_edit_image_webhook;
|
||||
pub mod enqueue_nano_banana_2_edit_image_webhook;
|
||||
pub mod enqueue_flux_pro_kontext_max_edit_webhook;
|
||||
pub mod enqueue_gemini_25_flash_edit_webhook;
|
||||
pub mod enqueue_gpt_image_1_edit_image_webhook;
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use crate::requests::http::image::http_seedream_5_text_to_image::{http_seedream_5_text_to_image, SeedreamV5LiteTextToImageInput};
|
||||
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueBytedanceSeedreamV5LiteTextToImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
|
||||
// Optional args
|
||||
pub num_images: Option<EnqueueBytedanceSeedreamV5LiteTextToImageNumImages>,
|
||||
pub max_images: Option<EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages>,
|
||||
pub image_size: Option<EnqueueBytedanceSeedreamV5LiteTextToImageSize>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV5LiteTextToImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV5LiteTextToImageSize {
|
||||
// Square
|
||||
Square,
|
||||
SquareHd,
|
||||
// Tall
|
||||
PortraitFourThree,
|
||||
PortraitSixteenNine,
|
||||
// Wide
|
||||
LandscapeFourThree,
|
||||
LandscapeSixteenNine,
|
||||
// Auto
|
||||
Auto2k,
|
||||
Auto3k, // NB: v5 uses auto_3K instead of v4's auto_4K
|
||||
}
|
||||
|
||||
|
||||
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV5LiteTextToImageArgs<'_, U> {
|
||||
fn calculate_cost_in_cents(&self) -> UsdCents {
|
||||
// TODO(bt): Verify actual pricing for Seedream v5 Lite on fal.ai.
|
||||
let unit_cost = 4;
|
||||
let cost = match self.num_images {
|
||||
None => unit_cost,
|
||||
Some(EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::One) => unit_cost,
|
||||
Some(EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Two) => unit_cost * 2,
|
||||
Some(EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Three) => unit_cost * 3,
|
||||
Some(EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Four) => unit_cost * 4,
|
||||
};
|
||||
cost as UsdCents
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn enqueue_bytedance_seedream_v5_lite_text_to_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueBytedanceSeedreamV5LiteTextToImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = args.num_images
|
||||
.map(|n| match n {
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Four => 4,
|
||||
});
|
||||
|
||||
let max_images = args.max_images
|
||||
.map(|n| match n {
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::Four => 4,
|
||||
});
|
||||
|
||||
let image_size = args.image_size
|
||||
.map(|s| match s {
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageSize::Square => "square",
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageSize::SquareHd => "square_hd",
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageSize::PortraitFourThree => "portrait_4_3",
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageSize::PortraitSixteenNine => "portrait_16_9",
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageSize::LandscapeFourThree => "landscape_4_3",
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageSize::LandscapeSixteenNine => "landscape_16_9",
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageSize::Auto2k => "auto_2K",
|
||||
EnqueueBytedanceSeedreamV5LiteTextToImageSize::Auto3k => "auto_3K",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = SeedreamV5LiteTextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
// Optionals
|
||||
num_images,
|
||||
max_images,
|
||||
image_size,
|
||||
// Constants
|
||||
enable_safety_checker: Some(false),
|
||||
};
|
||||
|
||||
let result = http_seedream_5_text_to_image(request)
|
||||
.with_api_key(&args.api_key.0)
|
||||
.queue_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::image::text::enqueue_bytedance_seedream_v5_lite_text_to_image_webhook::{enqueue_bytedance_seedream_v5_lite_text_to_image_webhook, EnqueueBytedanceSeedreamV5LiteTextToImageArgs, EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages, EnqueueBytedanceSeedreamV5LiteTextToImageNumImages, EnqueueBytedanceSeedreamV5LiteTextToImageSize};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueBytedanceSeedreamV5LiteTextToImageArgs {
|
||||
prompt: "an anime girl is riding a t-rex in the forest",
|
||||
num_images: Some(EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Two),
|
||||
max_images: Some(EnqueueBytedanceSeedreamV5LiteTextToImageMaxImages::Two),
|
||||
image_size: Some(EnqueueBytedanceSeedreamV5LiteTextToImageSize::LandscapeSixteenNine),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_bytedance_seedream_v5_lite_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use crate::requests::http::image::http_nano_banana_2_text_to_image::{nano_banana_2_text_to_image, NanoBanana2TextToImageInput};
|
||||
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueNanoBanana2TextToImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub num_images: EnqueueNanoBanana2TextToImageNumImages,
|
||||
|
||||
// Optional args
|
||||
pub resolution: Option<EnqueueNanoBanana2TextToImageResolution>,
|
||||
pub aspect_ratio: Option<EnqueueNanoBanana2TextToImageAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBanana2TextToImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBanana2TextToImageResolution {
|
||||
HalfK, // "0.5K"
|
||||
OneK, // "1K" (default)
|
||||
TwoK, // "2K"
|
||||
FourK, // "4K"
|
||||
}
|
||||
|
||||
/// auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
|
||||
/// Default: "auto"
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBanana2TextToImageAspectRatio {
|
||||
// Automatic (default)
|
||||
Auto,
|
||||
// Square
|
||||
OneByOne,
|
||||
// Wide
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
ThreeByTwo,
|
||||
SixteenByNine,
|
||||
TwentyOneByNine,
|
||||
// Tall
|
||||
FourByFive,
|
||||
ThreeByFour,
|
||||
TwoByThree,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
|
||||
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueNanoBanana2TextToImageArgs<'_, U> {
|
||||
fn calculate_cost_in_cents(&self) -> UsdCents {
|
||||
// TODO(bt): Verify actual pricing for Nano Banana 2 on fal.ai.
|
||||
// 4K outputs may be charged at double the standard rate.
|
||||
let cost = match self.resolution {
|
||||
None => 15,
|
||||
Some(EnqueueNanoBanana2TextToImageResolution::HalfK) => 8,
|
||||
Some(EnqueueNanoBanana2TextToImageResolution::OneK) => 15,
|
||||
Some(EnqueueNanoBanana2TextToImageResolution::TwoK) => 15,
|
||||
Some(EnqueueNanoBanana2TextToImageResolution::FourK) => 30,
|
||||
};
|
||||
let cost = match self.num_images {
|
||||
EnqueueNanoBanana2TextToImageNumImages::One => cost,
|
||||
EnqueueNanoBanana2TextToImageNumImages::Two => cost * 2,
|
||||
EnqueueNanoBanana2TextToImageNumImages::Three => cost * 3,
|
||||
EnqueueNanoBanana2TextToImageNumImages::Four => cost * 4,
|
||||
};
|
||||
cost as UsdCents
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn enqueue_nano_banana_2_text_to_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueNanoBanana2TextToImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
EnqueueNanoBanana2TextToImageNumImages::One => 1,
|
||||
EnqueueNanoBanana2TextToImageNumImages::Two => 2,
|
||||
EnqueueNanoBanana2TextToImageNumImages::Three => 3,
|
||||
EnqueueNanoBanana2TextToImageNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueNanoBanana2TextToImageResolution::HalfK => "0.5K",
|
||||
EnqueueNanoBanana2TextToImageResolution::OneK => "1K",
|
||||
EnqueueNanoBanana2TextToImageResolution::TwoK => "2K",
|
||||
EnqueueNanoBanana2TextToImageResolution::FourK => "4K",
|
||||
})
|
||||
.map(|r| r.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|ar| match ar {
|
||||
// Auto
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::Auto => "auto",
|
||||
// Square
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::OneByOne => "1:1",
|
||||
// Wide
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::FiveByFour => "5:4",
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::FourByThree => "4:3",
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::ThreeByTwo => "3:2",
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::TwentyOneByNine => "21:9",
|
||||
// Tall
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::FourByFive => "4:5",
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::ThreeByFour => "3:4",
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::TwoByThree => "2:3",
|
||||
EnqueueNanoBanana2TextToImageAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|ar| ar.to_string());
|
||||
|
||||
let request = NanoBanana2TextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
num_images: Some(num_images),
|
||||
// Optionals
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
// Constants
|
||||
output_format: Some("png".to_string()),
|
||||
limit_generations: None,
|
||||
safety_tolerance: Some("6".to_string()),
|
||||
enable_web_search: None,
|
||||
seed: None,
|
||||
};
|
||||
|
||||
let result = nano_banana_2_text_to_image(request)
|
||||
.with_api_key(&args.api_key.0)
|
||||
.queue_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::image::text::enqueue_nano_banana_2_text_to_image_webhook::{enqueue_nano_banana_2_text_to_image_webhook, EnqueueNanoBanana2TextToImageArgs, EnqueueNanoBanana2TextToImageAspectRatio, EnqueueNanoBanana2TextToImageNumImages, EnqueueNanoBanana2TextToImageResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueNanoBanana2TextToImageArgs {
|
||||
prompt: "an anime girl riding on the back of a t-rex",
|
||||
num_images: EnqueueNanoBanana2TextToImageNumImages::One,
|
||||
aspect_ratio: Some(EnqueueNanoBanana2TextToImageAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueNanoBanana2TextToImageResolution::TwoK),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_nano_banana_2_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod enqueue_bytedance_seedream_v4_text_to_image_webhook;
|
||||
pub mod enqueue_bytedance_seedream_v4p5_text_to_image_webhook;
|
||||
pub mod enqueue_bytedance_seedream_v5_lite_text_to_image_webhook;
|
||||
pub mod enqueue_nano_banana_2_text_to_image_webhook;
|
||||
pub mod enqueue_flux_1_dev_text_to_image_webhook;
|
||||
pub mod enqueue_flux_1_schnell_text_to_image_webhook;
|
||||
pub mod enqueue_flux_pro_11_text_to_image_webhook;
|
||||
|
||||
+2
@@ -25,10 +25,12 @@ pub async fn handle_image_edit_artcraft(
|
||||
ImageEditModel::Gemini25Flash | ImageEditModel::NanoBanana => {
|
||||
handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanaBanana, GenerationModel::NanoBanana).await
|
||||
},
|
||||
ImageEditModel::NanoBanana2 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanaBanana2, GenerationModel::NanoBanana2).await,
|
||||
ImageEditModel::NanoBananaPro => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanaBananaPro, GenerationModel::NanoBananaPro).await,
|
||||
ImageEditModel::GptImage1 => handle_artcraft_gpt_image_1_edit(request, app, app_data_root, app_env_configs, storyteller_creds_manager).await,
|
||||
ImageEditModel::GptImage1p5 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::GptImage1p5, GenerationModel::GptImage1p5).await,
|
||||
ImageEditModel::Seedream4 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream4, GenerationModel::Seedream4).await,
|
||||
ImageEditModel::Seedream4p5 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream4p5, GenerationModel::Seedream4p5).await,
|
||||
ImageEditModel::Seedream5Lite => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream5Lite, GenerationModel::Seedream5Lite).await,
|
||||
}
|
||||
}
|
||||
|
||||
+11
-5
@@ -60,24 +60,30 @@ pub enum ImageEditModel {
|
||||
#[serde(rename = "nano_banana")]
|
||||
NanoBanana,
|
||||
|
||||
#[serde(rename = "nano_banana_2")]
|
||||
NanoBanana2,
|
||||
|
||||
#[serde(rename = "nano_banana_pro")]
|
||||
NanoBananaPro,
|
||||
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
|
||||
|
||||
#[serde(rename = "gpt_image_1p5")]
|
||||
GptImage1p5,
|
||||
|
||||
|
||||
#[serde(rename = "seedream_4")]
|
||||
Seedream4,
|
||||
|
||||
|
||||
#[serde(rename = "seedream_4p5")]
|
||||
Seedream4p5,
|
||||
|
||||
|
||||
#[serde(rename = "seedream_5_lite")]
|
||||
Seedream5Lite,
|
||||
|
||||
// #[serde(rename = "qwen")]
|
||||
// Qwen,
|
||||
//
|
||||
//
|
||||
// #[serde(rename = "seededit_3")]
|
||||
// SeedEdit3,
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ pub fn image_edit_model_to_model_type(model: ImageEditModel) -> ModelType {
|
||||
ImageEditModel::FluxProKontextMax => ModelType::FluxProKontextMax,
|
||||
ImageEditModel::Gemini25Flash => ModelType::NanoBanana,
|
||||
ImageEditModel::NanoBanana => ModelType::NanoBanana,
|
||||
ImageEditModel::NanoBanana2 => ModelType::NanoBanana2,
|
||||
ImageEditModel::NanoBananaPro => ModelType::NanoBananaPro,
|
||||
ImageEditModel::GptImage1 => ModelType::GptImage1,
|
||||
ImageEditModel::GptImage1p5 => ModelType::GptImage1p5,
|
||||
ImageEditModel::Seedream4 => ModelType::Seedream4,
|
||||
ImageEditModel::Seedream4p5 => ModelType::Seedream4p5,
|
||||
ImageEditModel::Seedream5Lite => ModelType::Seedream5Lite,
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -40,8 +40,10 @@ pub async fn handle_text_to_image_artcraft(
|
||||
TextToImageModel::Gemini25Flash | TextToImageModel::NanoBanana => {
|
||||
handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanaBanana, GenerationModel::NanoBanana).await
|
||||
},
|
||||
TextToImageModel::NanoBanana2 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanaBanana2, GenerationModel::NanoBanana2).await,
|
||||
TextToImageModel::NanoBananaPro => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanaBananaPro, GenerationModel::NanoBananaPro).await,
|
||||
TextToImageModel::Seedream4 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream4, GenerationModel::Seedream4).await,
|
||||
TextToImageModel::Seedream4p5 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream4p5, GenerationModel::Seedream4p5).await,
|
||||
TextToImageModel::Seedream5Lite => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream5Lite, GenerationModel::Seedream5Lite).await,
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -65,12 +65,16 @@ pub enum TextToImageModel {
|
||||
Gemini25Flash,
|
||||
#[serde(rename = "nano_banana")]
|
||||
NanoBanana,
|
||||
#[serde(rename = "nano_banana_2")]
|
||||
NanoBanana2,
|
||||
#[serde(rename = "nano_banana_pro")]
|
||||
NanoBananaPro,
|
||||
#[serde(rename = "seedream_4")]
|
||||
Seedream4,
|
||||
#[serde(rename = "seedream_4p5")]
|
||||
Seedream4p5,
|
||||
#[serde(rename = "seedream_5_lite")]
|
||||
Seedream5Lite,
|
||||
|
||||
// Generic Midjourney model, version unknown.
|
||||
#[serde(rename = "midjourney")]
|
||||
|
||||
+2
@@ -13,9 +13,11 @@ pub fn text_to_image_model_to_model_type(model: TextToImageModel) -> ModelType {
|
||||
TextToImageModel::GptImage1p5 => ModelType::GptImage1p5,
|
||||
TextToImageModel::Gemini25Flash => ModelType::NanoBanana,
|
||||
TextToImageModel::NanoBanana => ModelType::NanoBanana,
|
||||
TextToImageModel::NanoBanana2 => ModelType::NanoBanana2,
|
||||
TextToImageModel::NanoBananaPro => ModelType::NanoBananaPro,
|
||||
TextToImageModel::Seedream4 => ModelType::Seedream4,
|
||||
TextToImageModel::Seedream4p5 => ModelType::Seedream4p5,
|
||||
TextToImageModel::Seedream5Lite => ModelType::Seedream5Lite,
|
||||
TextToImageModel::Midjourney => ModelType::Midjourney,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,16 @@ pub enum GenerationModel {
|
||||
Gemini25Flash,
|
||||
#[serde(rename = "nano_banana")]
|
||||
NanoBanana,
|
||||
#[serde(rename = "nano_banana_2")]
|
||||
NanoBanana2,
|
||||
#[serde(rename = "nano_banana_pro")]
|
||||
NanoBananaPro,
|
||||
#[serde(rename = "seedream_4")]
|
||||
Seedream4,
|
||||
#[serde(rename = "seedream_4p5")]
|
||||
Seedream4p5,
|
||||
#[serde(rename = "seedream_5_lite")]
|
||||
Seedream5Lite,
|
||||
|
||||
#[serde(rename = "hunyuan_3d_2_0")]
|
||||
Hunyuan3d2_0,
|
||||
|
||||
@@ -17,10 +17,14 @@ pub enum CommonImageModel {
|
||||
GptImage1p5,
|
||||
#[serde(rename = "nano_banana")]
|
||||
NanaBanana,
|
||||
#[serde(rename = "nano_banana_2")]
|
||||
NanaBanana2,
|
||||
#[serde(rename = "nano_banana_pro")]
|
||||
NanaBananaPro,
|
||||
#[serde(rename = "seedream_4")]
|
||||
Seedream4,
|
||||
#[serde(rename = "seedream_4p5")]
|
||||
Seedream4p5,
|
||||
#[serde(rename = "seedream_5_lite")]
|
||||
Seedream5Lite,
|
||||
}
|
||||
|
||||
@@ -45,12 +45,16 @@ pub enum ModelType {
|
||||
Gemini25Flash,
|
||||
#[serde(rename = "nano_banana")]
|
||||
NanoBanana,
|
||||
#[serde(rename = "nano_banana_2")]
|
||||
NanoBanana2,
|
||||
#[serde(rename = "nano_banana_pro")]
|
||||
NanoBananaPro,
|
||||
#[serde(rename = "seedream_4")]
|
||||
Seedream4,
|
||||
#[serde(rename = "seedream_4p5")]
|
||||
Seedream4p5,
|
||||
#[serde(rename = "seedream_5_lite")]
|
||||
Seedream5Lite,
|
||||
|
||||
/// Midjourney without distinguishing a model type or version
|
||||
#[serde(rename = "midjourney")]
|
||||
@@ -144,9 +148,11 @@ impl ModelType {
|
||||
Self::Qwen => "qwen",
|
||||
Self::Gemini25Flash => "gemini_25_flash",
|
||||
Self::NanoBanana => "nano_banana",
|
||||
Self::NanoBanana2 => "nano_banana_2",
|
||||
Self::NanoBananaPro => "nano_banana_pro",
|
||||
Self::Seedream4 => "seedream_4",
|
||||
Self::Seedream4p5 => "seedream_4p5",
|
||||
Self::Seedream5Lite => "seedream_5_lite",
|
||||
Self::Midjourney => "midjourney",
|
||||
Self::MidjourneyV6 => "midjourney_v6",
|
||||
Self::MidjourneyV6p1 => "midjourney_v6p1",
|
||||
@@ -199,9 +205,11 @@ impl ModelType {
|
||||
"qwen" => Ok(Self::Qwen),
|
||||
"gemini_25_flash" => Ok(Self::Gemini25Flash),
|
||||
"nano_banana" => Ok(Self::NanoBanana),
|
||||
"nano_banana_2" => Ok(Self::NanoBanana2),
|
||||
"nano_banana_pro" => Ok(Self::NanoBananaPro),
|
||||
"seedream_4" => Ok(Self::Seedream4),
|
||||
"seedream_4p5" => Ok(Self::Seedream4p5),
|
||||
"seedream_5_lite" => Ok(Self::Seedream5Lite),
|
||||
"midjourney" => Ok(Self::Midjourney),
|
||||
"midjourney_v6" => Ok(Self::MidjourneyV6),
|
||||
"midjourney_v6p1" => Ok(Self::MidjourneyV6p1),
|
||||
@@ -258,9 +266,11 @@ impl ModelType {
|
||||
Self::Qwen,
|
||||
Self::Gemini25Flash,
|
||||
Self::NanoBanana,
|
||||
Self::NanoBanana2,
|
||||
Self::NanoBananaPro,
|
||||
Self::Seedream4,
|
||||
Self::Seedream4p5,
|
||||
Self::Seedream5Lite,
|
||||
Self::Midjourney,
|
||||
Self::MidjourneyV6,
|
||||
Self::MidjourneyV6p1,
|
||||
@@ -322,9 +332,11 @@ mod tests {
|
||||
assert_serialization(ModelType::Qwen, "qwen");
|
||||
assert_serialization(ModelType::Gemini25Flash, "gemini_25_flash");
|
||||
assert_serialization(ModelType::NanoBanana, "nano_banana");
|
||||
assert_serialization(ModelType::NanoBanana2, "nano_banana_2");
|
||||
assert_serialization(ModelType::NanoBananaPro, "nano_banana_pro");
|
||||
assert_serialization(ModelType::Seedream4, "seedream_4");
|
||||
assert_serialization(ModelType::Seedream4p5, "seedream_4p5");
|
||||
assert_serialization(ModelType::Seedream5Lite, "seedream_5_lite");
|
||||
assert_serialization(ModelType::Midjourney, "midjourney");
|
||||
assert_serialization(ModelType::MidjourneyV6, "midjourney_v6");
|
||||
assert_serialization(ModelType::MidjourneyV6p1, "midjourney_v6p1");
|
||||
@@ -374,9 +386,11 @@ mod tests {
|
||||
assert_eq!(ModelType::Qwen.to_str(), "qwen");
|
||||
assert_eq!(ModelType::Gemini25Flash.to_str(), "gemini_25_flash");
|
||||
assert_eq!(ModelType::NanoBanana.to_str(), "nano_banana");
|
||||
assert_eq!(ModelType::NanoBanana2.to_str(), "nano_banana_2");
|
||||
assert_eq!(ModelType::NanoBananaPro.to_str(), "nano_banana_pro");
|
||||
assert_eq!(ModelType::Seedream4.to_str(), "seedream_4");
|
||||
assert_eq!(ModelType::Seedream4p5.to_str(), "seedream_4p5");
|
||||
assert_eq!(ModelType::Seedream5Lite.to_str(), "seedream_5_lite");
|
||||
assert_eq!(ModelType::Midjourney.to_str(), "midjourney");
|
||||
assert_eq!(ModelType::MidjourneyV6.to_str(), "midjourney_v6");
|
||||
assert_eq!(ModelType::MidjourneyV6p1.to_str(), "midjourney_v6p1");
|
||||
@@ -428,9 +442,11 @@ mod tests {
|
||||
assert_eq!(ModelType::from_str("qwen").unwrap(), ModelType::Qwen);
|
||||
assert_eq!(ModelType::from_str("gemini_25_flash").unwrap(), ModelType::Gemini25Flash);
|
||||
assert_eq!(ModelType::from_str("nano_banana").unwrap(), ModelType::NanoBanana);
|
||||
assert_eq!(ModelType::from_str("nano_banana_2").unwrap(), ModelType::NanoBanana2);
|
||||
assert_eq!(ModelType::from_str("nano_banana_pro").unwrap(), ModelType::NanoBananaPro);
|
||||
assert_eq!(ModelType::from_str("seedream_4").unwrap(), ModelType::Seedream4);
|
||||
assert_eq!(ModelType::from_str("seedream_4p5").unwrap(), ModelType::Seedream4p5);
|
||||
assert_eq!(ModelType::from_str("seedream_5_lite").unwrap(), ModelType::Seedream5Lite);
|
||||
assert_eq!(ModelType::from_str("midjourney").unwrap(), ModelType::Midjourney);
|
||||
assert_eq!(ModelType::from_str("midjourney_v6").unwrap(), ModelType::MidjourneyV6);
|
||||
assert_eq!(ModelType::from_str("midjourney_v6p1").unwrap(), ModelType::MidjourneyV6p1);
|
||||
@@ -466,7 +482,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = ModelType::all_variants();
|
||||
assert_eq!(variants.len(), 45);
|
||||
assert_eq!(variants.len(), 47);
|
||||
// Image models
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Dev));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Schnell));
|
||||
@@ -483,9 +499,11 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Qwen));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Gemini25Flash));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::NanoBanana));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::NanoBanana2));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::NanoBananaPro));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Seedream4));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Seedream4p5));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Seedream5Lite));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Midjourney));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::MidjourneyV6));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::MidjourneyV6p1));
|
||||
|
||||
+6
-3
@@ -11,6 +11,7 @@ use artcraft_router::api::common_aspect_ratio::CommonAspectRatio as RouterAspect
|
||||
use artcraft_router::api::common_image_model::CommonImageModel as RouterImageModel;
|
||||
use artcraft_router::api::common_resolution::CommonResolution as RouterResolution;
|
||||
use artcraft_router::api::provider::Provider as RouterProvider;
|
||||
use artcraft_router::client::generation_mode_mismatch_strategy::GenerationModeMismatchStrategy;
|
||||
use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use artcraft_router::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
|
||||
@@ -40,13 +41,13 @@ pub async fn estimate_image_cost_handler(
|
||||
let router_request = GenerateImageRequest {
|
||||
model: router_model,
|
||||
provider: router_provider,
|
||||
prompt: None,
|
||||
image_inputs: None,
|
||||
prompt: None, // NB: Prompt is immaterial to cost estimation
|
||||
image_inputs: None, // TODO: Only some models charge for this - we'll need to add later.
|
||||
resolution: router_resolution,
|
||||
aspect_ratio: router_aspect_ratio,
|
||||
image_batch_count: request.image_batch_count,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
@@ -127,9 +128,11 @@ fn map_image_model(model: CommonImageModel) -> Result<RouterImageModel, HandlerE
|
||||
CommonImageModel::FluxPro11Ultra => RouterImageModel::FluxPro11Ultra,
|
||||
CommonImageModel::GptImage1p5 => RouterImageModel::GptImage1p5,
|
||||
CommonImageModel::NanaBanana => RouterImageModel::NanaBanana,
|
||||
CommonImageModel::NanaBanana2 => RouterImageModel::NanaBanana2,
|
||||
CommonImageModel::NanaBananaPro => RouterImageModel::NanaBananaPro,
|
||||
CommonImageModel::Seedream4 => RouterImageModel::Seedream4,
|
||||
CommonImageModel::Seedream4p5 => RouterImageModel::Seedream4p5,
|
||||
CommonImageModel::Seedream5Lite => RouterImageModel::Seedream5Lite,
|
||||
};
|
||||
Ok(router_model)
|
||||
}
|
||||
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::lookup::lookup_image_urls_as_optional_list::lookup_image_urls_as_optional_list;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen::{BytedanceSeedream5LiteMultiFunctionImageGenImageSize, BytedanceSeedream5LiteMultiFunctionImageGenNumImages, BytedanceSeedream5LiteMultiFunctionImageGenRequest, BytedanceSeedream5LiteMultiFunctionImageGenResponse};
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::model_type::ModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v5_lite_edit_image_webhook::{enqueue_bytedance_seedream_v5_lite_edit_image_webhook, EnqueueBytedanceSeedreamV5LiteEditImageArgs, EnqueueBytedanceSeedreamV5LiteEditImageNumImages, EnqueueBytedanceSeedreamV5LiteEditImageSize};
|
||||
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v5_lite_text_to_image_webhook::{enqueue_bytedance_seedream_v5_lite_text_to_image_webhook, EnqueueBytedanceSeedreamV5LiteTextToImageArgs, EnqueueBytedanceSeedreamV5LiteTextToImageNumImages, EnqueueBytedanceSeedreamV5LiteTextToImageSize};
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{error, info, warn};
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
|
||||
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
|
||||
use sqlx::Acquire;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
|
||||
/// Bytedance Seedream 5 Lite Multi-Function (generate + edit)
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Generate Images (Multi-Function)",
|
||||
path = "/v1/generate/image/multi_function/bytedance_seedream_5_lite",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = BytedanceSeedream5LiteMultiFunctionImageGenResponse),
|
||||
),
|
||||
params(
|
||||
("request" = BytedanceSeedream5LiteMultiFunctionImageGenRequest, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn bytedance_seedream_5_lite_multi_function_image_gen_handler(
|
||||
http_request: HttpRequest,
|
||||
request: Json<BytedanceSeedream5LiteMultiFunctionImageGenRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<BytedanceSeedream5LiteMultiFunctionImageGenResponse>, CommonWebError> {
|
||||
|
||||
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
|
||||
|
||||
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
|
||||
}
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_token = match maybe_user_session.as_ref() {
|
||||
Some(session) => &session.user_token,
|
||||
None => {
|
||||
return Err(CommonWebError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
let image_urls = match request.image_media_tokens.as_ref() {
|
||||
Some(media_tokens) => {
|
||||
info!("Looking up image media tokens: {:?}", media_tokens);
|
||||
lookup_image_urls_as_optional_list(
|
||||
&http_request,
|
||||
&mut mysql_connection,
|
||||
server_state.server_environment,
|
||||
media_tokens,
|
||||
).await?
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error inserting idempotency token: {:?}", err);
|
||||
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
|
||||
})?;
|
||||
|
||||
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
|
||||
|
||||
let apriori_job_token = InferenceJobToken::generate();
|
||||
|
||||
let fal_result;
|
||||
|
||||
if let Some(input_image_urls) = image_urls.as_deref() {
|
||||
info!("seedream 5 lite edit image");
|
||||
|
||||
let num_images = match request.num_images {
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenNumImages::One) => EnqueueBytedanceSeedreamV5LiteEditImageNumImages::One,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenNumImages::Two) => EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Two,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenNumImages::Three) => EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Three,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenNumImages::Four) => EnqueueBytedanceSeedreamV5LiteEditImageNumImages::Four,
|
||||
None => EnqueueBytedanceSeedreamV5LiteEditImageNumImages::One,
|
||||
};
|
||||
|
||||
let image_size = match request.image_size {
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::Square) => EnqueueBytedanceSeedreamV5LiteEditImageSize::Square,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::SquareHd) => EnqueueBytedanceSeedreamV5LiteEditImageSize::SquareHd,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::PortraitFourThree) => EnqueueBytedanceSeedreamV5LiteEditImageSize::PortraitFourThree,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::PortraitSixteenNine) => EnqueueBytedanceSeedreamV5LiteEditImageSize::PortraitSixteenNine,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::LandscapeFourThree) => EnqueueBytedanceSeedreamV5LiteEditImageSize::LandscapeFourThree,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::LandscapeSixteenNine) => EnqueueBytedanceSeedreamV5LiteEditImageSize::LandscapeSixteenNine,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::Auto2k) => EnqueueBytedanceSeedreamV5LiteEditImageSize::Auto2k,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::Auto3k) => EnqueueBytedanceSeedreamV5LiteEditImageSize::Auto3k,
|
||||
None => EnqueueBytedanceSeedreamV5LiteEditImageSize::SquareHd,
|
||||
};
|
||||
|
||||
let args = EnqueueBytedanceSeedreamV5LiteEditImageArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
image_urls: input_image_urls.to_owned(),
|
||||
num_images: Some(num_images),
|
||||
max_images: None,
|
||||
image_size: Some(image_size),
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
};
|
||||
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
|
||||
info!("Charging wallet: {}", cost);
|
||||
|
||||
attempt_wallet_deduction_else_common_web_error(
|
||||
user_token,
|
||||
Some(apriori_job_token.as_str()),
|
||||
cost,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
|
||||
fal_result = enqueue_bytedance_seedream_v5_lite_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error calling enqueue_bytedance_seedream_v5_lite_edit_image_webhook: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
} else {
|
||||
info!("seedream 5 lite text-to-image");
|
||||
|
||||
let num_images = match request.num_images {
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenNumImages::One) => EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::One,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenNumImages::Two) => EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Two,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenNumImages::Three) => EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Three,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenNumImages::Four) => EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::Four,
|
||||
None => EnqueueBytedanceSeedreamV5LiteTextToImageNumImages::One,
|
||||
};
|
||||
|
||||
let image_size = match request.image_size {
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::Square) => EnqueueBytedanceSeedreamV5LiteTextToImageSize::Square,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::SquareHd) => EnqueueBytedanceSeedreamV5LiteTextToImageSize::SquareHd,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::PortraitFourThree) => EnqueueBytedanceSeedreamV5LiteTextToImageSize::PortraitFourThree,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::PortraitSixteenNine) => EnqueueBytedanceSeedreamV5LiteTextToImageSize::PortraitSixteenNine,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::LandscapeFourThree) => EnqueueBytedanceSeedreamV5LiteTextToImageSize::LandscapeFourThree,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::LandscapeSixteenNine) => EnqueueBytedanceSeedreamV5LiteTextToImageSize::LandscapeSixteenNine,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::Auto2k) => EnqueueBytedanceSeedreamV5LiteTextToImageSize::Auto2k,
|
||||
Some(BytedanceSeedream5LiteMultiFunctionImageGenImageSize::Auto3k) => EnqueueBytedanceSeedreamV5LiteTextToImageSize::Auto3k,
|
||||
None => EnqueueBytedanceSeedreamV5LiteTextToImageSize::SquareHd,
|
||||
};
|
||||
|
||||
let args = EnqueueBytedanceSeedreamV5LiteTextToImageArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
num_images: Some(num_images),
|
||||
max_images: None,
|
||||
image_size: Some(image_size),
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
};
|
||||
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
|
||||
info!("Charging wallet: {}", cost);
|
||||
|
||||
attempt_wallet_deduction_else_common_web_error(
|
||||
user_token,
|
||||
Some(apriori_job_token.as_str()),
|
||||
cost,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
|
||||
fal_result = enqueue_bytedance_seedream_v5_lite_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error calling enqueue_bytedance_seedream_v5_lite_text_to_image_webhook: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
}
|
||||
|
||||
let external_job_id = fal_result.request_id
|
||||
.ok_or_else(|| {
|
||||
warn!("Fal request_id is None");
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
info!("Fal request_id: {}", external_job_id);
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let mut transaction = mysql_connection
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let prompt_result = insert_prompt(InsertPromptArgs {
|
||||
maybe_apriori_prompt_token: None,
|
||||
prompt_type: PromptType::ArtcraftApp,
|
||||
maybe_creator_user_token: Some(&user_token),
|
||||
maybe_model_type: Some(ModelType::Seedream5Lite),
|
||||
maybe_generation_provider: Some(GenerationProvider::Artcraft),
|
||||
maybe_positive_prompt: request.prompt.as_deref(),
|
||||
maybe_negative_prompt: None,
|
||||
maybe_other_args: None,
|
||||
creator_ip_address: &ip_address,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let prompt_token = match prompt_result {
|
||||
Ok(token) => Some(token),
|
||||
Err(err) => {
|
||||
warn!("Error inserting prompt: {:?}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(media_tokens) = &request.image_media_tokens {
|
||||
if let Some(token) = prompt_token.as_ref() {
|
||||
let result = insert_batch_prompt_context_items(InsertBatchArgs {
|
||||
prompt_token: token.clone(),
|
||||
items: media_tokens.iter().map(|token| {
|
||||
PromptContextItem {
|
||||
media_token: token.clone(),
|
||||
context_semantic_type: PromptContextSemanticType::Imgref,
|
||||
}
|
||||
}).collect(),
|
||||
transaction: &mut transaction,
|
||||
}).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
warn!("Error inserting batch prompt context items: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
|
||||
apriori_job_token: &apriori_job_token,
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
maybe_external_third_party_id: &external_job_id,
|
||||
fal_category: FalCategory::ImageGeneration,
|
||||
maybe_inference_args: None,
|
||||
maybe_prompt_token: prompt_token.as_ref(),
|
||||
maybe_creator_user_token: Some(&user_token),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let job_token = match db_result {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
warn!("Error inserting generic inference job for FAL queue: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error committing MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
Ok(Json(BytedanceSeedream5LiteMultiFunctionImageGenResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
}))
|
||||
}
|
||||
+2
@@ -1,5 +1,7 @@
|
||||
pub mod bytedance_seedream_v4_multi_function_image_gen_handler;
|
||||
pub mod bytedance_seedream_v4p5_multi_function_image_gen_handler;
|
||||
pub mod bytedance_seedream_5_lite_multi_function_image_gen_handler;
|
||||
pub mod gpt_image_1p5_multi_function_image_gen_handler;
|
||||
pub mod nano_banana_2_multi_function_image_gen_handler;
|
||||
pub mod nano_banana_multi_function_image_gen_handler;
|
||||
pub mod nano_banana_pro_multi_function_image_gen_handler;
|
||||
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::lookup::lookup_image_urls_as_optional_list::lookup_image_urls_as_optional_list;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::image::multi_function::nano_banana_2_multi_function_image_gen::{NanaBanana2MultiFunctionImageGenAspectRatio, NanaBanana2MultiFunctionImageGenImageResolution, NanaBanana2MultiFunctionImageGenNumImages, NanaBanana2MultiFunctionImageGenRequest, NanaBanana2MultiFunctionImageGenResponse};
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::model_type::ModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_nano_banana_2_edit_image_webhook::{enqueue_nano_banana_2_edit_image_webhook, EnqueueNanoBanana2EditImageArgs, EnqueueNanoBanana2EditImageAspectRatio, EnqueueNanoBanana2EditImageNumImages, EnqueueNanoBanana2EditImageResolution};
|
||||
use fal_client::requests::webhook::image::text::enqueue_nano_banana_2_text_to_image_webhook::{enqueue_nano_banana_2_text_to_image_webhook, EnqueueNanoBanana2TextToImageArgs, EnqueueNanoBanana2TextToImageAspectRatio, EnqueueNanoBanana2TextToImageNumImages, EnqueueNanoBanana2TextToImageResolution};
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{error, info, warn};
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
|
||||
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
|
||||
use sqlx::Acquire;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
|
||||
/// Nano Banana 2 Multi-Function (generate + edit)
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Generate Images (Multi-Function)",
|
||||
path = "/v1/generate/image/multi_function/nano_banana_2",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = NanaBanana2MultiFunctionImageGenResponse),
|
||||
),
|
||||
params(
|
||||
("request" = NanaBanana2MultiFunctionImageGenRequest, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn nano_banana_2_multi_function_image_gen_handler(
|
||||
http_request: HttpRequest,
|
||||
request: Json<NanaBanana2MultiFunctionImageGenRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<NanaBanana2MultiFunctionImageGenResponse>, CommonWebError> {
|
||||
|
||||
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
|
||||
|
||||
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
|
||||
}
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_token = match maybe_user_session.as_ref() {
|
||||
Some(session) => &session.user_token,
|
||||
None => {
|
||||
return Err(CommonWebError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
let image_urls = match request.image_media_tokens.as_ref() {
|
||||
Some(media_tokens) => {
|
||||
info!("Looking up image media tokens: {:?}", media_tokens);
|
||||
lookup_image_urls_as_optional_list(
|
||||
&http_request,
|
||||
&mut mysql_connection,
|
||||
server_state.server_environment,
|
||||
media_tokens,
|
||||
).await?
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error inserting idempotency token: {:?}", err);
|
||||
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
|
||||
})?;
|
||||
|
||||
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
|
||||
|
||||
let apriori_job_token = InferenceJobToken::generate();
|
||||
|
||||
let fal_result;
|
||||
|
||||
if let Some(input_image_urls) = image_urls.as_deref() {
|
||||
info!("nano banana 2 edit image");
|
||||
|
||||
let num_images = match request.num_images {
|
||||
Some(NanaBanana2MultiFunctionImageGenNumImages::One) => EnqueueNanoBanana2EditImageNumImages::One,
|
||||
Some(NanaBanana2MultiFunctionImageGenNumImages::Two) => EnqueueNanoBanana2EditImageNumImages::Two,
|
||||
Some(NanaBanana2MultiFunctionImageGenNumImages::Three) => EnqueueNanoBanana2EditImageNumImages::Three,
|
||||
Some(NanaBanana2MultiFunctionImageGenNumImages::Four) => EnqueueNanoBanana2EditImageNumImages::Four,
|
||||
None => EnqueueNanoBanana2EditImageNumImages::One,
|
||||
};
|
||||
|
||||
let resolution = match request.resolution {
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::HalfK) => Some(EnqueueNanoBanana2EditImageResolution::HalfK),
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::OneK) => Some(EnqueueNanoBanana2EditImageResolution::OneK),
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::TwoK) => Some(EnqueueNanoBanana2EditImageResolution::TwoK),
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::FourK) => Some(EnqueueNanoBanana2EditImageResolution::FourK),
|
||||
None => Some(EnqueueNanoBanana2EditImageResolution::OneK),
|
||||
};
|
||||
|
||||
let aspect_ratio = match request.aspect_ratio {
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::Auto) => Some(EnqueueNanoBanana2EditImageAspectRatio::Auto),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::OneByOne) => Some(EnqueueNanoBanana2EditImageAspectRatio::OneByOne),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::FiveByFour) => Some(EnqueueNanoBanana2EditImageAspectRatio::FiveByFour),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::FourByThree) => Some(EnqueueNanoBanana2EditImageAspectRatio::FourByThree),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::ThreeByTwo) => Some(EnqueueNanoBanana2EditImageAspectRatio::ThreeByTwo),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::SixteenByNine) => Some(EnqueueNanoBanana2EditImageAspectRatio::SixteenByNine),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::TwentyOneByNine) => Some(EnqueueNanoBanana2EditImageAspectRatio::TwentyOneByNine),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::FourByFive) => Some(EnqueueNanoBanana2EditImageAspectRatio::FourByFive),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::ThreeByFour) => Some(EnqueueNanoBanana2EditImageAspectRatio::ThreeByFour),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::TwoByThree) => Some(EnqueueNanoBanana2EditImageAspectRatio::TwoByThree),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::NineBySixteen) => Some(EnqueueNanoBanana2EditImageAspectRatio::NineBySixteen),
|
||||
None => Some(EnqueueNanoBanana2EditImageAspectRatio::OneByOne),
|
||||
};
|
||||
|
||||
let args = EnqueueNanoBanana2EditImageArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
image_urls: input_image_urls.to_owned(),
|
||||
num_images,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
};
|
||||
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
|
||||
info!("Charging wallet: {}", cost);
|
||||
|
||||
attempt_wallet_deduction_else_common_web_error(
|
||||
user_token,
|
||||
Some(apriori_job_token.as_str()),
|
||||
cost,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
|
||||
fal_result = enqueue_nano_banana_2_edit_image_webhook(args)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error calling enqueue_nano_banana_2_edit_image_webhook: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
} else {
|
||||
info!("nano banana 2 text-to-image");
|
||||
|
||||
let num_images = match request.num_images {
|
||||
Some(NanaBanana2MultiFunctionImageGenNumImages::One) => EnqueueNanoBanana2TextToImageNumImages::One,
|
||||
Some(NanaBanana2MultiFunctionImageGenNumImages::Two) => EnqueueNanoBanana2TextToImageNumImages::Two,
|
||||
Some(NanaBanana2MultiFunctionImageGenNumImages::Three) => EnqueueNanoBanana2TextToImageNumImages::Three,
|
||||
Some(NanaBanana2MultiFunctionImageGenNumImages::Four) => EnqueueNanoBanana2TextToImageNumImages::Four,
|
||||
None => EnqueueNanoBanana2TextToImageNumImages::One,
|
||||
};
|
||||
|
||||
let resolution = match request.resolution {
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::HalfK) => Some(EnqueueNanoBanana2TextToImageResolution::HalfK),
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::OneK) => Some(EnqueueNanoBanana2TextToImageResolution::OneK),
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::TwoK) => Some(EnqueueNanoBanana2TextToImageResolution::TwoK),
|
||||
Some(NanaBanana2MultiFunctionImageGenImageResolution::FourK) => Some(EnqueueNanoBanana2TextToImageResolution::FourK),
|
||||
None => Some(EnqueueNanoBanana2TextToImageResolution::OneK),
|
||||
};
|
||||
|
||||
let aspect_ratio = match request.aspect_ratio {
|
||||
// NB: "auto" is only for edit image, not text-to-image
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::Auto) => Some(EnqueueNanoBanana2TextToImageAspectRatio::OneByOne),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::OneByOne) => Some(EnqueueNanoBanana2TextToImageAspectRatio::OneByOne),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::FiveByFour) => Some(EnqueueNanoBanana2TextToImageAspectRatio::FiveByFour),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::FourByThree) => Some(EnqueueNanoBanana2TextToImageAspectRatio::FourByThree),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::ThreeByTwo) => Some(EnqueueNanoBanana2TextToImageAspectRatio::ThreeByTwo),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::SixteenByNine) => Some(EnqueueNanoBanana2TextToImageAspectRatio::SixteenByNine),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::TwentyOneByNine) => Some(EnqueueNanoBanana2TextToImageAspectRatio::TwentyOneByNine),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::FourByFive) => Some(EnqueueNanoBanana2TextToImageAspectRatio::FourByFive),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::ThreeByFour) => Some(EnqueueNanoBanana2TextToImageAspectRatio::ThreeByFour),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::TwoByThree) => Some(EnqueueNanoBanana2TextToImageAspectRatio::TwoByThree),
|
||||
Some(NanaBanana2MultiFunctionImageGenAspectRatio::NineBySixteen) => Some(EnqueueNanoBanana2TextToImageAspectRatio::NineBySixteen),
|
||||
None => Some(EnqueueNanoBanana2TextToImageAspectRatio::OneByOne),
|
||||
};
|
||||
|
||||
let args = EnqueueNanoBanana2TextToImageArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
num_images,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
};
|
||||
|
||||
let cost = args.calculate_cost_in_cents();
|
||||
|
||||
info!("Charging wallet: {}", cost);
|
||||
|
||||
attempt_wallet_deduction_else_common_web_error(
|
||||
user_token,
|
||||
Some(apriori_job_token.as_str()),
|
||||
cost,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
|
||||
fal_result = enqueue_nano_banana_2_text_to_image_webhook(args)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error calling enqueue_nano_banana_2_text_to_image_webhook: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
}
|
||||
|
||||
let external_job_id = fal_result.request_id
|
||||
.ok_or_else(|| {
|
||||
warn!("Fal request_id is None");
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
info!("Fal request_id: {}", external_job_id);
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let mut transaction = mysql_connection
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let prompt_result = insert_prompt(InsertPromptArgs {
|
||||
maybe_apriori_prompt_token: None,
|
||||
prompt_type: PromptType::ArtcraftApp,
|
||||
maybe_creator_user_token: Some(&user_token),
|
||||
maybe_model_type: Some(ModelType::NanoBanana2),
|
||||
maybe_generation_provider: Some(GenerationProvider::Artcraft),
|
||||
maybe_positive_prompt: request.prompt.as_deref(),
|
||||
maybe_negative_prompt: None,
|
||||
maybe_other_args: None,
|
||||
creator_ip_address: &ip_address,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let prompt_token = match prompt_result {
|
||||
Ok(token) => Some(token),
|
||||
Err(err) => {
|
||||
warn!("Error inserting prompt: {:?}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(media_tokens) = &request.image_media_tokens {
|
||||
if let Some(token) = prompt_token.as_ref() {
|
||||
let result = insert_batch_prompt_context_items(InsertBatchArgs {
|
||||
prompt_token: token.clone(),
|
||||
items: media_tokens.iter().map(|token| {
|
||||
PromptContextItem {
|
||||
media_token: token.clone(),
|
||||
context_semantic_type: PromptContextSemanticType::Imgref,
|
||||
}
|
||||
}).collect(),
|
||||
transaction: &mut transaction,
|
||||
}).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
warn!("Error inserting batch prompt context items: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
|
||||
apriori_job_token: &apriori_job_token,
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
maybe_external_third_party_id: &external_job_id,
|
||||
fal_category: FalCategory::ImageGeneration,
|
||||
maybe_inference_args: None,
|
||||
maybe_prompt_token: prompt_token.as_ref(),
|
||||
maybe_creator_user_token: Some(&user_token),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let job_token = match db_result {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
warn!("Error inserting generic inference job for FAL queue: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error committing MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
Ok(Json(NanaBanana2MultiFunctionImageGenResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
}))
|
||||
}
|
||||
+10
@@ -14,7 +14,9 @@ use crate::http_server::endpoints::generate::image::inpaint::flux_dev_juggernaut
|
||||
use crate::http_server::endpoints::generate::image::inpaint::flux_pro_1_inpaint_handler::flux_pro_1_inpaint_image_handler;
|
||||
use crate::http_server::endpoints::generate::image::multi_function::bytedance_seedream_v4_multi_function_image_gen_handler::bytedance_seedream_v4_multi_function_image_gen_handler;
|
||||
use crate::http_server::endpoints::generate::image::multi_function::bytedance_seedream_v4p5_multi_function_image_gen_handler::bytedance_seedream_v4p5_multi_function_image_gen_handler;
|
||||
use crate::http_server::endpoints::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen_handler::bytedance_seedream_5_lite_multi_function_image_gen_handler;
|
||||
use crate::http_server::endpoints::generate::image::multi_function::gpt_image_1p5_multi_function_image_gen_handler::gpt_image_1p5_multi_function_image_gen_handler;
|
||||
use crate::http_server::endpoints::generate::image::multi_function::nano_banana_2_multi_function_image_gen_handler::nano_banana_2_multi_function_image_gen_handler;
|
||||
use crate::http_server::endpoints::generate::image::multi_function::nano_banana_multi_function_image_gen_handler::nano_banana_multi_function_image_gen_handler;
|
||||
use crate::http_server::endpoints::generate::image::multi_function::nano_banana_pro_multi_function_image_gen_handler::nano_banana_pro_multi_function_image_gen_handler;
|
||||
use crate::http_server::endpoints::generate::image::remove_image_background_handler::remove_image_background_handler;
|
||||
@@ -81,10 +83,18 @@ where
|
||||
.route(web::post().to(nano_banana_multi_function_image_gen_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/nano_banana_2")
|
||||
.route(web::post().to(nano_banana_2_multi_function_image_gen_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/nano_banana_pro")
|
||||
.route(web::post().to(nano_banana_pro_multi_function_image_gen_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
.service(web::resource("/bytedance_seedream_5_lite")
|
||||
.route(web::post().to(bytedance_seedream_5_lite_multi_function_image_gen_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
.service(web::scope("/edit")
|
||||
.service(web::resource("/flux_pro_kontext_max")
|
||||
|
||||
Reference in New Issue
Block a user