mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
flux pro inpaint handler
This commit is contained in:
1 parent
bc5e26b3ea
commit
8a87e1da37
11 files changed
+408
-2
No files matched your search
+36
@@ -0,0 +1,36 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const FLUX_PRO_KONTEXT_MAX_EDIT_IMAGE_PATH: &str = "/v1/generate/image/edit/flux_pro_kontext_max";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct FluxProKontextMaxEditImageRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// The image we're editing.
|
||||
pub image_media_token: MediaFileToken,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<FluxProKontextMaxEditImageNumImages>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FluxProKontextMaxEditImageNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct FluxProKontextMaxEditImageResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod gpt_image_1_edit_image;
|
||||
mod flux_pro_kontext_max_edit_image;
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const FLUX_PRO_1_INPAINT_PATH: &str = "/v1/generate/image/inpaint/flux_pro_1";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct FluxPro1InpaintImageRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// The image we're editing.
|
||||
pub image_media_token: MediaFileToken,
|
||||
|
||||
/// The mask to use against the image.
|
||||
pub mask_media_token: MediaFileToken,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<FluxPro1InpaintImageNumImages>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FluxPro1InpaintImageNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct FluxPro1InpaintImageResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod flux_pro_1_inpaint_image;
|
||||
@@ -4,4 +4,5 @@ pub mod generate_flux_1_schnell_text_to_image;
|
||||
pub mod generate_flux_pro_11_text_to_image;
|
||||
pub mod generate_flux_pro_11_ultra_text_to_image;
|
||||
pub mod generate_gpt_image_1_text_to_image;
|
||||
pub mod inpaint;
|
||||
pub mod remove_image_background;
|
||||
+21
-1
@@ -19,6 +19,12 @@ pub enum PromptContextSemanticType {
|
||||
/// Image-to-Video ending frame
|
||||
VidEndFrame,
|
||||
|
||||
/// Source image, eg. for inpainting.
|
||||
Imgsrc,
|
||||
|
||||
/// Image mask, eg. for inpainting.
|
||||
Imgmask,
|
||||
|
||||
/// Standard image reference without a semantic type (e.g. Sora/ChatGPT 4o/gpt-image-1)
|
||||
Imgref,
|
||||
|
||||
@@ -38,6 +44,8 @@ impl PromptContextSemanticType {
|
||||
match self {
|
||||
Self::VidStartFrame => "vid_start_frame",
|
||||
Self::VidEndFrame => "vid_end_frame",
|
||||
Self::Imgsrc => "imgsrc",
|
||||
Self::Imgmask => "imgmask",
|
||||
Self::Imgref => "imgref",
|
||||
Self::ImgrefCharacter => "imgref_character",
|
||||
Self::ImgrefStyle => "imgref_style",
|
||||
@@ -49,6 +57,8 @@ impl PromptContextSemanticType {
|
||||
match value {
|
||||
"vid_start_frame" => Ok(Self::VidStartFrame),
|
||||
"vid_end_frame" => Ok(Self::VidEndFrame),
|
||||
"imgsrc" => Ok(Self::Imgsrc),
|
||||
"imgmask" => Ok(Self::Imgmask),
|
||||
"imgref" => Ok(Self::Imgref),
|
||||
"imgref_character" => Ok(Self::ImgrefCharacter),
|
||||
"imgref_style" => Ok(Self::ImgrefStyle),
|
||||
@@ -63,6 +73,8 @@ impl PromptContextSemanticType {
|
||||
BTreeSet::from([
|
||||
Self::VidStartFrame,
|
||||
Self::VidEndFrame,
|
||||
Self::Imgsrc,
|
||||
Self::Imgmask,
|
||||
Self::Imgref,
|
||||
Self::ImgrefCharacter,
|
||||
Self::ImgrefStyle,
|
||||
@@ -83,6 +95,8 @@ mod tests {
|
||||
fn test_serialization() {
|
||||
assert_serialization(PromptContextSemanticType::VidStartFrame, "vid_start_frame");
|
||||
assert_serialization(PromptContextSemanticType::VidEndFrame, "vid_end_frame");
|
||||
assert_serialization(PromptContextSemanticType::Imgsrc, "imgsrc");
|
||||
assert_serialization(PromptContextSemanticType::Imgmask, "imgmask");
|
||||
assert_serialization(PromptContextSemanticType::Imgref, "imgref");
|
||||
assert_serialization(PromptContextSemanticType::ImgrefCharacter, "imgref_character");
|
||||
assert_serialization(PromptContextSemanticType::ImgrefStyle, "imgref_style");
|
||||
@@ -97,6 +111,8 @@ mod tests {
|
||||
fn to_str() {
|
||||
assert_eq!(PromptContextSemanticType::VidStartFrame.to_str(), "vid_start_frame");
|
||||
assert_eq!(PromptContextSemanticType::VidEndFrame.to_str(), "vid_end_frame");
|
||||
assert_eq!(PromptContextSemanticType::Imgsrc.to_str(), "imgsrc");
|
||||
assert_eq!(PromptContextSemanticType::Imgmask.to_str(), "imgmask");
|
||||
assert_eq!(PromptContextSemanticType::Imgref.to_str(), "imgref");
|
||||
assert_eq!(PromptContextSemanticType::ImgrefCharacter.to_str(), "imgref_character");
|
||||
assert_eq!(PromptContextSemanticType::ImgrefStyle.to_str(), "imgref_style");
|
||||
@@ -107,6 +123,8 @@ mod tests {
|
||||
fn from_str() {
|
||||
assert_eq!(PromptContextSemanticType::from_str("vid_start_frame").unwrap(), PromptContextSemanticType::VidStartFrame);
|
||||
assert_eq!(PromptContextSemanticType::from_str("vid_end_frame").unwrap(), PromptContextSemanticType::VidEndFrame);
|
||||
assert_eq!(PromptContextSemanticType::from_str("imgsrc").unwrap(), PromptContextSemanticType::Imgsrc);
|
||||
assert_eq!(PromptContextSemanticType::from_str("imgmask").unwrap(), PromptContextSemanticType::Imgmask);
|
||||
assert_eq!(PromptContextSemanticType::from_str("imgref").unwrap(), PromptContextSemanticType::Imgref);
|
||||
assert_eq!(PromptContextSemanticType::from_str("imgref_character").unwrap(), PromptContextSemanticType::ImgrefCharacter);
|
||||
assert_eq!(PromptContextSemanticType::from_str("imgref_style").unwrap(), PromptContextSemanticType::ImgrefStyle);
|
||||
@@ -121,9 +139,11 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = PromptContextSemanticType::all_variants();
|
||||
assert_eq!(variants.len(), 6);
|
||||
assert_eq!(variants.len(), 8);
|
||||
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::VidStartFrame));
|
||||
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::VidEndFrame));
|
||||
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::Imgsrc));
|
||||
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::Imgmask));
|
||||
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::Imgref));
|
||||
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::ImgrefCharacter));
|
||||
assert_eq!(variants.pop_first(), Some(PromptContextSemanticType::ImgrefStyle));
|
||||
|
||||
@@ -17,6 +17,8 @@ pub enum ModelType {
|
||||
Flux1Dev,
|
||||
#[serde(rename = "flux_1_schnell")]
|
||||
Flux1Schnell,
|
||||
#[serde(rename = "flux_pro_1")]
|
||||
FluxPro1,
|
||||
#[serde(rename = "flux_pro_1p1")]
|
||||
FluxPro11,
|
||||
#[serde(rename = "flux_pro_1p1_ultra")]
|
||||
@@ -26,6 +28,10 @@ pub enum ModelType {
|
||||
#[serde(rename = "recraft_3")]
|
||||
Recraft3,
|
||||
|
||||
//// Image Infill models
|
||||
//#[serde(rename = "flux_pro_1_infill")]
|
||||
//FluxPro1Infill,
|
||||
|
||||
// Video models
|
||||
#[serde(rename = "kling_1p6_pro")]
|
||||
Kling16Pro,
|
||||
@@ -57,6 +63,7 @@ impl ModelType {
|
||||
// Image models
|
||||
Self::Flux1Dev => "flux_1_dev",
|
||||
Self::Flux1Schnell => "flux_1_schnell",
|
||||
Self::FluxPro1 => "flux_pro_1",
|
||||
Self::FluxPro11 => "flux_pro_1p1",
|
||||
Self::FluxPro11Ultra => "flux_pro_1p1_ultra",
|
||||
Self::GptImage1 => "gpt_image_1",
|
||||
@@ -80,6 +87,7 @@ impl ModelType {
|
||||
// Image models
|
||||
"flux_1_dev" => Ok(Self::Flux1Dev),
|
||||
"flux_1_schnell" => Ok(Self::Flux1Schnell),
|
||||
"flux_pro_1" => Ok(Self::FluxPro1),
|
||||
"flux_pro_1p1" => Ok(Self::FluxPro11),
|
||||
"flux_pro_1p1_ultra" => Ok(Self::FluxPro11Ultra),
|
||||
"gpt_image_1" => Ok(Self::GptImage1),
|
||||
@@ -107,6 +115,7 @@ impl ModelType {
|
||||
// Image models
|
||||
Self::Flux1Dev,
|
||||
Self::Flux1Schnell,
|
||||
Self::FluxPro1,
|
||||
Self::FluxPro11,
|
||||
Self::FluxPro11Ultra,
|
||||
Self::GptImage1,
|
||||
@@ -139,6 +148,7 @@ mod tests {
|
||||
// Image models
|
||||
assert_serialization(ModelType::Flux1Dev, "flux_1_dev");
|
||||
assert_serialization(ModelType::Flux1Schnell, "flux_1_schnell");
|
||||
assert_serialization(ModelType::FluxPro1, "flux_pro_1");
|
||||
assert_serialization(ModelType::FluxPro11, "flux_pro_1p1");
|
||||
assert_serialization(ModelType::FluxPro11Ultra, "flux_pro_1p1_ultra");
|
||||
assert_serialization(ModelType::GptImage1, "gpt_image_1");
|
||||
@@ -159,6 +169,7 @@ mod tests {
|
||||
// Image models
|
||||
assert_eq!(ModelType::Flux1Dev.to_str(), "flux_1_dev");
|
||||
assert_eq!(ModelType::Flux1Schnell.to_str(), "flux_1_schnell");
|
||||
assert_eq!(ModelType::FluxPro1.to_str(), "flux_pro_1");
|
||||
assert_eq!(ModelType::FluxPro11.to_str(), "flux_pro_1p1");
|
||||
assert_eq!(ModelType::FluxPro11Ultra.to_str(), "flux_pro_1p1_ultra");
|
||||
assert_eq!(ModelType::GptImage1.to_str(), "gpt_image_1");
|
||||
@@ -181,6 +192,7 @@ mod tests {
|
||||
// Image models
|
||||
assert_eq!(ModelType::from_str("flux_1_dev").unwrap(), ModelType::Flux1Dev);
|
||||
assert_eq!(ModelType::from_str("flux_1_schnell").unwrap(), ModelType::Flux1Schnell);
|
||||
assert_eq!(ModelType::from_str("flux_pro_1").unwrap(), ModelType::FluxPro1);
|
||||
assert_eq!(ModelType::from_str("flux_pro_1p1").unwrap(), ModelType::FluxPro11);
|
||||
assert_eq!(ModelType::from_str("flux_pro_1p1_ultra").unwrap(), ModelType::FluxPro11Ultra);
|
||||
assert_eq!(ModelType::from_str("gpt_image_1").unwrap(), ModelType::GptImage1);
|
||||
@@ -199,10 +211,11 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = ModelType::all_variants();
|
||||
assert_eq!(variants.len(), 13);
|
||||
assert_eq!(variants.len(), 14);
|
||||
// Image models
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Dev));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Schnell));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::FluxPro1));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::FluxPro11));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::FluxPro11Ultra));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::GptImage1));
|
||||
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::state::server_state::ServerState;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest, FluxPro1InpaintImageResponse};
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
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::creds::open_ai_api_key::OpenAiApiKey;
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::enqueue_gpt_image_1_edit_image_webhook;
|
||||
use fal_client::requests::webhook::image::infill::enqueue_flux_pro_1_infill_webhook::{enqueue_flux_pro_1_infill_webhook, FluxPro1InfillArgs, FluxPro1InfillNumImages};
|
||||
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::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::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
|
||||
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 utoipa::ToSchema;
|
||||
|
||||
/// Flux Pro-1 Inpainting
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Generate Images (Inpaint)",
|
||||
path = "/v1/generate/image/inpaint/flux_pro_1",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = FluxPro1InpaintImageResponse),
|
||||
),
|
||||
params(
|
||||
("request" = FluxPro1InpaintImageRequest, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn flux_pro_1_inpaint_image_handler(
|
||||
http_request: HttpRequest,
|
||||
request: Json<FluxPro1InpaintImageRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<FluxPro1InpaintImageResponse>, CommonWebError> {
|
||||
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);
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
const CAN_SEE_DELETED: bool = false;
|
||||
|
||||
let media_tokens = vec![
|
||||
request.image_media_token.clone(),
|
||||
request.mask_media_token.clone(),
|
||||
];
|
||||
|
||||
let result = batch_get_media_files_by_tokens_with_connection(
|
||||
&mut mysql_connection,
|
||||
&media_tokens,
|
||||
CAN_SEE_DELETED,
|
||||
).await;
|
||||
|
||||
let media_files = match result {
|
||||
Ok(files) => files,
|
||||
Err(err) => {
|
||||
error!("Error getting media files by tokens: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
if media_files.len() != media_tokens.len() {
|
||||
warn!("Wrong number of media files returned for tokens: {} found for {} tokens", media_files.len(), media_tokens.len());
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
format!("Not all media files could be found. Media files found: {}, tokens provided: {}",
|
||||
media_files.len(), media_tokens.len())));
|
||||
}
|
||||
|
||||
let media_domain = get_media_domain(&http_request);
|
||||
|
||||
let image_media_file = media_files.iter()
|
||||
.find(|file| file.token == request.image_media_token)
|
||||
.ok_or_else(|| {
|
||||
warn!("Image media file not found for token: {}", request.image_media_token);
|
||||
CommonWebError::BadInputWithSimpleMessage("Image media file not found".to_string())
|
||||
})?;
|
||||
|
||||
let mask_media_file = media_files.iter()
|
||||
.find(|file| file.token == request.mask_media_token)
|
||||
.ok_or_else(|| {
|
||||
warn!("Mask media file not found for token: {}", request.mask_media_token);
|
||||
CommonWebError::BadInputWithSimpleMessage("Mask media file not found".to_string())
|
||||
})?;
|
||||
|
||||
let image_url = {
|
||||
let public_bucket_path = MediaFileBucketPath::from_object_hash(
|
||||
&image_media_file.public_bucket_directory_hash,
|
||||
image_media_file.maybe_public_bucket_prefix.as_deref(),
|
||||
image_media_file.maybe_public_bucket_extension.as_deref());
|
||||
|
||||
let media_links = MediaLinksBuilder::from_media_path_and_env(
|
||||
media_domain,
|
||||
server_state.server_environment,
|
||||
&public_bucket_path);
|
||||
|
||||
media_links.cdn_url.to_string()
|
||||
};
|
||||
|
||||
let mask_image_url = {
|
||||
let public_bucket_path = MediaFileBucketPath::from_object_hash(
|
||||
&mask_media_file.public_bucket_directory_hash,
|
||||
mask_media_file.maybe_public_bucket_prefix.as_deref(),
|
||||
mask_media_file.maybe_public_bucket_extension.as_deref());
|
||||
|
||||
let media_links = MediaLinksBuilder::from_media_path_and_env(
|
||||
media_domain,
|
||||
server_state.server_environment,
|
||||
&public_bucket_path);
|
||||
|
||||
media_links.cdn_url.to_string()
|
||||
};
|
||||
|
||||
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 num_images = match request.num_images {
|
||||
Some(FluxPro1InpaintImageNumImages::One) => FluxPro1InfillNumImages::One,
|
||||
Some(FluxPro1InpaintImageNumImages::Two) => FluxPro1InfillNumImages::Two,
|
||||
Some(FluxPro1InpaintImageNumImages::Three) => FluxPro1InfillNumImages::Three,
|
||||
Some(FluxPro1InpaintImageNumImages::Four) => FluxPro1InfillNumImages::Four,
|
||||
None => FluxPro1InfillNumImages::One, // Default to One
|
||||
};
|
||||
|
||||
let args = FluxPro1InfillArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
image_url: image_url,
|
||||
mask_url: mask_image_url,
|
||||
num_images,
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
};
|
||||
|
||||
let fal_result = enqueue_flux_pro_1_infill_webhook(args)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error calling enqueue_flux_pro_1_infill_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
|
||||
})?;
|
||||
|
||||
// NB: Don't fail the job if the query fails.
|
||||
let prompt_result = insert_prompt(InsertPromptArgs {
|
||||
maybe_apriori_prompt_token: None,
|
||||
prompt_type: PromptType::ArtcraftApp,
|
||||
maybe_creator_user_token: maybe_user_session
|
||||
.as_ref()
|
||||
.map(|s| &s.user_token),
|
||||
// TODO(bt,2025-07-31): Should we have an "inpaint" specific variant?
|
||||
// Depends on how we want to model provider / feature matrices, routing, and results going forward.
|
||||
maybe_model_type: Some(ModelType::FluxPro1),
|
||||
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 // Don't fail the job if the prompt insertion fails.
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(token) = prompt_token.as_ref() {
|
||||
let result = insert_batch_prompt_context_items(InsertBatchArgs {
|
||||
prompt_token: token.clone(),
|
||||
items: vec![
|
||||
PromptContextItem {
|
||||
media_token: image_media_file.token.clone(),
|
||||
context_semantic_type: PromptContextSemanticType::Imgsrc,
|
||||
},
|
||||
PromptContextItem {
|
||||
media_token: mask_media_file.token.clone(),
|
||||
context_semantic_type: PromptContextSemanticType::Imgmask,
|
||||
},
|
||||
],
|
||||
transaction: &mut transaction,
|
||||
}).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
// NB: Fail open.
|
||||
warn!("Error inserting batch prompt context items: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
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_prompt_token: prompt_token.as_ref(),
|
||||
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_executor: &mut *transaction,
|
||||
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(FluxPro1InpaintImageResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
}))
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
pub mod flux_pro_1_inpaint_handler;
|
||||
@@ -4,4 +4,5 @@ pub mod generate_flux_1_schnell_text_to_image_handler;
|
||||
pub mod generate_flux_pro_11_text_to_image_handler;
|
||||
pub mod generate_flux_pro_11_ultra_text_to_image_handler;
|
||||
pub mod generate_gpt_image_1_text_to_image_handler;
|
||||
pub mod inpaint;
|
||||
pub mod remove_image_background_handler;
|
||||
@@ -4,6 +4,7 @@ use crate::http_server::endpoints::generate::image::generate_flux_1_schnell_text
|
||||
use crate::http_server::endpoints::generate::image::generate_flux_pro_11_text_to_image_handler::generate_flux_pro_11_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::generate_gpt_image_1_text_to_image_handler::generate_gpt_image_1_text_to_image_handler;
|
||||
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::remove_image_background_handler::remove_image_background_handler;
|
||||
use crate::http_server::endpoints::generate::object::generate_hunyuan_2_0_image_to_3d_handler::generate_hunyuan_2_0_image_to_3d_handler;
|
||||
use crate::http_server::endpoints::generate::object::generate_hunyuan_2_1_image_to_3d_handler::generate_hunyuan_2_1_image_to_3d_handler;
|
||||
@@ -36,6 +37,12 @@ where
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
.service(web::scope("/inpaint")
|
||||
.service(web::resource("/flux_pro_1")
|
||||
.route(web::post().to(flux_pro_1_inpaint_image_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
.service(web::resource("/flux_1_dev_text_to_image")
|
||||
.route(web::post().to(generate_flux_1_dev_text_to_image_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
|
||||
Reference in new issue
Block a user