diff --git a/crates/artcraftx/src/commands/app_state/reset_artcraft_command.rs b/crates/artcraftx/src/commands/app_state/reset_artcraft_command.rs index 7ee7774b..970fe344 100644 --- a/crates/artcraftx/src/commands/app_state/reset_artcraft_command.rs +++ b/crates/artcraftx/src/commands/app_state/reset_artcraft_command.rs @@ -1,4 +1,3 @@ -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::EnqueueEditImageSuccessResponse; use crate::commands::response::shorthand::ResponseOrErrorMessage; use crate::commands::response::success_response_wrapper::SerializeMarker; use crate::state::app_preferences::app_preferences_manager::AppPreferencesManager; diff --git a/crates/artcraftx/src/commands/enqueue/image_bg_removal/artcraft/handle_generic_bg_removal_artcraft.rs b/crates/artcraftx/src/commands/background_removal/artcraft/handle_generic_bg_removal_artcraft.rs similarity index 91% rename from crates/artcraftx/src/commands/enqueue/image_bg_removal/artcraft/handle_generic_bg_removal_artcraft.rs rename to crates/artcraftx/src/commands/background_removal/artcraft/handle_generic_bg_removal_artcraft.rs index e2f3f8d7..a9ab0c46 100644 --- a/crates/artcraftx/src/commands/enqueue/image_bg_removal/artcraft/handle_generic_bg_removal_artcraft.rs +++ b/crates/artcraftx/src/commands/background_removal/artcraft/handle_generic_bg_removal_artcraft.rs @@ -1,7 +1,6 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason}; -use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason}; +use crate::commands::background_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::events::basic_sendable_event_trait::BasicSendableEvent; use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider}; use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; diff --git a/crates/artcraftx/src/commands/enqueue/image_bg_removal/artcraft/mod.rs b/crates/artcraftx/src/commands/background_removal/artcraft/mod.rs similarity index 100% rename from crates/artcraftx/src/commands/enqueue/image_bg_removal/artcraft/mod.rs rename to crates/artcraftx/src/commands/background_removal/artcraft/mod.rs diff --git a/crates/artcraftx/src/commands/enqueue/image_bg_removal/enqueue_image_bg_removal_command.rs b/crates/artcraftx/src/commands/background_removal/enqueue_image_bg_removal_command.rs similarity index 95% rename from crates/artcraftx/src/commands/enqueue/image_bg_removal/enqueue_image_bg_removal_command.rs rename to crates/artcraftx/src/commands/background_removal/enqueue_image_bg_removal_command.rs index cea65bac..f9c39775 100644 --- a/crates/artcraftx/src/commands/enqueue/image_bg_removal/enqueue_image_bg_removal_command.rs +++ b/crates/artcraftx/src/commands/background_removal/enqueue_image_bg_removal_command.rs @@ -1,7 +1,7 @@ -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::enqueue::image_bg_removal::artcraft::handle_generic_bg_removal_artcraft::handle_generic_bg_removal_artcraft; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors; +use crate::commands::generate::generate_error::{BadInputReason, GenerateError}; +use crate::commands::background_removal::artcraft::handle_generic_bg_removal_artcraft::handle_generic_bg_removal_artcraft; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; use crate::commands::response::shorthand::{Response, ResponseOrErrorType}; use crate::commands::response::success_response_wrapper::SerializeMarker; diff --git a/crates/artcraftx/src/commands/enqueue/image_bg_removal/mod.rs b/crates/artcraftx/src/commands/background_removal/mod.rs similarity index 100% rename from crates/artcraftx/src/commands/enqueue/image_bg_removal/mod.rs rename to crates/artcraftx/src/commands/background_removal/mod.rs diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_artcraft_flux_kontext_edit.rs b/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_artcraft_flux_kontext_edit.rs deleted file mode 100644 index ec5448d5..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_artcraft_flux_kontext_edit.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use anyhow::anyhow; -use artcraft_client::api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::{FluxProKontextMaxEditImageNumImages, FluxProKontextMaxEditImageRequest}; -use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest}; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use log::{error, info}; -use artcraft_client::endpoints::generate::image::edit::flux_pro_kontext_max_edit_image::flux_pro_kontext_max_edit_image; -use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image; -use tauri::AppHandle; - -pub async fn handle_artcraft_flux_kontext_edit( - request: &EnqueueEditImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let creds = match storyteller_creds_manager.get_credentials()? { - Some(creds) => creds, - None => { - return Err(GenerateError::needs_storyteller_credentials()); - }, - }; - - info!("Calling Artcraft flux kontext max edit (edit) ..."); - - let uuid_idempotency_token = generate_random_uuid(); - - let num_images = match request.image_count { - 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 mut maybe_media_token = None; - - let list_count = request.image_media_tokens - .as_ref() - .map(|tokens| tokens.len()) - .unwrap_or(0); - - if request.scene_image_media_token.is_some() && list_count > 0 { - return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments( - "Cannot specify both scene_image_media_token and image_media_tokens for Flux Kontext Max".to_string()))); - } - - if list_count > 1 { - return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments( - "Cannot specify more than one image in image_media_tokens for Flux Kontext Max".to_string()))); - } - - if let Some(media_token) = &request.scene_image_media_token { - maybe_media_token = Some(media_token.clone()); - } - - let maybe_list_media_token = request.image_media_tokens - .as_ref() - .map(|tokens| tokens.first()) - .flatten(); - - if let Some(media_token) = maybe_list_media_token { - maybe_media_token = Some(media_token.clone()); - } - - let media_token = match maybe_media_token { - Some(token) => token, - None => { - return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments( - "No input image specified for Flux Kontext Max edit".to_string()))); - }, - }; - - let request = FluxProKontextMaxEditImageRequest { - uuid_idempotency_token, - prompt: Some(request.prompt.clone()), - image_media_token: media_token, - num_images, - }; - - let result = flux_pro_kontext_max_edit_image( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - - let job_id = match result { - Ok(enqueued) => { - // TODO(bt,2025-07-05): Enqueue job token? - info!("Successfully enqueued Artcraft gpt-image-1. Job token: {}", - enqueued.inference_job_token); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft gpt-image-1: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Artcraft, - model: Some(GenerationModel::FluxProKontextMax), - provider_job_id: Some(job_id.to_string()), - task_type: TaskType::ImageGeneration, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_artcraft_gpt_image_1_edit.rs b/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_artcraft_gpt_image_1_edit.rs deleted file mode 100644 index 06089592..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_artcraft_gpt_image_1_edit.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use anyhow::anyhow; -use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest}; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use log::{error, info}; -use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image; -use tauri::AppHandle; - -const MAX_IMAGES: usize = 4; - -pub async fn handle_artcraft_gpt_image_1_edit( - request: &EnqueueEditImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let creds = match storyteller_creds_manager.get_credentials()? { - Some(creds) => creds, - None => { - return Err(GenerateError::needs_storyteller_credentials()); - }, - }; - - info!("Calling Artcraft gpt-image-1 (edit) ..."); - - let uuid_idempotency_token = generate_random_uuid(); - - let image_quality = request.image_quality - .map(|quality| match quality { - EditImageQuality::Auto => GptImage1EditImageImageQuality::Auto, - EditImageQuality::High => GptImage1EditImageImageQuality::High, - EditImageQuality::Medium => GptImage1EditImageImageQuality::Medium, - EditImageQuality::Low => GptImage1EditImageImageQuality::Low, - }); - - let image_size = request.aspect_ratio - .map(|size| match size { - EditImageSize::Auto => GptImage1EditImageImageSize::Square, - EditImageSize::Square => GptImage1EditImageImageSize::Square, - EditImageSize::Tall => GptImage1EditImageImageSize::Vertical, - EditImageSize::Wide => GptImage1EditImageImageSize::Horizontal, - }); - - let num_images = match request.image_count { - None => None, - Some(1) => Some(GptImage1EditImageNumImages::One), - Some(2) => Some(GptImage1EditImageNumImages::Two), - Some(3) => Some(GptImage1EditImageNumImages::Three), - Some(4) => Some(GptImage1EditImageNumImages::Four), - Some(other) => { - return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { - min: 1, - max: 4, - requested: other, - })); - }, - }; - - let mut media_tokens = Vec::with_capacity(10); - - if let Some(scene_image_media_token) = request.scene_image_media_token.clone() { - media_tokens.push(scene_image_media_token); - } - - if let Some(image_media_tokens) = request.image_media_tokens.as_ref() { - media_tokens.extend_from_slice(image_media_tokens); - } - - if media_tokens.len() > MAX_IMAGES { - return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages { - min: 1, - max: MAX_IMAGES as u32, - provided: media_tokens.len() as u32, - })); - } - - let request = GptImage1EditImageRequest { - uuid_idempotency_token, - prompt: Some(request.prompt.clone()), - image_media_tokens: Some(media_tokens), - image_size, - num_images, - image_quality, - }; - - let result = gpt_image_1_edit_image( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - - let job_id = match result { - Ok(enqueued) => { - // TODO(bt,2025-07-05): Enqueue job token? - info!("Successfully enqueued Artcraft gpt-image-1. Job token: {}", - enqueued.inference_job_token); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft gpt-image-1: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Artcraft, - model: Some(GenerationModel::GptImage1), - provider_job_id: Some(job_id.to_string()), - task_type: TaskType::ImageGeneration, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_image_edit_artcraft.rs b/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_image_edit_artcraft.rs deleted file mode 100644 index f786f8b3..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_image_edit_artcraft.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::deprecated::image_edit::artcraft::handle_artcraft_flux_kontext_edit::handle_artcraft_flux_kontext_edit; -use crate::commands::deprecated::image_edit::artcraft::handle_artcraft_gpt_image_1_edit::handle_artcraft_gpt_image_1_edit; -use crate::commands::deprecated::image_edit::artcraft::handle_image_edit_artcraft_via_router::handle_image_edit_artcraft_via_router; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use router::api::router_image_model::RouterImageModel; -use tauri::AppHandle; - -pub async fn handle_image_edit_artcraft( - model: ImageEditModel, - request: &EnqueueEditImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - match model { - ImageEditModel::FluxProKontextMax => handle_artcraft_flux_kontext_edit(request, app, app_data_root, app_env_configs, storyteller_creds_manager).await, - ImageEditModel::Gemini25Flash | ImageEditModel::NanoBanana => { - handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBanana, GenerationModel::NanoBanana).await - }, - ImageEditModel::NanoBanana2 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBanana2, GenerationModel::NanoBanana2).await, - ImageEditModel::NanoBananaPro => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBananaPro, 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, RouterImageModel::GptImage1p5, GenerationModel::GptImage1p5).await, - ImageEditModel::Seedream4 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream4, GenerationModel::Seedream4).await, - ImageEditModel::Seedream4p5 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream4p5, GenerationModel::Seedream4p5).await, - ImageEditModel::Seedream5Lite => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream5Lite, GenerationModel::Seedream5Lite).await, - ImageEditModel::QwenEdit2511Angles => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::QwenEdit2511Angles, GenerationModel::QwenEdit2511Angles).await, - ImageEditModel::Flux2LoraAngles => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Flux2LoraAngles, GenerationModel::Flux2LoraAngles).await, - } -} diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_image_edit_artcraft_via_router.rs b/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_image_edit_artcraft_via_router.rs deleted file mode 100644 index f89c02a0..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/handle_image_edit_artcraft_via_router.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio as DesktopCommonAspectRatio; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason}; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{ - EditImageResolution, EditImageSize, EnqueueEditImageCommand, -}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use router::api::router_aspect_ratio::RouterAspectRatio; -use router::api::router_image_model::RouterImageModel; -use router::api::router_resolution::RouterResolution; -use router::api::image_list_ref::ImageListRef; -use router::api::router_provider::RouterProvider; -use router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; -use router::client::router_artcraft_client::RouterArtcraftClient; -use router::client::router_client::RouterClient; -use router::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder; -use router::generate::generate_image::image_generation_draft_or_request::ImageGenerationDraftOrRequest; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use log::{error, info}; - -pub(super) const MAX_EDIT_IMAGES: usize = 10; - -pub(super) async fn handle_image_edit_artcraft_via_router( - request: &EnqueueEditImageCommand, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, - model: RouterImageModel, - generation_model: GenerationModel, -) -> Result { - 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, - )); - - let mut media_tokens = Vec::with_capacity(MAX_EDIT_IMAGES); - - if let Some(scene_token) = request.scene_image_media_token.clone() { - media_tokens.push(scene_token); - } - if let Some(tokens) = request.image_media_tokens.as_ref() { - media_tokens.extend_from_slice(tokens); - } - - if media_tokens.len() > MAX_EDIT_IMAGES { - return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages { - min: 1, - max: MAX_EDIT_IMAGES as u32, - provided: media_tokens.len() as u32, - })); - } - - let image_inputs = if media_tokens.is_empty() { - None - } else { - Some(ImageListRef::MediaFileTokens(media_tokens)) - }; - - let aspect_ratio = get_aspect_ratio_edit(request); - let resolution = get_resolution_edit(request); - - let router_request = GenerateImageRequestBuilder { - model, - provider: RouterProvider::Artcraft, - prompt: Some(request.prompt.clone()), - image_inputs, - resolution, - aspect_ratio, - quality: None, // TODO(bt): Add quality - image_batch_count: request.image_count.map(|n| n as u16), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, - generation_mode_mismatch_strategy: None, - idempotency_token: None, - horizontal_angle: request.horizontal_angle, - vertical_angle: request.vertical_angle, - zoom: request.zoom, - }; - - let dor = router_request.build2()?; - - info!("Image Generation Request: {:?}", dor); - - let request = match dor { - ImageGenerationDraftOrRequest::Request(req) => req, - // Artcraft never returns a Draft — image-token resolution happens server-side. - // The only draft-producing models today are Kinovi-Midjourney variants, - // which are routed to a separate provider. - ImageGenerationDraftOrRequest::Draft(_) => unreachable!( - "Artcraft router should never produce a draft for image edits" - ), - }; - - let response = match request.send_request(&client).await { - Ok(resp) => { - info!("Successfully enqueued."); - resp - } - Err(err) => { - error!("Failed to enqueue: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - let job_id = response - .get_artcraft_payload() - .map(|p| p.inference_job_token.to_string()) - .ok_or(GenerateError::ResponseHadNoJobTokens)?; - - info!("Job token: {}", job_id); - - Ok(TaskEnqueueSuccess { - task_type: TaskType::ImageGeneration, - model: Some(generation_model), - provider: GenerationProvider::Artcraft, - provider_job_id: Some(job_id), - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} - -fn get_aspect_ratio_edit(request: &EnqueueEditImageCommand) -> Option { - if let Some(ar) = request.common_aspect_ratio { - return Some(convert_desktop_aspect_ratio(ar)); - } - if let Some(ar) = request.aspect_ratio { - return Some(match ar { - EditImageSize::Auto => RouterAspectRatio::Auto, - EditImageSize::Square => RouterAspectRatio::Square, - EditImageSize::Wide => RouterAspectRatio::Wide, - EditImageSize::Tall => RouterAspectRatio::Tall, - }); - } - None -} - -fn get_resolution_edit(request: &EnqueueEditImageCommand) -> Option { - request.image_resolution.map(|res| match res { - EditImageResolution::OneK => RouterResolution::OneK, - EditImageResolution::TwoK => RouterResolution::TwoK, - EditImageResolution::FourK => RouterResolution::FourK, - }) -} - -fn convert_desktop_aspect_ratio(ar: DesktopCommonAspectRatio) -> RouterAspectRatio { - match ar { - DesktopCommonAspectRatio::Auto => RouterAspectRatio::Auto, - DesktopCommonAspectRatio::Square => RouterAspectRatio::Square, - DesktopCommonAspectRatio::WideThreeByTwo => RouterAspectRatio::WideThreeByTwo, - DesktopCommonAspectRatio::WideFourByThree => RouterAspectRatio::WideFourByThree, - DesktopCommonAspectRatio::WideFiveByFour => RouterAspectRatio::WideFiveByFour, - DesktopCommonAspectRatio::WideSixteenByNine => RouterAspectRatio::WideSixteenByNine, - DesktopCommonAspectRatio::WideTwentyOneByNine => RouterAspectRatio::WideTwentyOneByNine, - DesktopCommonAspectRatio::TallTwoByThree => RouterAspectRatio::TallTwoByThree, - DesktopCommonAspectRatio::TallThreeByFour => RouterAspectRatio::TallThreeByFour, - DesktopCommonAspectRatio::TallFourByFive => RouterAspectRatio::TallFourByFive, - DesktopCommonAspectRatio::TallNineBySixteen => RouterAspectRatio::TallNineBySixteen, - DesktopCommonAspectRatio::TallNineByTwentyOne => RouterAspectRatio::TallNineByTwentyOne, - DesktopCommonAspectRatio::Wide => RouterAspectRatio::Wide, - DesktopCommonAspectRatio::Tall => RouterAspectRatio::Tall, - DesktopCommonAspectRatio::Auto2k => RouterAspectRatio::Auto2k, - DesktopCommonAspectRatio::Auto4k => RouterAspectRatio::Auto4k, - DesktopCommonAspectRatio::SquareHd => RouterAspectRatio::SquareHd, - } -} diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/mod.rs b/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/mod.rs deleted file mode 100644 index 49b2aaf9..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/artcraft/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod handle_image_edit_artcraft_via_router; -mod handle_artcraft_flux_kontext_edit; -mod handle_artcraft_gpt_image_1_edit; - -pub (super) mod handle_image_edit_artcraft; diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/enqueue_edit_image_command.rs b/crates/artcraftx/src/commands/deprecated/image_edit/enqueue_edit_image_command.rs deleted file mode 100644 index 56ec5b43..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/enqueue_edit_image_command.rs +++ /dev/null @@ -1,424 +0,0 @@ -use crate::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio; -use crate::api_adapters::quality::common_quality::CommonQuality; -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::deprecated::image_edit::artcraft::handle_image_edit_artcraft::handle_image_edit_artcraft; -use crate::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type; -use crate::commands::deprecated::image_edit::sora::handle_image_edit_sora::handle_image_edit_sora; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; -use crate::commands::response::shorthand::{Response, ResponseOrErrorType}; -use crate::commands::response::success_response_wrapper::SerializeMarker; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; -use crate::events::sendable_event_trait::SendableEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; -use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType}; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::data_dir::subdirectory::trait_data_subdir::DataSubdir; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::state::task_database::TaskDatabase; -use crate::utils::get_url_file_extension::get_url_file_extension; -use crate::utils::simple_http_download::simple_http_download; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use artcraft_client::endpoints::media_files::get_media_file::get_media_file; -use artcraft_client::error::storyteller_error::StorytellerError; -use artcraft_client::utils::api_host::ApiHost; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; -use errors::AnyhowError; -use log::{error, info, warn}; -use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest}; -use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest}; -use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; -use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages}; -use serde_derive::{Deserialize, Serialize}; -use sqlite_database::queries::create_task::{create_task, CreateTaskArgs}; -use std::time::Duration; -use tauri::{AppHandle, Manager, State}; -use tokens::tokens::media_files::MediaFileToken; - -/// This is used in the Tauri command bridge. -/// Don't change the serializations without coordinating with the frontend. -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum ImageEditModel { - #[serde(rename = "flux_pro_kontext_max")] - FluxProKontextMax, - - #[serde(rename = "gemini_25_flash")] - Gemini25Flash, - - #[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_edit_2511_angles")] - QwenEdit2511Angles, - - #[serde(rename = "flux_2_lora_angles")] - Flux2LoraAngles, - -// #[serde(rename = "qwen")] -// Qwen, -// -// #[serde(rename = "seededit_3")] -// SeedEdit3, -} - -#[derive(Deserialize, Debug)] -pub struct EnqueueEditImageCommand { - /// The provider to use (defaults to Artcraft/Storyteller). - /// Not all (provider, model) combinations are valid. - pub provider: Option, - - /// The model to use. - pub model: Option, - - /// Images to use for the image edit. - /// The first image is typically a 2D canvas or 3D stage, but doesn't have to be. - /// There must be at least one image. - pub image_media_tokens: Option>, - - /// If set, this becomes the first image in the image media tokens (pushing back - /// each of the `image_media_tokens` by one). - /// This is useful if we want to do prompt engineering. - pub scene_image_media_token: Option, - - /// The user's image generation prompt. - pub prompt: String, - - /// Turn off the system prompt. - pub disable_system_prompt: Option, - - /// Number of images to generate. - pub image_count: Option, - - /// Aspect ratio. - #[deprecated(note="use common_aspect_ratio")] - pub aspect_ratio: Option, - - /// New field for aspect ratio. - /// Not all models support each of these aspect ratios, but we can choose or interpolate sensibly. - pub common_aspect_ratio: Option, - - /// Image quality. - pub image_quality: Option, - - /// Image resolution - pub image_resolution: Option, - - /// Quality. - /// Tends to be used by OpenAI models. - pub quality: Option, - - /// OPTIONAL. - /// Only for image angle edit models. - pub horizontal_angle: Option, - - /// OPTIONAL. - /// Only for image angle edit models. - pub vertical_angle: Option, - - /// OPTIONAL. - /// Only for image angle edit models. - pub zoom: Option, - - /// OPTIONAL. - /// Name of the frontend caller. - /// We'll use this to selectively trigger events. - pub frontend_caller: Option, - - /// OPTIONAL. - /// A frontend-defined identifier that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_id: Option, - - /// OPTIONAL. - /// A frontend-defined payload that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_payload: Option, -} - -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum EditImageSize { - Auto, - Square, - Wide, - Tall, -} - -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum EditImageQuality { - Auto, - High, - Medium, - Low, -} - -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum EditImageResolution { - /// 1K - nano banana pro - OneK, - /// 2K - nano banana pro - TwoK, - /// 4K - nano banana pro - FourK, -} - -#[derive(Serialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum EnqueueEditImageErrorType { - /// Caller didn't specify a model - ModelNotSpecified, - - /// No model available for image generation - NoProviderAvailable, - - /// Generic bad request error - BadRequest, - - /// Generic server error - ServerError, - - /// The user is sending too many requests - TooManyConcurrentTasks, - /// User is not logged into Sora! - SoraLoginRequired, - /// The user needs to create a Sora account - SoraUsernameNotYetCreated, - /// The Sora service is having problems. Try again soon. - SoraIsHavingProblems, -} - -#[derive(Serialize)] -pub struct EnqueueEditImageSuccessResponse { -} - -impl SerializeMarker for EnqueueEditImageSuccessResponse {} - -#[tauri::command] -pub async fn enqueue_edit_image_command( - app: AppHandle, - request: EnqueueEditImageCommand, - app_data_root: State<'_, AppDataRoot>, - app_env_configs: State<'_, AppEnvConfigs>, - artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>, - provider_priority_store: State<'_, ProviderPriorityStore>, - task_database: State<'_, TaskDatabase>, - storyteller_creds_manager: State<'_, StorytellerCredentialManager>, - sora_creds_manager: State<'_, SoraCredentialManager>, - sora_task_queue: State<'_, SoraTaskQueue>, -) -> ResponseOrErrorType { - - info!("enqueue_edit_image_command called; image media tokens : {:?}, full request: {:?}", - &request.image_media_tokens, &request); - - let result = handle_request( - &request, - &app, - &app_data_root, - &app_env_configs, - &artcraft_usage_tracker, - &provider_priority_store, - &task_database, - &storyteller_creds_manager, - &sora_creds_manager, - &sora_task_queue, - ).await; - - match result { - Err(err) => { - error!("Error enqueuing contextual edit image: {:?}", err); - - notify_frontend_of_errors(&app, &err).await; - - // TODO: Derive from err. Make service provider optional. - let event = GenerationEnqueueFailureEvent { - action: GenerationAction::GenerateImage, - service: GenerationServiceProvider::Artcraft, // FIXME: This is wrong. - model: None, - reason: None, - }; - - if let Err(err) = event.send(&app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - Err(error_to_tauri_response(err)) - } - Ok(event) => { - let event = GenerationEnqueueSuccessEvent { - action: event.to_frontend_event_action(), - service: event.to_frontend_event_service(), - model: event.model, - }; - - if let Err(err) = event.send(&app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - CreditsBalanceChangedEvent{}.send_infallible(&app); - - Ok(EnqueueEditImageSuccessResponse {}.into()) - } - } -} - -pub async fn handle_request( - request: &EnqueueEditImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - artcraft_usage_tracker: &ArtcraftUsageTracker, - provider_priority_store: &ProviderPriorityStore, - task_database: &TaskDatabase, - storyteller_creds_manager: &StorytellerCredentialManager, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - - let model = match request.model { - Some(model) => model, - None => { - return Err(GenerateError::no_model_specified()) - } - }; - - let provider = request.provider - .unwrap_or(GenerationProvider::Artcraft); - - info!("edit image with {:?} via provider {:?}", &model, &provider); - - let mut success_event = match provider { - GenerationProvider::Artcraft => { - handle_image_edit_artcraft( - model, - request, - app, - app_data_root, - app_env_configs, - storyteller_creds_manager, - ).await? - } - GenerationProvider::Sora => { - handle_image_edit_sora( - model, - request, - app, - app_data_root, - app_env_configs, - sora_creds_manager, - sora_task_queue, - ).await? - } - _ => { - return Err(GenerateError::BadProviderForModel { - provider, - model: image_edit_model_to_model_type(model), - }) - } - }; - - match request.frontend_caller { - Some(TauriCommandCaller::ImageEditor) => { - // NB: Certain image generations enqueued in the inpainting editor are classed - // as "image_generation" and won't update the inpainting UI. This is a hack to - // temporarily fix the UI event binding. - // TODO: The frontend shouldn't require us to change the type to inpainting. - success_event.task_type = TaskType::ImageInpaintEdit; - } - _ => {} // fall-through - } - - let result = success_event - .insert_into_task_database_with_frontend_payload( - task_database, - request.frontend_caller.clone(), - request.frontend_subscriber_id.as_deref(), - request.frontend_subscriber_payload.as_deref(), - ) - .await; - - if let Err(err) = result { - error!("Failed to create task in database: {:?}", err); - // NB: Fail open, but find a way to flag this. - } - - // TODO: We don't know that this is strictly the edit page. It could be the stage. It might also be nice to distinguish inpainting. - let num_images = request.image_count.map(|count| count as u16).unwrap_or(1); - if let Err(err) = artcraft_usage_tracker.record_image_generation(num_images, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::EditPage) { - // NB: Fail open. - warn!("Failed to report usage: {:?}", err); - } - - Ok(success_event) -} - -fn error_to_tauri_response(error: GenerateError) -> CommandErrorResponseWrapper { - let mut status = CommandErrorStatus::ServerError; - let mut error_type = EnqueueEditImageErrorType::ServerError; - let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.".to_string(); - - match error { - GenerateError::BadInput(BadInputReason::NoModelSpecified) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueEditImageErrorType::ModelNotSpecified; - error_message = "No model specified for image generation".to_string(); - } - GenerateError::NoProviderAvailable => { - status = CommandErrorStatus::ServerError; - error_type = EnqueueEditImageErrorType::NoProviderAvailable; - error_message = "No configured provider available for image generation".to_string(); - } - GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { min, max, requested }) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueEditImageErrorType::BadRequest; - error_message = format!("Invalid number of images requested ({}). Must be between {} and {}", requested, min, max); - - } - GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages{ min, max, provided }) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueEditImageErrorType::BadRequest; - error_message = format!("Invalid number of input images ({}). Must be between {} and {}", provided, min, max); - } - _ => {} // Fall-through for now - } - - CommandErrorResponseWrapper { - status, - error_message: Some(error_message.to_string()), - error_type: Some(error_type), - error_details: None, - } -} diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/image_edit_models.rs b/crates/artcraftx/src/commands/deprecated/image_edit/image_edit_models.rs deleted file mode 100644 index ad54600e..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/image_edit_models.rs +++ /dev/null @@ -1,19 +0,0 @@ -use enums::common::generation::common_model_type::CommonModelType; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::ImageEditModel; - -pub fn image_edit_model_to_model_type(model: ImageEditModel) -> CommonModelType { - match model { - ImageEditModel::FluxProKontextMax => CommonModelType::FluxProKontextMax, - ImageEditModel::Gemini25Flash => CommonModelType::NanoBanana, - ImageEditModel::NanoBanana => CommonModelType::NanoBanana, - ImageEditModel::NanoBanana2 => CommonModelType::NanoBanana2, - ImageEditModel::NanoBananaPro => CommonModelType::NanoBananaPro, - ImageEditModel::GptImage1 => CommonModelType::GptImage1, - ImageEditModel::GptImage1p5 => CommonModelType::GptImage1p5, - ImageEditModel::Seedream4 => CommonModelType::Seedream4, - ImageEditModel::Seedream4p5 => CommonModelType::Seedream4p5, - ImageEditModel::Seedream5Lite => CommonModelType::Seedream5Lite, - ImageEditModel::QwenEdit2511Angles => CommonModelType::QwenEdit2511Angles, - ImageEditModel::Flux2LoraAngles => CommonModelType::Flux2LoraAngles, - } -} diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/mod.rs b/crates/artcraftx/src/commands/deprecated/image_edit/mod.rs deleted file mode 100644 index 146d1663..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub (super) mod artcraft; -pub (super) mod sora; -pub mod enqueue_edit_image_command; -pub mod image_edit_models; - diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/sora/handle_image_edit_sora.rs b/crates/artcraftx/src/commands/deprecated/image_edit/sora/handle_image_edit_sora.rs deleted file mode 100644 index 4a7e0ba3..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/sora/handle_image_edit_sora.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel}; -use crate::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type; -use crate::commands::deprecated::image_edit::sora::handle_sora_gpt_image_1_edit::handle_sora_gpt_image_1_edit; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use enums::common::generation_provider::GenerationProvider; -use tauri::AppHandle; - -pub async fn handle_image_edit_sora( - model: ImageEditModel, - request: &EnqueueEditImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - match model { - ImageEditModel::GptImage1 => { - handle_sora_gpt_image_1_edit( - request, - app, - app_data_root, - app_env_configs, - sora_creds_manager, - sora_task_queue, - ).await - }, - _ => { - Err(GenerateError::BadProviderForModel { - provider: GenerationProvider::Sora, - model: image_edit_model_to_model_type(model), - }) - } - } -} diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/sora/handle_sora_gpt_image_1_edit.rs b/crates/artcraftx/src/commands/deprecated/image_edit/sora/handle_sora_gpt_image_1_edit.rs deleted file mode 100644 index aea9274f..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/sora/handle_sora_gpt_image_1_edit.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageSize, EnqueueEditImageCommand, EnqueueEditImageErrorType}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::data_dir::subdirectory::trait_data_subdir::DataSubdir; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::utils::get_url_file_extension::get_url_file_extension; -use crate::utils::simple_http_download::simple_http_download; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use anyhow::anyhow; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use errors::AnyhowResult; -use uuid_utils::uuid::generate_random_uuid; -use log::{error, info, warn}; -use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest}; -use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest}; -use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; -use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages}; -use std::time::Duration; -use artcraft_client::endpoints::media_files::get_media_file::get_media_file; -use tauri::AppHandle; - -const MAX_IMAGES: usize = 4; -const SORA_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds - -const SORA_IMAGE_REMIX_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds - -const DEFAULT_ASPECT_RATIO : EditImageSize = EditImageSize::Square; - -pub async fn handle_sora_gpt_image_1_edit( - request: &EnqueueEditImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - - let mut creds = match sora_creds_manager.get_credentials() { - Ok(Some(creds)) => creds, - Ok(None) => { - warn!("No Sora credentials found."); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app); - return Err(GenerateError::needs_sora_credentials()); - } - Err(err) => { - error!("Failed to get Sora credentials: {:?}", err); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app); - return Err(GenerateError::needs_sora_credentials()); - } - }; - - let mut media_tokens = Vec::with_capacity(10); - - if let Some(scene_image_media_token) = request.scene_image_media_token.clone() { - media_tokens.push(scene_image_media_token); - } - - if let Some(image_media_tokens) = request.image_media_tokens.as_ref() { - media_tokens.extend_from_slice(image_media_tokens); - } - - if media_tokens.len() > MAX_IMAGES { - return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages { - min: 1, - max: MAX_IMAGES as u32, - provided: media_tokens.len() as u32, - })); - } - - info!("Calling get media file API: {:?}", app_env_configs.storyteller_host); - - let mut files_to_upload_to_sora = Vec::with_capacity(10); - - // TODO(bt,2025-07-07): This is inefficient. Cache and parallelize this. - for media_token in media_tokens.iter() { - info!("Using media token: {:?}", media_token); - - let response = get_media_file( - &app_env_configs.storyteller_host, - media_token - ).await?; - - let media_file_url = &response.media_file.media_links.cdn_url; - let extension_with_dot = get_url_file_extension(media_file_url) - .map(|ext| format!(".{}", ext)) - .unwrap_or_else(|| ".png".to_string()); - - let filename = format!("{}{}", response.media_file.token.as_str(), extension_with_dot); - let filename = app_data_root.downloads_dir().path().join(&filename); - - simple_http_download(&media_file_url, &filename).await?; - - files_to_upload_to_sora.push(filename); - } - - let credential_updated = maybe_upgrade_or_renew_session(&mut creds) - .await - .map_err(|err| { - error!("Failed to upgrade or renew session: {:?}", err); - err - })?; - - if credential_updated { - info!("Storing updated credentials"); - sora_creds_manager.set_credentials(&creds)?; - } - - let mut sora_media_tokens = Vec::with_capacity(files_to_upload_to_sora.len()); - - for (i, file_path) in files_to_upload_to_sora.iter().enumerate() { - info!("Uploading image {} of {}...", (i+1), files_to_upload_to_sora.len()); - - let (response, maybe_new_credentials) = - image_upload_from_file_with_session_auto_renew(ImageUploadFromFileAutoRenewRequest { - file_path, - credentials: &creds, - request_timeout: Some(SORA_IMAGE_UPLOAD_TIMEOUT), // TODO: Centralize and make configurable. - }).await?; - - if let Some(new_creds) = maybe_new_credentials { - info!("Storing updated credentials."); - sora_creds_manager.set_credentials(&new_creds)?; - creds = new_creds; - } - - sora_media_tokens.push(response.id); - } - - let aspect_ratio = request.aspect_ratio.unwrap_or(DEFAULT_ASPECT_RATIO); - - let aspect_ratio = match aspect_ratio { - EditImageSize::Auto => ImageSize::Square, - EditImageSize::Square => ImageSize::Square, - EditImageSize::Wide => ImageSize::Wide, - EditImageSize::Tall => ImageSize::Tall, - }; - - info!("Calling Sora image generation..."); - - // TODO(bt,2025-04-21): Download media tokens. - // Note: This is incredibly inefficient. We should keep a local cache. - // Also, if they've already been uploaded to OpenAI, we shouldn't continue to re-upload. - - let (response, maybe_new_creds) = - image_remix_with_session_auto_renew(ImageRemixAutoRenewRequest { - prompt: request.prompt.to_string(), - num_images: NumImages::One, - image_size: aspect_ratio, - sora_media_tokens: sora_media_tokens.clone(), - credentials: &creds, - request_timeout: Some(SORA_IMAGE_REMIX_TIMEOUT), - }).await?; - - if let Some(new_creds) = maybe_new_creds { - info!("Storing updated credentials."); - sora_creds_manager.set_credentials(&new_creds)?; - } - - info!("New Sora Task ID: {:?} ", response.task_id); - - sora_task_queue.insert(&response.task_id)?; - - Ok(TaskEnqueueSuccess { - task_type: TaskType::ImageGeneration, - model: Some(GenerationModel::GptImage1), - provider: GenerationProvider::Sora, - provider_job_id: Some(response.task_id.to_string()), - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} diff --git a/crates/artcraftx/src/commands/deprecated/image_edit/sora/mod.rs b/crates/artcraftx/src/commands/deprecated/image_edit/sora/mod.rs deleted file mode 100644 index f7b715be..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_edit/sora/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod handle_sora_gpt_image_1_edit; -pub (super) mod handle_image_edit_sora; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_flux_dev_juggernaut_inpaint.rs b/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_flux_dev_juggernaut_inpaint.rs deleted file mode 100644 index fdb64580..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_flux_dev_juggernaut_inpaint.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand}; -use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use anyhow::anyhow; -use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest}; -use artcraft_client::api_defs::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::{FluxDevJuggernautInpaintImageNumImages, FluxDevJuggernautInpaintImageRequest}; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes; -use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions; -use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask; -use log::{error, info}; -use mimetypes::mimetype_info::mimetype_info::MimetypeInfo; -use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; -use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image; -use artcraft_client::endpoints::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::flux_dev_juggernaut_inpaint_image; -use artcraft_client::endpoints::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image; -use artcraft_client::endpoints::media_files::get_media_file::get_media_file; -use artcraft_client::endpoints::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs}; -use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file; -use tauri::AppHandle; -use tokens::tokens::media_files::MediaFileToken; - -pub async fn handle_artcraft_flux_dev_juggernaut_inpaint( - request: &EnqueueInpaintImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let creds = match storyteller_creds_manager.get_credentials()? { - Some(creds) => creds, - None => { - return Err(GenerateError::needs_storyteller_credentials()); - }, - }; - - let image_media_token = match &request.image_media_token { - Some(token) => token.clone(), - None => { - return Err(GenerateError::required_source_image_not_provided()); - }, - }; - - let mask_media_token = get_mask( - request, - app_env_configs, - &creds, - ).await?; - - info!("Calling Artcraft flux dev juggernaut inpaint ..."); - - let uuid_idempotency_token = generate_random_uuid(); - - let num_images = match request.image_count { - 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 request = FluxDevJuggernautInpaintImageRequest { - uuid_idempotency_token, - prompt: Some(request.prompt.clone()), - image_media_token, - mask_media_token, - num_images, - }; - - let result = flux_dev_juggernaut_inpaint_image( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - - let job_id = match result { - Ok(enqueued) => { - // TODO(bt,2025-07-05): Enqueue job token? - info!("Successfully enqueued Artcraft flux dev juggernaut inpaint. Job token: {}", - enqueued.inference_job_token); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft flux dev juggernaut inpaint: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Artcraft, - model: Some(GenerationModel::FluxDevJuggernaut), - provider_job_id: Some(job_id.to_string()), - task_type: TaskType::ImageInpaintEdit, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} - -async fn get_mask( - request: &EnqueueInpaintImageCommand, - app_env_configs: &AppEnvConfigs, - storyteller_creds: &StorytellerCredentialSet, -) -> Result { - - if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() { - return Err(GenerateError::both_image_mask_media_token_and_bytes_supplied()); - } - - if let Some(token) = request.mask_image_media_token.as_ref() { - return Ok(token.clone()); - }; - - let image_bytes = request.mask_image_raw_bytes.as_ref() - .ok_or(GenerateError::required_source_image_mask_not_provided())?; - - let image_bytes = normalize_image_bytes_to_flux_mask(image_bytes) - .map_err(|err| { - error!("Failed to convert image bytes to png: {:?}", err); - GenerateError::AnyhowError(anyhow!("Failed to convert image bytes to png mask")) - })?; - - info!("Uploading image media file from bytes..."); - - let result = upload_image_media_file_from_bytes(UploadImageBytesArgs { - api_host: &app_env_configs.storyteller_host, - maybe_creds: Some(&storyteller_creds), - image_bytes: image_bytes.0, - image_type: ImageType::Png, - is_intermediate_system_file: true, - maybe_generation_provider: None, - }).await - .map_err(|err| { - error!("Failed to upload image media file: {:?}", err); - GenerateError::from(err) - })?; - - Ok(result.media_file_token) -} diff --git a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_flux_pro_1_inpaint.rs b/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_flux_pro_1_inpaint.rs deleted file mode 100644 index 6154c292..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_flux_pro_1_inpaint.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand}; -use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use anyhow::anyhow; -use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest}; -use artcraft_client::api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest}; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes; -use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions; -use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask; -use log::{error, info}; -use mimetypes::mimetype_info::mimetype_info::MimetypeInfo; -use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; -use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image; -use artcraft_client::endpoints::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image; -use artcraft_client::endpoints::media_files::get_media_file::get_media_file; -use artcraft_client::endpoints::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs}; -use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file; -use tauri::AppHandle; -use tokens::tokens::media_files::MediaFileToken; - -pub async fn handle_artcraft_flux_pro_1_inpaint( - request: &EnqueueInpaintImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let creds = match storyteller_creds_manager.get_credentials()? { - Some(creds) => creds, - None => { - return Err(GenerateError::needs_storyteller_credentials()); - }, - }; - - let image_media_token = match &request.image_media_token { - Some(token) => token.clone(), - None => { - return Err(GenerateError::required_source_image_not_provided()); - }, - }; - - let mask_media_token = get_mask( - request, - app_env_configs, - &creds, - ).await?; - - info!("Calling Artcraft flux pro 1 inpaint ..."); - - let uuid_idempotency_token = generate_random_uuid(); - - let num_images = match request.image_count { - None => None, - Some(1) => Some(FluxPro1InpaintImageNumImages::One), - Some(2) => Some(FluxPro1InpaintImageNumImages::Two), - Some(3) => Some(FluxPro1InpaintImageNumImages::Three), - Some(4) => Some(FluxPro1InpaintImageNumImages::Four), - Some(other) => { - return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { - min: 1, - max: 4, - requested: other, - })); - }, - }; - - let request = FluxPro1InpaintImageRequest { - uuid_idempotency_token, - prompt: Some(request.prompt.clone()), - image_media_token, - mask_media_token, - num_images, - }; - - let result = flux_pro_1_inpaint_image( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - - let job_id = match result { - Ok(enqueued) => { - // TODO(bt,2025-07-05): Enqueue job token? - info!("Successfully enqueued Artcraft flux pro 1 inpaint. Job token: {}", - enqueued.inference_job_token); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft flux pro 1 inpaint: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Artcraft, - model: Some(GenerationModel::FluxPro1), - provider_job_id: Some(job_id.to_string()), - task_type: TaskType::ImageInpaintEdit, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} - -async fn get_mask( - request: &EnqueueInpaintImageCommand, - app_env_configs: &AppEnvConfigs, - storyteller_creds: &StorytellerCredentialSet, -) -> Result { - - if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() { - return Err(GenerateError::both_image_mask_media_token_and_bytes_supplied()); - } - - if let Some(token) = request.mask_image_media_token.as_ref() { - return Ok(token.clone()); - }; - - let image_bytes = request.mask_image_raw_bytes.as_ref() - .ok_or(GenerateError::required_source_image_mask_not_provided())?; - - let image_bytes = normalize_image_bytes_to_flux_mask(image_bytes) - .map_err(|err| { - error!("Failed to convert image bytes to png: {:?}", err); - GenerateError::AnyhowError(anyhow!("could not encode image mask to png")) - })?; - - info!("Uploading image media file from bytes..."); - - let result = upload_image_media_file_from_bytes(UploadImageBytesArgs { - api_host: &app_env_configs.storyteller_host, - maybe_creds: Some(&storyteller_creds), - image_bytes: image_bytes.0, - image_type: ImageType::Png, - is_intermediate_system_file: true, - maybe_generation_provider: None, - }).await - .map_err(|err| { - error!("Failed to upload image media file: {:?}", err); - GenerateError::from(err) - })?; - - Ok(result.media_file_token) -} diff --git a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_flux_pro_kontext_inpaint.rs b/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_flux_pro_kontext_inpaint.rs deleted file mode 100644 index a0441db0..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_flux_pro_kontext_inpaint.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand}; -use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use anyhow::anyhow; -use artcraft_client::api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::{FluxProKontextMaxEditImageNumImages, FluxProKontextMaxEditImageRequest}; -use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest}; -use artcraft_client::api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest}; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes; -use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions; -use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask; -use log::{error, info}; -use mimetypes::mimetype_info::mimetype_info::MimetypeInfo; -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 artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image; -use artcraft_client::endpoints::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image; -use artcraft_client::endpoints::media_files::get_media_file::get_media_file; -use artcraft_client::endpoints::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs}; -use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file; -use tauri::AppHandle; -use tokens::tokens::media_files::MediaFileToken; - -pub async fn handle_artcraft_flux_pro_kontext_inpaint( - request: &EnqueueInpaintImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let creds = match storyteller_creds_manager.get_credentials()? { - Some(creds) => creds, - None => { - return Err(GenerateError::needs_storyteller_credentials()); - }, - }; - - let image_media_token = match &request.image_media_token { - Some(token) => token.clone(), - None => { - return Err(GenerateError::required_source_image_not_provided()); - }, - }; - - //let image_media_file = get_media_file( - // &app_env_configs.storyteller_host, - // &image_media_token, - //).await.map_err(|err| { - // error!("Failed to get media file for image: {:?}", err); - // InternalImageInpaintError::StorytellerError(err) - //})?; - - // TODO: Experiment in seeing if we can use the mask in any useful way. - - // let mask_media_token = get_mask( - // request, - // app_env_configs, - // &creds, - // ).await?; - - info!("Calling Artcraft flux pro kontext inpaint ..."); - - let uuid_idempotency_token = generate_random_uuid(); - - let num_images = match request.image_count { - 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 request = FluxProKontextMaxEditImageRequest { - uuid_idempotency_token, - prompt: Some(request.prompt.clone()), - image_media_token, - num_images, - }; - - let result = flux_pro_kontext_max_edit_image( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - - - let job_id = match result { - Ok(enqueued) => { - // TODO(bt,2025-07-05): Enqueue job token? - info!("Successfully enqueued Artcraft flux pro kontext inpaint. Job token: {}", - enqueued.inference_job_token); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft flux pro kontext inpaint: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Artcraft, - model: Some(GenerationModel::FluxProKontextMax), - provider_job_id: Some(job_id.to_string()), - task_type: TaskType::ImageInpaintEdit, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} diff --git a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_nano_banana_inpaint.rs b/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_nano_banana_inpaint.rs deleted file mode 100644 index 5fc539d5..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_artcraft_nano_banana_inpaint.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand}; -use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use anyhow::anyhow; -use artcraft_client::api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::{FluxProKontextMaxEditImageNumImages, FluxProKontextMaxEditImageRequest}; -use artcraft_client::api_defs::generate::image::edit::gemini_25_flash_edit_image::{Gemini25FlashEditImageNumImages, Gemini25FlashEditImageRequest}; -use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest}; -use artcraft_client::api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest}; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes; -use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions; -use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask; -use log::{error, info}; -use mimetypes::mimetype_info::mimetype_info::MimetypeInfo; -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 artcraft_client::endpoints::generate::image::edit::gemini_25_flash_edit_image::gemini_25_flash_edit_image; -use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image; -use artcraft_client::endpoints::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image; -use artcraft_client::endpoints::media_files::get_media_file::get_media_file; -use artcraft_client::endpoints::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs}; -use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file; -use tauri::AppHandle; -use tokens::tokens::media_files::MediaFileToken; - -pub async fn handle_artcraft_nano_banana_inpaint( - request: &EnqueueInpaintImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let creds = match storyteller_creds_manager.get_credentials()? { - Some(creds) => creds, - None => { - return Err(GenerateError::needs_storyteller_credentials()); - }, - }; - - let image_media_token = match &request.image_media_token { - Some(token) => token.clone(), - None => { - return Err(GenerateError::required_source_image_not_provided()); - }, - }; - - info!("Calling Artcraft gemini 2.5 flash inpaint ..."); - - let uuid_idempotency_token = generate_random_uuid(); - - let num_images = match request.image_count { - None => None, - Some(1) => Some(Gemini25FlashEditImageNumImages::One), - Some(2) => Some(Gemini25FlashEditImageNumImages::Two), - Some(3) => Some(Gemini25FlashEditImageNumImages::Three), - Some(4) => Some(Gemini25FlashEditImageNumImages::Four), - Some(other) => { - return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { - min: 1, - max: 4, - requested: other, - })); - }, - }; - - let request = Gemini25FlashEditImageRequest { - uuid_idempotency_token, - prompt: Some(request.prompt.clone()), - image_media_tokens: Some(vec![image_media_token]), - num_images, - image_quality: None, - }; - - let result = gemini_25_flash_edit_image( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - - let job_id = match result { - Ok(enqueued) => { - // TODO(bt,2025-07-05): Enqueue job token? - info!("Successfully enqueued Artcraft gemini 2.5 flash inpaint. Job token: {}", - enqueued.inference_job_token); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft gemini 2.5 flash inpaint: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Artcraft, - model: Some(GenerationModel::Gemini25Flash), - provider_job_id: Some(job_id.to_string()), - task_type: TaskType::ImageInpaintEdit, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} diff --git a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_inpaint_artcraft.rs b/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_inpaint_artcraft.rs deleted file mode 100644 index c677d969..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/handle_inpaint_artcraft.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::deprecated::image_inpaint::artcraft::handle_artcraft_flux_dev_juggernaut_inpaint::handle_artcraft_flux_dev_juggernaut_inpaint; -use crate::commands::deprecated::image_inpaint::artcraft::handle_artcraft_flux_pro_1_inpaint::handle_artcraft_flux_pro_1_inpaint; -use crate::commands::deprecated::image_inpaint::artcraft::handle_artcraft_flux_pro_kontext_inpaint::handle_artcraft_flux_pro_kontext_inpaint; -use crate::commands::deprecated::image_inpaint::artcraft::handle_artcraft_nano_banana_inpaint::handle_artcraft_nano_banana_inpaint; -use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::{EnqueueInpaintImageCommand, ImageInpaintModel}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use tauri::AppHandle; - -pub async fn handle_inpaint_artcraft( - model: ImageInpaintModel, - request: &EnqueueInpaintImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - match model { - ImageInpaintModel::FluxDevJuggernaut => { - handle_artcraft_flux_dev_juggernaut_inpaint( - request, - app, - app_data_root, - app_env_configs, - storyteller_creds_manager, - ).await - } - ImageInpaintModel::FluxPro1 => { - handle_artcraft_flux_pro_1_inpaint( - request, - app, - app_data_root, - app_env_configs, - storyteller_creds_manager, - ).await - } - ImageInpaintModel::FluxProKontextMax => { - handle_artcraft_flux_pro_kontext_inpaint( - request, - app, - app_data_root, - app_env_configs, - storyteller_creds_manager, - ).await - } - ImageInpaintModel::Gemini25Flash => { - handle_artcraft_nano_banana_inpaint( - request, - app, - app_data_root, - app_env_configs, - storyteller_creds_manager, - ).await - } - } -} diff --git a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/mod.rs b/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/mod.rs deleted file mode 100644 index b0b915f5..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_inpaint/artcraft/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub (super) mod handle_inpaint_artcraft; - -mod handle_artcraft_flux_pro_1_inpaint; -mod handle_artcraft_flux_dev_juggernaut_inpaint; -mod handle_artcraft_flux_pro_kontext_inpaint; -mod handle_artcraft_nano_banana_inpaint; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/deprecated/image_inpaint/enqueue_image_inpaint_command.rs b/crates/artcraftx/src/commands/deprecated/image_inpaint/enqueue_image_inpaint_command.rs deleted file mode 100644 index 2ee8ba12..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_inpaint/enqueue_image_inpaint_command.rs +++ /dev/null @@ -1,348 +0,0 @@ -use crate::api_adapters::quality::common_quality::CommonQuality; -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type; -use crate::commands::deprecated::image_inpaint::artcraft::handle_inpaint_artcraft::handle_inpaint_artcraft; -use crate::commands::deprecated::image_inpaint::inpaint_models::image_inpaint_model_to_model_type; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; -use crate::commands::response::shorthand::{Response, ResponseOrErrorType}; -use crate::commands::response::success_response_wrapper::SerializeMarker; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; -use crate::events::sendable_event_trait::SendableEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; -use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType}; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::data_dir::subdirectory::trait_data_subdir::DataSubdir; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::state::task_database::TaskDatabase; -use crate::utils::get_url_file_extension::get_url_file_extension; -use crate::utils::simple_http_download::simple_http_download; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use artcraft_client::endpoints::media_files::get_media_file::get_media_file; -use artcraft_client::error::storyteller_error::StorytellerError; -use artcraft_client::utils::api_host::ApiHost; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; -use errors::AnyhowError; -use log::{error, info, warn}; -use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest}; -use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest}; -use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; -use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages}; -use serde_derive::{Deserialize, Serialize}; -use sqlite_database::queries::create_task::{create_task, CreateTaskArgs}; -use std::time::Duration; -use tauri::{AppHandle, Manager, State}; -use tokens::tokens::media_files::MediaFileToken; - -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum ImageInpaintModel { - /* - * Mask-based inpainting models - */ - - #[serde(rename = "flux_dev_juggernaut")] - FluxDevJuggernaut, - - #[serde(rename = "flux_pro_1")] - FluxPro1, - - /* - * Non-inpainting, instructive editing models - * NB: We're supporting these to keep the app simple and not over-complicate the javascript - */ - - #[serde(rename = "flux_pro_kontext_max")] - FluxProKontextMax, - - #[serde(rename = "gemini_25_flash")] - Gemini25Flash, -} - -#[derive(Deserialize, Debug)] -pub struct EnqueueInpaintImageCommand { - /// The provider to use (defaults to Artcraft/Storyteller). - /// Not all (provider, model) combinations are valid. - pub provider: Option, - - /// REQUIRED (Option is just for error messages). - /// The model to use. - pub model: Option, - - /// REQUIRED (Option is just for error messages). - /// The source image to edit. - pub image_media_token: Option, - - /// REQUIRED: Supply this *XOR* `mask_image_raw_bytes`. - /// The mask to focus the edit (already uploaded). - pub mask_image_media_token: Option, - - /// REQUIRED: Supply this *XOR* `mask_image_media_token`. - /// The mask to focus the edit (raw bytes). - pub mask_image_raw_bytes: Option>, - - /// REQUIRED. - /// The user's image generation prompt. - pub prompt: String, - - /// Number of images to generate. - pub image_count: Option, - - /// Quality. - /// Tends to be used by OpenAI models. - pub quality: Option, - - /// If true, force the dimensions of the source image and mask image to match. - pub require_matching_dimensions: Option, - - /// OPTIONAL. - /// Name of the frontend caller. - /// We'll use this to selectively trigger events. - pub frontend_caller: Option, - - /// OPTIONAL. - /// A frontend-defined identifier that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_id: Option, - - /// OPTIONAL. - /// A frontend-defined payload that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_payload: Option, -} - -#[derive(Serialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum EnqueueInpaintImageErrorType { - /// Caller didn't specify a model - ModelNotSpecified, - - /// No model available for image generation - NoProviderAvailable, - - /// No source image was supplied. - NoSourceImageSpecified, - - /// No mask image was supplied. - NoMaskImageSpecified, - - /// Too many mask images were supplied. - MultipleMaskImagesSpecified, - - /// Bad mask image was supplied. - BadMaskImage, - - /// Generic bad request error - BadRequest, - - /// Generic server error - ServerError, -} - -#[derive(Serialize)] -pub struct EnqueueImageInpaintSuccessResponse { -} - -impl SerializeMarker for EnqueueImageInpaintSuccessResponse {} - -#[tauri::command] -pub async fn enqueue_image_inpaint_command( - app: AppHandle, - request: EnqueueInpaintImageCommand, - app_data_root: State<'_, AppDataRoot>, - app_env_configs: State<'_, AppEnvConfigs>, - artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>, - provider_priority_store: State<'_, ProviderPriorityStore>, - task_database: State<'_, TaskDatabase>, - storyteller_creds_manager: State<'_, StorytellerCredentialManager>, - sora_creds_manager: State<'_, SoraCredentialManager>, - sora_task_queue: State<'_, SoraTaskQueue>, -) -> ResponseOrErrorType { - - info!("enqueue_image_inpaint_command called; model: {:?}, image_media_token: {:?}, prompt: {:?}, image_count: {:?}", - &request.model, &request.image_media_token, &request.prompt, &request.image_count); - - let result = handle_request( - &request, - &app, - &app_data_root, - &app_env_configs, - &artcraft_usage_tracker, - &provider_priority_store, - &task_database, - &storyteller_creds_manager, - &sora_creds_manager, - &sora_task_queue, - ).await; - - match result { - Err(err) => { - error!("Error enqueuing inpaint image: {:?}", err); - - notify_frontend_of_errors(&app, &err).await; - - // TODO: Derive from err. Make service provider optional. - let event = GenerationEnqueueFailureEvent { - action: GenerationAction::GenerateImage, - service: GenerationServiceProvider::Artcraft, // FIXME: This is wrong. - model: None, - reason: None, - }; - - if let Err(err) = event.send(&app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - Err(error_to_tauri_response(err)) - } - Ok(event) => { - let event = GenerationEnqueueSuccessEvent { - action: event.to_frontend_event_action(), - service: event.to_frontend_event_service(), - model: event.model, - }; - - if let Err(err) = event.send(&app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - CreditsBalanceChangedEvent{}.send_infallible(&app); - - Ok(EnqueueImageInpaintSuccessResponse {}.into()) - } - } -} - -pub async fn handle_request( - request: &EnqueueInpaintImageCommand, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - artcraft_usage_tracker: &ArtcraftUsageTracker, - provider_priority_store: &ProviderPriorityStore, - task_database: &TaskDatabase, - storyteller_creds_manager: &StorytellerCredentialManager, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - - let model = match request.model { - Some(model) => model, - None => { - return Err(GenerateError::no_model_specified()) - } - }; - - let provider = request.provider - .unwrap_or(GenerationProvider::Artcraft); - - info!("image inpaint with {:?} via provider {:?}", &model, &provider); - - let success_event = match provider { - GenerationProvider::Artcraft => { - handle_inpaint_artcraft( - model, - request, - app, - app_data_root, - app_env_configs, - storyteller_creds_manager, - ).await? - } - _ => { - return Err(GenerateError::BadProviderForModel { - provider, - model: image_inpaint_model_to_model_type(model), - }) - } - }; - - let result = success_event - .insert_into_task_database_with_frontend_payload( - task_database, - request.frontend_caller, - request.frontend_subscriber_id.as_deref(), - request.frontend_subscriber_payload.as_deref() - ) - .await; - - if let Err(err) = result { - error!("Failed to create task in database: {:?}", err); - // NB: Fail open, but find a way to flag this. - } - - // TODO: We don't know that this is strictly the edit page. It could be the stage. It might also be nice to distinguish inpainting. - let num_images = request.image_count.map(|count| count as u16).unwrap_or(1); - if let Err(err) = artcraft_usage_tracker.record_image_generation(num_images, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::EditPage) { - // NB: Fail open. - warn!("Failed to report usage: {:?}", err); - } - - Ok(success_event) -} - -pub fn error_to_tauri_response(error: GenerateError) -> CommandErrorResponseWrapper { - let mut status = CommandErrorStatus::ServerError; - let mut error_type = EnqueueInpaintImageErrorType::ServerError; - let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.".to_string(); - - match error { - GenerateError::BadInput(BadInputReason::NoModelSpecified) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueInpaintImageErrorType::ModelNotSpecified; - error_message = "No model specified for image generation".to_string(); - } - GenerateError::NoProviderAvailable => { - status = CommandErrorStatus::ServerError; - error_type = EnqueueInpaintImageErrorType::NoProviderAvailable; - error_message = "No configured provider available for image generation".to_string(); - } - GenerateError::BadInput(BadInputReason::RequiredSourceImageNotProvided) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueInpaintImageErrorType::NoSourceImageSpecified; - error_message = "No source image was provided".to_string(); - } - GenerateError::BadInput(BadInputReason::RequiredSourceImageMaskNotProvided) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueInpaintImageErrorType::NoMaskImageSpecified; - error_message = "No mask image was provided".to_string(); - } - GenerateError::BadInput(BadInputReason::BothImageMaskMediaTokenAndBytesSupplied) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueInpaintImageErrorType::MultipleMaskImagesSpecified; - error_message = "multiple mask images provided".to_string(); - } - GenerateError::BadInput(BadInputReason::CannotDetermineImageMimeType) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueInpaintImageErrorType::BadMaskImage; - error_message = "bad mask image (mime)".to_string(); - } - GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { min, max, requested }) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueInpaintImageErrorType::BadRequest; - error_message = format!("Invalid number of images requested ({}). Must be between {} and {}", requested, min, max); - } - GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages{ min, max, provided }) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueInpaintImageErrorType::BadRequest; - error_message = format!("Invalid number of input images ({}). Must be between {} and {}", provided, min, max); - } - _ => {}, // Other cases fall through. - } - - CommandErrorResponseWrapper { - status, - error_message: Some(error_message.to_string()), - error_type: Some(error_type), - error_details: None, - } -} diff --git a/crates/artcraftx/src/commands/deprecated/image_inpaint/inpaint_models.rs b/crates/artcraftx/src/commands/deprecated/image_inpaint/inpaint_models.rs deleted file mode 100644 index 6dd3ce33..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_inpaint/inpaint_models.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::ImageInpaintModel; -use enums::common::generation::common_model_type::CommonModelType; - -pub fn image_inpaint_model_to_model_type(model: ImageInpaintModel) -> CommonModelType { - match model { - ImageInpaintModel::FluxDevJuggernaut => CommonModelType::FluxDevJuggernaut, - ImageInpaintModel::FluxPro1 => CommonModelType::FluxPro1, - ImageInpaintModel::FluxProKontextMax => CommonModelType::FluxProKontextMax, - ImageInpaintModel::Gemini25Flash => CommonModelType::NanoBanana, - } -} diff --git a/crates/artcraftx/src/commands/deprecated/image_inpaint/mod.rs b/crates/artcraftx/src/commands/deprecated/image_inpaint/mod.rs deleted file mode 100644 index f57a2782..00000000 --- a/crates/artcraftx/src/commands/deprecated/image_inpaint/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub (super) mod inpaint_models; -pub mod enqueue_image_inpaint_command; - -mod artcraft; diff --git a/crates/artcraftx/src/commands/deprecated/mod.rs b/crates/artcraftx/src/commands/deprecated/mod.rs deleted file mode 100644 index 73029865..00000000 --- a/crates/artcraftx/src/commands/deprecated/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod image_edit; -pub mod image_inpaint; -pub mod text_to_image; diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs deleted file mode 100644 index 285963a7..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use artcraft_client::api_defs::generate::image::text::generate_gpt_image_1_text_to_image::{GenerateGptImage1TextToImageImageQuality, GenerateGptImage1TextToImageImageSize, GenerateGptImage1TextToImageNumImages, GenerateGptImage1TextToImageRequest}; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use log::{error, info}; -use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; -use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest}; -use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages}; -use std::time::Duration; -use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image; -use artcraft_client::endpoints::generate::image::text::generate_gpt_image_1_text_to_image::generate_gpt_image_1_text_to_image; -use tauri::AppHandle; - -pub async fn handle_artcraft_gpt_image_1_text_to_image( - request: &EnqueueTextToImageRequest, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let creds = match storyteller_creds_manager.get_credentials()? { - Some(creds) => creds, - None => { - return Err(GenerateError::needs_storyteller_credentials()); - }, - }; - - info!("Calling Artcraft gpt-image-1 ..."); - - let uuid_idempotency_token = generate_random_uuid(); - - let image_quality = Some(GenerateGptImage1TextToImageImageQuality::High); - - //let image_quality = request.image_quality - // .map(|quality| match quality { - // EditImageQuality::Auto => GenerateGptImage1TextToImageImageQuality::Auto, - // EditImageQuality::High => GenerateGptImage1TextToImageImageQuality::High, - // EditImageQuality::Medium => GenerateGptImage1TextToImageImageQuality::Medium, - // EditImageQuality::Low => GenerateGptImage1TextToImageImageQuality::Low, - // }); - - let image_size = request.aspect_ratio - .map(|size| match size { - TextToImageSize::Auto => GenerateGptImage1TextToImageImageSize::Square, - TextToImageSize::Square => GenerateGptImage1TextToImageImageSize::Square, - TextToImageSize::Tall => GenerateGptImage1TextToImageImageSize::Vertical, - TextToImageSize::Wide => GenerateGptImage1TextToImageImageSize::Horizontal, - }); - - let num_images = match request.number_images { - None => None, - Some(1) => Some(GenerateGptImage1TextToImageNumImages::One), - Some(2) => Some(GenerateGptImage1TextToImageNumImages::Two), - Some(3) => Some(GenerateGptImage1TextToImageNumImages::Three), - Some(4) => Some(GenerateGptImage1TextToImageNumImages::Four), - // TODO: Error - //Some(other) => { - // return Err(InternalImageError::InvalidNumberOfRequestedImages { - // min: 1, - // max: 4, - // requested: other, - // }); - //}, - _ => Some(GenerateGptImage1TextToImageNumImages::One), // Default to one image if invalid number - }; - - let request = GenerateGptImage1TextToImageRequest { - uuid_idempotency_token, - prompt: request.prompt.clone(), - image_size, - num_images, - image_quality, - }; - - let result = generate_gpt_image_1_text_to_image( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - - let job_id = match result { - Ok(enqueued) => { - // TODO(bt,2025-07-05): Enqueue job token? - info!("Successfully enqueued Artcraft gpt-image-1. Job token: {}", - enqueued.inference_job_token); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft gpt-image-1: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Artcraft, - model: Some(GenerationModel::GptImage1), - provider_job_id: Some(job_id.to_string()), - task_type: TaskType::ImageGeneration, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/handle_text_to_image_artcraft.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/handle_text_to_image_artcraft.rs deleted file mode 100644 index 96b4cf0a..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/handle_text_to_image_artcraft.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::artcraft::handle_artcraft_gpt_image_1_text_to_image::handle_artcraft_gpt_image_1_text_to_image; -use crate::commands::deprecated::text_to_image::artcraft::handle_text_to_image_artcraft_via_router::handle_text_to_image_artcraft_via_router; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageModel}; -use crate::commands::deprecated::text_to_image::text_to_image_models::text_to_image_model_to_model_type; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use router::api::router_image_model::RouterImageModel; -use enums::common::generation_provider::GenerationProvider; -use tauri::AppHandle; - -pub async fn handle_text_to_image_artcraft( - model: TextToImageModel, - request: &EnqueueTextToImageRequest, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - match model { - TextToImageModel::Midjourney | TextToImageModel::GrokImage => { - Err(GenerateError::BadProviderForModel { - provider: GenerationProvider::Artcraft, - model: text_to_image_model_to_model_type(model), - }) - } - TextToImageModel::Recraft3 => { - Err(GenerateError::NotYetImplemented(format!("not yet implemented in Artcraft"))) - } - TextToImageModel::Flux1Dev => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Flux1Dev, GenerationModel::Flux1Dev).await, - TextToImageModel::Flux1Schnell => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Flux1Schnell, GenerationModel::Flux1Schnell).await, - TextToImageModel::FluxPro11 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::FluxPro11, GenerationModel::FluxPro11).await, - TextToImageModel::FluxPro11Ultra => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::FluxPro11Ultra, GenerationModel::FluxPro11Ultra).await, - TextToImageModel::GptImage1 => handle_artcraft_gpt_image_1_text_to_image(request, app_env_configs, storyteller_creds_manager).await, - TextToImageModel::GptImage1p5 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::GptImage1p5, GenerationModel::GptImage1p5).await, - TextToImageModel::GptImage2 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::GptImage2, GenerationModel::GptImage2).await, - TextToImageModel::Gemini25Flash | TextToImageModel::NanoBanana => { - handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBanana, GenerationModel::NanoBanana).await - }, - TextToImageModel::NanoBanana2 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBanana2, GenerationModel::NanoBanana2).await, - TextToImageModel::NanoBananaPro => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBananaPro, GenerationModel::NanoBananaPro).await, - TextToImageModel::Seedream4 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream4, GenerationModel::Seedream4).await, - TextToImageModel::Seedream4p5 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream4p5, GenerationModel::Seedream4p5).await, - TextToImageModel::Seedream5Lite => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream5Lite, GenerationModel::Seedream5Lite).await, - } -} diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/handle_text_to_image_artcraft_via_router.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/handle_text_to_image_artcraft_via_router.rs deleted file mode 100644 index 3b7b4b0a..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/handle_text_to_image_artcraft_via_router.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio as CommonAspectRatio2; -use crate::api_adapters::resolution::common_resolution::CommonResolution as CommonResolution2; -use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{ - EnqueueTextToImageRequest, TextToImageResolution, TextToImageSize, -}; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use router::api::router_aspect_ratio::RouterAspectRatio; -use router::api::router_image_model::RouterImageModel; -use router::api::router_resolution::RouterResolution; -use router::api::router_quality::RouterQuality; -use router::api::image_list_ref::ImageListRef; -use router::api::router_provider::RouterProvider; -use router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; -use router::client::router_artcraft_client::RouterArtcraftClient; -use router::client::router_client::RouterClient; -use router::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder; -use router::generate::generate_image::image_generation_draft_or_request::ImageGenerationDraftOrRequest; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use log::{error, info}; - -pub(super) async fn handle_text_to_image_artcraft_via_router( - request: &EnqueueTextToImageRequest, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, - model: RouterImageModel, - generation_model: GenerationModel, -) -> Result { - 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, - )); - - let image_inputs = request.image_media_tokens.clone().map(ImageListRef::MediaFileTokens); - - let aspect_ratio = get_aspect_ratio_t2i(request); - let resolution = get_resolution_t2i(request); - let quality = get_quality_t2i(request); - - let router_request = GenerateImageRequestBuilder { - model, - provider: RouterProvider::Artcraft, - prompt: request.prompt.clone(), - image_inputs, - resolution, - aspect_ratio, - quality, - image_batch_count: request.number_images.map(|n| n as u16), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, - generation_mode_mismatch_strategy: None, - idempotency_token: None, - horizontal_angle: None, - vertical_angle: None, - zoom: None, - }; - - let dor = router_request.build2()?; - - info!("Image Generation Request: {:?}", dor); - - let request = match dor { - ImageGenerationDraftOrRequest::Request(req) => req, - // Artcraft never returns a Draft — image-token resolution happens server-side. - // The only draft-producing models today are Kinovi-Midjourney variants, - // which are routed to a separate provider. - ImageGenerationDraftOrRequest::Draft(_) => unreachable!( - "Artcraft router should never produce a draft for text-to-image" - ), - }; - - let response = match request.send_request(&client).await { - Ok(resp) => { - info!("Successfully enqueued."); - resp - } - Err(err) => { - error!("Failed to enqueue: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - let job_id = response - .get_artcraft_payload() - .map(|p| p.inference_job_token.to_string()) - .ok_or(GenerateError::ResponseHadNoJobTokens)?; - - Ok(TaskEnqueueSuccess { - task_type: TaskType::ImageGeneration, - model: Some(generation_model), - provider: GenerationProvider::Artcraft, - provider_job_id: Some(job_id), - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} - -fn get_aspect_ratio_t2i(request: &EnqueueTextToImageRequest) -> Option { - if let Some(ar) = request.common_aspect_ratio { - return Some(convert_desktop_aspect_ratio(ar)); - } - if let Some(ar) = request.aspect_ratio { - return Some(match ar { - TextToImageSize::Auto => RouterAspectRatio::Auto, - TextToImageSize::Square => RouterAspectRatio::Square, - TextToImageSize::Wide => RouterAspectRatio::Wide, - TextToImageSize::Tall => RouterAspectRatio::Tall, - }); - } - None -} - -fn get_resolution_t2i(request: &EnqueueTextToImageRequest) -> Option { - if let Some(res) = request.common_resolution { - return Some(convert_desktop_resolution(res)); - } - if let Some(res) = request.image_resolution { - return Some(match res { - TextToImageResolution::OneK => RouterResolution::OneK, - TextToImageResolution::TwoK => RouterResolution::TwoK, - TextToImageResolution::FourK => RouterResolution::FourK, - }); - } - None -} - -fn get_quality_t2i(request: &EnqueueTextToImageRequest) -> Option { - request.quality.map(|quality| quality.to_artcraft_router_type()) -} - -fn convert_desktop_aspect_ratio(ar: CommonAspectRatio2) -> RouterAspectRatio { - match ar { - CommonAspectRatio2::Auto => RouterAspectRatio::Auto, - CommonAspectRatio2::Square => RouterAspectRatio::Square, - CommonAspectRatio2::WideThreeByTwo => RouterAspectRatio::WideThreeByTwo, - CommonAspectRatio2::WideFourByThree => RouterAspectRatio::WideFourByThree, - CommonAspectRatio2::WideFiveByFour => RouterAspectRatio::WideFiveByFour, - CommonAspectRatio2::WideSixteenByNine => RouterAspectRatio::WideSixteenByNine, - CommonAspectRatio2::WideTwentyOneByNine => RouterAspectRatio::WideTwentyOneByNine, - CommonAspectRatio2::TallTwoByThree => RouterAspectRatio::TallTwoByThree, - CommonAspectRatio2::TallThreeByFour => RouterAspectRatio::TallThreeByFour, - CommonAspectRatio2::TallFourByFive => RouterAspectRatio::TallFourByFive, - CommonAspectRatio2::TallNineBySixteen => RouterAspectRatio::TallNineBySixteen, - CommonAspectRatio2::TallNineByTwentyOne => RouterAspectRatio::TallNineByTwentyOne, - CommonAspectRatio2::Wide => RouterAspectRatio::Wide, - CommonAspectRatio2::Tall => RouterAspectRatio::Tall, - CommonAspectRatio2::Auto2k => RouterAspectRatio::Auto2k, - CommonAspectRatio2::Auto4k => RouterAspectRatio::Auto4k, - CommonAspectRatio2::SquareHd => RouterAspectRatio::SquareHd, - } -} - -fn convert_desktop_resolution(res: CommonResolution2) -> RouterResolution { - match res { - CommonResolution2::OneK => RouterResolution::OneK, - CommonResolution2::TwoK => RouterResolution::TwoK, - CommonResolution2::ThreeK => RouterResolution::ThreeK, - CommonResolution2::FourK => RouterResolution::FourK, - CommonResolution2::HalfK => RouterResolution::HalfK, - CommonResolution2::FourEightyP => RouterResolution::FourEightyP, - CommonResolution2::SevenTwentyP => RouterResolution::SevenTwentyP, - CommonResolution2::TenEightyP => RouterResolution::TenEightyP, - } -} diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/mod.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/mod.rs deleted file mode 100644 index 572b1249..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/artcraft/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub (super) mod handle_text_to_image_artcraft; - -mod handle_text_to_image_artcraft_via_router; -mod handle_artcraft_gpt_image_1_text_to_image; diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/enqueue_text_to_image_command.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/enqueue_text_to_image_command.rs deleted file mode 100644 index 8457f8a8..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/enqueue_text_to_image_command.rs +++ /dev/null @@ -1,425 +0,0 @@ -use crate::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio; -use crate::api_adapters::quality::common_quality::CommonQuality; -use crate::api_adapters::resolution::common_resolution::CommonResolution; -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason}; -use crate::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::artcraft::handle_text_to_image_artcraft::handle_text_to_image_artcraft; -use crate::commands::deprecated::text_to_image::grok::handle_grok::handle_grok; -use crate::commands::deprecated::text_to_image::midjourney::handle_midjourney::handle_midjourney; -use crate::commands::deprecated::text_to_image::sora::handle_text_to_image_sora::handle_text_to_image_sora; -use crate::commands::deprecated::text_to_image::text_to_image_models::text_to_image_model_to_model_type; -use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; -use crate::commands::response::shorthand::Response; -use crate::commands::response::success_response_wrapper::SerializeMarker; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; -use crate::events::sendable_event_trait::SendableEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; -use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType}; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::{Provider, ProviderPriorityStore}; -use crate::state::task_database::TaskDatabase; -use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; -use crate::services::grok::state::grok_image_prompt_queue::GrokImagePromptQueue; -use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; -use log::{error, info, warn}; -use serde::{Deserialize, Serialize}; -use sqlite_database::queries::create_task::{create_task, CreateTaskArgs}; -use tauri::{AppHandle, State}; -use tokens::tokens::media_files::MediaFileToken; - -/// This is used in the Tauri command bridge. -/// Don't change the serializations without coordinating with the frontend. -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum TextToImageModel { - #[serde(rename = "flux_1_dev")] - Flux1Dev, - #[serde(rename = "flux_1_schnell")] - Flux1Schnell, - #[serde(rename = "flux_pro_11")] - FluxPro11, - #[serde(rename = "flux_pro_11_ultra")] - FluxPro11Ultra, - #[serde(rename = "grok_image")] - GrokImage, - #[serde(rename = "recraft_3")] - Recraft3, - - #[serde(rename = "gpt_image_1")] - GptImage1, - #[serde(rename = "gpt_image_1p5")] - GptImage1p5, - #[serde(rename = "gpt_image_2")] - GptImage2, - #[serde(rename = "gemini_25_flash")] - 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")] - Midjourney, -} - -#[derive(Deserialize, Debug)] -pub struct EnqueueTextToImageRequest { - /// The provider to use (defaults to Artcraft/Storyteller). - /// Not all (provider, model) combinations are valid. - pub provider: Option, - - /// The model to use. - pub model: Option, - - /// Text prompt for the image generation. Required. - pub prompt: Option, - - /// New field for aspect ratio. - /// Not all models support each of these aspect ratios, but we can choose or interpolate sensibly. - pub common_aspect_ratio: Option, - - /// New field for resolution. - /// Not all models support each of these resolutions, but we can choose or interpolate sensibly. - pub common_resolution: Option, - - /// Quality. - /// Tends to be used by OpenAI models. - pub quality: Option, - - /// Aspect ratio. - #[deprecated(note="use common_aspect_ratio")] - pub aspect_ratio: Option, - - /// Aspect ratio. - #[deprecated(note="use common_resolution")] - pub image_resolution: Option, - - /// The number of images to generate. - pub number_images: Option, - - /// OPTIONAL. - /// Reference images (without semantics) - /// The purpose varies on a model-by-model basis, but they - /// are not semantically treated as "style reference", - /// "character/object reference", etc. - pub image_media_tokens: Option>, - - /// OPTIONAL. - /// Name of the frontend caller. - /// We'll use this to selectively trigger events. - pub frontend_caller: Option, - - /// OPTIONAL. - /// A frontend-defined identifier that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_id: Option, - - /// OPTIONAL. - /// A frontend-defined payload that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_payload: Option, -} - -// TODO(bt,2025-07-14): Support other aspect ratios / resolutions - -// Flux Dev has: 4:3, 16:9, 3:4, 9:16, and custom. -// Flux Schnell has: 4:3, 16:9, 3:4, 9:16, and custom. -// Flux Pro has: 4:3, 16:9, 3:4, 9:16, and custom. -// Flux Pro Ulra has: 4:3, 16:9, 3:4, 9:16, and custom. - -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum TextToImageSize { - Auto, - Square, - Wide, - Tall, -} - -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum TextToImageResolution { - /// 1K - nano banana pro - OneK, - /// 2K - nano banana pro - TwoK, - /// 4K - nano banana pro - FourK, -} - -#[derive(Serialize)] -pub struct EnqueueTextToImageSuccessResponse { -} - -impl SerializeMarker for EnqueueTextToImageSuccessResponse {} - -#[derive(Serialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum EnqueueTextToImageErrorType { - /// Caller didn't specify a model - ModelNotSpecified, - /// No model available for image generation - NoProviderAvailable, - /// Generic server error - ServerError, - /// No Fal API key available - NeedsFalApiKey, - /// Fal had an API error - FalError, - /// Needs to be logged into Artcraft - NeedsStorytellerCredentials, -} - -#[tauri::command] -pub async fn enqueue_text_to_image_command( - request: EnqueueTextToImageRequest, - app: AppHandle, - app_data_root: State<'_, AppDataRoot>, - app_env_configs: State<'_, AppEnvConfigs>, - artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>, - provider_priority_store: State<'_, ProviderPriorityStore>, - task_database: State<'_, TaskDatabase>, - mj_creds_manager: State<'_, MidjourneyCredentialManager>, - grok_creds_manager: State<'_, GrokCredentialManager>, - grok_image_prompt_queue: State<'_, GrokImagePromptQueue>, - storyteller_creds_manager: State<'_, StorytellerCredentialManager>, - sora_creds_manager: State<'_, SoraCredentialManager>, - sora_task_queue: State<'_, SoraTaskQueue>, -) -> Response { - - info!("enqueue_text_to_image called"); - - info!("request: {:?}", request); - - let result = handle_request( - request, - &app, - &app_data_root, - &artcraft_usage_tracker, - &provider_priority_store, - &task_database, - &mj_creds_manager, - &grok_creds_manager, - &grok_image_prompt_queue, - &storyteller_creds_manager, - &app_env_configs, - &sora_creds_manager, - &sora_task_queue, - ).await; - - match result { - Err(err) => { - error!("error: {:?}", err); - - notify_frontend_of_errors(&app, &err).await; - - let mut status = CommandErrorStatus::ServerError; - let mut error_type = EnqueueTextToImageErrorType::ServerError; - let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem."; - - match err { - GenerateError::BadInput(BadInputReason::NoModelSpecified) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueTextToImageErrorType::ModelNotSpecified; - error_message = "No model specified for image generation"; - } - GenerateError::NoProviderAvailable => { - status = CommandErrorStatus::ServerError; - error_type = EnqueueTextToImageErrorType::NoProviderAvailable; - error_message = "No configured provider available for image generation"; - } - GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => { - status = CommandErrorStatus::Unauthorized; - error_type = EnqueueTextToImageErrorType::NeedsFalApiKey; - error_message = "You need to set a FAL api key"; - }, - _ => {}, // Fall-through - } - - Err(CommandErrorResponseWrapper { - status, - error_message: Some(error_message.to_string()), - error_type: Some(error_type), - error_details: None, - }) - } - Ok(event) => { - let event = GenerationEnqueueSuccessEvent { - action: event.to_frontend_event_action(), - service: event.to_frontend_event_service(), - model: event.model, - }; - - if let Err(err) = event.send(&app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - CreditsBalanceChangedEvent{}.send_infallible(&app); - - Ok(EnqueueTextToImageSuccessResponse {}.into()) - } - } -} - -pub async fn handle_request( - request: EnqueueTextToImageRequest, - app: &AppHandle, - app_data_root: &AppDataRoot, - artcraft_usage_tracker: &ArtcraftUsageTracker, - provider_priority_store: &ProviderPriorityStore, - task_database: &TaskDatabase, - mj_creds_manager: &MidjourneyCredentialManager, - grok_creds_manager: &GrokCredentialManager, - grok_image_prompt_queue: &GrokImagePromptQueue, - storyteller_creds_manager: &StorytellerCredentialManager, - app_env_configs: &AppEnvConfigs, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - - let result = dispatch_request( - &request, - &app, - &app_data_root, - &provider_priority_store, - &storyteller_creds_manager, - &app_env_configs, - &mj_creds_manager, - &grok_creds_manager, - &grok_image_prompt_queue, - &sora_creds_manager, - &sora_task_queue, - ).await; - - let success_event = match result { - Err(err) => return Err(err), - Ok(event) => event, - }; - - let result = success_event - .insert_into_task_database_with_frontend_payload( - task_database, - request.frontend_caller, - request.frontend_subscriber_id.as_deref(), - request.frontend_subscriber_payload.as_deref(), - ) - .await; - - if let Err(err) = result { - error!("Failed to create task in database: {:?}", err); - // NB: Fail open, but find a way to flag this. - } - - let num_images = request.number_images.map(|n| n as u16).unwrap_or(1); - - let is_image_to_image = request.image_media_tokens - .as_ref() - .map(|tokens| !tokens.is_empty()) - .unwrap_or(false); - - let usage_type = if is_image_to_image { - ArtcraftUsageType::ImageToResult - } else { - ArtcraftUsageType::TextToResult - }; - - if let Err(err) = artcraft_usage_tracker.record_image_generation(num_images, usage_type, ArtcraftUsagePage::ImagePage) { - // NB: Fail open. - warn!("Failed to report usage: {:?}", err); - } - - Ok(success_event) -} - -pub async fn dispatch_request( - request: &EnqueueTextToImageRequest, - app: &AppHandle, - app_data_root: &AppDataRoot, - provider_priority_store: &ProviderPriorityStore, - storyteller_creds_manager: &StorytellerCredentialManager, - app_env_configs: &AppEnvConfigs, - mj_creds_manager: &MidjourneyCredentialManager, - grok_creds_manager: &GrokCredentialManager, - grok_image_prompt_queue: &GrokImagePromptQueue, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - - let model = match request.model { - Some(model) => model, - None => { - return Err(GenerateError::no_model_specified()) - } - }; - - let provider = match (model, request.provider) { - (TextToImageModel::GrokImage, _) => GenerationProvider::Grok, - (TextToImageModel::Midjourney, _) => GenerationProvider::Midjourney, - _ => request.provider.unwrap_or(GenerationProvider::Artcraft), - }; - - info!("generate image with {:?} via provider {:?}", &model, &provider); - - match provider { - GenerationProvider::Artcraft => { - handle_text_to_image_artcraft( - model, - request, - app, - app_data_root, - app_env_configs, - storyteller_creds_manager, - ).await - } - GenerationProvider::Grok => { - handle_grok( - app, - request, - app_env_configs, - grok_creds_manager, - grok_image_prompt_queue, - ).await - } - GenerationProvider::Midjourney => { - handle_midjourney( - app, - request, - app_env_configs, - mj_creds_manager, - ).await - } - GenerationProvider::Sora => { - handle_text_to_image_sora( - request, - app, - sora_creds_manager, - sora_task_queue, - ).await - } - _ => { - Err(GenerateError::BadProviderForModel { - provider, - model: text_to_image_model_to_model_type(model), - }) - } - } -} diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/grok/handle_grok.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/grok/handle_grok.rs deleted file mode 100644 index 3fd23ef3..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/grok/handle_grok.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; -use crate::services::grok::state::grok_image_prompt_queue::{GrokImagePromptQueue, PromptItem}; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use grok_consumer_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio; -use uuid_utils::uuid::generate_random_uuid; -use tauri::AppHandle; - -pub async fn handle_grok( - app: &AppHandle, - request: &EnqueueTextToImageRequest, - app_env_configs: &AppEnvConfigs, - creds_manager: &GrokCredentialManager, - grok_image_prompt_queue: &GrokImagePromptQueue, -) -> Result { - - - //// TODO: We can request population of the user info if absent or expired. - //let websocket = get_websocket( - // app, - // creds_manager, - // websocket_manager, - //).await?; - // - //let _result = prompt_websocket_image(PromptWebsocketImageArgs { - // websocket_wrapped: &websocket, - // prompt: request.prompt.as_deref().unwrap_or_else(||""), - //}).await?; - // - //let request_id = listen_for_websocket_request_id(ListenForWebsocketRequestIdArgs { - // websocket: &websocket, - // timeout: Duration::from_millis(10_000), - //}).await?; - // - //let request_id = match request_id.request_id { - // Some(request_id) => request_id.to_string(), - // None => { - // warn!("No request id from Grok image generation (websocket)"); - // return Err(GenerateError::ProviderFailure(ProviderFailureReason::GrokJobEnqueueFailed)); - // } - //}; - // - //info!("Successfully enqueued MidJourney. Job token: {}", request_id); - - let job_id = generate_random_uuid(); - - let prompt = request.prompt - .as_deref() - .map(|prompt| prompt.trim().to_string()) - .unwrap_or_else(|| "".to_string()); - - let aspect_ratio = get_aspect_ratio(request); - - grok_image_prompt_queue.enqueue(PromptItem { - task_id: job_id.clone(), - prompt, - aspect_ratio: aspect_ratio.unwrap_or(ClientMessageAspectRatio::Square), - })?; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Grok, - model: Some(GenerationModel::GrokImage), - provider_job_id: Some(job_id), - task_type: TaskType::ImageGeneration, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} - -fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option { - if let Some(common_aspect_ratio) = request.common_aspect_ratio { - // Handle modern aspect ratio - let aspect = aspect_ratio_to_grok_image(common_aspect_ratio); - return Some(aspect); - } - - if let Some(aspect_ratio) = request.aspect_ratio { - // Handle deprecated aspect ratio - return match aspect_ratio { - TextToImageSize::Auto => Some(ClientMessageAspectRatio::Square), - TextToImageSize::Square => Some(ClientMessageAspectRatio::Square), - TextToImageSize::Wide => Some(ClientMessageAspectRatio::WideThreeByTwo), - TextToImageSize::Tall => Some(ClientMessageAspectRatio::TallTwoByThree), - } - } - - None -} diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/grok/mod.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/grok/mod.rs deleted file mode 100644 index 181e756f..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/grok/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub (super) mod handle_grok; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/midjourney/handle_midjourney.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/midjourney/handle_midjourney.rs deleted file mode 100644 index 8cd7bcf4..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/midjourney/handle_midjourney.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason, ProviderFailureReason}; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent; -use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::events::warning_events::flash_user_input_error_event::FlashUserInputErrorEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use log::{error, info}; -use midjourney_client::client::midjourney_hostname::MidjourneyHostname; -use midjourney_client::endpoints::submit_job::{submit_job, SubmitJobRequest}; -use midjourney_client::error::midjourney_api_error::MidjourneyApiError; -use midjourney_client::recipes::channel_id::ChannelId; -use midjourney_client::recipes::text_to_image::{text_to_image, TextToImageError, TextToImageRequest}; -use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; -use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest}; -use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages}; -use std::time::Duration; -use tauri::AppHandle; -use tokens::tokens::media_files::MediaFileToken; - -pub async fn handle_midjourney( - app: &AppHandle, - request: &EnqueueTextToImageRequest, - app_env_configs: &AppEnvConfigs, - mj_creds_manager: &MidjourneyCredentialManager, -) -> Result { - - let creds = match mj_creds_manager.maybe_copy_cookie_store() { - Ok(Some(creds)) => creds, - Ok(None) => { - error!("Midjourney credentials not found."); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app); - return Err(GenerateError::needs_midjourney_credentials()); - } - Err(err) => { - error!("Error reading Midjourney credentials: {:?}", err); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app); - return Err(GenerateError::needs_midjourney_credentials()); - }, - }; - - // TODO: We can request population of the user info if absent or expired. - - let user_info = match mj_creds_manager.maybe_copy_user_info() { - Ok(Some(user_info)) => user_info, - Ok(None) => { - return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo)); - } - Err(err) => { - error!("Error reading Midjourney user info: {:?}", err); - return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo)); - }, - }; - - let channel_id = match user_info.user_id { - Some(user_id) => ChannelId::UserId(user_id), - None => { - error!("Midjourney user info does not contain a user ID."); - return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserId)); - } - }; - - info!("Calling midjourney ..."); - - let cookie_header = creds.to_cookie_string(); - - let prompt = request.prompt - .as_deref() - .unwrap_or(""); - - let result = text_to_image(TextToImageRequest { - prompt, - channel_id: &channel_id, - hostname: MidjourneyHostname::Standard, - cookie_header, - }).await; - - let result = match result { - Ok(result) => result, - Err(err) => { - error!("Failed to use MidJourney: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - let job_id = match result.maybe_job_id { - Some(job_id) => job_id, - None => { - error!("Failed to enqueue MidJourney: No job ID returned."); - return handle_midjourney_errors(app, result.maybe_errors); - } - }; - - info!("Successfully enqueued MidJourney. Job token: {}", job_id); - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Midjourney, - model: Some(GenerationModel::Midjourney), - provider_job_id: Some(job_id), - task_type: TaskType::ImageGeneration, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} - -fn handle_midjourney_errors( - app: &AppHandle, - maybe_errors: Option> -) -> Result { - if let Some(errors) = maybe_errors { - if !errors.is_empty() { - let messages: Vec = errors.iter() - .map(|e| format!("{:?}", e)) - .collect(); - - let combined_message = messages.join("; "); - - let event = FlashUserInputErrorEvent { - message: format!("Midjourney Error: {}", combined_message), - }; - - if let Err(err) = event.send(&app) { - error!("Failed to send FlashUserInputErrorEvent: {:?}", err); // Fail open - } - } - } - - Err(GenerateError::ProviderFailure(ProviderFailureReason::MidjourneyJobEnqueueFailed)) -} diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/midjourney/mod.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/midjourney/mod.rs deleted file mode 100644 index 18048875..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/midjourney/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub (super) mod handle_midjourney; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/mod.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/mod.rs deleted file mode 100644 index e3a7d428..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub (super) mod text_to_image_models; -pub mod enqueue_text_to_image_command; - -mod artcraft; -mod grok; -mod midjourney; -mod sora; diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/sora/handle_gpt_image_1_sora_text_to_image.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/sora/handle_gpt_image_1_sora_text_to_image.rs deleted file mode 100644 index 75fd0598..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/sora/handle_gpt_image_1_sora_text_to_image.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image; -use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_sora_native_gpt_image_1::aspect_ratio_to_sora_native_gpt_image_1; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use errors::AnyhowResult; -use grok_consumer_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio; -use log::{error, info}; -use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; -use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest}; -use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages}; -use std::time::Duration; -use tauri::AppHandle; - -const SORA_SIMPLE_IMAGE_GEN_TIMEOUT : Duration = Duration::from_millis(1000 * 30); // 30 seconds - -pub async fn handle_gpt_image_1_sora_text_to_image( - request: &EnqueueTextToImageRequest, - app: &AppHandle, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - - let mut creds = match sora_creds_manager.get_credentials() { - Ok(Some(creds)) => creds, - Ok(None) => { - error!("Sora credentials not found."); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app); - return Err(GenerateError::needs_sora_credentials()); - } - Err(err) => { - error!("Error reading Sora credentials: {:?}", err); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app); - return Err(GenerateError::needs_sora_credentials()); - } - }; - - let credential_updated = maybe_upgrade_or_renew_session(&mut creds) - .await - .map_err(|err| { - error!("Failed to upgrade or renew session: {:?}", err); - err - })?; - - if credential_updated { - info!("Storing updated credentials"); - sora_creds_manager.set_credentials(&creds)?; - } - - let prompt = request.prompt.as_deref().unwrap_or(""); - - let aspect_ratio = get_aspect_ratio(request) - .unwrap_or(ImageSize::Square); - - let num_images = match request.number_images { - None => NumImages::One, - Some(1) => NumImages::One, - Some(2) => NumImages::Two, - Some(3) => NumImages::Two, // NB: There is no option for three images! - Some(4) => NumImages::Four, - _ => NumImages::One, - }; - - let result = - simple_image_gen_with_session_auto_renew(SimpleImageGenAutoRenewRequest { - prompt: prompt.to_string(), - num_images, - image_size: aspect_ratio, - credentials: &creds, - request_timeout: Some(SORA_SIMPLE_IMAGE_GEN_TIMEOUT), - }).await; - - let (response, maybe_new_creds) = - match result { - Ok((response, maybe_new_creds)) => (response, maybe_new_creds), - Err(err) => { - let event = GenerationEnqueueFailureEvent { - action: GenerationAction::GenerateImage, - service: GenerationServiceProvider::Sora, - model: None, - reason: None, - }; - - if let Err(err) = event.send(app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - return Err(GenerateError::from(err)); - } - }; - - if let Some(new_creds) = maybe_new_creds { - info!("Storing updated credentials."); - sora_creds_manager.set_credentials(&new_creds)?; - } - - info!("New Sora Task ID: {:?} ", response.task_id); - - sora_task_queue.insert(&response.task_id)?; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Sora, - task_type: TaskType::ImageGeneration, - model: Some(GenerationModel::GptImage1), - provider_job_id: Some(response.task_id.to_string()), - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} - -fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option { - if let Some(common_aspect_ratio) = request.common_aspect_ratio { - // Handle modern aspect ratio - let aspect = aspect_ratio_to_sora_native_gpt_image_1(common_aspect_ratio); - return Some(aspect); - } - - if let Some(aspect_ratio) = request.aspect_ratio { - // Handle deprecated aspect ratio - return match aspect_ratio { - TextToImageSize::Auto => Some(ImageSize::Square), - TextToImageSize::Square => Some(ImageSize::Square), - TextToImageSize::Wide => Some(ImageSize::Wide), - TextToImageSize::Tall => Some(ImageSize::Tall), - } - } - - None -} diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/sora/handle_text_to_image_sora.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/sora/handle_text_to_image_sora.rs deleted file mode 100644 index a7714e92..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/sora/handle_text_to_image_sora.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::EnqueueTextToImageRequest; -use crate::commands::deprecated::text_to_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; -use tauri::AppHandle; - -pub async fn handle_text_to_image_sora( - request: &EnqueueTextToImageRequest, - app: &AppHandle, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - handle_gpt_image_1_sora_text_to_image( - request, - app, - sora_creds_manager, - sora_task_queue, - ).await -} diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/sora/mod.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/sora/mod.rs deleted file mode 100644 index 9a9529d3..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/sora/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub (super) mod handle_text_to_image_sora; -mod handle_gpt_image_1_sora_text_to_image; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/deprecated/text_to_image/text_to_image_models.rs b/crates/artcraftx/src/commands/deprecated/text_to_image/text_to_image_models.rs deleted file mode 100644 index 795eac95..00000000 --- a/crates/artcraftx/src/commands/deprecated/text_to_image/text_to_image_models.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::TextToImageModel; -use enums::common::generation::common_model_type::CommonModelType; - -pub fn text_to_image_model_to_model_type(model: TextToImageModel) -> CommonModelType { - match model { - TextToImageModel::Flux1Dev => CommonModelType::Flux1Dev, - TextToImageModel::Flux1Schnell => CommonModelType::Flux1Schnell, - TextToImageModel::FluxPro11 => CommonModelType::FluxPro11, - TextToImageModel::FluxPro11Ultra => CommonModelType::FluxPro11Ultra, - TextToImageModel::GrokImage => CommonModelType::GrokImage, - TextToImageModel::Recraft3 => CommonModelType::Recraft3, - TextToImageModel::GptImage1 => CommonModelType::GptImage1, - TextToImageModel::GptImage1p5 => CommonModelType::GptImage1p5, - TextToImageModel::GptImage2 => CommonModelType::GptImage2, - TextToImageModel::Gemini25Flash => CommonModelType::NanoBanana, - TextToImageModel::NanoBanana => CommonModelType::NanoBanana, - TextToImageModel::NanoBanana2 => CommonModelType::NanoBanana2, - TextToImageModel::NanoBananaPro => CommonModelType::NanoBananaPro, - TextToImageModel::Seedream4 => CommonModelType::Seedream4, - TextToImageModel::Seedream4p5 => CommonModelType::Seedream4p5, - TextToImageModel::Seedream5Lite => CommonModelType::Seedream5Lite, - TextToImageModel::Midjourney => CommonModelType::Midjourney, - } -} diff --git a/crates/artcraftx/src/commands/enqueue/common/mod.rs b/crates/artcraftx/src/commands/enqueue/common/mod.rs deleted file mode 100644 index 89f14fcc..00000000 --- a/crates/artcraftx/src/commands/enqueue/common/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod maybe_notify_frontend_of_billing_errors; -pub mod maybe_notify_frontend_of_credential_errors; -pub mod maybe_notify_frontend_of_grok_errors; -pub mod notify_frontend_of_errors; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/enqueue/common/notify_frontend_of_errors.rs b/crates/artcraftx/src/commands/enqueue/common/notify_frontend_of_errors.rs deleted file mode 100644 index 41c82a4f..00000000 --- a/crates/artcraftx/src/commands/enqueue/common/notify_frontend_of_errors.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::commands::enqueue::common::maybe_notify_frontend_of_billing_errors::maybe_notify_frontend_of_billing_errors; -use crate::commands::enqueue::common::maybe_notify_frontend_of_credential_errors::maybe_notify_frontend_of_credential_errors; -use crate::commands::enqueue::common::maybe_notify_frontend_of_grok_errors::maybe_notify_frontend_of_grok_errors; -use crate::commands::enqueue::generate_error::GenerateError; -use tauri::AppHandle; - -pub async fn notify_frontend_of_errors( - app: &AppHandle, - error: &GenerateError, -) { - maybe_notify_frontend_of_billing_errors(app, error).await; - maybe_notify_frontend_of_credential_errors(app, error).await; - maybe_notify_frontend_of_grok_errors(app, error).await; -} diff --git a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/get_storyteller_creds_or_error.rs b/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/get_storyteller_creds_or_error.rs deleted file mode 100644 index 32cb3ebd..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/get_storyteller_creds_or_error.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::generation_events::common::GenerationAction; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; -use log::error; -use tauri::AppHandle; - -pub(super) fn get_storyteller_creds_or_error( - app: &AppHandle, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - match storyteller_creds_manager.get_credentials()? { - Some(creds) => Ok(creds), - None => { - error!("No Artcraft credentials are set. Can't perform action."); - let event = - GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::GenerateGaussian); - - event.send_infallible(app); - - Err(GenerateError::needs_storyteller_credentials()) - } - } -} diff --git a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/handle_artcraft_gaussian.rs b/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/handle_artcraft_gaussian.rs deleted file mode 100644 index 71db3d3f..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/handle_artcraft_gaussian.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::image_to_gaussian::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::commands::enqueue::image_to_gaussian::artcraft::handle_artcraft_splat_via_router::handle_artcraft_splat_via_router; -use crate::commands::enqueue::image_to_gaussian::enqueue_image_to_gaussian_command::{EnqueueImageToGaussianRequest, GaussianModel}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use router::api::router_splat_model::RouterSplatModel; -use tauri::AppHandle; - -pub async fn handle_gaussian_artcraft( - request: &EnqueueImageToGaussianRequest, - app: &AppHandle, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - let creds = get_storyteller_creds_or_error(app, storyteller_creds_manager)?; - - match request.model { - None => Err(GenerateError::no_model_specified()), - Some(GaussianModel::Marble0p1Mini) => { - handle_artcraft_splat_via_router(request, app_env_configs, &creds, RouterSplatModel::Marble0p1Mini, GenerationModel::WorldlabsMarble0p1Mini).await - } - Some( - GaussianModel::Marble0p1Plus | - GaussianModel::WorldLabsMarble // Legacy variant — route to Plus - ) => { - handle_artcraft_splat_via_router(request, app_env_configs, &creds, RouterSplatModel::Marble0p1Plus, GenerationModel::WorldlabsMarble0p1Plus).await - } - } -} diff --git a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/handle_artcraft_splat_via_router.rs b/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/handle_artcraft_splat_via_router.rs deleted file mode 100644 index ebdd1203..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/handle_artcraft_splat_via_router.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::image_to_gaussian::enqueue_image_to_gaussian_command::EnqueueImageToGaussianRequest; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; -use router::api::router_splat_model::RouterSplatModel; -use router::api::image_list_ref::ImageListRef; -use router::api::router_provider::RouterProvider; -use router::client::router_artcraft_client::RouterArtcraftClient; -use router::client::router_client::RouterClient; -use router::generate::generate_splat::generate_splat_request::GenerateSplatRequest; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use log::{error, info}; - -pub(super) async fn handle_artcraft_splat_via_router( - request: &EnqueueImageToGaussianRequest, - app_env_configs: &AppEnvConfigs, - creds: &StorytellerCredentialSet, - model: RouterSplatModel, - generation_model: GenerationModel, -) -> Result { - let client = RouterClient::Artcraft(RouterArtcraftClient::new( - app_env_configs.storyteller_host.clone(), - creds.clone(), - )); - - let router_request = GenerateSplatRequest { - model, - provider: RouterProvider::Artcraft, - prompt: request.prompt.clone(), - reference_images: request.image_media_tokens.clone().map(ImageListRef::MediaFileTokens), - idempotency_token: None, - }; - - let plan = router_request.build()?; - - info!("Splat Generation Plan: {:?}", plan); - - let response = match plan.generate_splat(&client).await { - Ok(resp) => { - info!("Successfully enqueued splat generation."); - resp - } - Err(err) => { - error!("Failed to enqueue splat generation: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - let job_id = response.get_artcraft_payload() - .map(|p| p.inference_job_token.to_string()) - .ok_or(GenerateError::ResponseHadNoJobTokens)?; - - Ok(TaskEnqueueSuccess { - task_type: TaskType::ImageGeneration, - model: Some(generation_model), - provider: GenerationProvider::Artcraft, - provider_job_id: Some(job_id), - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} diff --git a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/mod.rs b/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/mod.rs deleted file mode 100644 index b4eb5153..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/artcraft/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod get_storyteller_creds_or_error; -mod handle_artcraft_splat_via_router; -pub(super) mod handle_artcraft_gaussian; diff --git a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/enqueue_image_to_gaussian_command.rs b/crates/artcraftx/src/commands/enqueue/image_to_gaussian/enqueue_image_to_gaussian_command.rs deleted file mode 100644 index 404a2204..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/enqueue_image_to_gaussian_command.rs +++ /dev/null @@ -1,262 +0,0 @@ -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason}; -use crate::commands::enqueue::image_to_gaussian::artcraft::handle_artcraft_gaussian::handle_gaussian_artcraft; -use crate::commands::enqueue::image_to_gaussian::worldlabs::handle_worldlabs_marble::handle_worldlabs_marble; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; -use crate::commands::response::shorthand::Response; -use crate::commands::response::success_response_wrapper::SerializeMarker; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; -use crate::events::sendable_event_trait::SendableEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::{Provider, ProviderPriorityStore}; -use crate::state::task_database::TaskDatabase; -use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; -use crate::services::grok::state::grok_image_prompt_queue::GrokImagePromptQueue; -use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use crate::services::worldlabs::state::worldlabs_credential_manager::WorldlabsCredentialManager; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; -use log::{error, info, warn}; -use serde::{Deserialize, Serialize}; -use sqlite_database::queries::create_task::{create_task, CreateTaskArgs}; -use tauri::{AppHandle, State}; -use tokens::tokens::media_files::MediaFileToken; -use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; -use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType}; - -/// This is used in the Tauri command bridge. -/// Don't change the serializations without coordinating with the frontend. -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum GaussianModel { - #[serde(rename = "world_labs_marble")] - WorldLabsMarble, - - #[serde(rename = "marble_0p1_mini")] - Marble0p1Mini, - - #[serde(rename = "marble_0p1_plus")] - Marble0p1Plus, -} - -#[derive(Deserialize)] -pub struct EnqueueImageToGaussianRequest { - /// REQUIRED. - /// The model to use. - pub model: Option, - - /// OPTIONAL. - /// The provider to use (defaults to Artcraft/Storyteller). - /// Not all (provider, model) combinations are valid. - pub provider: Option, - - /// OPTIONAL. - /// Text prompt for the gaussian generation. Required. - pub prompt: Option, - - /// OPTIONAL. - /// Images for the gaussian generation - pub image_media_tokens: Option>, - - /// OPTIONAL. - /// Name of the frontend caller. - /// We'll use this to selectively trigger events. - pub frontend_caller: Option, - - /// OPTIONAL. - /// A frontend-defined identifier that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_id: Option, - - /// OPTIONAL. - /// A frontend-defined payload that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_payload: Option, -} - -#[derive(Serialize)] -pub struct EnqueueImageToGaussianSuccessResponse { -} - -impl SerializeMarker for EnqueueImageToGaussianSuccessResponse {} - -#[derive(Serialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum EnqueueImageToGaussianErrorType { - /// Caller didn't specify a model - ModelNotSpecified, - /// No model available for image generation - NoProviderAvailable, - /// Generic server error - ServerError, - /// Needs to be logged into Artcraft - NeedsStorytellerCredentials, -} - -#[tauri::command] -pub async fn enqueue_image_to_gaussian_command( - request: EnqueueImageToGaussianRequest, - app: AppHandle, - app_data_root: State<'_, AppDataRoot>, - app_env_configs: State<'_, AppEnvConfigs>, - artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>, - task_database: State<'_, TaskDatabase>, - storyteller_creds_manager: State<'_, StorytellerCredentialManager>, - worldlabs_creds_manager: State<'_, WorldlabsCredentialManager>, -) -> Response { - - info!("enqueue_image_to_gaussian called"); - - let result = handle_request( - request, - &app, - &app_data_root, - &artcraft_usage_tracker, - &task_database, - &storyteller_creds_manager, - &worldlabs_creds_manager, - &app_env_configs, - ).await; - - match result { - Err(err) => { - error!("error: {:?}", err); - - notify_frontend_of_errors(&app, &err).await; - - let mut status = CommandErrorStatus::ServerError; - let mut error_type = EnqueueImageToGaussianErrorType::ServerError; - let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem."; - - match err { - GenerateError::BadInput(BadInputReason::NoModelSpecified) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueImageToGaussianErrorType::ModelNotSpecified; - error_message = "No model specified for image generation"; - } - GenerateError::NoProviderAvailable => { - status = CommandErrorStatus::ServerError; - error_type = EnqueueImageToGaussianErrorType::NoProviderAvailable; - error_message = "No configured provider available for image generation"; - } - _ => {}, // Fall-through - } - - Err(CommandErrorResponseWrapper { - status, - error_message: Some(error_message.to_string()), - error_type: Some(error_type), - error_details: None, - }) - } - Ok(event) => { - let event = GenerationEnqueueSuccessEvent { - action: event.to_frontend_event_action(), - service: event.to_frontend_event_service(), - model: event.model, - }; - - if let Err(err) = event.send(&app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - CreditsBalanceChangedEvent{}.send_infallible(&app); - - Ok(EnqueueImageToGaussianSuccessResponse {}.into()) - } - } -} - -pub async fn handle_request( - request: EnqueueImageToGaussianRequest, - app: &AppHandle, - app_data_root: &AppDataRoot, - artcraft_usage_tracker: &ArtcraftUsageTracker, - task_database: &TaskDatabase, - storyteller_creds_manager: &StorytellerCredentialManager, - worldlabs_creds_manager: &WorldlabsCredentialManager, - app_env_configs: &AppEnvConfigs, -) -> Result { - - let result = dispatch_request( - &request, - &app, - &app_data_root, - &storyteller_creds_manager, - &worldlabs_creds_manager, - &app_env_configs, - ).await; - - let success_event = match result { - Err(err) => return Err(err), - Ok(event) => event, - }; - - let result = success_event - .insert_into_task_database_with_frontend_payload( - task_database, - request.frontend_caller, - request.frontend_subscriber_id.as_deref(), - request.frontend_subscriber_payload.as_deref(), - ) - .await; - - if let Err(err) = result { - error!("Failed to create task in database: {:?}", err); - // NB: Fail open, but find a way to flag this. - } - - if let Err(err) = artcraft_usage_tracker.record_object_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::OtherPage) { - // NB: Fail open. - warn!("Failed to report usage: {:?}", err); - } - - Ok(success_event) -} - -pub async fn dispatch_request( - request: &EnqueueImageToGaussianRequest, - app: &AppHandle, - app_data_root: &AppDataRoot, - storyteller_creds_manager: &StorytellerCredentialManager, - worldlabs_creds_manager: &WorldlabsCredentialManager, - app_env_configs: &AppEnvConfigs, -) -> Result { - - if request.model.is_none() { - return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified)); - } - - let provider = request.provider.unwrap_or(GenerationProvider::Artcraft); - - match provider { - GenerationProvider::WorldLabs => { - handle_worldlabs_marble( - app, - app_data_root, - app_env_configs, - request, - worldlabs_creds_manager, - ).await - } - GenerationProvider::Artcraft => { - handle_gaussian_artcraft( - request, - app, - app_env_configs, - storyteller_creds_manager, - ).await - } - _ => { - Err(GenerateError::NoProviderAvailable) - } - } -} diff --git a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/mod.rs b/crates/artcraftx/src/commands/enqueue/image_to_gaussian/mod.rs deleted file mode 100644 index 59dc5bdc..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod artcraft; -pub mod enqueue_image_to_gaussian_command; -mod worldlabs; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/worldlabs/handle_worldlabs_marble.rs b/crates/artcraftx/src/commands/enqueue/image_to_gaussian/worldlabs/handle_worldlabs_marble.rs deleted file mode 100644 index e22246b7..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/worldlabs/handle_worldlabs_marble.rs +++ /dev/null @@ -1,239 +0,0 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason}; -use crate::commands::enqueue::image_to_gaussian::enqueue_image_to_gaussian_command::EnqueueImageToGaussianRequest; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; -use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::data_dir::subdirectory::trait_data_subdir::DataSubdir; -use crate::utils::get_url_file_extension::get_url_file_extension; -use crate::utils::simple_http_download::simple_http_download; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use crate::services::worldlabs::state::worldlabs_credential_manager::WorldlabsCredentialManager; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use log::{error, info}; -use std::path::PathBuf; -use artcraft_client::endpoints::media_files::get_media_file::get_media_file; -use tauri::AppHandle; -use tokens::tokens::media_files::MediaFileToken; -use worldlabs_consumer_client::credentials::world_labs_cookies::WorldLabsCookies; -use worldlabs_consumer_client::recipes::upload_image_and_create_world_with_retry::{upload_image_and_create_world_with_retry, FileBytesOrPath, UploadImageAndCreateWorldWithRetryArgs}; - -pub(super) const MAX_IMAGES: usize = 10; - -pub async fn handle_worldlabs_marble( - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - request: &EnqueueImageToGaussianRequest, - worldlabs_creds_manager: &WorldlabsCredentialManager, -) -> Result { - - let world_labs_cookies = match worldlabs_creds_manager.maybe_copy_typed_cookies()? { - Some(cookies) => cookies, - None => { - error!("No WorldLabs cookies!"); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::WorldLabs, &app); - return Err(GenerateError::needs_worldlabs_credentials()); - } - }; - - let world_labs_bearer = match worldlabs_creds_manager.maybe_copy_bearer_token()? { - Some(bearer) => bearer, - None => { - error!("No WorldLabs bearer token!"); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::WorldLabs, &app); - return Err(GenerateError::needs_worldlabs_credentials()); - } - }; - - let world_labs_refresh = match worldlabs_creds_manager.maybe_copy_refresh_token()? { - Some(bearer) => bearer, - None => { - error!("No WorldLabs refresh token!"); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::WorldLabs, &app); - return Err(GenerateError::needs_worldlabs_credentials()); - } - }; - - let maybe_prompt = request.prompt - .as_deref() - .map(|prompt| prompt.trim().to_string()); - - info!("Downloading image file..."); - - let file_path = download_file( - app_data_root, - app_env_configs, - request, - ).await?; - - info!("Enqueueing WorldLabs request..."); - - let response = upload_image_and_create_world_with_retry(UploadImageAndCreateWorldWithRetryArgs { - cookies: &world_labs_cookies, - bearer_token: &world_labs_bearer, - refresh_token: &world_labs_refresh, - individual_request_timeout: None, - file: FileBytesOrPath::Path(file_path), - }).await?; - - let run_id = response.run_id; - let world_id = response.world_id; - - info!("Run ID: {:?}", run_id); - info!("World ID: {:?}", world_id); - - if let Some(new_access) = response.maybe_new_access_tokens { - info!("New access tokens were generated; saving."); - worldlabs_creds_manager.replace_bearer_and_refresh_token( - new_access.bearer_token, - new_access.refresh_token - )?; - - worldlabs_creds_manager.persist_to_disk()?; - } - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::WorldLabs, - model: Some(GenerationModel::WorldlabsMarble), - provider_job_id: Some(world_id.0), - task_type: TaskType::ImageGeneration, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} - -async fn download_file( - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - request: &EnqueueImageToGaussianRequest, -) -> Result { - - let maybe_media_token = request.image_media_tokens - .as_ref() - .and_then(|tokens| tokens.get(0)); - - let media_token = match maybe_media_token { - Some(token) => token, - None => { - return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages { - min: 1, - max: MAX_IMAGES as u32, - provided: 0, - })); - } - }; - - info!("Calling get media file API: {:?}", app_env_configs.storyteller_host); - - // TODO(bt,2025-12-18): Add a cache. - info!("Using media token: {:?}", media_token); - - let response = get_media_file( - &app_env_configs.storyteller_host, - media_token - ).await?; - - let media_file_url = &response.media_file.media_links.cdn_url; - let extension_with_dot = get_url_file_extension(media_file_url) - .map(|ext| format!(".{}", ext)) - .unwrap_or_else(|| ".png".to_string()); - - let filename = format!("{}{}", response.media_file.token.as_str(), extension_with_dot); - let filename = app_data_root.downloads_dir().path().join(&filename); - - simple_http_download(&media_file_url, &filename).await?; - - Ok(filename) -} - -//async fn download_files( -// app_data_root: &AppDataRoot, -// app_env_configs: &AppEnvConfigs, -// request: &EnqueueImageToGaussianRequest, -//) -> Result, GenerateError> { -// -// let maybe_media_token = request.image_media_tokens -// .as_ref() -// .and_then(|tokens| tokens.get(0)); -// -// -// let media_tokens = match request.image_media_tokens.as_ref() { -// Some(media_tokens) => media_tokens.to_vec(), -// None => { -// return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages { -// min: 1, -// max: MAX_IMAGES as u32, -// provided: 0, -// })); -// } -// }; -// -// if media_tokens.len() > MAX_IMAGES { -// return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages { -// min: 1, -// max: MAX_IMAGES as u32, -// provided: media_tokens.len() as u32, -// })); -// } -// -// info!("Calling get media file API: {:?}", app_env_configs.storyteller_host); -// -// let mut local_files_to_upload = Vec::with_capacity(10); -// -// // TODO(bt,2025-07-07): This is inefficient. Cache and parallelize this. -// // TODO(bt,2025-12-18): Add a cache. -// for media_token in media_tokens.iter() { -// info!("Using media token: {:?}", media_token); -// -// let response = get_media_file( -// &app_env_configs.storyteller_host, -// media_token -// ).await?; -// -// let media_file_url = &response.media_file.media_links.cdn_url; -// let extension_with_dot = get_url_file_extension(media_file_url) -// .map(|ext| format!(".{}", ext)) -// .unwrap_or_else(|| ".png".to_string()); -// -// let filename = format!("{}{}", response.media_file.token.as_str(), extension_with_dot); -// let filename = app_data_root.downloads_dir().path().join(&filename); -// -// simple_http_download(&media_file_url, &filename).await?; -// -// local_files_to_upload.push(filename); -// } -// -// Ok(local_files_to_upload) -//} - -// TODO: -// fn handle_midjourney_errors( -// app: &AppHandle, -// maybe_errors: Option> -// ) -> Result { -// if let Some(errors) = maybe_errors { -// if !errors.is_empty() { -// let messages: Vec = errors.iter() -// .map(|e| format!("{:?}", e)) -// .collect(); -// -// let combined_message = messages.join("; "); -// -// let event = FlashUserInputErrorEvent { -// message: format!("Midjourney Error: {}", combined_message), -// }; -// -// if let Err(err) = event.send(&app) { -// error!("Failed to send FlashUserInputErrorEvent: {:?}", err); // Fail open -// } -// } -// } -// -// Err(GenerateError::ProviderFailure(ProviderFailureReason::MidjourneyJobEnqueueFailed)) -// } diff --git a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/worldlabs/mod.rs b/crates/artcraftx/src/commands/enqueue/image_to_gaussian/worldlabs/mod.rs deleted file mode 100644 index 73ec59ed..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_gaussian/worldlabs/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub (super) mod handle_worldlabs_marble; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/enqueue/image_to_object/artcraft/handle_artcraft_object.rs b/crates/artcraftx/src/commands/enqueue/image_to_object/artcraft/handle_artcraft_object.rs deleted file mode 100644 index 328a4ea7..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_object/artcraft/handle_artcraft_object.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use artcraft_client::api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::GenerateHunyuan20ImageTo3dRequest; -use artcraft_client::api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::GenerateHunyuan21ImageTo3dRequest; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use log::{error, info}; -use artcraft_client::endpoints::generate::object::generate_hunyuan_3d_2_0_image_to_3d::generate_hunyuan3d_2_0_image_to_3d; -use artcraft_client::endpoints::generate::object::generate_hunyuan_3d_2_1_image_to_3d::generate_hunyuan3d_2_1_image_to_3d; -use tauri::AppHandle; -use artcraft_client::api_defs::generate::object::multi_function::hunyuan3d_v3_multi_function_object_gen::Hunyuan3dV3MultiFunctionObjectGenRequest; -use artcraft_client::endpoints::generate::object::multi_function::hunyuan3d_v3_multi_function_object_gen::hunyuan3d_v3_multi_function_object_gen; - -pub async fn handle_artcraft_object( - request: &EnqueueImageTo3dObjectRequest, - app: &AppHandle, - app_env_configs: &AppEnvConfigs, - app_data_root: &AppDataRoot, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let creds = match storyteller_creds_manager.get_credentials()? { - Some(creds) => creds, - None => { - error!("No Artcraft credentials are set. Can't perform action."); - let event = - GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::ImageTo3d); - - if let Err(err) = event.send(&app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - return Err(GenerateError::needs_storyteller_credentials()); - }, - }; - - info!("Calling Artcraft image to 3d ..."); - - let uuid_idempotency_token = generate_random_uuid(); - - let mut used_model = None; - - let job_token = match request.model { - None => { - return Err(GenerateError::no_model_specified()); - } - Some( - EnqueueImageTo3dObjectModel::Hunyuan3d2 | - EnqueueImageTo3dObjectModel::Hunyuan3d2_0 - ) => { - info!("enqueue Artcraft Hunyuan 3D 2.0"); - used_model = Some(GenerationModel::Hunyuan3d2_0); - let request = GenerateHunyuan20ImageTo3dRequest { - uuid_idempotency_token, - media_file_token: request.image_media_token.clone(), - }; - let result = generate_hunyuan3d_2_0_image_to_3d( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - match result { - Ok(enqueued) => { - info!("Successfully enqueued Artcraft Hunyuan 3D 2.0"); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft Hunyuan 3D 2.0: {:?}", err); - return Err(GenerateError::from(err)); - } - } - } - Some(EnqueueImageTo3dObjectModel::Hunyuan3d2_1) => { - info!("enqueue Artcraft Hunyuan 3D 2.1"); - used_model = Some(GenerationModel::Hunyuan3d2_1); - let request = GenerateHunyuan21ImageTo3dRequest { - uuid_idempotency_token, - media_file_token: request.image_media_token.clone(), - }; - let result = generate_hunyuan3d_2_1_image_to_3d( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - match result { - Ok(enqueued) => { - info!("Successfully enqueued Artcraft Hunyuan 3D 2.1"); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft Hunyuan 3D 2.1: {:?}", err); - return Err(GenerateError::from(err)); - } - } - } - Some(EnqueueImageTo3dObjectModel::Hunyuan3d3) => { - info!("enqueue Artcraft Hunyuan 3D 3"); - used_model = Some(GenerationModel::Hunyuan3d3); - let request = Hunyuan3dV3MultiFunctionObjectGenRequest { - uuid_idempotency_token, - image_media_token: request.image_media_token.clone(), - prompt: None, - back_image_media_token: None, - left_image_media_token: None, - right_image_media_token: None, - face_count: None, - generate_type: None, - polygon_type: None, - enable_pbr: None, - }; - let result = hunyuan3d_v3_multi_function_object_gen( - &app_env_configs.storyteller_host, - Some(&creds), - request, - ).await; - match result { - Ok(enqueued) => { - info!("Successfully enqueued Artcraft Hunyuan 3D 3"); - enqueued.inference_job_token - } - Err(err) => { - error!("Failed to use Artcraft Hunyuan 3D 3: {:?}", err); - return Err(GenerateError::from(err)); - } - } - } - }; - - info!("Successfully enqueued 3D generation"); - - //let event = GenerationEnqueueSuccessEvent { - // action: GenerationAction::ImageTo3d, - // service: GenerationServiceProvider::Artcraft, - // model: None, - //}; - // - //if let Err(err) = event.send(app) { - // error!("Failed to emit event: {:?}", err); // Fail open. - //} - - Ok(TaskEnqueueSuccess { - task_type: TaskType::ObjectGeneration, - model: used_model, - provider: GenerationProvider::Artcraft, - provider_job_id: Some(job_token.to_string()), - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} diff --git a/crates/artcraftx/src/commands/enqueue/image_to_object/artcraft/mod.rs b/crates/artcraftx/src/commands/enqueue/image_to_object/artcraft/mod.rs deleted file mode 100644 index f623ebcb..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_object/artcraft/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub (super) mod handle_artcraft_object; diff --git a/crates/artcraftx/src/commands/enqueue/image_to_object/enqueue_image_to_3d_object_command.rs b/crates/artcraftx/src/commands/enqueue/image_to_object/enqueue_image_to_3d_object_command.rs deleted file mode 100644 index 1ca56a17..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_object/enqueue_image_to_3d_object_command.rs +++ /dev/null @@ -1,217 +0,0 @@ -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason}; -use crate::commands::enqueue::image_to_object::artcraft::handle_artcraft_object::handle_artcraft_object; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; -use crate::commands::response::shorthand::Response; -use crate::commands::response::success_response_wrapper::SerializeMarker; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent; -use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::{Provider, ProviderPriorityStore}; -use crate::state::task_database::TaskDatabase; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; -use log::{error, info, warn}; -use serde_derive::{Deserialize, Serialize}; -use tauri::{AppHandle, State}; -use tokens::tokens::media_files::MediaFileToken; -use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; -use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType}; - -#[derive(Deserialize)] -pub struct EnqueueImageTo3dObjectRequest { - /// Image media file; the image to remove the background from. - /// TODO: In the future we may support base64 images, URLs, or file paths here. - pub image_media_token: Option, - - /// The model to use. - pub model: Option, - - /// OPTIONAL. - /// Name of the frontend caller. - /// We'll use this to selectively trigger events. - pub frontend_caller: Option, - - /// OPTIONAL. - /// A frontend-defined identifier that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_id: Option, - - /// OPTIONAL. - /// A frontend-defined payload that we'll send back to the frontend - /// as a Tauri event on task completion. - pub frontend_subscriber_payload: Option, -} - -#[derive(Deserialize, Debug, Copy, Clone)] -#[serde(rename_all = "snake_case")] -pub enum EnqueueImageTo3dObjectModel { - #[deprecated(note="Use `hunyuan_3d_2_0` instead")] - #[serde(rename = "hunyuan_3d_2")] - Hunyuan3d2, - #[serde(rename = "hunyuan_3d_2_0")] - Hunyuan3d2_0, - #[serde(rename = "hunyuan_3d_2_1")] - Hunyuan3d2_1, - #[serde(rename = "hunyuan_3d_3")] - Hunyuan3d3, -} - -#[derive(Serialize)] -pub struct EnqueueImageTo3dObjectSuccessResponse { -} - -impl SerializeMarker for EnqueueImageTo3dObjectSuccessResponse {} - -#[derive(Serialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum EnqueueImageTo3dObjectErrorType { - /// Caller didn't specify a model - ModelNotSpecified, - /// No model available for object generation - NoProviderAvailable, - /// Generic server error - ServerError, - /// No Fal API key available - NeedsFalApiKey, - /// Fal had an API error - FalError, - /// Needs to be logged into Artcraft - NeedsStorytellerCredentials, -} - -#[tauri::command] -pub async fn enqueue_image_to_3d_object_command( - app: AppHandle, - request: EnqueueImageTo3dObjectRequest, - app_env_configs: State<'_, AppEnvConfigs>, - app_data_root: State<'_, AppDataRoot>, - artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>, - provider_priority_store: State<'_, ProviderPriorityStore>, - task_database: State<'_, TaskDatabase>, - storyteller_creds_manager: State<'_, StorytellerCredentialManager>, - sora_task_queue: State<'_, SoraTaskQueue>, -) -> Response { - - info!("enqueue_image_to_3d_object_command called"); - - let result = handle_request( - request, - &app, - &app_env_configs, - &app_data_root, - &artcraft_usage_tracker, - &provider_priority_store, - &task_database, - &storyteller_creds_manager, - ).await; - - match result { - Err(err) => { - error!("error: {:?}", err); - - notify_frontend_of_errors(&app, &err).await; - - let mut status = CommandErrorStatus::ServerError; - let mut error_type = EnqueueImageTo3dObjectErrorType::ServerError; - let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem."; - - match err { - GenerateError::BadInput(BadInputReason::NoModelSpecified) => { - status = CommandErrorStatus::BadRequest; - error_type = EnqueueImageTo3dObjectErrorType::ModelNotSpecified; - error_message = "No model specified for object generation"; - } - GenerateError::NoProviderAvailable => { - status = CommandErrorStatus::ServerError; - error_type = EnqueueImageTo3dObjectErrorType::NoProviderAvailable; - error_message = "No configured provider available for object generation"; - } - GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => { - status = CommandErrorStatus::Unauthorized; - error_type = EnqueueImageTo3dObjectErrorType::NeedsFalApiKey; - error_message = "You need to set a FAL api key"; - }, - GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials) => { - status = CommandErrorStatus::Unauthorized; - error_type = EnqueueImageTo3dObjectErrorType::NeedsStorytellerCredentials; - error_message = "You need to be logged into Artcraft."; - } - _ => {}, // Fall-through - } - - Err(CommandErrorResponseWrapper { - status, - error_message: Some(error_message.to_string()), - error_type: Some(error_type), - error_details: None, - }) - } - Ok(event) => { - let event = GenerationEnqueueSuccessEvent { - action: event.to_frontend_event_action(), - service: event.to_frontend_event_service(), - model: event.model, - }; - - if let Err(err) = event.send(&app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - CreditsBalanceChangedEvent{}.send_infallible(&app); - - Ok(EnqueueImageTo3dObjectSuccessResponse {}.into()) - } - } -} - - -pub async fn handle_request( - request: EnqueueImageTo3dObjectRequest, - app: &AppHandle, - app_env_configs: &AppEnvConfigs, - app_data_root: &AppDataRoot, - artcraft_usage_tracker: &ArtcraftUsageTracker, - provider_priority_store: &ProviderPriorityStore, - task_database: &TaskDatabase, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let result = handle_artcraft_object( - &request, - &app, - &app_env_configs, - &app_data_root, - &storyteller_creds_manager, - ).await; - - let success_event = match result { - Err(err) => return Err(err), - Ok(event) => event, - }; - - let result = success_event - .insert_into_task_database_with_frontend_payload( - task_database, - request.frontend_caller.clone(), - request.frontend_subscriber_id.as_deref(), - request.frontend_subscriber_payload.as_deref(), - ) - .await; - - if let Err(err) = result { - error!("Failed to create task in database: {:?}", err); - // NB: Fail open, but find a way to flag this. - } - - if let Err(err) = artcraft_usage_tracker.record_object_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::ObjectPage) { - // NB: Fail open. - warn!("Failed to report usage: {:?}", err); - } - - Ok(success_event) -} diff --git a/crates/artcraftx/src/commands/enqueue/image_to_object/mod.rs b/crates/artcraftx/src/commands/enqueue/image_to_object/mod.rs deleted file mode 100644 index de940c5c..00000000 --- a/crates/artcraftx/src/commands/enqueue/image_to_object/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod enqueue_image_to_3d_object_command; -mod artcraft; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/enqueue/mod.rs b/crates/artcraftx/src/commands/enqueue/mod.rs deleted file mode 100644 index 73ce2476..00000000 --- a/crates/artcraftx/src/commands/enqueue/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod common; -pub mod generate_error; -pub mod image_bg_removal; -pub mod image_to_gaussian; -pub mod image_to_object; -pub mod task_enqueue_success; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/generate/common/generation_credential.rs b/crates/artcraftx/src/commands/generate/common/generation_credential.rs index 9b3e55a1..bbb420f6 100644 --- a/crates/artcraftx/src/commands/generate/common/generation_credential.rs +++ b/crates/artcraftx/src/commands/generate/common/generation_credential.rs @@ -1,6 +1,6 @@ use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; -use crate::commands::enqueue::generate_error::{CredentialProblemReason, GenerateError}; +use crate::commands::generate::generate_error::{CredentialProblemReason, GenerateError}; use crate::credentials::credential::Credential; use crate::state::data_dir::app_data_root::AppDataRoot; diff --git a/crates/artcraftx/src/commands/enqueue/common/maybe_notify_frontend_of_billing_errors.rs b/crates/artcraftx/src/commands/generate/common/maybe_notify_frontend_of_billing_errors.rs similarity index 93% rename from crates/artcraftx/src/commands/enqueue/common/maybe_notify_frontend_of_billing_errors.rs rename to crates/artcraftx/src/commands/generate/common/maybe_notify_frontend_of_billing_errors.rs index 9e07cd13..26b808a8 100644 --- a/crates/artcraftx/src/commands/enqueue/common/maybe_notify_frontend_of_billing_errors.rs +++ b/crates/artcraftx/src/commands/generate/common/maybe_notify_frontend_of_billing_errors.rs @@ -1,4 +1,4 @@ -use crate::commands::enqueue::generate_error::{BillingIssueReason, BillingProvider, GenerateError, ProviderFailureReason}; +use crate::commands::generate::generate_error::{BillingIssueReason, BillingProvider, GenerateError, ProviderFailureReason}; use crate::events::functional_events::show_provider_billing_modal_event::ShowProviderBillingModalEvent; use enums::common::generation_provider::GenerationProvider; use log::warn; diff --git a/crates/artcraftx/src/commands/enqueue/common/maybe_notify_frontend_of_credential_errors.rs b/crates/artcraftx/src/commands/generate/common/maybe_notify_frontend_of_credential_errors.rs similarity index 92% rename from crates/artcraftx/src/commands/enqueue/common/maybe_notify_frontend_of_credential_errors.rs rename to crates/artcraftx/src/commands/generate/common/maybe_notify_frontend_of_credential_errors.rs index 37cb4609..3a2e19c7 100644 --- a/crates/artcraftx/src/commands/enqueue/common/maybe_notify_frontend_of_credential_errors.rs +++ b/crates/artcraftx/src/commands/generate/common/maybe_notify_frontend_of_credential_errors.rs @@ -1,6 +1,6 @@ use tauri::AppHandle; -use crate::commands::enqueue::generate_error::{CredentialProblemReason, GenerateError}; +use crate::commands::generate::generate_error::{CredentialProblemReason, GenerateError}; use crate::events::basic_sendable_event_trait::BasicSendableEvent; use crate::events::functional_events::show_credential_error_modal_event::ShowCredentialErrorModalEvent; diff --git a/crates/artcraftx/src/commands/enqueue/common/maybe_notify_frontend_of_grok_errors.rs b/crates/artcraftx/src/commands/generate/common/maybe_notify_frontend_of_grok_errors.rs similarity index 93% rename from crates/artcraftx/src/commands/enqueue/common/maybe_notify_frontend_of_grok_errors.rs rename to crates/artcraftx/src/commands/generate/common/maybe_notify_frontend_of_grok_errors.rs index 02db6bac..f17fc683 100644 --- a/crates/artcraftx/src/commands/enqueue/common/maybe_notify_frontend_of_grok_errors.rs +++ b/crates/artcraftx/src/commands/generate/common/maybe_notify_frontend_of_grok_errors.rs @@ -1,4 +1,4 @@ -use crate::commands::enqueue::generate_error::{GenerateError, ProviderFailureReason}; +use crate::commands::generate::generate_error::{GenerateError, ProviderFailureReason}; use crate::events::basic_sendable_event_trait::BasicSendableEvent; use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider}; use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; diff --git a/crates/artcraftx/src/commands/generate/common/mod.rs b/crates/artcraftx/src/commands/generate/common/mod.rs index f4c42703..4b885703 100644 --- a/crates/artcraftx/src/commands/generate/common/mod.rs +++ b/crates/artcraftx/src/commands/generate/common/mod.rs @@ -1,3 +1,7 @@ +pub mod maybe_notify_frontend_of_billing_errors; +pub mod maybe_notify_frontend_of_credential_errors; +pub mod maybe_notify_frontend_of_grok_errors; +pub mod notify_frontend_of_errors; pub mod generation_credential; pub mod router_image_request_to_artcraft_prompt; pub mod router_video_request_to_artcraft_prompt; diff --git a/crates/artcraftx/src/commands/generate/common/notify_frontend_of_errors.rs b/crates/artcraftx/src/commands/generate/common/notify_frontend_of_errors.rs new file mode 100644 index 00000000..78575c40 --- /dev/null +++ b/crates/artcraftx/src/commands/generate/common/notify_frontend_of_errors.rs @@ -0,0 +1,14 @@ +use crate::commands::generate::common::maybe_notify_frontend_of_billing_errors::maybe_notify_frontend_of_billing_errors; +use crate::commands::generate::common::maybe_notify_frontend_of_credential_errors::maybe_notify_frontend_of_credential_errors; +use crate::commands::generate::common::maybe_notify_frontend_of_grok_errors::maybe_notify_frontend_of_grok_errors; +use crate::commands::generate::generate_error::GenerateError; +use tauri::AppHandle; + +pub async fn notify_frontend_of_errors( + app: &AppHandle, + error: &GenerateError, +) { + maybe_notify_frontend_of_billing_errors(app, error).await; + maybe_notify_frontend_of_credential_errors(app, error).await; + maybe_notify_frontend_of_grok_errors(app, error).await; +} diff --git a/crates/artcraftx/src/commands/generate/generate_audio/generate_audio_command.rs b/crates/artcraftx/src/commands/generate/generate_audio/generate_audio_command.rs index a5e363d0..2d1a6ac3 100644 --- a/crates/artcraftx/src/commands/generate/generate_audio/generate_audio_command.rs +++ b/crates/artcraftx/src/commands/generate/generate_audio/generate_audio_command.rs @@ -1,6 +1,6 @@ -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_audio::handle_credential_router::handle_credential_router; use crate::commands::generate::generate_audio::request::{ TauriGenerateAudioErrorType, TauriGenerateAudioRequest, TauriGenerateAudioResponse, diff --git a/crates/artcraftx/src/commands/generate/generate_audio/handle_credential_router.rs b/crates/artcraftx/src/commands/generate/generate_audio/handle_credential_router.rs index c605b9a4..47af01ed 100644 --- a/crates/artcraftx/src/commands/generate/generate_audio/handle_credential_router.rs +++ b/crates/artcraftx/src/commands/generate/generate_audio/handle_credential_router.rs @@ -12,8 +12,8 @@ use router::api::image_list_ref::ImageListRef; use crate::api_adapters::models::audio::tauri_audio_model_to_generation_model::tauri_audio_model_to_generation_model; use crate::api_adapters::models::audio::tauri_audio_model_to_router_model::tauri_audio_model_to_router_model; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential}; use crate::commands::generate::generate_audio::request::TauriGenerateAudioRequest; use crate::credentials::artcraft_api_host::maybe_artcraft_api_host_for_service; diff --git a/crates/artcraftx/src/commands/enqueue/generate_error.rs b/crates/artcraftx/src/commands/generate/generate_error.rs similarity index 100% rename from crates/artcraftx/src/commands/enqueue/generate_error.rs rename to crates/artcraftx/src/commands/generate/generate_error.rs diff --git a/crates/artcraftx/src/commands/generate/generate_image/generate_image_command.rs b/crates/artcraftx/src/commands/generate/generate_image/generate_image_command.rs index 35e7b48d..f31c2630 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/generate_image_command.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/generate_image_command.rs @@ -1,6 +1,6 @@ -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors; +use crate::commands::generate::generate_error::{GenerateError, MissingCredentialsReason}; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_image::providers::router::handle_credential_router::handle_credential_router; use crate::commands::generate::generate_image::tauri_generate_image_request::{ TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse, diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs index 47fec240..42994be8 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs @@ -1,7 +1,7 @@ use log::info; -use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::{GenerateError, MissingCredentialsReason}; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_legacy::handle_artcraft_via_legacy; use crate::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_omni_endpoint::handle_artcraft_via_omni_endpoint; use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs index 033cb438..a463bb12 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs @@ -1,7 +1,7 @@ use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_image::providers::artcraft::legacy::artcraft_flux_2_lora_angles::handle_flux_2_lora_angles; use crate::commands::generate::generate_image::providers::artcraft::legacy::artcraft_flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint; use crate::commands::generate::generate_image::providers::artcraft::legacy::artcraft_flux_pro_kontext_edit::handle_flux_pro_kontext_edit; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs index 79514ccb..c22b755f 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs @@ -7,8 +7,8 @@ use log::{error, info}; use tokens::tokens::media_files::MediaFileToken; use uuid_utils::uuid::generate_random_uuid; use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::api_adapters::models::image::tauri_image_model_to_enums_model::tauri_image_model_to_enums_model; use crate::api_adapters::models::image::tauri_image_model_to_generation_model::tauri_image_model_to_generation_model; use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_2_lora_angles.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_2_lora_angles.rs index 9e7ee6d0..2d285cfe 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_2_lora_angles.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_2_lora_angles.rs @@ -12,8 +12,8 @@ use router::generate::generate_image::image_generation_draft_or_request::ImageGe use enums::common::generation_provider::GenerationProvider; use enums::tauri::tasks::task_type::TaskType; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; use crate::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles; use crate::events::generation_events::common::GenerationModel; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_dev_juggernaut_inpaint.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_dev_juggernaut_inpaint.rs index bbd484b3..45a34b53 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_dev_juggernaut_inpaint.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_dev_juggernaut_inpaint.rs @@ -11,8 +11,8 @@ use uuid_utils::uuid::generate_random_uuid; use tokens::tokens::media_files::MediaFileToken; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::{BadInputReason, GenerateError}; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; use crate::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles; use crate::events::generation_events::common::GenerationModel; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_pro_kontext_edit.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_pro_kontext_edit.rs index 9ff7e4ca..6acf815f 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_pro_kontext_edit.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_flux_pro_kontext_edit.rs @@ -10,8 +10,8 @@ use enums::tauri::tasks::task_type::TaskType; use tokens::tokens::media_files::MediaFileToken; use uuid_utils::uuid::generate_random_uuid; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::{BadInputReason, GenerateError}; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; use crate::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles; use crate::events::generation_events::common::GenerationModel; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_qwen_edit_2511_angles.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_qwen_edit_2511_angles.rs index 74cfa581..db83d257 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_qwen_edit_2511_angles.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/artcraft/legacy/artcraft_qwen_edit_2511_angles.rs @@ -12,8 +12,8 @@ use router::generate::generate_image::image_generation_draft_or_request::ImageGe use enums::common::generation_provider::GenerationProvider; use enums::tauri::tasks::task_type::TaskType; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; use crate::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles; use crate::events::generation_events::common::GenerationModel; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/grok/handle_grok.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/grok/handle_grok.rs deleted file mode 100644 index 0ff5b73f..00000000 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/grok/handle_grok.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; -use crate::services::grok::state::grok_image_prompt_queue::{GrokImagePromptQueue, PromptItem}; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use grok_consumer_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio; -use uuid_utils::uuid::generate_random_uuid; -use tauri::AppHandle; - -pub async fn handle_grok( - app: &AppHandle, - request: &EnqueueTextToImageRequest, - app_env_configs: &AppEnvConfigs, - creds_manager: &GrokCredentialManager, - grok_image_prompt_queue: &GrokImagePromptQueue, -) -> Result { - - - //// TODO: We can request population of the user info if absent or expired. - //let websocket = get_websocket( - // app, - // creds_manager, - // websocket_manager, - //).await?; - // - //let _result = prompt_websocket_image(PromptWebsocketImageArgs { - // websocket_wrapped: &websocket, - // prompt: request.prompt.as_deref().unwrap_or_else(||""), - //}).await?; - // - //let request_id = listen_for_websocket_request_id(ListenForWebsocketRequestIdArgs { - // websocket: &websocket, - // timeout: Duration::from_millis(10_000), - //}).await?; - // - //let request_id = match request_id.request_id { - // Some(request_id) => request_id.to_string(), - // None => { - // warn!("No request id from Grok image generation (websocket)"); - // return Err(GenerateError::ProviderFailure(ProviderFailureReason::GrokJobEnqueueFailed)); - // } - //}; - // - //info!("Successfully enqueued MidJourney. Job token: {}", request_id); - - let job_id = generate_random_uuid(); - - let prompt = request.prompt - .as_deref() - .map(|prompt| prompt.trim().to_string()) - .unwrap_or_else(|| "".to_string()); - - let aspect_ratio = get_aspect_ratio(request); - - grok_image_prompt_queue.enqueue(PromptItem { - task_id: job_id.clone(), - prompt, - aspect_ratio: aspect_ratio.unwrap_or(ClientMessageAspectRatio::Square), - })?; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Grok, - model: Some(GenerationModel::GrokImage), - provider_job_id: Some(job_id), - task_type: TaskType::ImageGeneration, - }) -} - -fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option { - if let Some(common_aspect_ratio) = request.common_aspect_ratio { - // Handle modern aspect ratio - let aspect = aspect_ratio_to_grok_image(common_aspect_ratio); - return Some(aspect); - } - - if let Some(aspect_ratio) = request.aspect_ratio { - // Handle deprecated aspect ratio - return match aspect_ratio { - TextToImageSize::Auto => Some(ClientMessageAspectRatio::Square), - TextToImageSize::Square => Some(ClientMessageAspectRatio::Square), - TextToImageSize::Wide => Some(ClientMessageAspectRatio::WideThreeByTwo), - TextToImageSize::Tall => Some(ClientMessageAspectRatio::TallTwoByThree), - } - } - - None -} diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/grok/mod.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/grok/mod.rs deleted file mode 100644 index 181e756f..00000000 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/grok/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub (super) mod handle_grok; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/midjourney/handle_midjourney.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/midjourney/handle_midjourney.rs deleted file mode 100644 index cdf95a75..00000000 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/midjourney/handle_midjourney.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason, ProviderFailureReason}; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent; -use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::events::warning_events::flash_user_input_error_event::FlashUserInputErrorEvent; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::ProviderPriorityStore; -use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use uuid_utils::uuid::generate_random_uuid; -use log::{error, info}; -use midjourney_client::client::midjourney_hostname::MidjourneyHostname; -use midjourney_client::endpoints::submit_job::{submit_job, SubmitJobRequest}; -use midjourney_client::error::midjourney_api_error::MidjourneyApiError; -use midjourney_client::recipes::channel_id::ChannelId; -use midjourney_client::recipes::text_to_image::{text_to_image, TextToImageError, TextToImageRequest}; -use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; -use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest}; -use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages}; -use std::time::Duration; -use tauri::AppHandle; -use tokens::tokens::media_files::MediaFileToken; - -pub async fn handle_midjourney( - app: &AppHandle, - request: &EnqueueTextToImageRequest, - app_env_configs: &AppEnvConfigs, - mj_creds_manager: &MidjourneyCredentialManager, -) -> Result { - - let creds = match mj_creds_manager.maybe_copy_cookie_store() { - Ok(Some(creds)) => creds, - Ok(None) => { - error!("Midjourney credentials not found."); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app); - return Err(GenerateError::needs_midjourney_credentials()); - } - Err(err) => { - error!("Error reading Midjourney credentials: {:?}", err); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app); - return Err(GenerateError::needs_midjourney_credentials()); - }, - }; - - // TODO: We can request population of the user info if absent or expired. - - let user_info = match mj_creds_manager.maybe_copy_user_info() { - Ok(Some(user_info)) => user_info, - Ok(None) => { - return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo)); - } - Err(err) => { - error!("Error reading Midjourney user info: {:?}", err); - return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo)); - }, - }; - - let channel_id = match user_info.user_id { - Some(user_id) => ChannelId::UserId(user_id), - None => { - error!("Midjourney user info does not contain a user ID."); - return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserId)); - } - }; - - info!("Calling midjourney ..."); - - let cookie_header = creds.to_cookie_string(); - - let prompt = request.prompt - .as_deref() - .unwrap_or(""); - - let result = text_to_image(TextToImageRequest { - prompt, - channel_id: &channel_id, - hostname: MidjourneyHostname::Standard, - cookie_header, - }).await; - - let result = match result { - Ok(result) => result, - Err(err) => { - error!("Failed to use MidJourney: {:?}", err); - return Err(GenerateError::from(err)); - } - }; - - let job_id = match result.maybe_job_id { - Some(job_id) => job_id, - None => { - error!("Failed to enqueue MidJourney: No job ID returned."); - return handle_midjourney_errors(app, result.maybe_errors); - } - }; - - info!("Successfully enqueued MidJourney. Job token: {}", job_id); - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Midjourney, - model: Some(GenerationModel::Midjourney), - provider_job_id: Some(job_id), - task_type: TaskType::ImageGeneration, - }) -} - -fn handle_midjourney_errors( - app: &AppHandle, - maybe_errors: Option> -) -> Result { - if let Some(errors) = maybe_errors { - if !errors.is_empty() { - let messages: Vec = errors.iter() - .map(|e| format!("{:?}", e)) - .collect(); - - let combined_message = messages.join("; "); - - let event = FlashUserInputErrorEvent { - message: format!("Midjourney Error: {}", combined_message), - }; - - if let Err(err) = event.send(&app) { - error!("Failed to send FlashUserInputErrorEvent: {:?}", err); // Fail open - } - } - } - - Err(GenerateError::ProviderFailure(ProviderFailureReason::MidjourneyJobEnqueueFailed)) -} diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/midjourney/mod.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/midjourney/mod.rs deleted file mode 100644 index 18048875..00000000 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/midjourney/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub (super) mod handle_midjourney; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_api_providers.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_api_providers.rs index 3866c308..f6bc979b 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_api_providers.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_api_providers.rs @@ -17,8 +17,8 @@ use tokens::tokens::prompts::PromptToken; use crate::api_adapters::models::image::tauri_image_model_to_generation_model::tauri_image_model_to_generation_model; use crate::api_adapters::models::image::tauri_image_model_to_router_model::tauri_image_model_to_router_model; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::common::router_image_request_to_artcraft_prompt::router_image_request_to_artcraft_prompt; use crate::commands::generate::generate_image::providers::router::utils::convert_enums_to_router::{convert_aspect_ratio, convert_quality, convert_resolution}; use crate::commands::generate::generate_image::providers::router::utils::map_media_files_to_urls::map_media_file_tokens_to_cdn_urls; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_credential_router.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_credential_router.rs index 015d28fc..0c875987 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_credential_router.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_credential_router.rs @@ -13,8 +13,8 @@ use tokens::tokens::media_files::MediaFileToken; use crate::api_adapters::models::image::tauri_image_model_to_generation_model::tauri_image_model_to_generation_model; use crate::api_adapters::models::image::tauri_image_model_to_router_model::tauri_image_model_to_router_model; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential}; use crate::commands::generate::generate_image::providers::router::handle_api_providers::handle_api_key_provider; use crate::commands::generate::generate_image::providers::router::utils::convert_enums_to_router::{convert_aspect_ratio, convert_quality, convert_resolution}; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_router.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_router.rs index ded03096..b1b7a8be 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_router.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_router.rs @@ -1,8 +1,8 @@ use enums::common::generation_provider::GenerationProvider; use log::info; -use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::{GenerateError, MissingCredentialsReason}; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_image::providers::router::handle_api_providers::handle_api_key_provider; use crate::commands::generate::generate_image::providers::router::handle_web_login_providers::handle_web_login_provider; use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_web_login_providers.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_web_login_providers.rs index 381c79a9..cec0b5c2 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_web_login_providers.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/router/handle_web_login_providers.rs @@ -1,7 +1,7 @@ use enums::common::generation_provider::GenerationProvider; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; use crate::providers::credentials::payload::web_login::WebLoginData; diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/router/utils/map_media_files_to_urls.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/router/utils/map_media_files_to_urls.rs index b2b21cc9..26424e7e 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/router/utils/map_media_files_to_urls.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/providers/router/utils/map_media_files_to_urls.rs @@ -3,7 +3,7 @@ use artcraft_client::utils::api_host::ApiHost; use log::info; use tokens::tokens::media_files::MediaFileToken; -use crate::commands::enqueue::generate_error::GenerateError; +use crate::commands::generate::generate_error::GenerateError; /// Resolve a list of media file tokens to their CDN URLs. /// diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/sora/handle_gpt_image_1_sora_text_to_image.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/sora/handle_gpt_image_1_sora_text_to_image.rs deleted file mode 100644 index e0fa27f8..00000000 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/sora/handle_gpt_image_1_sora_text_to_image.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image; -use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_sora_native_gpt_image_1::aspect_ratio_to_sora_native_gpt_image_1; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; -use crate::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; -use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use errors::AnyhowResult; -use grok_consumer_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio; -use log::{error, info}; -use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session; -use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest}; -use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages}; -use std::time::Duration; -use tauri::AppHandle; - -const SORA_SIMPLE_IMAGE_GEN_TIMEOUT : Duration = Duration::from_millis(1000 * 30); // 30 seconds - -pub async fn handle_gpt_image_1_sora_text_to_image( - request: &EnqueueTextToImageRequest, - app: &AppHandle, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - - let mut creds = match sora_creds_manager.get_credentials() { - Ok(Some(creds)) => creds, - Ok(None) => { - error!("Sora credentials not found."); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app); - return Err(GenerateError::needs_sora_credentials()); - } - Err(err) => { - error!("Error reading Sora credentials: {:?}", err); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app); - return Err(GenerateError::needs_sora_credentials()); - } - }; - - let credential_updated = maybe_upgrade_or_renew_session(&mut creds) - .await - .map_err(|err| { - error!("Failed to upgrade or renew session: {:?}", err); - err - })?; - - if credential_updated { - info!("Storing updated credentials"); - sora_creds_manager.set_credentials(&creds)?; - } - - let prompt = request.prompt.as_deref().unwrap_or(""); - - let aspect_ratio = get_aspect_ratio(request) - .unwrap_or(ImageSize::Square); - - let num_images = match request.number_images { - None => NumImages::One, - Some(1) => NumImages::One, - Some(2) => NumImages::Two, - Some(3) => NumImages::Two, // NB: There is no option for three images! - Some(4) => NumImages::Four, - _ => NumImages::One, - }; - - let result = - simple_image_gen_with_session_auto_renew(SimpleImageGenAutoRenewRequest { - prompt: prompt.to_string(), - num_images, - image_size: aspect_ratio, - credentials: &creds, - request_timeout: Some(SORA_SIMPLE_IMAGE_GEN_TIMEOUT), - }).await; - - let (response, maybe_new_creds) = - match result { - Ok((response, maybe_new_creds)) => (response, maybe_new_creds), - Err(err) => { - let event = GenerationEnqueueFailureEvent { - action: GenerationAction::GenerateImage, - service: GenerationServiceProvider::Sora, - model: None, - reason: None, - }; - - if let Err(err) = event.send(app) { - error!("Failed to emit event: {:?}", err); // Fail open. - } - - return Err(GenerateError::from(err)); - } - }; - - if let Some(new_creds) = maybe_new_creds { - info!("Storing updated credentials."); - sora_creds_manager.set_credentials(&new_creds)?; - } - - info!("New Sora Task ID: {:?} ", response.task_id); - - sora_task_queue.insert(&response.task_id)?; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Sora, - task_type: TaskType::ImageGeneration, - model: Some(GenerationModel::GptImage1), - provider_job_id: Some(response.task_id.to_string()), - }) -} - -fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option { - if let Some(common_aspect_ratio) = request.common_aspect_ratio { - // Handle modern aspect ratio - let aspect = aspect_ratio_to_sora_native_gpt_image_1(common_aspect_ratio); - return Some(aspect); - } - - if let Some(aspect_ratio) = request.aspect_ratio { - // Handle deprecated aspect ratio - return match aspect_ratio { - TextToImageSize::Auto => Some(ImageSize::Square), - TextToImageSize::Square => Some(ImageSize::Square), - TextToImageSize::Wide => Some(ImageSize::Wide), - TextToImageSize::Tall => Some(ImageSize::Tall), - } - } - - None -} diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/sora/handle_text_to_image_sora.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/sora/handle_text_to_image_sora.rs deleted file mode 100644 index 16e0000b..00000000 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/sora/handle_text_to_image_sora.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::EnqueueTextToImageRequest; -use crate::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; -use tauri::AppHandle; - -pub async fn handle_text_to_image_sora( - request: &EnqueueTextToImageRequest, - app: &AppHandle, - sora_creds_manager: &SoraCredentialManager, - sora_task_queue: &SoraTaskQueue, -) -> Result { - handle_gpt_image_1_sora_text_to_image( - request, - app, - sora_creds_manager, - sora_task_queue, - ).await -} diff --git a/crates/artcraftx/src/commands/generate/generate_image/providers/sora/mod.rs b/crates/artcraftx/src/commands/generate/generate_image/providers/sora/mod.rs deleted file mode 100644 index 9a9529d3..00000000 --- a/crates/artcraftx/src/commands/generate/generate_image/providers/sora/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub (super) mod handle_text_to_image_sora; -mod handle_gpt_image_1_sora_text_to_image; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/generate/generate_image/utils/parse_semantic_media_files.rs b/crates/artcraftx/src/commands/generate/generate_image/utils/parse_semantic_media_files.rs index 000fe8c0..adaafd58 100644 --- a/crates/artcraftx/src/commands/generate/generate_image/utils/parse_semantic_media_files.rs +++ b/crates/artcraftx/src/commands/generate/generate_image/utils/parse_semantic_media_files.rs @@ -4,7 +4,7 @@ use artcraft_client::credentials::storyteller_credential_set::StorytellerCredent use artcraft_client::utils::api_host::ApiHost; use tokens::tokens::media_files::MediaFileToken; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; +use crate::commands::generate::generate_error::{BadInputReason, GenerateError}; use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; use crate::utils::upload_bytes_to_media_file::upload_image_bytes_as_media_file::upload_image_bytes_as_media_file; use crate::utils::upload_bytes_to_media_file::upload_mask_image_bytes_as_media_file::upload_mask_image_bytes_as_media_file; diff --git a/crates/artcraftx/src/commands/generate/generate_mesh/generate_mesh_command.rs b/crates/artcraftx/src/commands/generate/generate_mesh/generate_mesh_command.rs index d77f70d4..695ccd99 100644 --- a/crates/artcraftx/src/commands/generate/generate_mesh/generate_mesh_command.rs +++ b/crates/artcraftx/src/commands/generate/generate_mesh/generate_mesh_command.rs @@ -1,6 +1,6 @@ -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_mesh::handle_credential_router::handle_credential_router; use crate::commands::generate::generate_mesh::request::{ TauriGenerateMeshErrorType, TauriGenerateMeshRequest, TauriGenerateMeshResponse, diff --git a/crates/artcraftx/src/commands/generate/generate_mesh/handle_credential_router.rs b/crates/artcraftx/src/commands/generate/generate_mesh/handle_credential_router.rs index f9a6ae4e..62ec0d82 100644 --- a/crates/artcraftx/src/commands/generate/generate_mesh/handle_credential_router.rs +++ b/crates/artcraftx/src/commands/generate/generate_mesh/handle_credential_router.rs @@ -13,8 +13,8 @@ use router::api::mesh_ref::MeshRef; use crate::api_adapters::models::mesh::tauri_mesh_model_to_generation_model::tauri_mesh_model_to_generation_model; use crate::api_adapters::models::mesh::tauri_mesh_model_to_router_model::tauri_mesh_model_to_router_model; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential}; use crate::commands::generate::generate_mesh::request::TauriGenerateMeshRequest; use crate::credentials::artcraft_api_host::maybe_artcraft_api_host_for_service; diff --git a/crates/artcraftx/src/commands/generate/generate_mesh/request.rs b/crates/artcraftx/src/commands/generate/generate_mesh/request.rs index 3f746858..5f1a7e74 100644 --- a/crates/artcraftx/src/commands/generate/generate_mesh/request.rs +++ b/crates/artcraftx/src/commands/generate/generate_mesh/request.rs @@ -71,9 +71,9 @@ pub struct TauriGenerateMeshRequest { /// model ids (`CommonMeshModel` serde strings). #[derive(Clone, Copy, Debug, Deserialize)] pub enum TauriMeshModel { - #[serde(rename = "hunyuan_3d_2p0")] + #[serde(rename = "hunyuan_3d_2p0", alias = "hunyuan_3d_2_0", alias = "hunyuan_3d_2")] Hunyuan3d2p0, - #[serde(rename = "hunyuan_3d_2p1")] + #[serde(rename = "hunyuan_3d_2p1", alias = "hunyuan_3d_2_1")] Hunyuan3d2p1, #[serde(rename = "hunyuan_3d_3")] Hunyuan3d3, diff --git a/crates/artcraftx/src/commands/generate/generate_splat/generate_splat_command.rs b/crates/artcraftx/src/commands/generate/generate_splat/generate_splat_command.rs index 8229df9d..cb0c8b28 100644 --- a/crates/artcraftx/src/commands/generate/generate_splat/generate_splat_command.rs +++ b/crates/artcraftx/src/commands/generate/generate_splat/generate_splat_command.rs @@ -1,6 +1,6 @@ -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_splat::handle_credential_router::handle_credential_router; use crate::commands::generate::generate_splat::request::{ TauriGenerateSplatErrorType, TauriGenerateSplatRequest, TauriGenerateSplatResponse, diff --git a/crates/artcraftx/src/commands/generate/generate_splat/handle_credential_router.rs b/crates/artcraftx/src/commands/generate/generate_splat/handle_credential_router.rs index 1953d177..2a8a05b3 100644 --- a/crates/artcraftx/src/commands/generate/generate_splat/handle_credential_router.rs +++ b/crates/artcraftx/src/commands/generate/generate_splat/handle_credential_router.rs @@ -12,8 +12,8 @@ use router::api::video_ref::VideoRef; use crate::api_adapters::models::splat::tauri_splat_model_to_generation_model::tauri_splat_model_to_generation_model; use crate::api_adapters::models::splat::tauri_splat_model_to_router_model::tauri_splat_model_to_router_model; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential}; use crate::commands::generate::generate_splat::request::TauriGenerateSplatRequest; use crate::credentials::artcraft_api_host::maybe_artcraft_api_host_for_service; diff --git a/crates/artcraftx/src/commands/generate/generate_video/artcraft/get_storyteller_creds_or_error.rs b/crates/artcraftx/src/commands/generate/generate_video/artcraft/get_storyteller_creds_or_error.rs index 97a30056..bbf8b78b 100644 --- a/crates/artcraftx/src/commands/generate/generate_video/artcraft/get_storyteller_creds_or_error.rs +++ b/crates/artcraftx/src/commands/generate/generate_video/artcraft/get_storyteller_creds_or_error.rs @@ -1,4 +1,4 @@ -use crate::commands::enqueue::generate_error::GenerateError; +use crate::commands::generate::generate_error::GenerateError; use crate::events::basic_sendable_event_trait::BasicSendableEvent; use crate::events::generation_events::common::GenerationAction; use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; diff --git a/crates/artcraftx/src/commands/generate/generate_video/artcraft/handle_artcraft_video.rs b/crates/artcraftx/src/commands/generate/generate_video/artcraft/handle_artcraft_video.rs index 86283a37..156d1f3d 100644 --- a/crates/artcraftx/src/commands/generate/generate_video/artcraft/handle_artcraft_video.rs +++ b/crates/artcraftx/src/commands/generate/generate_video/artcraft/handle_artcraft_video.rs @@ -1,7 +1,7 @@ use crate::api_adapters::models::video::tauri_video_model_to_generation_model::tauri_video_model_to_generation_model; use crate::api_adapters::models::video::tauri_video_model_to_router_model::tauri_video_model_to_router_model; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; use crate::commands::generate::generate_video::artcraft::handle_artcraft_video_via_router::handle_artcraft_video_via_router; use crate::commands::generate::generate_video::request::TauriGenerateVideoRequest; diff --git a/crates/artcraftx/src/commands/generate/generate_video/artcraft/handle_artcraft_video_via_router.rs b/crates/artcraftx/src/commands/generate/generate_video/artcraft/handle_artcraft_video_via_router.rs index 5fe2419f..f039b6f0 100644 --- a/crates/artcraftx/src/commands/generate/generate_video/artcraft/handle_artcraft_video_via_router.rs +++ b/crates/artcraftx/src/commands/generate/generate_video/artcraft/handle_artcraft_video_via_router.rs @@ -1,6 +1,6 @@ -use crate::commands::enqueue::generate_error::GenerateError; +use crate::commands::generate::generate_error::GenerateError; use crate::commands::generate::generate_video::request::TauriGenerateVideoRequest; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::events::generation_events::common::GenerationModel; use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; use artcraft_client::utils::api_host::ApiHost; diff --git a/crates/artcraftx/src/commands/generate/generate_video/generate_video_command.rs b/crates/artcraftx/src/commands/generate/generate_video/generate_video_command.rs index a920382f..1527b96e 100644 --- a/crates/artcraftx/src/commands/generate/generate_video/generate_video_command.rs +++ b/crates/artcraftx/src/commands/generate/generate_video/generate_video_command.rs @@ -1,6 +1,6 @@ -use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors; +use crate::commands::generate::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason}; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::generate_video::handle_credential_router::handle_credential_router; use crate::commands::generate::generate_video::request::{ TauriGenerateVideoErrorType, TauriGenerateVideoRequest, TauriGenerateVideoResponse, diff --git a/crates/artcraftx/src/commands/generate/generate_video/grok/handle_grok_video.rs b/crates/artcraftx/src/commands/generate/generate_video/grok/handle_grok_video.rs deleted file mode 100644 index 65de4f63..00000000 --- a/crates/artcraftx/src/commands/generate/generate_video/grok/handle_grok_video.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError}; -use crate::commands::deprecated::image_edit::enqueue_edit_image_command::EnqueueEditImageCommand; -use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand; -use crate::commands::generate::generate_video::request::{TauriGenerateVideoRequest, TauriVideoModel, GrokAspectRatio, SoraOrientation}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; -use crate::events::generation_events::common::GenerationModel; -use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::state::data_dir::app_data_root::AppDataRoot; -use crate::state::provider_priority::{Provider, ProviderPriorityStore}; -use crate::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir; -use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; -use crate::services::grok::util::upload_image_media_file_to_grok::{upload_image_media_file_to_grok, UploadImageMediaFileToGrok}; -use crate::services::sora::state::sora_credential_manager::SoraCredentialManager; -use crate::services::sora::state::sora_task_queue::SoraTaskQueue; -use crate::services::sora::utils::upload_images_to_sora::{upload_images_to_sora, UploadImagesToSoraArgs}; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use grok_consumer_client::credentials::grok_cookies::GrokCookies; -use grok_consumer_client::credentials::grok_full_credentials::GrokFullCredentials; -use grok_consumer_client::datatypes::api::aspect_ratio::AspectRatio; -use grok_consumer_client::datatypes::file_upload_spec::FileUploadSpec; -use grok_consumer_client::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs}; -use grok_consumer_client::recipes::upload_image_and_generate_video::{upload_image_and_generate_video, UploadImageAndGenerateVideo}; -use grok_consumer_client::recipes::upload_image_and_generate_video_with_retry::{upload_image_and_generate_video_with_retry, UploadImageAndGenerateVideoWithRetry}; -use log::{error, info, warn}; -use openai_sora_client::recipes::generate_sora2_video::generate_sora2_video_with_session_auto_renew::generate_sora2_video_with_session_auto_renew; -use openai_sora_client::requests::generate_sora2_video::generate_sora2_video::{GenerateSora2VideoArgs, Orientation}; -use std::time::Duration; -use tauri::AppHandle; - -const GROK_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds - -pub async fn handle_grok_video( - request: &TauriGenerateVideoRequest, - app: &AppHandle, - app_data_root: &AppDataRoot, - app_env_configs: &AppEnvConfigs, - grok_credential_manager: &GrokCredentialManager, -) -> Result { - - let creds = get_grok_creds(app, grok_credential_manager).await?; - - let image_token = match request.image_media_token.as_ref() { - Some(token) => token, - None => { - return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages { min: 1, max: 1, provided: 0 })) - } - }; - - info!("Downloading image media file..."); - - let local_image_file = download_media_file_to_temp_dir( - &app_env_configs, - &app_data_root, - image_token, - ).await?; - - let aspect_ratio = match request.grok_aspect_ratio { - None => AspectRatio::WideThreeByTwo, - Some(GrokAspectRatio::Landscape) => AspectRatio::WideThreeByTwo, - Some(GrokAspectRatio::Portrait) => AspectRatio::TallTwoByThree, - Some(GrokAspectRatio::Square) => AspectRatio::Square, - }; - - info!("Calling Grok Video generate..."); - - let upload_result = upload_image_and_generate_video_with_retry(UploadImageAndGenerateVideoWithRetry { - credentials: &creds, - file: FileUploadSpec::Path(local_image_file.path()), - prompt: request.prompt.as_deref(), - aspect_ratio: Some(aspect_ratio), - wait_for_generation: false, - individual_request_timeout: Some(GROK_IMAGE_UPLOAD_TIMEOUT), - mode: None, - }).await; - - let post_id = match upload_result { - Err(err) => { - error!("Failed to use Grok video: {:?}", err); - return Err(GenerateError::from(err)); - } - Ok(result) => { - if let Some(secrets) = result.maybe_new_client_secrets { - info!("New Grok secrets generated! Storing them in the credential manager..."); - grok_credential_manager.replace_client_secrets_only(secrets)?; - } - info!("Successfully enqueued Grok Video: post_id = {:?}", result.upload_result.post_id); - result.upload_result.post_id - } - }; - - // NB: Grok's consumer API doesn't take a version selector; this only affects - // which model is recorded for the task queue / events. - let generation_model = match request.model { - Some(TauriVideoModel::GrokImagineVideo1p5) => GenerationModel::GrokImagineVideo1p5, - _ => GenerationModel::GrokVideo, - }; - - Ok(TaskEnqueueSuccess { - provider: GenerationProvider::Grok, - model: Some(generation_model), - provider_job_id: Some(post_id.to_string()), - task_type: TaskType::VideoGeneration, - maybe_queue_status_url: None, - maybe_prompt_token: None, - maybe_queue_response_url: None, - }) -} - -async fn get_grok_creds(app: &AppHandle, grok_credential_manager: &GrokCredentialManager) -> Result { - if let Some(creds) = grok_credential_manager.maybe_copy_full_credentials()? { - return Ok(creds); - } - - let cookies = match grok_credential_manager.maybe_copy_cookie_store()? { - Some(cookies) => cookies.to_cookie_string(), - None => { - warn!("No Grok Cookie stored. Must login."); - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Grok, &app); - return Err(GenerateError::needs_grok_credentials()) - } - }; - - let cookies = GrokCookies::new(cookies); - - info!("Requesting Grok client secrets..."); - - let upgraded = request_client_secrets(RequestClientSecretsArgs { - cookies: &cookies, - }).await; - - match upgraded { - Err(err) => { - error!("Failed to fetch Grok client secrets: {}", err); // NB: Fall-through - } - Ok(secrets) => { - info!("Grok client secrets successfully upgraded..."); - let full_creds = GrokFullCredentials::from_cookies_and_client_secrets(cookies, secrets); - grok_credential_manager.replace_full_credentials(full_creds.clone())?; - info!("Persisting grok credentials to disk..."); - grok_credential_manager.persist_to_disk()?; - return Ok(full_creds) - } - } - - warn!("Grok upgrade failed. Try logging in again..."); - - ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Grok, &app); - Err(GenerateError::needs_grok_credentials()) -} diff --git a/crates/artcraftx/src/commands/generate/generate_video/grok/mod.rs b/crates/artcraftx/src/commands/generate/generate_video/grok/mod.rs deleted file mode 100644 index 045dcc94..00000000 --- a/crates/artcraftx/src/commands/generate/generate_video/grok/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub (super) mod handle_grok_video; \ No newline at end of file diff --git a/crates/artcraftx/src/commands/generate/generate_video/handle_credential_router.rs b/crates/artcraftx/src/commands/generate/generate_video/handle_credential_router.rs index 992b8a53..c7b7e3ce 100644 --- a/crates/artcraftx/src/commands/generate/generate_video/handle_credential_router.rs +++ b/crates/artcraftx/src/commands/generate/generate_video/handle_credential_router.rs @@ -2,8 +2,8 @@ use log::info; use crate::api_adapters::models::video::tauri_video_model_to_generation_model::tauri_video_model_to_generation_model; use crate::api_adapters::models::video::tauri_video_model_to_router_model::tauri_video_model_to_router_model; -use crate::commands::enqueue::generate_error::GenerateError; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::generate_error::GenerateError; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential}; use crate::commands::generate::generate_video::artcraft::handle_artcraft_video_via_router::handle_artcraft_video_via_router; use crate::commands::generate::generate_video::request::TauriGenerateVideoRequest; diff --git a/crates/artcraftx/src/commands/generate/generate_video/mod.rs b/crates/artcraftx/src/commands/generate/generate_video/mod.rs index 02ee6f67..fea0afe4 100644 --- a/crates/artcraftx/src/commands/generate/generate_video/mod.rs +++ b/crates/artcraftx/src/commands/generate/generate_video/mod.rs @@ -2,5 +2,4 @@ pub mod generate_video_command; mod handle_credential_router; pub mod request; pub mod artcraft; -pub mod grok; pub mod sora2; diff --git a/crates/artcraftx/src/commands/generate/generate_video/sora2/handle_sora_sora2.rs b/crates/artcraftx/src/commands/generate/generate_video/sora2/handle_sora_sora2.rs index 2db08b74..fad037f1 100644 --- a/crates/artcraftx/src/commands/generate/generate_video/sora2/handle_sora_sora2.rs +++ b/crates/artcraftx/src/commands/generate/generate_video/sora2/handle_sora_sora2.rs @@ -1,6 +1,6 @@ -use crate::commands::enqueue::generate_error::GenerateError; +use crate::commands::generate::generate_error::GenerateError; use crate::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; -use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess; use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; use crate::events::generation_events::common::GenerationModel; use crate::state::app_env_configs::app_env_configs::AppEnvConfigs; diff --git a/crates/artcraftx/src/commands/generate/mod.rs b/crates/artcraftx/src/commands/generate/mod.rs index 013bb883..a505e0d2 100644 --- a/crates/artcraftx/src/commands/generate/mod.rs +++ b/crates/artcraftx/src/commands/generate/mod.rs @@ -4,4 +4,6 @@ pub mod generate_mesh; pub mod generate_splat; pub mod generate_video; pub mod common; +pub mod generate_error; +pub mod task_enqueue_success; pub mod models; diff --git a/crates/artcraftx/src/commands/enqueue/task_enqueue_success.rs b/crates/artcraftx/src/commands/generate/task_enqueue_success.rs similarity index 100% rename from crates/artcraftx/src/commands/enqueue/task_enqueue_success.rs rename to crates/artcraftx/src/commands/generate/task_enqueue_success.rs diff --git a/crates/artcraftx/src/commands/mod.rs b/crates/artcraftx/src/commands/mod.rs index 63d10fde..f4ec1d29 100644 --- a/crates/artcraftx/src/commands/mod.rs +++ b/crates/artcraftx/src/commands/mod.rs @@ -1,9 +1,9 @@ pub mod app_preferences; pub mod app_state; +pub mod background_removal; pub mod cost_estimate; pub mod credentials; pub mod download; -pub mod enqueue; pub mod flip_image; pub mod generate; pub mod get_app_info_command; @@ -13,4 +13,3 @@ pub mod platform_info_command; pub mod providers; pub mod response; pub mod task_queue; -pub mod deprecated; \ No newline at end of file diff --git a/crates/artcraftx/src/lib.rs b/crates/artcraftx/src/lib.rs index 320a992e..b6002f0c 100644 --- a/crates/artcraftx/src/lib.rs +++ b/crates/artcraftx/src/lib.rs @@ -33,9 +33,7 @@ use crate::commands::generate::models::video::list_video_models_command::list_vi use crate::commands::download::download_directory_reveal_command::download_directory_reveal_command; use crate::commands::download::download_media_file_command::download_media_file_command; use crate::commands::download::download_url_command::download_url_command; -use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::enqueue_image_bg_removal_command; -use crate::commands::enqueue::image_to_gaussian::enqueue_image_to_gaussian_command::enqueue_image_to_gaussian_command; -use crate::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::enqueue_image_to_3d_object_command; +use crate::commands::background_removal::enqueue_image_bg_removal_command::enqueue_image_bg_removal_command; use crate::commands::generate::generate_audio::generate_audio_command::generate_audio_command; use crate::commands::generate::generate_image::generate_image_command::generate_image_command; use crate::commands::generate::generate_mesh::generate_mesh_command::generate_mesh_command; @@ -222,8 +220,6 @@ pub fn run() { download_media_file_command, download_url_command, enqueue_image_bg_removal_command, - enqueue_image_to_3d_object_command, - enqueue_image_to_gaussian_command, estimate_image_cost_command, estimate_splat_cost_command, estimate_video_cost_command, diff --git a/crates/artcraftx/src/services/grok/util/get_or_upgrade_grok_full_credentials.rs b/crates/artcraftx/src/services/grok/util/get_or_upgrade_grok_full_credentials.rs index c06d09a6..fbec9f34 100644 --- a/crates/artcraftx/src/services/grok/util/get_or_upgrade_grok_full_credentials.rs +++ b/crates/artcraftx/src/services/grok/util/get_or_upgrade_grok_full_credentials.rs @@ -1,4 +1,4 @@ -use crate::commands::enqueue::generate_error::GenerateError; +use crate::commands::generate::generate_error::GenerateError; use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; use grok_consumer_client::credentials::grok_cookies::GrokCookies; diff --git a/crates/artcraftx/src/services/worldlabs/threads/worldlabs_marble_task_polling.rs b/crates/artcraftx/src/services/worldlabs/threads/worldlabs_marble_task_polling.rs index eaae5739..06a2d4f7 100644 --- a/crates/artcraftx/src/services/worldlabs/threads/worldlabs_marble_task_polling.rs +++ b/crates/artcraftx/src/services/worldlabs/threads/worldlabs_marble_task_polling.rs @@ -1,4 +1,4 @@ -use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; +use crate::commands::generate::generate_error::{GenerateError, MissingCredentialsReason}; use crate::events::basic_sendable_event_trait::BasicSendableEvent; use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider}; use crate::events::generation_events::generation_complete_event::GenerationCompleteEvent; diff --git a/crates/artcraftx/src/utils/upload_bytes_to_media_file/upload_image_bytes_as_media_file.rs b/crates/artcraftx/src/utils/upload_bytes_to_media_file/upload_image_bytes_as_media_file.rs index 8826e874..a86e1b5e 100644 --- a/crates/artcraftx/src/utils/upload_bytes_to_media_file/upload_image_bytes_as_media_file.rs +++ b/crates/artcraftx/src/utils/upload_bytes_to_media_file/upload_image_bytes_as_media_file.rs @@ -7,7 +7,7 @@ use artcraft_client::endpoints::media_files::upload_image_media_file_from_bytes: use artcraft_client::utils::api_host::ApiHost; use tokens::tokens::media_files::MediaFileToken; -use crate::commands::enqueue::generate_error::GenerateError; +use crate::commands::generate::generate_error::GenerateError; /// Upload raw image bytes to ArtCraft, returning the resulting MediaFileToken. /// diff --git a/crates/artcraftx/src/utils/upload_bytes_to_media_file/upload_mask_image_bytes_as_media_file.rs b/crates/artcraftx/src/utils/upload_bytes_to_media_file/upload_mask_image_bytes_as_media_file.rs index 0e529347..3ea3ee50 100644 --- a/crates/artcraftx/src/utils/upload_bytes_to_media_file/upload_mask_image_bytes_as_media_file.rs +++ b/crates/artcraftx/src/utils/upload_bytes_to_media_file/upload_mask_image_bytes_as_media_file.rs @@ -8,7 +8,7 @@ use artcraft_client::utils::api_host::ApiHost; use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask; use tokens::tokens::media_files::MediaFileToken; -use crate::commands::enqueue::generate_error::GenerateError; +use crate::commands::generate::generate_error::GenerateError; /// Upload raw mask image bytes to ArtCraft, returning the resulting MediaFileToken. /// diff --git a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DComposer.tsx b/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DComposer.tsx index b58cb7af..e851d1c7 100644 --- a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DComposer.tsx +++ b/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DComposer.tsx @@ -6,11 +6,7 @@ import { twMerge } from "tailwind-merge"; import { MediaUploadApi } from "@storyteller/api"; import { GenerateIconButton } from "@storyteller/ui-button"; import { useCostBreakdownModalStore } from "@storyteller/ui-pricing-modal"; -import { - EnqueueImageTo3dObject, - EnqueueImageTo3dObjectModel, - GenerateSplat, -} from "@storyteller/tauri-api"; +import { GenerateMesh, GenerateSplat } from "@storyteller/tauri-api"; import { SPLAT_MODELS } from "@storyteller/model-list"; import { ClassyModelSelector, @@ -39,7 +35,7 @@ interface TrayImage { } const MAX_WORLD_IMAGES = 10; -const DEFAULT_OBJECT_MODEL_ID = EnqueueImageTo3dObjectModel.Hunyuan3d3; +const DEFAULT_OBJECT_MODEL_ID = "hunyuan_3d_3"; const generateId = () => typeof crypto !== "undefined" && crypto.randomUUID @@ -56,9 +52,7 @@ export const ImageTo3DComposer = ({ variant }: ImageTo3DComposerProps) => { const maxImages = isWorld ? MAX_WORLD_IMAGES : 1; const selectedModel = useSelectedModel(pageId); - const selectedObjectModelId = - (selectedModel?.id as EnqueueImageTo3dObjectModel | undefined) ?? - DEFAULT_OBJECT_MODEL_ID; + const selectedObjectModelId = selectedModel?.id ?? DEFAULT_OBJECT_MODEL_ID; const { busy, completed } = useComposerTasks(isWorld ? "splat" : "mesh"); @@ -163,9 +157,11 @@ export const ImageTo3DComposer = ({ variant }: ImageTo3DComposerProps) => { frontend_caller: "mini_app", frontend_subscriber_id: subscriberId, }) - : await EnqueueImageTo3dObject({ - image_media_token: readyTokens[0], + : await GenerateMesh({ + credential_id: selectedAccountId ?? undefined, model: selectedObjectModelId, + prompt: prompt.trim() || undefined, + reference_image_media_tokens: readyTokens, frontend_caller: "mini_app", frontend_subscriber_id: subscriberId, }); diff --git a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx b/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx deleted file mode 100644 index 887e13d8..00000000 --- a/frontend/apps/artcraft/app/src/components/experiences/ImageTo3DExperience.tsx +++ /dev/null @@ -1,1205 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import { animated, useSpring } from "@react-spring/web"; -import { Button, GenerateButton } from "@storyteller/ui-button"; -import { TabSelector } from "@storyteller/ui-tab-selector"; -import { Tooltip } from "@storyteller/ui-tooltip"; -import { Viewer3D } from "@storyteller/ui-viewer-3d"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faCube, - faImages, - faPlus, - faUpload, - faXmark, -} from "@fortawesome/pro-solid-svg-icons"; -import { twMerge } from "tailwind-merge"; -import { useImageTo3DStore } from "../../pages/PageImageTo3DObject/ImageTo3DStore"; -import { useImageTo3DWorldStore } from "../../pages/PageImageTo3DWorld/ImageTo3DWorldStore"; -import { MediaUploadApi, downloadFileFromUrl } from "@storyteller/api"; -import { GalleryItem, GalleryModal } from "@storyteller/ui-gallery-modal"; -import { - EnqueueImageTo3dObject, - EnqueueImageTo3dObjectModel, - EnqueueImageToGaussian, -} from "@storyteller/tauri-api"; -import { toast } from "react-hot-toast"; -import { v4 as uuidv4 } from "uuid"; -import { SPLAT_MODELS } from "@storyteller/model-list"; -import { - ClassyModelSelector, - IMAGE_TO_3D_OBJECT_PAGE_MODEL_LIST, - IMAGE_TO_3D_WORLD_PAGE_MODEL_LIST, - ModelPage, - useSelectedModel, - useSelectedProviderForModel, -} from "@storyteller/ui-model-selector"; -import { - CostCalculatorButton, - useCostBreakdownModalStore, -} from "@storyteller/ui-pricing-modal"; -import { HelpMenuButton } from "@storyteller/ui-help-menu"; - -type Mode = "image" | "text"; -type Variant = "object" | "world"; - -interface WorldImage { - id: string; - preview: string; - mediaToken: string | null; - name: string; - isUploading: boolean; -} - -const MAX_WORLD_IMAGES = 10; - -interface ImageTo3DExperienceProps { - title: string; - subtitle: string; - variant: Variant; - backgroundImage?: string; -} - -const MODE_TABS = [ - { id: "image", label: "Image to 3D" }, - // { id: "text", label: "Text to 3D" }, -] satisfies { id: Mode; label: string }[]; - -const formatTime = (timestamp: number) => { - const date = new Date(timestamp); - return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); -}; - -const generateId = () => - typeof crypto !== "undefined" && crypto.randomUUID - ? crypto.randomUUID() - : Math.random().toString(36).slice(2, 10); - -const WORLD_MODEL_PAGE = ModelPage.ImageTo3DWorld; -const OBJECT_MODEL_PAGE = ModelPage.ImageTo3DObject; -const DEFAULT_OBJECT_MODEL_ID = EnqueueImageTo3dObjectModel.Hunyuan3d3; - -export const ImageTo3DExperience = ({ - title, - subtitle, - variant, - backgroundImage, -}: ImageTo3DExperienceProps) => { - const [activeMode, setActiveMode] = useState("image"); - const selectedWorldModel = useSelectedModel(WORLD_MODEL_PAGE); - const selectedWorldProvider = useSelectedProviderForModel( - WORLD_MODEL_PAGE, - selectedWorldModel?.id, - ); - const selectedObjectModel = useSelectedModel(OBJECT_MODEL_PAGE); - const selectedObjectModelId = - (selectedObjectModel?.id as EnqueueImageTo3dObjectModel | undefined) ?? - DEFAULT_OBJECT_MODEL_ID; - const worldCredits = useCostBreakdownModalStore( - (s) => s.estimatedCreditsByPage[WORLD_MODEL_PAGE], - ); - const objectCredits = useCostBreakdownModalStore( - (s) => s.estimatedCreditsByPage[OBJECT_MODEL_PAGE], - ); - const [uploadedPreview, setUploadedPreview] = useState(null); - const [uploadedName, setUploadedName] = useState(null); - const [prompt, setPrompt] = useState(""); - const [isGenerating, setIsGenerating] = useState(false); - const [isUploading, setIsUploading] = useState(false); - const [dragActive, setDragActive] = useState(false); - const fileInputRef = useRef(null); - const textareaRef = useRef(null); - const worldFileInputRef = useRef(null); - const worldTextareaRef = useRef(null); - const [worldImages, setWorldImages] = useState([]); - const [worldPrompt, setWorldPrompt] = useState(""); - const [previewImage, setPreviewImage] = useState(null); - - const [selectedResultId, setSelectedResultId] = useState(null); - const promptContentRef = useRef(null); - const [promptHeight, setPromptHeight] = useState(400); - const [vh, setVh] = useState( - typeof window !== "undefined" ? window.innerHeight : 800, - ); - - const objectResults = useImageTo3DStore((s) => s.results); - const objectStartGeneration = useImageTo3DStore((s) => s.startGeneration); - const objectFailGeneration = useImageTo3DStore((s) => s.failGeneration); - const objectReset = useImageTo3DStore((s) => s.reset); - - const worldResults = useImageTo3DWorldStore((s) => s.results); - const worldStartGeneration = useImageTo3DWorldStore((s) => s.startGeneration); - const worldFailGeneration = useImageTo3DWorldStore((s) => s.failGeneration); - const worldReset = useImageTo3DWorldStore((s) => s.reset); - const pendingExternalImage = useImageTo3DWorldStore( - (s) => s.pendingExternalImage, - ); - const clearPendingExternalImage = useImageTo3DWorldStore( - (s) => s.clearPendingExternalImage, - ); - const pendingObjectImage = useImageTo3DStore((s) => s.pendingExternalImage); - const clearPendingObjectImage = useImageTo3DStore( - (s) => s.clearPendingExternalImage, - ); - - const results = variant === "object" ? objectResults : worldResults; - const resetResults = variant === "object" ? objectReset : worldReset; - const [uploadedMediaToken, setUploadedMediaToken] = useState( - null, - ); - const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false); - const [selectedGalleryImages, setSelectedGalleryImages] = useState( - [], - ); - - useEffect(() => { - // Read from store directly to avoid Strict Mode double-execution adding the image twice - if (variant === "world") { - const pending = useImageTo3DWorldStore.getState().pendingExternalImage; - if (pending) { - setWorldImages((prev) => { - if (prev.length >= MAX_WORLD_IMAGES) return prev; - return [ - ...prev, - { - id: generateId(), - preview: pending.url, - mediaToken: pending.mediaToken, - name: "Library Image", - isUploading: false, - }, - ]; - }); - clearPendingExternalImage(); - } - } else if (variant === "object") { - const pending = useImageTo3DStore.getState().pendingExternalImage; - if (pending) { - setUploadedPreview(pending.url); - setUploadedMediaToken(pending.mediaToken); - setUploadedName("Library Image"); - clearPendingObjectImage(); - } - } - }, [ - variant, - pendingExternalImage, - clearPendingExternalImage, - pendingObjectImage, - clearPendingObjectImage, - ]); - - useEffect(() => { - const onResize = () => setVh(window.innerHeight); - window.addEventListener("resize", onResize); - return () => window.removeEventListener("resize", onResize); - }, []); - - useEffect(() => { - const el = promptContentRef.current; - if (!el || typeof ResizeObserver === "undefined") return; - const update = () => setPromptHeight(el.offsetHeight); - update(); - const ro = new ResizeObserver(() => update()); - ro.observe(el); - return () => ro.disconnect(); - }, []); - - useEffect(() => { - if (textareaRef.current) { - textareaRef.current.style.height = "auto"; - textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; - } - if (worldTextareaRef.current) { - worldTextareaRef.current.style.height = "auto"; - worldTextareaRef.current.style.height = `${worldTextareaRef.current.scrollHeight}px`; - } - }); - - const handleFiles = async (files?: FileList | null) => { - if (!files || files.length === 0) return; - const file = files[0]; - if (!file.type.startsWith("image/")) return; - - setUploadedName(file.name); - setUploadedMediaToken(null); - setIsUploading(true); - - const reader = new FileReader(); - reader.onload = (e) => { - const dataUrl = e.target?.result as string; - setUploadedPreview(dataUrl); - }; - reader.readAsDataURL(file); - - try { - const mediaUploadApi = new MediaUploadApi(); - const uuid = uuidv4(); - - const uploadResult = await mediaUploadApi.UploadImage({ - blob: file, - fileName: file.name, - uuid: uuid, - }); - - if (!uploadResult.success || !uploadResult.data) { - throw new Error("Failed to upload image"); - } - - setUploadedMediaToken(uploadResult.data); - } catch (error) { - toast.error("Failed to upload image"); - setUploadedPreview(null); - setUploadedName(null); - } finally { - setIsUploading(false); - } - }; - - const handleFileChange = (event: React.ChangeEvent) => { - handleFiles(event.target.files); - event.target.value = ""; - }; - - const handleDrop = (event: React.DragEvent) => { - event.preventDefault(); - event.stopPropagation(); - setDragActive(false); - handleFiles(event.dataTransfer?.files); - }; - - const handlePickFromLibrary = () => { - setIsGalleryModalOpen(true); - }; - - const handleWorldFiles = async (files: File[]) => { - const remaining = MAX_WORLD_IMAGES - worldImages.length; - const imageFiles = files - .filter((f) => f.type.startsWith("image/")) - .slice(0, remaining); - - await Promise.all( - imageFiles.map(async (file) => { - const imageId = generateId(); - - const preview = await new Promise((resolve) => { - const reader = new FileReader(); - reader.onload = (e) => resolve(e.target?.result as string); - reader.readAsDataURL(file); - }); - - setWorldImages((prev) => [ - ...prev, - { - id: imageId, - preview, - mediaToken: null, - name: file.name, - isUploading: true, - }, - ]); - - try { - const mediaUploadApi = new MediaUploadApi(); - const uploadResult = await mediaUploadApi.UploadImage({ - blob: file, - fileName: file.name, - uuid: uuidv4(), - }); - - if (!uploadResult.success || !uploadResult.data) { - throw new Error("Upload failed"); - } - - setWorldImages((prev) => - prev.map((img) => - img.id === imageId - ? { ...img, mediaToken: uploadResult.data!, isUploading: false } - : img, - ), - ); - } catch { - toast.error(`Failed to upload ${file.name}`); - setWorldImages((prev) => prev.filter((img) => img.id !== imageId)); - } - }), - ); - }; - - const removeWorldImage = (imageId: string) => { - setWorldImages((prev) => prev.filter((img) => img.id !== imageId)); - }; - - const handleImageSelect = (id: string) => { - setSelectedGalleryImages((prev) => { - if (prev.includes(id)) return prev.filter((x) => x !== id); - if (variant === "world") { - const max = MAX_WORLD_IMAGES - worldImages.length; - if (prev.length >= max) return prev; - return [...prev, id]; - } - return [id]; - }); - }; - - const handleGallerySelect = async (selectedItems: GalleryItem[]) => { - if (variant === "world") { - const remaining = MAX_WORLD_IMAGES - worldImages.length; - const newImages: WorldImage[] = selectedItems - .slice(0, remaining) - .filter((item) => item.fullImage) - .map((item) => ({ - id: generateId(), - preview: item.fullImage!, - mediaToken: item.id, - name: item.label || "Library Image", - isUploading: false, - })); - setWorldImages((prev) => [...prev, ...newImages]); - setIsGalleryModalOpen(false); - setSelectedGalleryImages([]); - return; - } - - const item = selectedItems[0]; - if (!item || !item.fullImage) { - toast.error("No image selected"); - return; - } - - if (isUploading) return; - - setIsGalleryModalOpen(false); - setSelectedGalleryImages([]); - - setUploadedName(item.label || "Library Image"); - setUploadedPreview(item.fullImage); - setUploadedMediaToken(item.id); - }; - - const handleGenerate = async () => { - if (isGenerating) return; - - if (variant === "world") { - const readyTokens = worldImages - .filter((img) => img.mediaToken && !img.isUploading) - .map((img) => img.mediaToken!); - if (readyTokens.length === 0) return; - } else { - if (isUploading) return; - if (activeMode === "image" && !uploadedMediaToken) return; - if (activeMode === "text" && prompt.trim().length <= 3) return; - } - - setIsGenerating(true); - const subscriberId = generateId(); - - try { - if (variant === "world") { - const readyTokens = worldImages - .filter((img) => img.mediaToken && !img.isUploading) - .map((img) => img.mediaToken!); - const note = - worldPrompt.trim() || - `${worldImages.length} image${worldImages.length !== 1 ? "s" : ""}`; - const firstPreview = worldImages[0]?.preview; - - worldStartGeneration("image", note, firstPreview, subscriberId); - setSelectedResultId(subscriberId); - - window.__storeTaskEnqueueMeta?.({ - prompt: worldPrompt.trim() || undefined, - refImageUrls: worldImages - .map((img) => img.preview) - .filter(Boolean) as string[], - modelType: - (selectedWorldModel as any)?.tauriId || - (SPLAT_MODELS[0] as any)?.tauriId || - String(selectedWorldModel ?? SPLAT_MODELS[0]), - timestamp: Date.now(), - }); - - const result = await EnqueueImageToGaussian({ - image_media_tokens: readyTokens, - prompt: worldPrompt.trim() || undefined, - model: selectedWorldModel ?? SPLAT_MODELS[0], - provider: selectedWorldProvider, - frontend_caller: "mini_app", - frontend_subscriber_id: subscriberId, - }); - - if ("error_type" in result) { - throw new Error(result.error_message || result.error_type); - } - } else { - const snapshotPrompt = prompt.trim(); - const snapshotPreview = uploadedPreview || undefined; - const snapshotName = uploadedName; - const note = - activeMode === "text" - ? snapshotPrompt - : snapshotName || "Generated Model"; - - objectStartGeneration( - activeMode, - note, - snapshotPreview, - false, - subscriberId, - ); - setSelectedResultId(subscriberId); - - window.__storeTaskEnqueueMeta?.({ - prompt: snapshotPrompt || undefined, - refImageUrls: snapshotPreview ? [snapshotPreview] : undefined, - modelType: selectedObjectModel?.tauriId || selectedObjectModelId, - timestamp: Date.now(), - }); - - const result = await EnqueueImageTo3dObject({ - image_media_token: uploadedMediaToken || undefined, - model: selectedObjectModelId, - frontend_caller: "mini_app", - frontend_subscriber_id: subscriberId, - }); - - if ("error_type" in result) { - throw new Error(result.error_message || result.error_type); - } - - if (activeMode === "text") { - setPrompt(""); - } - } - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "An unexpected error occurred"; - toast.error(`Failed to generate 3D model: ${errorMessage}`); - if (variant === "world") { - worldFailGeneration(subscriberId); - } else { - objectFailGeneration(subscriberId); - } - } finally { - setIsGenerating(false); - } - }; - - const canGenerate = useMemo(() => { - if (isGenerating) return false; - if (variant === "world") { - return worldImages.some((img) => img.mediaToken && !img.isUploading); - } - if (isUploading) return false; - if (activeMode === "image") { - return Boolean(uploadedMediaToken); - } - if (activeMode === "text") { - return prompt.trim().length > 3; - } - return true; - }, [ - variant, - activeMode, - uploadedMediaToken, - prompt, - isGenerating, - isUploading, - worldImages, - ]); - - const hasResults = results.length > 0; - const showPromptAtBottom = hasResults; - - // Animation logic - const bottomMarginPx = 24; - const bottomOffsetPx = promptHeight + bottomMarginPx; - - const centerTop = vh / 2 - promptHeight / 2 + 80; - const bottomTop = vh - bottomOffsetPx; - - const targetTop = showPromptAtBottom - ? Math.max(0, bottomTop) - : Math.max(0, centerTop); - - const promptAnim = useSpring({ - top: targetTop, - config: { tension: 200, friction: 28, mass: 1.1 }, - }); - - const renderAddImageTile = () => ( - - - - - } - > -
{ - event.preventDefault(); - event.stopPropagation(); - setDragActive(true); - }} - onDragOver={(event) => { - event.preventDefault(); - event.stopPropagation(); - }} - onDragLeave={(event) => { - event.preventDefault(); - event.stopPropagation(); - if (!event.currentTarget.contains(event.relatedTarget as Node)) { - setDragActive(false); - } - }} - onDrop={handleDrop} - onClick={() => fileInputRef.current?.click()} - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - fileInputRef.current?.click(); - } - }} - > - - {!hasResults && ( - - Add Image - - )} -
-
- ); - - const renderImageMode = () => ( -
- {uploadedPreview ? ( -
!isUploading && fileInputRef.current?.click()} - > - Reference - {isUploading && ( -
-
-
- )} - {!isUploading && ( - - )} -
- ) : ( - renderAddImageTile() - )} -
- ); - - const promptInputId = `image-to-3d-${variant}-prompt`; - - const renderTextMode = () => ( -
-