diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs index 0e10e5765d..59cf30085a 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs @@ -1,40 +1,65 @@ use log::info; -use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_legacy::handle_artcraft_via_legacy; use crate::core::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_omni_endpoint::handle_artcraft_via_omni_endpoint; use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; +use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; +use crate::core::commands::generate::generate_image::utils::parse_semantic_media_files::parse_semantic_media_files; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; + /// Dispatch an image generation request to Artcraft. -/// -/// Tries the omni endpoint first. If the model isn't supported there, -/// falls back to the legacy artcraft_router path. pub async fn handle_artcraft( request: &TauriGenerateImageRequest, app_env_configs: &AppEnvConfigs, storyteller_creds_manager: &StorytellerCredentialManager, ) -> Result { - // Try the omni endpoint first. - let omni_result = handle_artcraft_via_omni_endpoint( - request, - app_env_configs, - storyteller_creds_manager, - ).await; + // Resolve credentials. + let creds = match storyteller_creds_manager.get_credentials()? { + Some(creds) => creds, + None => return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials)), + }; - match omni_result { - Ok(success) => Ok(success), - Err(GenerateError::NotYetImplemented(_)) => { - // Model not supported by omni endpoint — fall back to legacy. - info!("Model not supported by omni endpoint, falling back to legacy artcraft_router path."); - handle_artcraft_via_legacy( - request, - app_env_configs, - storyteller_creds_manager, - ).await - } - Err(err) => Err(err), + // Resolve semantic media files (upload raw bytes if provided). + let semantic_media_files = parse_semantic_media_files( + request, + &creds, + &app_env_configs.storyteller_host, + ).await?; + + let use_legacy = request.model + .is_some_and(|m| is_legacy_only_model(m)); + + if use_legacy { + info!("Model {:?} is legacy-only, routing to artcraft_router path.", request.model); + handle_artcraft_via_legacy( + request, + &semantic_media_files, + &creds, + app_env_configs, + ).await + } else { + handle_artcraft_via_omni_endpoint( + request, + &semantic_media_files, + &creds, + app_env_configs, + ).await } } + +fn is_legacy_only_model(model: TauriImageModel) -> bool { + matches!( + model, + TauriImageModel::GrokImage + | TauriImageModel::Recraft3 + | TauriImageModel::Midjourney + | TauriImageModel::FluxProKontextMax + | TauriImageModel::QwenEdit2511Angles + | TauriImageModel::Flux2LoraAngles + | TauriImageModel::FluxDevJuggernaut + ) +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs index d45c8f2bba..e4ea97ba75 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs @@ -1,95 +1,49 @@ -use artcraft_router::api::common_image_model::CommonImageModel; -use artcraft_router::api::image_list_ref::ImageListRef; -use artcraft_router::api::provider::Provider; -use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; -use artcraft_router::client::router_artcraft_client::RouterArtcraftClient; -use artcraft_router::client::router_client::RouterClient; -use artcraft_router::generate::generate_image::generate_image_request::GenerateImageRequest; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use log::{error, info}; +use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; -use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; +use crate::core::commands::enqueue::generate_error::GenerateError; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::generate::generate_image::providers::artcraft::model_mapping::{ - map_to_generation_model, map_to_router_image_model, -}; +use crate::core::commands::generate::generate_image::providers::artcraft::legacy::artcraft_flux_2_lora_angles::handle_flux_2_lora_angles; +use crate::core::commands::generate::generate_image::providers::artcraft::legacy::artcraft_flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint; +use crate::core::commands::generate::generate_image::providers::artcraft::legacy::artcraft_flux_pro_kontext_edit::handle_flux_pro_kontext_edit; +use crate::core::commands::generate::generate_image::providers::artcraft::legacy::artcraft_qwen_edit_2511_angles::handle_qwen_edit_2511_angles; use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; +use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; +use crate::core::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -/// Handle image generation via the legacy artcraft_router path. +/// Handle image generation via legacy model-specific paths. /// -/// This is used for models not yet supported by the omni endpoint -/// (eg. Recraft, FluxProKontextMax, edit/inpaint models). +/// Dispatches to dedicated handlers for each supported legacy model. pub async fn handle_artcraft_via_legacy( request: &TauriGenerateImageRequest, + semantic_media_files: &SemanticMediaFiles, + creds: &StorytellerCredentialSet, app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, ) -> Result { let model = request.model.ok_or(GenerateError::no_model_specified())?; - let router_model = map_to_router_image_model(model) - .ok_or(GenerateError::NotYetImplemented( - format!("Model {:?} is not supported via the legacy router path", model), - ))?; - - let generation_model = map_to_generation_model(model); - - let creds = match storyteller_creds_manager.get_credentials()? { - Some(creds) => creds, - None => return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials)), - }; - - let client = RouterClient::Artcraft(RouterArtcraftClient::new( - app_env_configs.storyteller_host.clone(), - creds, - )); - - // Build image inputs: prepend canvas image if present. - let mut image_tokens = request.image_media_tokens.clone().unwrap_or_default(); - if let Some(canvas_token) = &request.canvas_image_media_token { - image_tokens.insert(0, canvas_token.clone()); + match model { + TauriImageModel::FluxDevJuggernaut => { + handle_flux_dev_juggernaut_inpaint(request, semantic_media_files, creds, app_env_configs).await + } + TauriImageModel::FluxProKontextMax => { + handle_flux_pro_kontext_edit(request, semantic_media_files, creds, app_env_configs).await + } + TauriImageModel::QwenEdit2511Angles => { + handle_qwen_edit_2511_angles(request, semantic_media_files, creds, app_env_configs).await + } + TauriImageModel::Flux2LoraAngles => { + handle_flux_2_lora_angles(request, semantic_media_files, creds, app_env_configs).await + } + TauriImageModel::Midjourney | TauriImageModel::Recraft3 => { + Err(GenerateError::NotYetImplemented( + format!("Model {:?} is not supported by provider Artcraft", model), + )) + } + other => { + Err(GenerateError::NotYetImplemented( + format!("Model {:?} was incorrectly handled", other), + )) + } } - let image_inputs = if image_tokens.is_empty() { None } else { Some(ImageListRef::MediaFileTokens(image_tokens)) }; - - let router_request = GenerateImageRequest { - model: router_model, - provider: Provider::Artcraft, - prompt: request.prompt.clone(), - image_inputs, - resolution: None, // TODO: Convert from request.resolution - aspect_ratio: None, // TODO: Convert from request.aspect_ratio - quality: None, // TODO: Convert from request.quality - image_batch_count: request.batch_size.map(|n| n as u16), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, - generation_mode_mismatch_strategy: None, - idempotency_token: None, - horizontal_angle: request.adjust_horizontal_angle, - vertical_angle: request.adjust_vertical_angle, - zoom: request.adjust_zoom, - }; - - let plan = router_request.build()?; - - info!("Legacy image generation plan: {:?}", plan); - - let response = plan.generate_image(&client).await.map_err(|err| { - error!("Legacy image generation failed: {:?}", err); - GenerateError::from(err) - })?; - - let job_id = response - .get_artcraft_payload() - .map(|p| p.inference_job_token.to_string()) - .ok_or(GenerateError::ResponseHadNoJobTokens)?; - - info!("Legacy image generation succeeded: job_id={}", job_id); - - Ok(TaskEnqueueSuccess { - task_type: TaskType::ImageGeneration, - model: Some(generation_model), - provider: GenerationProvider::Artcraft, - provider_job_id: Some(job_id), - }) } diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_2_lora_angles.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_2_lora_angles.rs new file mode 100644 index 0000000000..516dfa0b4b --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_2_lora_angles.rs @@ -0,0 +1,93 @@ +use log::{error, info}; + +use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; +use artcraft_router::api::common_image_model::CommonImageModel; +use artcraft_router::api::image_list_ref::ImageListRef; +use artcraft_router::api::provider::Provider; +use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use artcraft_router::client::router_artcraft_client::RouterArtcraftClient; +use artcraft_router::client::router_client::RouterClient; +use artcraft_router::generate::generate_image::generate_image_request::GenerateImageRequest; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::tasks::task_type::TaskType; + +use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; +use crate::core::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles; +use crate::core::events::generation_events::common::GenerationModel; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; + +/// Handle Flux2LoraAngles image editing via the artcraft_router. +pub async fn handle_flux_2_lora_angles( + request: &TauriGenerateImageRequest, + semantic_media_files: &SemanticMediaFiles, + creds: &StorytellerCredentialSet, + app_env_configs: &AppEnvConfigs, +) -> Result { + let client = RouterClient::Artcraft(RouterArtcraftClient::new( + app_env_configs.storyteller_host.clone(), + creds.clone(), + )); + + let image_inputs = build_image_inputs(request, semantic_media_files); + + let router_request = GenerateImageRequest { + model: CommonImageModel::Flux2LoraAngles, + provider: Provider::Artcraft, + prompt: request.prompt.clone(), + image_inputs, + resolution: None, + aspect_ratio: None, + quality: None, + image_batch_count: request.batch_size.map(|n| n as u16), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, + generation_mode_mismatch_strategy: None, + idempotency_token: None, + horizontal_angle: request.adjust_horizontal_angle, + vertical_angle: request.adjust_vertical_angle, + zoom: request.adjust_zoom, + }; + + let plan = router_request.build()?; + + info!("Flux2LoraAngles plan: {:?}", plan); + + let response = plan.generate_image(&client).await.map_err(|err| { + error!("Flux2LoraAngles generation failed: {:?}", err); + GenerateError::from(err) + })?; + + let job_id = response + .get_artcraft_payload() + .map(|p| p.inference_job_token.to_string()) + .ok_or(GenerateError::ResponseHadNoJobTokens)?; + + info!("Flux2LoraAngles succeeded: job_id={}", job_id); + + Ok(TaskEnqueueSuccess { + task_type: TaskType::ImageGeneration, + model: Some(GenerationModel::Flux2LoraAngles), + provider: GenerationProvider::Artcraft, + provider_job_id: Some(job_id), + }) +} + +fn build_image_inputs( + request: &TauriGenerateImageRequest, + semantic_media_files: &SemanticMediaFiles, +) -> Option { + let mut tokens = Vec::new(); + + if let Some(scene_token) = &semantic_media_files.scene_image_media_token { + tokens.push(scene_token.clone()); + } + if let Some(canvas_token) = &semantic_media_files.canvas_image_media_token { + tokens.push(canvas_token.clone()); + } + if let Some(media_tokens) = &request.image_media_tokens { + tokens.extend(media_tokens.clone()); + } + + if tokens.is_empty() { None } else { Some(ImageListRef::MediaFileTokens(tokens)) } +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_dev_juggernaut_inpaint.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_dev_juggernaut_inpaint.rs new file mode 100644 index 0000000000..b6e2f90d12 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_dev_juggernaut_inpaint.rs @@ -0,0 +1,98 @@ +use log::{error, info}; + +use artcraft_api_defs::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::{ + FluxDevJuggernautInpaintImageNumImages, FluxDevJuggernautInpaintImageRequest, +}; +use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; +use artcraft_client::endpoints::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::flux_dev_juggernaut_inpaint_image; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::tasks::task_type::TaskType; +use uuid_utils::uuid::generate_random_uuid; + +use tokens::tokens::media_files::MediaFileToken; + +use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError}; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; +use crate::core::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles; +use crate::core::events::generation_events::common::GenerationModel; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; + +/// Handle FluxDevJuggernaut inpainting via the legacy dedicated endpoint. +/// +/// Requires a source image (from scene, canvas, or image_media_tokens) and an inpainting mask. +pub async fn handle_flux_dev_juggernaut_inpaint( + request: &TauriGenerateImageRequest, + semantic_media_files: &SemanticMediaFiles, + creds: &StorytellerCredentialSet, + app_env_configs: &AppEnvConfigs, +) -> Result { + let image_media_token = get_first_image_token(request, semantic_media_files) + .ok_or(GenerateError::required_source_image_not_provided())?; + + let mask_media_token = semantic_media_files.inpainting_mask_image_media_token.clone() + .ok_or(GenerateError::required_source_image_mask_not_provided())?; + + let num_images = match request.batch_size { + None => None, + Some(1) => Some(FluxDevJuggernautInpaintImageNumImages::One), + Some(2) => Some(FluxDevJuggernautInpaintImageNumImages::Two), + Some(3) => Some(FluxDevJuggernautInpaintImageNumImages::Three), + Some(4) => Some(FluxDevJuggernautInpaintImageNumImages::Four), + Some(other) => { + return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { + min: 1, + max: 4, + requested: other, + })); + } + }; + + let uuid_idempotency_token = generate_random_uuid(); + + let inpaint_request = FluxDevJuggernautInpaintImageRequest { + uuid_idempotency_token, + prompt: request.prompt.clone(), + image_media_token, + mask_media_token, + num_images, + }; + + info!("Calling FluxDevJuggernaut inpaint..."); + + let response = flux_dev_juggernaut_inpaint_image( + &app_env_configs.storyteller_host, + Some(creds), + inpaint_request, + ).await.map_err(|err| { + error!("FluxDevJuggernaut inpaint failed: {:?}", err); + GenerateError::from(err) + })?; + + info!("FluxDevJuggernaut inpaint succeeded: job_token={}", response.inference_job_token); + + Ok(TaskEnqueueSuccess { + task_type: TaskType::ImageInpaintEdit, + model: Some(GenerationModel::FluxDevJuggernaut), + provider: GenerationProvider::Artcraft, + provider_job_id: Some(response.inference_job_token.to_string()), + }) +} + +fn get_first_image_token( + request: &TauriGenerateImageRequest, + semantic_media_files: &SemanticMediaFiles, +) -> Option { + if let Some(scene_token) = &semantic_media_files.scene_image_media_token { + return Some(scene_token.clone()); + } + if let Some(canvas_token) = &semantic_media_files.canvas_image_media_token { + return Some(canvas_token.clone()); + } + if let Some(media_tokens) = &request.image_media_tokens { + if let Some(token) = media_tokens.first() { + return Some(token.clone()); + } + } + None +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_pro_kontext_edit.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_pro_kontext_edit.rs new file mode 100644 index 0000000000..12236bcc67 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_pro_kontext_edit.rs @@ -0,0 +1,96 @@ +use log::{error, info}; + +use artcraft_api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::{ + FluxProKontextMaxEditImageNumImages, FluxProKontextMaxEditImageRequest, +}; +use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; +use artcraft_client::endpoints::generate::image::edit::flux_pro_kontext_max_edit_image::flux_pro_kontext_max_edit_image; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::tasks::task_type::TaskType; +use tokens::tokens::media_files::MediaFileToken; +use uuid_utils::uuid::generate_random_uuid; + +use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError}; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; +use crate::core::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles; +use crate::core::events::generation_events::common::GenerationModel; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; + +/// Handle FluxProKontextMax image editing via the legacy dedicated endpoint. +/// +/// Consolidates both the "edit" and "inpaint" legacy paths — both call the same +/// Kontext Max edit endpoint with a single source image. +pub async fn handle_flux_pro_kontext_edit( + request: &TauriGenerateImageRequest, + semantic_media_files: &SemanticMediaFiles, + creds: &StorytellerCredentialSet, + app_env_configs: &AppEnvConfigs, +) -> Result { + let image_media_token = get_first_image_token(request, semantic_media_files) + .ok_or(GenerateError::BadInput(BadInputReason::WrongImageArguments( + "No input image specified for Flux Kontext Max edit".to_string(), + )))?; + + let num_images = match request.batch_size { + None => None, + Some(1) => Some(FluxProKontextMaxEditImageNumImages::One), + Some(2) => Some(FluxProKontextMaxEditImageNumImages::Two), + Some(3) => Some(FluxProKontextMaxEditImageNumImages::Three), + Some(4) => Some(FluxProKontextMaxEditImageNumImages::Four), + Some(other) => { + return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { + min: 1, + max: 4, + requested: other, + })); + } + }; + + let uuid_idempotency_token = generate_random_uuid(); + + let kontext_request = FluxProKontextMaxEditImageRequest { + uuid_idempotency_token, + prompt: request.prompt.clone(), + image_media_token, + num_images, + }; + + info!("Calling FluxProKontextMax edit..."); + + let response = flux_pro_kontext_max_edit_image( + &app_env_configs.storyteller_host, + Some(creds), + kontext_request, + ).await.map_err(|err| { + error!("FluxProKontextMax edit failed: {:?}", err); + GenerateError::from(err) + })?; + + info!("FluxProKontextMax edit succeeded: job_token={}", response.inference_job_token); + + Ok(TaskEnqueueSuccess { + task_type: TaskType::ImageGeneration, + model: Some(GenerationModel::FluxProKontextMax), + provider: GenerationProvider::Artcraft, + provider_job_id: Some(response.inference_job_token.to_string()), + }) +} + +fn get_first_image_token( + request: &TauriGenerateImageRequest, + semantic_media_files: &SemanticMediaFiles, +) -> Option { + if let Some(scene_token) = &semantic_media_files.scene_image_media_token { + return Some(scene_token.clone()); + } + if let Some(canvas_token) = &semantic_media_files.canvas_image_media_token { + return Some(canvas_token.clone()); + } + if let Some(media_tokens) = &request.image_media_tokens { + if let Some(token) = media_tokens.first() { + return Some(token.clone()); + } + } + None +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_qwen_edit_2511_angles.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_qwen_edit_2511_angles.rs new file mode 100644 index 0000000000..3958173711 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/artcraft_qwen_edit_2511_angles.rs @@ -0,0 +1,93 @@ +use log::{error, info}; + +use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; +use artcraft_router::api::common_image_model::CommonImageModel; +use artcraft_router::api::image_list_ref::ImageListRef; +use artcraft_router::api::provider::Provider; +use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use artcraft_router::client::router_artcraft_client::RouterArtcraftClient; +use artcraft_router::client::router_client::RouterClient; +use artcraft_router::generate::generate_image::generate_image_request::GenerateImageRequest; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::tasks::task_type::TaskType; + +use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; +use crate::core::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles; +use crate::core::events::generation_events::common::GenerationModel; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; + +/// Handle QwenEdit2511Angles image editing via the artcraft_router. +pub async fn handle_qwen_edit_2511_angles( + request: &TauriGenerateImageRequest, + semantic_media_files: &SemanticMediaFiles, + creds: &StorytellerCredentialSet, + app_env_configs: &AppEnvConfigs, +) -> Result { + let client = RouterClient::Artcraft(RouterArtcraftClient::new( + app_env_configs.storyteller_host.clone(), + creds.clone(), + )); + + let image_inputs = build_image_inputs(request, semantic_media_files); + + let router_request = GenerateImageRequest { + model: CommonImageModel::QwenEdit2511Angles, + provider: Provider::Artcraft, + prompt: request.prompt.clone(), + image_inputs, + resolution: None, + aspect_ratio: None, + quality: None, + image_batch_count: request.batch_size.map(|n| n as u16), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, + generation_mode_mismatch_strategy: None, + idempotency_token: None, + horizontal_angle: request.adjust_horizontal_angle, + vertical_angle: request.adjust_vertical_angle, + zoom: request.adjust_zoom, + }; + + let plan = router_request.build()?; + + info!("QwenEdit2511Angles plan: {:?}", plan); + + let response = plan.generate_image(&client).await.map_err(|err| { + error!("QwenEdit2511Angles generation failed: {:?}", err); + GenerateError::from(err) + })?; + + let job_id = response + .get_artcraft_payload() + .map(|p| p.inference_job_token.to_string()) + .ok_or(GenerateError::ResponseHadNoJobTokens)?; + + info!("QwenEdit2511Angles succeeded: job_id={}", job_id); + + Ok(TaskEnqueueSuccess { + task_type: TaskType::ImageGeneration, + model: Some(GenerationModel::QwenEdit2511Angles), + provider: GenerationProvider::Artcraft, + provider_job_id: Some(job_id), + }) +} + +fn build_image_inputs( + request: &TauriGenerateImageRequest, + semantic_media_files: &SemanticMediaFiles, +) -> Option { + let mut tokens = Vec::new(); + + if let Some(scene_token) = &semantic_media_files.scene_image_media_token { + tokens.push(scene_token.clone()); + } + if let Some(canvas_token) = &semantic_media_files.canvas_image_media_token { + tokens.push(canvas_token.clone()); + } + if let Some(media_tokens) = &request.image_media_tokens { + tokens.extend(media_tokens.clone()); + } + + if tokens.is_empty() { None } else { Some(ImageListRef::MediaFileTokens(tokens)) } +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/mod.rs new file mode 100644 index 0000000000..83dc104d10 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/legacy/mod.rs @@ -0,0 +1,4 @@ +pub mod artcraft_flux_2_lora_angles; +pub mod artcraft_flux_dev_juggernaut_inpaint; +pub mod artcraft_flux_pro_kontext_edit; +pub mod artcraft_qwen_edit_2511_angles; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs index 1e26f8e5da..fe5df8c3cf 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs @@ -1,9 +1,14 @@ +<<<<<<< HEAD use artcraft_router::api::common_aspect_ratio::CommonAspectRatio as RouterCommonAspectRatio; use artcraft_router::api::common_image_model::CommonImageModel as RouterCommonImageModel; use artcraft_router::api::common_resolution::CommonResolution as RouterCommonResolution; use enums::common::generation::common_aspect_ratio::CommonAspectRatio as OmniCommonAspectRatio; use enums::common::generation::common_image_model::CommonImageModel as OmniCommonImageModel; use enums::common::generation::common_resolution::CommonResolution as OmniCommonResolution; +======= +use artcraft_router::api::common_image_model::CommonImageModel as RouterCommonImageModel; +use enums::common::generation::common_image_model::CommonImageModel as OmniCommonImageModel; +>>>>>>> 13896d9306 (cleanup) use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; use crate::core::events::generation_events::common::GenerationModel; @@ -14,7 +19,10 @@ pub fn map_to_omni_image_model(model: TauriImageModel) -> Option Some(OmniCommonImageModel::Flux1Dev), TauriImageModel::Flux1Schnell => Some(OmniCommonImageModel::Flux1Schnell), +<<<<<<< HEAD TauriImageModel::FluxPro1 => Some(OmniCommonImageModel::FluxPro11), // TODO: Might be a slight mismatch +======= +>>>>>>> 13896d9306 (cleanup) TauriImageModel::FluxPro11 => Some(OmniCommonImageModel::FluxPro11), TauriImageModel::FluxPro11Ultra => Some(OmniCommonImageModel::FluxPro11Ultra), TauriImageModel::GptImage1 => Some(OmniCommonImageModel::GptImage1), @@ -27,6 +35,7 @@ pub fn map_to_omni_image_model(model: TauriImageModel) -> Option Some(OmniCommonImageModel::Seedream4), TauriImageModel::Seedream4p5 => Some(OmniCommonImageModel::Seedream4p5), TauriImageModel::Seedream5Lite => Some(OmniCommonImageModel::Seedream5Lite), +<<<<<<< HEAD // Not accounted for yet TauriImageModel::GrokImage => None, TauriImageModel::Recraft3 => None, @@ -35,6 +44,9 @@ pub fn map_to_omni_image_model(model: TauriImageModel) -> Option None, TauriImageModel::Flux2LoraAngles => None, TauriImageModel::FluxDevJuggernaut => None, +======= + _ => None, +>>>>>>> 13896d9306 (cleanup) } } @@ -42,6 +54,7 @@ pub fn map_to_omni_image_model(model: TauriImageModel) -> Option Option { match model { +<<<<<<< HEAD TauriImageModel::Flux1Dev => Some(RouterCommonImageModel::Flux1Dev), // Text-to-Image TauriImageModel::Flux1Schnell => Some(RouterCommonImageModel::Flux1Schnell), // Text-to-Image TauriImageModel::FluxPro1 => Some(RouterCommonImageModel::FluxPro11), // TODO: Might be a slight mismatch @@ -103,6 +116,25 @@ pub fn map_to_omni_resolution(res: RouterCommonResolution) -> OmniCommonResoluti RouterCommonResolution::FourEightyP => OmniCommonResolution::FourEightyP, RouterCommonResolution::SevenTwentyP => OmniCommonResolution::SevenTwentyP, RouterCommonResolution::TenEightyP => OmniCommonResolution::TenEightyP, +======= + TauriImageModel::Flux1Dev => Some(RouterCommonImageModel::Flux1Dev), + TauriImageModel::Flux1Schnell => Some(RouterCommonImageModel::Flux1Schnell), + TauriImageModel::FluxPro11 => Some(RouterCommonImageModel::FluxPro11), + TauriImageModel::FluxPro11Ultra => Some(RouterCommonImageModel::FluxPro11Ultra), + TauriImageModel::GptImage1 => Some(RouterCommonImageModel::GptImage1), + TauriImageModel::GptImage1p5 => Some(RouterCommonImageModel::GptImage1p5), + TauriImageModel::GptImage2 => Some(RouterCommonImageModel::GptImage2), + TauriImageModel::NanoBanana => Some(RouterCommonImageModel::NanoBanana), + TauriImageModel::NanoBanana2 => Some(RouterCommonImageModel::NanoBanana2), + TauriImageModel::NanoBananaPro => Some(RouterCommonImageModel::NanoBananaPro), + TauriImageModel::Gemini25Flash => Some(RouterCommonImageModel::NanoBanana), + TauriImageModel::Seedream4 => Some(RouterCommonImageModel::Seedream4), + TauriImageModel::Seedream4p5 => Some(RouterCommonImageModel::Seedream4p5), + TauriImageModel::Seedream5Lite => Some(RouterCommonImageModel::Seedream5Lite), + TauriImageModel::QwenEdit2511Angles => Some(RouterCommonImageModel::QwenEdit2511Angles), + TauriImageModel::Flux2LoraAngles => Some(RouterCommonImageModel::Flux2LoraAngles), + _ => None, +>>>>>>> 13896d9306 (cleanup) } } diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/grok/handle_grok.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/grok/handle_grok.rs index 8318f5f6e6..84271c2a57 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/grok/handle_grok.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/grok/handle_grok.rs @@ -1,7 +1,7 @@ use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image; use crate::core::commands::enqueue::generate_error::GenerateError; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; +use crate::core::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; use crate::core::events::generation_events::common::GenerationModel; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/midjourney/handle_midjourney.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/midjourney/handle_midjourney.rs index 848ebe8105..4eceda6d30 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/midjourney/handle_midjourney.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/midjourney/handle_midjourney.rs @@ -1,7 +1,7 @@ use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason, ProviderFailureReason}; -use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize}; +use crate::core::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; +use crate::core::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_gpt_image_1_sora_text_to_image.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_gpt_image_1_sora_text_to_image.rs index 82a1abedbf..04001a9d4f 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_gpt_image_1_sora_text_to_image.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_gpt_image_1_sora_text_to_image.rs @@ -2,7 +2,7 @@ use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_sora_native_gpt_image_1::aspect_ratio_to_sora_native_gpt_image_1; use crate::core::commands::enqueue::generate_error::GenerateError; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; +use crate::core::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_text_to_image_sora.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_text_to_image_sora.rs index a632ed8eca..112133f71a 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_text_to_image_sora.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_text_to_image_sora.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::EnqueueTextToImageRequest; +use crate::core::commands::deprecated::text_to_image::enqueue_text_to_image_command::EnqueueTextToImageRequest; use crate::core::commands::generate::generate_image::sora::handle_gpt_image_1_sora_text_to_image::handle_gpt_image_1_sora_text_to_image; use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; use crate::services::sora::state::sora_task_queue::SoraTaskQueue;