From 7be7227573e260d30d2e6c811e7faaf04c4bb259 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 16 Jun 2025 17:35:34 -0400 Subject: [PATCH] flux schnell --- .../generate_flux_1_dev_text_to_image.rs | 2 +- .../generate_flux_1_schnell_text_to_image.rs | 49 ++++++ .../src/generate/image/mod.rs | 1 + .../web/storyteller_web/src/docs/api_doc.rs | 10 ++ ...te_flux_1_schnell_text_to_image_handler.rs | 158 ++++++++++++++++++ .../endpoints/generate/image/mod.rs | 1 + .../http_server/routes/add_generate_routes.rs | 5 + 7 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 crates/schema/public/api/artcraft_api_defs/src/generate/image/generate_flux_1_schnell_text_to_image.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/generate_flux_1_schnell_text_to_image_handler.rs diff --git a/crates/schema/public/api/artcraft_api_defs/src/generate/image/generate_flux_1_dev_text_to_image.rs b/crates/schema/public/api/artcraft_api_defs/src/generate/image/generate_flux_1_dev_text_to_image.rs index be9743ca6e..421bd5e657 100644 --- a/crates/schema/public/api/artcraft_api_defs/src/generate/image/generate_flux_1_dev_text_to_image.rs +++ b/crates/schema/public/api/artcraft_api_defs/src/generate/image/generate_flux_1_dev_text_to_image.rs @@ -2,7 +2,7 @@ use serde_derive::{Deserialize, Serialize}; use tokens::tokens::generic_inference_jobs::InferenceJobToken; use utoipa::ToSchema; -pub const GENERATE_FLUX_PRO_11_ULTRA_TEXT_TO_IMAGE_PATH: &str = "/v1/generate/image/flux_1_dev_text_to_image"; +pub const GENERATE_FLUX_1_DEV_TEXT_TO_IMAGE_PATH: &str = "/v1/generate/image/flux_1_dev_text_to_image"; #[derive(Serialize, Deserialize, ToSchema)] pub struct GenerateFlux1DevTextToImageRequest { diff --git a/crates/schema/public/api/artcraft_api_defs/src/generate/image/generate_flux_1_schnell_text_to_image.rs b/crates/schema/public/api/artcraft_api_defs/src/generate/image/generate_flux_1_schnell_text_to_image.rs new file mode 100644 index 0000000000..a2d636fdf0 --- /dev/null +++ b/crates/schema/public/api/artcraft_api_defs/src/generate/image/generate_flux_1_schnell_text_to_image.rs @@ -0,0 +1,49 @@ +use serde_derive::{Deserialize, Serialize}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use utoipa::ToSchema; + +pub const GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH: &str = "/v1/generate/image/flux_1_schnell_text_to_image"; + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct GenerateFlux1SchnellTextToImageRequest { + /// Idempotency token to prevent duplicate requests. + pub uuid_idempotency_token: String, + + /// Text prompt to generate the image from. + pub prompt: Option, + + /// Aspect ratio of the output images. + pub aspect_ratio: Option, + + /// Number of images to generate. Default is one. + pub num_images: Option, +} + + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum GenerateFlux1SchnellTextToImageAspectRatio { + Square, // 1:1 + SquareHd, // 1:1 + LandscapeFourByThree, // 4:3 + LandscapeSixteenByNine, // 16:9 + PortraitThreeByFour, // 3:4 + PortraitNineBySixteen, // 9:16 + //Custom { width: u32, height: u32 }, // TODO +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum GenerateFlux1SchnellTextToImageNumImages { + One, // Default + Two, + Three, + Four, +} + + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct GenerateFlux1SchnellTextToImageResponse { + pub success: bool, + pub inference_job_token: InferenceJobToken, +} diff --git a/crates/schema/public/api/artcraft_api_defs/src/generate/image/mod.rs b/crates/schema/public/api/artcraft_api_defs/src/generate/image/mod.rs index e8e736a6dc..a3cb2b9f8c 100644 --- a/crates/schema/public/api/artcraft_api_defs/src/generate/image/mod.rs +++ b/crates/schema/public/api/artcraft_api_defs/src/generate/image/mod.rs @@ -1,3 +1,4 @@ pub mod generate_flux_1_dev_text_to_image; +pub mod generate_flux_1_schnell_text_to_image; pub mod generate_flux_pro_11_ultra_text_to_image; pub mod remove_image_background; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/docs/api_doc.rs b/crates/service/web/storyteller_web/src/docs/api_doc.rs index 5d9d5119fb..ca51cda643 100644 --- a/crates/service/web/storyteller_web/src/docs/api_doc.rs +++ b/crates/service/web/storyteller_web/src/docs/api_doc.rs @@ -4,6 +4,10 @@ use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::Gener use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageNumImages; use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageRequest; use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageResponse; +use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageAspectRatio; +use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageNumImages; +use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageRequest; +use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageResponse; use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest; use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageResponse; use billing_component::stripe::http_endpoints::checkout::create::stripe_create_checkout_session_error::CreateCheckoutSessionError; @@ -90,6 +94,7 @@ use crate::http_server::endpoints::featured_items::create_featured_item_handler: use crate::http_server::endpoints::featured_items::delete_featured_item_handler::*; use crate::http_server::endpoints::featured_items::get_is_featured_item_handler::*; use crate::http_server::endpoints::generate::image::generate_flux_1_dev_text_to_image_handler::generate_flux_1_dev_text_to_image_handler; +use crate::http_server::endpoints::generate::image::generate_flux_1_schnell_text_to_image_handler::generate_flux_1_schnell_text_to_image_handler; use crate::http_server::endpoints::generate::image::generate_flux_pro_11_ultra_text_to_image_handler::generate_flux_pro_11_ultra_text_to_image_handler; use crate::http_server::endpoints::image_studio::prompt::enqueue_studio_image_generation_handler::*; use crate::http_server::endpoints::image_studio::upload::upload_snapshot_media_file_handler::*; @@ -196,6 +201,7 @@ use crate::http_server::web_utils::response_success_helpers::*; crate::http_server::endpoints::featured_items::delete_featured_item_handler::delete_featured_item_handler, crate::http_server::endpoints::featured_items::get_is_featured_item_handler::get_is_featured_item_handler, crate::http_server::endpoints::generate::image::generate_flux_1_dev_text_to_image_handler::generate_flux_1_dev_text_to_image_handler, + crate::http_server::endpoints::generate::image::generate_flux_1_schnell_text_to_image_handler::generate_flux_1_schnell_text_to_image_handler, crate::http_server::endpoints::generate::image::generate_flux_pro_11_ultra_text_to_image_handler::generate_flux_pro_11_ultra_text_to_image_handler, crate::http_server::endpoints::image_studio::prompt::enqueue_studio_image_generation_handler::enqueue_studio_image_generation_handler, crate::http_server::endpoints::image_studio::upload::upload_snapshot_media_file_handler::upload_snapshot_media_file_handler, @@ -469,6 +475,10 @@ use crate::http_server::web_utils::response_success_helpers::*; GenerateFlux1DevTextToImageNumImages, GenerateFlux1DevTextToImageRequest, GenerateFlux1DevTextToImageResponse, + GenerateFlux1SchnellTextToImageAspectRatio, + GenerateFlux1SchnellTextToImageNumImages, + GenerateFlux1SchnellTextToImageRequest, + GenerateFlux1SchnellTextToImageResponse, GenerateFluxPro11UltraTextToImageRequest, GenerateFluxPro11UltraTextToImageResponse, GetInferenceJobStatusError, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/generate_flux_1_schnell_text_to_image_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/generate_flux_1_schnell_text_to_image_handler.rs new file mode 100644 index 0000000000..d39852bcf3 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/generate_flux_1_schnell_text_to_image_handler.rs @@ -0,0 +1,158 @@ +use std::fmt; +use std::sync::Arc; + +use crate::http_server::common_requests::media_file_token_path_info::MediaFileTokenPathInfo; +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::common_responses::media::media_links::MediaLinks; +use crate::http_server::deprecated_endpoints::engine::create_scene_handler::CreateSceneError; +use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain; +use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileUploadError; +use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; +use crate::http_server::web_utils::response_success_helpers::simple_json_success; +use crate::state::server_state::ServerState; +use crate::util::delete_role_disambiguation::{delete_role_disambiguation, DeleteRole}; +use actix_web::error::ResponseError; +use actix_web::http::StatusCode; +use actix_web::web::Json; +use actix_web::web::Path; +use actix_web::{web, HttpRequest, HttpResponse}; +use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageAspectRatio, GenerateFlux1SchnellTextToImageNumImages, GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse}; +use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath; +use enums::common::visibility::Visibility; +use fal_client::requests::webhook::image::enqueue_flux_1_schnell_text_to_image_webhook::enqueue_flux_1_schnell_text_to_image_webhook; +use fal_client::requests::webhook::image::enqueue_flux_1_schnell_text_to_image_webhook::{Flux1SchnellArgs, Flux1SchnellAspectRatio, Flux1SchnellNumImages}; +use http_server_common::request::get_request_ip::get_request_ip; +use http_server_common::response::serialize_as_json_error::serialize_as_json_error; +use idempotency::uuid::generate_random_uuid; +use log::{error, info, warn}; +use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::insert_generic_inference_job_for_fal_queue; +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::InsertGenericInferenceForFalArgs; +use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token; +use mysql_queries::queries::media_files::get::get_media_file::{get_media_file, MediaFile}; +use tokens::tokens::media_files::MediaFileToken; +use utoipa::ToSchema; + +/// Flux pro ultra text to image +#[utoipa::path( + post, + tag = "Generate Images", + path = "/v1/generate/image/flux_1_schnell_text_to_image", + responses( + (status = 200, description = "Success", body = GenerateFlux1SchnellTextToImageResponse), + ), + params( + ("request" = GenerateFlux1SchnellTextToImageRequest, description = "Payload for Request"), + ) +)] +pub async fn generate_flux_1_schnell_text_to_image_handler( + http_request: HttpRequest, + request: Json, + server_state: web::Data> +) -> Result, CommonWebError> { + let maybe_user_session = server_state + .session_checker + .maybe_get_user_session(&http_request, &server_state.mysql_pool) + .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); + + // TODO: Limit usage for new accounts. Billing, free credits metering, etc. + + //let user_session = match maybe_user_session { + // Some(session) => session, + // None => { + // warn!("not logged in"); + // return Err(CommonWebError::NotAuthorized); + // } + //}; + + if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); + } + + insert_idempotency_token(&request.uuid_idempotency_token, &server_state.mysql_pool) + .await + .map_err(|err| { + error!("Error inserting idempotency token: {:?}", err); + CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string()) + })?; + + const IS_MOD : bool = false; + + info!("Fal webhook URL: {}", server_state.fal.webhook_url); + + let aspect_ratio = match request.aspect_ratio { + Some(GenerateFlux1SchnellTextToImageAspectRatio::Square) => Flux1SchnellAspectRatio::Square, + Some(GenerateFlux1SchnellTextToImageAspectRatio::SquareHd) => Flux1SchnellAspectRatio::SquareHd, + Some(GenerateFlux1SchnellTextToImageAspectRatio::LandscapeFourByThree) => Flux1SchnellAspectRatio::LandscapeFourByThree, + Some(GenerateFlux1SchnellTextToImageAspectRatio::LandscapeSixteenByNine) => Flux1SchnellAspectRatio::LandscapeSixteenByNine, + Some(GenerateFlux1SchnellTextToImageAspectRatio::PortraitThreeByFour) => Flux1SchnellAspectRatio::PortraitThreeByFour, + Some(GenerateFlux1SchnellTextToImageAspectRatio::PortraitNineBySixteen) => Flux1SchnellAspectRatio::PortraitNineBySixteen, + None => Flux1SchnellAspectRatio::LandscapeSixteenByNine, // Default + }; + + let num_images = match request.num_images { + Some(GenerateFlux1SchnellTextToImageNumImages::One) => Flux1SchnellNumImages::One, + Some(GenerateFlux1SchnellTextToImageNumImages::Two) => Flux1SchnellNumImages::Two, + Some(GenerateFlux1SchnellTextToImageNumImages::Three) => Flux1SchnellNumImages::Three, + Some(GenerateFlux1SchnellTextToImageNumImages::Four) => Flux1SchnellNumImages::Four, + None => Flux1SchnellNumImages::One, // Default + }; + + let args = Flux1SchnellArgs { + prompt: request.prompt.as_deref().unwrap_or(""), + webhook_url: &server_state.fal.webhook_url, + api_key: &server_state.fal.api_key, + aspect_ratio, + num_images, + }; + + let fal_result = enqueue_flux_1_schnell_text_to_image_webhook(args) + .await + .map_err(|err| { + warn!("Error calling enqueue_flux_1_schnell_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 db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs { + uuid_idempotency_token: &request.uuid_idempotency_token, + maybe_external_third_party_id: &external_job_id, + fal_category: FalCategory::ImageGeneration, + maybe_inference_args: None, + maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token), + maybe_avt_token: maybe_avt_token.as_ref(), + creator_ip_address: &ip_address, + creator_set_visibility: Visibility::Public, + mysql_pool: &server_state.mysql_pool, + }).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); + } + }; + + Ok(Json(GenerateFlux1SchnellTextToImageResponse { + success: true, + inference_job_token: job_token, + })) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/mod.rs index b30389fa9b..a57b69f63d 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/image/mod.rs @@ -1,3 +1,4 @@ pub mod generate_flux_1_dev_text_to_image_handler; +pub mod generate_flux_1_schnell_text_to_image_handler; pub mod generate_flux_pro_11_ultra_text_to_image_handler; pub mod remove_image_background_handler; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/routes/add_generate_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/add_generate_routes.rs index 2bdb82cf5f..abd094cc90 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/add_generate_routes.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/add_generate_routes.rs @@ -1,4 +1,5 @@ use crate::http_server::endpoints::generate::image::generate_flux_1_dev_text_to_image_handler::generate_flux_1_dev_text_to_image_handler; +use crate::http_server::endpoints::generate::image::generate_flux_1_schnell_text_to_image_handler::generate_flux_1_schnell_text_to_image_handler; use crate::http_server::endpoints::generate::image::generate_flux_pro_11_ultra_text_to_image_handler::generate_flux_pro_11_ultra_text_to_image_handler; use crate::http_server::endpoints::generate::image::remove_image_background_handler::remove_image_background_handler; use crate::http_server::endpoints::generate::object::generate_hunyuan2_image_to_3d_handler::generate_hunyuan_2_image_to_3d_handler; @@ -25,6 +26,10 @@ where .route(web::post().to(generate_flux_1_dev_text_to_image_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) + .service(web::resource("/flux_1_schnell_text_to_image") + .route(web::post().to(generate_flux_1_schnell_text_to_image_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) .service(web::resource("/flux_pro_1.1_ultra_text_to_image") .route(web::post().to(generate_flux_pro_11_ultra_text_to_image_handler)) .route(web::head().to(|| HttpResponse::Ok()))