From 9945a825ed79446ba9faf708338f72b6a74016fb Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 29 Apr 2026 16:06:49 -0400 Subject: [PATCH 01/14] rename generate video command --- .../artcraft/handle_artcraft_video.rs | 66 --- .../enqueue_image_to_video_command.rs | 395 ------------------ .../commands/enqueue/image_to_video/mod.rs | 4 - .../artcraft/src/core/commands/enqueue/mod.rs | 1 - .../get_storyteller_creds_or_error.rs | 0 .../artcraft/handle_artcraft_kling_1p6_pro.rs | 6 +- .../handle_artcraft_kling_2p1_master.rs | 6 +- .../artcraft/handle_artcraft_kling_2p1_pro.rs | 6 +- .../handle_artcraft_kling_2p5_turbo_pro.rs | 6 +- .../artcraft/handle_artcraft_kling_2p6_pro.rs | 6 +- .../handle_artcraft_seedance_1_lite.rs | 6 +- .../artcraft/handle_artcraft_seedance_2p0.rs | 6 +- .../artcraft/handle_artcraft_sora2.rs | 6 +- .../artcraft/handle_artcraft_sora2_pro.rs | 6 +- .../artcraft/handle_artcraft_veo2.rs | 6 +- .../artcraft/handle_artcraft_veo3.rs | 6 +- .../artcraft/handle_artcraft_veo3_fast.rs | 6 +- .../artcraft/handle_artcraft_veo3p1.rs | 6 +- .../artcraft/handle_artcraft_veo3p1_fast.rs | 6 +- .../artcraft/handle_artcraft_video.rs | 66 +++ .../handle_artcraft_video_via_router.rs | 4 +- .../generate_video}/artcraft/mod.rs | 0 .../generate_video/generate_video_command.rs | 198 +++++++++ .../generate_video}/grok/handle_grok_video.rs | 4 +- .../generate_video}/grok/mod.rs | 0 .../commands/generate/generate_video/mod.rs | 5 + .../generate/generate_video/request.rs | 130 ++++++ .../sora2/handle_sora_sora2.rs | 4 +- .../generate_video}/sora2/mod.rs | 0 .../src/core/commands/generate/mod.rs | 1 + .../desktop/artcraft/src/core/commands/mod.rs | 1 + crates/desktop/artcraft/src/lib.rs | 4 +- 32 files changed, 451 insertions(+), 516 deletions(-) delete mode 100644 crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_video.rs delete mode 100644 crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/enqueue_image_to_video_command.rs delete mode 100644 crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/mod.rs rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/get_storyteller_creds_or_error.rs (100%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_kling_1p6_pro.rs (92%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_kling_2p1_master.rs (92%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_kling_2p1_pro.rs (92%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_kling_2p5_turbo_pro.rs (92%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_kling_2p6_pro.rs (92%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_seedance_1_lite.rs (92%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_seedance_2p0.rs (94%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_sora2.rs (93%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_sora2_pro.rs (92%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_veo2.rs (91%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_veo3.rs (91%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_veo3_fast.rs (91%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_veo3p1.rs (92%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_veo3p1_fast.rs (92%) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video.rs rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/handle_artcraft_video_via_router.rs (97%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/artcraft/mod.rs (100%) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_video/generate_video_command.rs rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/grok/handle_grok_video.rs (97%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/grok/mod.rs (100%) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_video/mod.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_video/request.rs rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/sora2/handle_sora_sora2.rs (95%) rename crates/desktop/artcraft/src/core/commands/{enqueue/image_to_video => generate/generate_video}/sora2/mod.rs (100%) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/mod.rs diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_video.rs b/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_video.rs deleted file mode 100644 index 8e44996da8..0000000000 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_video.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_1p6_pro::handle_artcraft_kling_1p6_pro; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_2p1_master::handle_artcraft_kling_2p1_master; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_2p1_pro::handle_artcraft_kling_2p1_pro; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_2p5_turbo_pro::handle_artcraft_kling_2p5_turbo_pro; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_kling_2p6_pro::handle_artcraft_kling_2p6_pro; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_video_via_router::handle_artcraft_video_via_router; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_seedance_1_lite::handle_artcraft_seedance_1_lite; -use crate::core::events::generation_events::common::GenerationModel; -use artcraft_router::api::common_video_model::CommonVideoModel; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_seedance_2p0::handle_artcraft_seedance_2p0; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_sora2::handle_artcraft_sora2; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_sora2_pro::handle_artcraft_sora2_pro; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_veo2::handle_artcraft_veo2; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_veo3::handle_artcraft_veo3; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_veo3_fast::handle_artcraft_veo3_fast; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_veo3p1::handle_artcraft_veo3p1; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_veo3p1_fast::handle_artcraft_veo3p1_fast; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, VideoModel}; -use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use anyhow::anyhow; -use tauri::AppHandle; - -pub async fn handle_video_artcraft( - request: &EnqueueImageToVideoRequest, - 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()) - }, - // Handled via router - Some(VideoModel::Kling3p0Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Pro, GenerationModel::Kling3p0Pro).await, - Some(VideoModel::Kling3p0Standard) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Standard, GenerationModel::Kling3p0Standard).await, - Some(VideoModel::Seedance1p5Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance1p5Pro, GenerationModel::Seedance1p5Pro).await, - Some(VideoModel::Seedance2p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance2p0, GenerationModel::Seedance2p0).await, - Some(VideoModel::Seedance2p0Fast) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance2p0Fast, GenerationModel::Seedance2p0Fast).await, - Some(VideoModel::HappyHorse1p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::HappyHorse1p0, GenerationModel::HappyHorse1p0).await, - // Legacy endpoints - Some(VideoModel::Kling16Pro) => handle_artcraft_kling_1p6_pro(request, app_env_configs, &creds).await, - Some(VideoModel::Kling21Pro) => handle_artcraft_kling_2p1_pro(request, app_env_configs, &creds).await, - Some(VideoModel::Kling21Master) => handle_artcraft_kling_2p1_master(request, app_env_configs, &creds).await, - Some(VideoModel::Kling2p5TurboPro) => handle_artcraft_kling_2p5_turbo_pro(request, app_env_configs, &creds).await, - Some(VideoModel::Kling2p6Pro) => handle_artcraft_kling_2p6_pro(request, app_env_configs, &creds).await, - Some(VideoModel::Seedance10Lite) => handle_artcraft_seedance_1_lite(request, app_env_configs, &creds).await, - Some(VideoModel::Sora2) => handle_artcraft_sora2(request, app_env_configs, &creds).await, - Some(VideoModel::Sora2Pro) => handle_artcraft_sora2_pro(request, app_env_configs, &creds).await, - Some(VideoModel::Veo2) => handle_artcraft_veo2(request, app_env_configs, &creds).await, - Some(VideoModel::Veo3) => handle_artcraft_veo3(request, app_env_configs, &creds).await, - Some(VideoModel::Veo3Fast) => handle_artcraft_veo3_fast(request, app_env_configs, &creds).await, - Some(VideoModel::Veo3p1) => handle_artcraft_veo3p1(request, app_env_configs, &creds).await, - Some(VideoModel::Veo3p1Fast) => handle_artcraft_veo3p1_fast(request, app_env_configs, &creds).await, - Some(_) => { - Err(GenerateError::AnyhowError( - anyhow!("wrong logic: another branch should handle this: {:?}", request.model))) - }, - } -} diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/enqueue_image_to_video_command.rs b/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/enqueue_image_to_video_command.rs deleted file mode 100644 index 4233de3cb9..0000000000 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/enqueue_image_to_video_command.rs +++ /dev/null @@ -1,395 +0,0 @@ -use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; -use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason}; -use crate::core::commands::enqueue::image_to_video::artcraft::handle_artcraft_video::handle_video_artcraft; -use crate::core::commands::enqueue::image_to_video::grok::handle_grok_video::handle_grok_video; -use crate::core::commands::enqueue::image_to_video::sora2::handle_sora_sora2::handle_sora_sora2; -use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; -use crate::core::commands::response::shorthand::Response; -use crate::core::commands::response::success_response_wrapper::SerializeMarker; -use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::core::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent; -use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; -use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; -use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType}; -use crate::core::state::data_dir::app_data_root::AppDataRoot; -use crate::core::state::provider_priority::{Provider, ProviderPriorityStore}; -use crate::core::state::task_database::TaskDatabase; -use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; -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_router::api::common_aspect_ratio::CommonAspectRatio; -use artcraft_router::api::common_resolution::CommonResolution; -use enums::common::generation_provider::GenerationProvider; -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::characters::CharacterToken; -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 VideoModel { - #[serde(rename = "grok_video")] - GrokVideo, - - #[serde(rename = "kling_1.6_pro")] - Kling16Pro, - - #[serde(rename = "kling_2.1_pro")] - Kling21Pro, - - #[serde(rename = "kling_2.1_master")] - Kling21Master, - - #[serde(rename = "kling_2p5_turbo_pro")] - Kling2p5TurboPro, - - #[serde(rename = "kling_2p6_pro")] - Kling2p6Pro, - - #[serde(rename = "kling_3p0_standard")] - Kling3p0Standard, - - #[serde(rename = "kling_3p0_pro")] - Kling3p0Pro, - - #[serde(rename = "happy_horse_1p0")] - HappyHorse1p0, - - #[serde(rename = "seedance_1.0_lite")] - Seedance10Lite, - - #[serde(rename = "seedance_1p5_pro")] - Seedance1p5Pro, - - #[serde(rename = "seedance_2p0")] - Seedance2p0, - - #[serde(rename = "seedance_2p0_fast")] - Seedance2p0Fast, - - #[serde(rename = "sora_2")] - Sora2, - - #[serde(rename = "sora_2_pro")] - Sora2Pro, - - #[serde(rename = "veo_2")] - Veo2, - - #[serde(rename = "veo_3")] - Veo3, - - #[serde(rename = "veo_3_fast")] - Veo3Fast, - - #[serde(rename = "veo_3p1")] - Veo3p1, - - #[serde(rename = "veo_3p1_fast")] - Veo3p1Fast, -} - -#[derive(Deserialize, Debug)] -pub struct EnqueueImageToVideoRequest { - /// The provider to use (defaults to Artcraft/Storyteller). - /// Not all (provider, model) combinations are valid. - pub provider: Option, - - /// REQUIRED. - /// The model to use. - pub model: Option, - - /// Currently REQUIRED by some downstream models. - /// Image media file; the starting frame of the video. - /// TODO: In the future we may support base64 images, URLs, or file paths here. - pub image_media_token: Option, - - /// Optional. - /// 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 end_frame_image_media_token: Option, - - /// OPTIONAL. - /// Reference images for the video generation (e.g. character consistency, props, etc.) - pub reference_image_media_tokens: Option>, - - /// OPTIONAL. - /// Reference videos for the video generation (e.g. video edits, etc.) - pub reference_video_media_tokens: Option>, - - /// OPTIONAL. - /// Reference audio for the video generation (e.g. voices, lip sync, music, etc.) - pub reference_audio_media_tokens: Option>, - - /// Optional character tokens to reference in the prompt. - /// Characters are referenced in prompts as @CharacterName. - pub reference_character_tokens: Option>, - - /// Optional. - /// Text prompt used to direct the video. - pub prompt: Option, - - /// Optional. - /// Generate the video with audio, if there is an option. - /// Typically, this costs money when presented as an option, so we default to off. - pub generate_audio: Option, - - /// OPTIONAL. - /// Aspect ratio for the generated video. - pub aspect_ratio: Option, - - /// OPTIONAL. - /// Resolution for the generated video. - pub resolution: Option, - - /// OPTIONAL. - /// Duration of the generated video in seconds. - pub duration_seconds: Option, - - /// OPTIONAL. - /// Number of videos to generate in one batch. - pub video_batch_count: Option, - - /// OPTIONAL. - /// Only for Sora2 model currently. - pub sora_orientation: Option, - - /// OPTIONAL. - /// Only for Grok model currently - pub grok_aspect_ratio: 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: Not sure how to handle so many different types of video (model) x (services). -#[derive(Deserialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum SoraOrientation { - Portrait, - Landscape, -} - -// TODO: Not sure how to handle so many different types of video (model) x (services). -#[derive(Deserialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum GrokAspectRatio { - Portrait, - Landscape, - Square -} - -#[derive(Serialize)] -pub struct EnqueueImageToVideoSuccessResponse { -} - -impl SerializeMarker for EnqueueImageToVideoSuccessResponse {} - -#[derive(Serialize, Debug)] -#[serde(rename_all = "snake_case")] -pub enum EnqueueImageToVideoErrorType { - /// Caller didn't specify a model - ModelNotSpecified, - /// No model available for video 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_video_command( - request: EnqueueImageToVideoRequest, - app: AppHandle, - app_env_configs: State<'_, AppEnvConfigs>, - app_data_root: State<'_, AppDataRoot>, - artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>, - provider_priority_store: State<'_, ProviderPriorityStore>, - task_database: State<'_, TaskDatabase>, - grok_creds_manager: State<'_, GrokCredentialManager>, - storyteller_creds_manager: State<'_, StorytellerCredentialManager>, - sora_task_queue: State<'_, SoraTaskQueue>, - sora_creds_manager: State<'_, SoraCredentialManager>, -) -> Response { - - info!("enqueue_image_to_video_command called, request: {:?}", request); - - let result = handle_request( - request, - &app, - &app_env_configs, - &app_data_root, - &artcraft_usage_tracker, - &provider_priority_store, - &task_database, - &grok_creds_manager, - &sora_creds_manager, - &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 = EnqueueImageToVideoErrorType::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 = EnqueueImageToVideoErrorType::ModelNotSpecified; - error_message = "No model specified for video generation"; - } - GenerateError::NoProviderAvailable => { - status = CommandErrorStatus::ServerError; - error_type = EnqueueImageToVideoErrorType::NoProviderAvailable; - error_message = "No configured provider available for video generation"; - } - GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => { - status = CommandErrorStatus::Unauthorized; - error_type = EnqueueImageToVideoErrorType::NeedsFalApiKey; - error_message = "You need to set a FAL api key"; - }, - GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials) => { - status = CommandErrorStatus::Unauthorized; - error_type = EnqueueImageToVideoErrorType::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(EnqueueImageToVideoSuccessResponse {}.into()) - } - } -} - - -pub async fn handle_request( - request: EnqueueImageToVideoRequest, - app: &AppHandle, - app_env_configs: &AppEnvConfigs, - app_data_root: &AppDataRoot, - artcraft_usage_tracker: &ArtcraftUsageTracker, - provider_priority_store: &ProviderPriorityStore, - task_database: &TaskDatabase, - grok_creds_manager: &GrokCredentialManager, - sora_creds_manager: &SoraCredentialManager, - storyteller_creds_manager: &StorytellerCredentialManager, -) -> Result { - - let model = match request.model { - Some(model) => model, - None => { - return Err(GenerateError::no_model_specified()) - } - }; - - let provider = match (model, request.provider) { - (VideoModel::GrokVideo, _) => GenerationProvider::Grok, - _ => request.provider.unwrap_or(GenerationProvider::Artcraft), - }; - - info!("generate video with {:?} via provider {:?}", &model, &provider); - - let result = match provider { - GenerationProvider::Grok => { - handle_grok_video( - &request, - app, - app_data_root, - app_env_configs, - grok_creds_manager, - ).await - } - GenerationProvider::Sora => { - handle_sora_sora2( - &request, - app, - app_data_root, - app_env_configs, - sora_creds_manager, - ).await - } - _ => { - handle_video_artcraft( - &request, - app, - app_env_configs, - 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, - 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_video_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::VideoPage) { - // NB: Fail open. - warn!("Failed to report usage: {:?}", err); - } - - Ok(success_event) -} diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/mod.rs b/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/mod.rs deleted file mode 100644 index 8aaaf239f5..0000000000 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod enqueue_image_to_video_command; -pub mod sora2; -mod grok; -mod artcraft; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/mod.rs b/crates/desktop/artcraft/src/core/commands/enqueue/mod.rs index b2f9aa2fc3..15cb9119e1 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/enqueue/mod.rs @@ -4,7 +4,6 @@ pub mod image_bg_removal; pub mod image_edit; pub mod image_inpaint; pub mod image_to_object; -pub mod image_to_video; pub mod task_enqueue_success; pub mod text_to_image; pub mod image_to_gaussian; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/get_storyteller_creds_or_error.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/get_storyteller_creds_or_error.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/get_storyteller_creds_or_error.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/get_storyteller_creds_or_error.rs diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_1p6_pro.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_1p6_pro.rs similarity index 92% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_1p6_pro.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_1p6_pro.rs index b9ee7a2679..ca84b1f8a5 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_1p6_pro.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_1p6_pro.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -26,7 +26,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_kling_1p6_pro( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p1_master.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p1_master.rs similarity index 92% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p1_master.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p1_master.rs index cc66d35dff..eb443f03fc 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p1_master.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p1_master.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -26,7 +26,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_kling_2p1_master( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p1_pro.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p1_pro.rs similarity index 92% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p1_pro.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p1_pro.rs index 5892fa1366..c085324560 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p1_pro.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p1_pro.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -25,7 +25,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_kling_2p1_pro( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p5_turbo_pro.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p5_turbo_pro.rs similarity index 92% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p5_turbo_pro.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p5_turbo_pro.rs index b8bf4015e9..032489b319 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p5_turbo_pro.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p5_turbo_pro.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -24,7 +24,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_kling_2p5_turbo_pro( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p6_pro.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p6_pro.rs similarity index 92% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p6_pro.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p6_pro.rs index b2c196c885..783fd9e48d 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_kling_2p6_pro.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_kling_2p6_pro.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -26,7 +26,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_kling_2p6_pro( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_seedance_1_lite.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_1_lite.rs similarity index 92% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_seedance_1_lite.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_1_lite.rs index 5e07d28a07..ac5cc62f49 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_seedance_1_lite.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_1_lite.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -27,7 +27,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_seedance_1_lite( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_seedance_2p0.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_2p0.rs similarity index 94% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_seedance_2p0.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_2p0.rs index 70e150fefe..fb948c8436 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_seedance_2p0.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_2p0.rs @@ -1,5 +1,5 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::EnqueueImageToVideoRequest; +use crate::core::commands::generate::generate_video::request::TauriGenerateVideoRequest; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::generation_events::common::GenerationModel; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; @@ -20,7 +20,7 @@ use enums::tauri::tasks::task_type::TaskType; use log::{error, info}; pub(super) async fn handle_artcraft_seedance_2p0( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { @@ -34,7 +34,7 @@ pub(super) async fn handle_artcraft_seedance_2p0( let reference_images = request.reference_image_media_tokens.clone().map(ImageListRef::MediaFileTokens); let router_request = GenerateVideoRequestBuilder { - model: CommonVideoModel::Seedance2p0, + model: CommonTauriVideoModel::Seedance2p0, provider: Provider::Artcraft, prompt: request.prompt.clone(), negative_prompt: None, diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_sora2.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_sora2.rs similarity index 93% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_sora2.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_sora2.rs index c8660aa9ab..f19ed8e319 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_sora2.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_sora2.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -28,7 +28,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_sora2( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_sora2_pro.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_sora2_pro.rs similarity index 92% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_sora2_pro.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_sora2_pro.rs index 9dec7ef6ae..da71f50a58 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_sora2_pro.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_sora2_pro.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -27,7 +27,7 @@ use artcraft_client::credentials::storyteller_credential_set::StorytellerCredent use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_sora2_pro( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo2.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo2.rs similarity index 91% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo2.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo2.rs index f90128a926..9dd9b1944f 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo2.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo2.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -25,7 +25,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_veo2( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3.rs similarity index 91% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3.rs index 9036b604e8..ff63be0f92 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -25,7 +25,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_veo3( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3_fast.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3_fast.rs similarity index 91% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3_fast.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3_fast.rs index 453f4768ec..9fe34fc7ee 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3_fast.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3_fast.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -26,7 +26,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_veo3_fast( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3p1.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3p1.rs similarity index 92% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3p1.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3p1.rs index fdad3af739..2636c02f84 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3p1.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3p1.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -24,7 +24,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_veo3p1( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3p1_fast.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3p1_fast.rs similarity index 92% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3p1_fast.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3p1_fast.rs index afbfb0e2f4..ab7dfe7591 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_veo3p1_fast.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_veo3p1_fast.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; @@ -24,7 +24,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub (super) async fn handle_artcraft_veo3p1_fast( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, ) -> Result { diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video.rs new file mode 100644 index 0000000000..855a4a08c9 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video.rs @@ -0,0 +1,66 @@ +use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_1p6_pro::handle_artcraft_kling_1p6_pro; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_2p1_master::handle_artcraft_kling_2p1_master; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_2p1_pro::handle_artcraft_kling_2p1_pro; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_2p5_turbo_pro::handle_artcraft_kling_2p5_turbo_pro; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_2p6_pro::handle_artcraft_kling_2p6_pro; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_video_via_router::handle_artcraft_video_via_router; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_seedance_1_lite::handle_artcraft_seedance_1_lite; +use crate::core::events::generation_events::common::GenerationModel; +use artcraft_router::api::common_video_model::CommonVideoModel; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_seedance_2p0::handle_artcraft_seedance_2p0; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_sora2::handle_artcraft_sora2; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_sora2_pro::handle_artcraft_sora2_pro; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_veo2::handle_artcraft_veo2; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_veo3::handle_artcraft_veo3; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_veo3_fast::handle_artcraft_veo3_fast; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_veo3p1::handle_artcraft_veo3p1; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_veo3p1_fast::handle_artcraft_veo3p1_fast; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, TauriVideoModel}; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; +use anyhow::anyhow; +use tauri::AppHandle; + +pub async fn handle_video_artcraft( + request: &TauriGenerateVideoRequest, + 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()) + }, + // Handled via router + Some(TauriVideoModel::Kling3p0Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Kling3p0Pro, GenerationModel::Kling3p0Pro).await, + Some(TauriVideoModel::Kling3p0Standard) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Kling3p0Standard, GenerationModel::Kling3p0Standard).await, + Some(TauriVideoModel::Seedance1p5Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Seedance1p5Pro, GenerationModel::Seedance1p5Pro).await, + Some(TauriVideoModel::Seedance2p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Seedance2p0, GenerationModel::Seedance2p0).await, + Some(TauriVideoModel::Seedance2p0Fast) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Seedance2p0Fast, GenerationModel::Seedance2p0Fast).await, + Some(TauriVideoModel::HappyHorse1p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::HappyHorse1p0, GenerationModel::HappyHorse1p0).await, + // Legacy endpoints + Some(TauriVideoModel::Kling16Pro) => handle_artcraft_kling_1p6_pro(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Kling21Pro) => handle_artcraft_kling_2p1_pro(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Kling21Master) => handle_artcraft_kling_2p1_master(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Kling2p5TurboPro) => handle_artcraft_kling_2p5_turbo_pro(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Kling2p6Pro) => handle_artcraft_kling_2p6_pro(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Seedance10Lite) => handle_artcraft_seedance_1_lite(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Sora2) => handle_artcraft_sora2(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Sora2Pro) => handle_artcraft_sora2_pro(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Veo2) => handle_artcraft_veo2(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Veo3) => handle_artcraft_veo3(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Veo3Fast) => handle_artcraft_veo3_fast(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Veo3p1) => handle_artcraft_veo3p1(request, app_env_configs, &creds).await, + Some(TauriVideoModel::Veo3p1Fast) => handle_artcraft_veo3p1_fast(request, app_env_configs, &creds).await, + Some(_) => { + Err(GenerateError::AnyhowError( + anyhow!("wrong logic: another branch should handle this: {:?}", request.model))) + }, + } +} diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_video_via_router.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video_via_router.rs similarity index 97% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_video_via_router.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video_via_router.rs index 033ad9b11b..2fed730e18 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/handle_artcraft_video_via_router.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video_via_router.rs @@ -1,5 +1,5 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::EnqueueImageToVideoRequest; +use crate::core::commands::generate::generate_video::request::TauriGenerateVideoRequest; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::generation_events::common::GenerationModel; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; @@ -22,7 +22,7 @@ use enums::tauri::tasks::task_type::TaskType; use log::{error, info}; pub(super) async fn handle_artcraft_video_via_router( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app_env_configs: &AppEnvConfigs, creds: &StorytellerCredentialSet, model: CommonVideoModel, diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/mod.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/artcraft/mod.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/mod.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_video/generate_video_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/generate_video_command.rs new file mode 100644 index 0000000000..b3dd4e954b --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/generate_video_command.rs @@ -0,0 +1,198 @@ +use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; +use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason}; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_video::handle_video_artcraft; +use crate::core::commands::generate::generate_video::grok::handle_grok_video::handle_grok_video; +use crate::core::commands::generate::generate_video::request::{ + TauriGenerateVideoErrorType, TauriGenerateVideoRequest, TauriGenerateVideoResponse, + TauriVideoModel, +}; +use crate::core::commands::generate::generate_video::sora2::handle_sora_sora2::handle_sora_sora2; +use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; +use crate::core::commands::response::shorthand::Response; +use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; +use crate::core::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent; +use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; +use crate::core::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType}; +use crate::core::state::data_dir::app_data_root::AppDataRoot; +use crate::core::state::provider_priority::{Provider, ProviderPriorityStore}; +use crate::core::state::task_database::TaskDatabase; +use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; +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 log::{error, info, warn}; +use tauri::{AppHandle, State}; + +#[tauri::command] +pub async fn generate_video_command( + request: TauriGenerateVideoRequest, + app: AppHandle, + app_env_configs: State<'_, AppEnvConfigs>, + app_data_root: State<'_, AppDataRoot>, + artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>, + provider_priority_store: State<'_, ProviderPriorityStore>, + task_database: State<'_, TaskDatabase>, + grok_creds_manager: State<'_, GrokCredentialManager>, + storyteller_creds_manager: State<'_, StorytellerCredentialManager>, + sora_task_queue: State<'_, SoraTaskQueue>, + sora_creds_manager: State<'_, SoraCredentialManager>, +) -> Response { + + info!("generate_video_command called, request: {:?}", request); + + let result = handle_request( + request, + &app, + &app_env_configs, + &app_data_root, + &artcraft_usage_tracker, + &provider_priority_store, + &task_database, + &grok_creds_manager, + &sora_creds_manager, + &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 = TauriGenerateVideoErrorType::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 = TauriGenerateVideoErrorType::ModelNotSpecified; + error_message = "No model specified for video generation"; + } + GenerateError::NoProviderAvailable => { + status = CommandErrorStatus::ServerError; + error_type = TauriGenerateVideoErrorType::NoProviderAvailable; + error_message = "No configured provider available for video generation"; + } + GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => { + status = CommandErrorStatus::Unauthorized; + error_type = TauriGenerateVideoErrorType::NeedsFalApiKey; + error_message = "You need to set a FAL api key"; + }, + GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials) => { + status = CommandErrorStatus::Unauthorized; + error_type = TauriGenerateVideoErrorType::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); + } + + CreditsBalanceChangedEvent{}.send_infallible(&app); + + Ok(TauriGenerateVideoResponse {}.into()) + } + } +} + +async fn handle_request( + request: TauriGenerateVideoRequest, + app: &AppHandle, + app_env_configs: &AppEnvConfigs, + app_data_root: &AppDataRoot, + artcraft_usage_tracker: &ArtcraftUsageTracker, + provider_priority_store: &ProviderPriorityStore, + task_database: &TaskDatabase, + grok_creds_manager: &GrokCredentialManager, + sora_creds_manager: &SoraCredentialManager, + storyteller_creds_manager: &StorytellerCredentialManager, +) -> Result { + + let model = match request.model { + Some(model) => model, + None => { + return Err(GenerateError::no_model_specified()) + } + }; + + let provider = match (model, request.provider) { + (TauriVideoModel::GrokVideo, _) => GenerationProvider::Grok, + _ => request.provider.unwrap_or(GenerationProvider::Artcraft), + }; + + info!("generate video with {:?} via provider {:?}", &model, &provider); + + let result = match provider { + GenerationProvider::Grok => { + handle_grok_video( + &request, + app, + app_data_root, + app_env_configs, + grok_creds_manager, + ).await + } + GenerationProvider::Sora => { + handle_sora_sora2( + &request, + app, + app_data_root, + app_env_configs, + sora_creds_manager, + ).await + } + _ => { + handle_video_artcraft( + &request, + app, + app_env_configs, + 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, + 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); + } + + if let Err(err) = artcraft_usage_tracker.record_video_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::VideoPage) { + warn!("Failed to report usage: {:?}", err); + } + + Ok(success_event) +} diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/grok/handle_grok_video.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/grok/handle_grok_video.rs similarity index 97% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/grok/handle_grok_video.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/grok/handle_grok_video.rs index bd6d1e0cb9..cd6098172d 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/grok/handle_grok_video.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/grok/handle_grok_video.rs @@ -1,7 +1,7 @@ use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError}; use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::EnqueueEditImageCommand; use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, GrokAspectRatio, SoraOrientation}; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, GrokAspectRatio, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; use crate::core::events::generation_events::common::GenerationModel; @@ -33,7 +33,7 @@ use tauri::AppHandle; const GROK_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds pub async fn handle_grok_video( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app: &AppHandle, app_data_root: &AppDataRoot, app_env_configs: &AppEnvConfigs, diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/grok/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/grok/mod.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/grok/mod.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/grok/mod.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_video/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/mod.rs new file mode 100644 index 0000000000..f7a6a5a7f9 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/mod.rs @@ -0,0 +1,5 @@ +pub mod generate_video_command; +pub mod request; +mod artcraft; +mod grok; +pub mod sora2; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_video/request.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/request.rs new file mode 100644 index 0000000000..eb3ae148e5 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/request.rs @@ -0,0 +1,130 @@ +use artcraft_router::api::common_aspect_ratio::CommonAspectRatio; +use artcraft_router::api::common_resolution::CommonResolution; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; +use serde_derive::{Deserialize, Serialize}; +use tokens::tokens::characters::CharacterToken; +use tokens::tokens::media_files::MediaFileToken; + +use crate::core::commands::response::success_response_wrapper::SerializeMarker; + +/// 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 TauriVideoModel { + #[serde(rename = "grok_video")] + GrokVideo, + + #[serde(rename = "kling_1.6_pro")] + Kling16Pro, + + #[serde(rename = "kling_2.1_pro")] + Kling21Pro, + + #[serde(rename = "kling_2.1_master")] + Kling21Master, + + #[serde(rename = "kling_2p5_turbo_pro")] + Kling2p5TurboPro, + + #[serde(rename = "kling_2p6_pro")] + Kling2p6Pro, + + #[serde(rename = "kling_3p0_standard")] + Kling3p0Standard, + + #[serde(rename = "kling_3p0_pro")] + Kling3p0Pro, + + #[serde(rename = "happy_horse_1p0")] + HappyHorse1p0, + + #[serde(rename = "seedance_1.0_lite")] + Seedance10Lite, + + #[serde(rename = "seedance_1p5_pro")] + Seedance1p5Pro, + + #[serde(rename = "seedance_2p0")] + Seedance2p0, + + #[serde(rename = "seedance_2p0_fast")] + Seedance2p0Fast, + + #[serde(rename = "sora_2")] + Sora2, + + #[serde(rename = "sora_2_pro")] + Sora2Pro, + + #[serde(rename = "veo_2")] + Veo2, + + #[serde(rename = "veo_3")] + Veo3, + + #[serde(rename = "veo_3_fast")] + Veo3Fast, + + #[serde(rename = "veo_3p1")] + Veo3p1, + + #[serde(rename = "veo_3p1_fast")] + Veo3p1Fast, +} + +#[derive(Deserialize, Debug)] +pub struct TauriGenerateVideoRequest { + pub provider: Option, + pub model: Option, + pub image_media_token: Option, + pub end_frame_image_media_token: Option, + pub reference_image_media_tokens: Option>, + pub reference_video_media_tokens: Option>, + pub reference_audio_media_tokens: Option>, + pub reference_character_tokens: Option>, + pub prompt: Option, + pub generate_audio: Option, + pub aspect_ratio: Option, + pub resolution: Option, + pub duration_seconds: Option, + pub video_batch_count: Option, + pub sora_orientation: Option, + pub grok_aspect_ratio: Option, + pub frontend_caller: Option, + pub frontend_subscriber_id: Option, + pub frontend_subscriber_payload: Option, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum SoraOrientation { + Portrait, + Landscape, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum GrokAspectRatio { + Portrait, + Landscape, + Square, +} + +#[derive(Serialize)] +pub struct TauriGenerateVideoResponse { +} + +impl SerializeMarker for TauriGenerateVideoResponse {} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum TauriGenerateVideoErrorType { + ModelNotSpecified, + NoProviderAvailable, + ServerError, + NeedsFalApiKey, + FalError, + NeedsStorytellerCredentials, +} diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/sora2/handle_sora_sora2.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/sora2/handle_sora_sora2.rs similarity index 95% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/sora2/handle_sora_sora2.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/sora2/handle_sora_sora2.rs index d61844eaae..8520b4dfa7 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/sora2/handle_sora_sora2.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/sora2/handle_sora_sora2.rs @@ -1,5 +1,5 @@ use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation}; +use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; use crate::core::events::generation_events::common::GenerationModel; @@ -19,7 +19,7 @@ use tauri::AppHandle; use tokens::tokens::media_files::MediaFileToken; pub async fn handle_sora_sora2( - request: &EnqueueImageToVideoRequest, + request: &TauriGenerateVideoRequest, app: &AppHandle, app_data_root: &AppDataRoot, app_env_configs: &AppEnvConfigs, diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/sora2/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/sora2/mod.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/enqueue/image_to_video/sora2/mod.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_video/sora2/mod.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/mod.rs new file mode 100644 index 0000000000..2492e000e2 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/mod.rs @@ -0,0 +1 @@ +pub mod generate_video; diff --git a/crates/desktop/artcraft/src/core/commands/mod.rs b/crates/desktop/artcraft/src/core/commands/mod.rs index 4620e1c9fc..175603efbb 100644 --- a/crates/desktop/artcraft/src/core/commands/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/mod.rs @@ -4,6 +4,7 @@ pub mod cost_estimate; pub mod download; pub mod enqueue; pub mod flip_image; +pub mod generate; pub mod get_app_info_command; pub mod load_without_cors_command; pub mod media_files; diff --git a/crates/desktop/artcraft/src/lib.rs b/crates/desktop/artcraft/src/lib.rs index e1a9b3a959..e29856dc90 100644 --- a/crates/desktop/artcraft/src/lib.rs +++ b/crates/desktop/artcraft/src/lib.rs @@ -17,8 +17,8 @@ use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::enqu use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::enqueue_image_inpaint_command; use crate::core::commands::enqueue::image_to_gaussian::enqueue_image_to_gaussian_command::enqueue_image_to_gaussian_command; use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::enqueue_image_to_3d_object_command; -use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::enqueue_image_to_video_command; use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::enqueue_text_to_image_command; +use crate::core::commands::generate::generate_video::generate_video_command::generate_video_command; use crate::core::commands::flip_image::flip_image; use crate::core::commands::get_app_info_command::get_app_info_command; use crate::core::commands::load_without_cors_command::load_without_cors_command; @@ -212,12 +212,12 @@ pub fn run() { enqueue_image_inpaint_command, enqueue_image_to_3d_object_command, enqueue_image_to_gaussian_command, - enqueue_image_to_video_command, enqueue_text_to_image_command, estimate_image_cost_command, estimate_splat_cost_command, estimate_video_cost_command, flip_image, + generate_video_command, get_app_info_command, get_app_preferences_command, get_provider_order_command, From afc9db10fed0c918532c0538665a4dcb941e9963 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 29 Apr 2026 16:20:09 -0400 Subject: [PATCH 02/14] fix requests --- .../src/core/commands/enqueue/common/mod.rs | 4 ++-- .../artcraft/handle_artcraft_video.rs | 21 ++++++++-------- .../generate_video/generate_video_command.rs | 14 ++++++++++- .../generate/generate_video/request.rs | 24 +++++++++++++++---- 4 files changed, 44 insertions(+), 19 deletions(-) diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/common/mod.rs b/crates/desktop/artcraft/src/core/commands/enqueue/common/mod.rs index d81449e79c..feab773fad 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/common/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/enqueue/common/mod.rs @@ -1,3 +1,3 @@ -mod maybe_notify_frontend_of_billing_errors; -mod maybe_notify_frontend_of_grok_errors; +pub mod maybe_notify_frontend_of_billing_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/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video.rs index 855a4a08c9..f6688483c9 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_video.rs @@ -1,15 +1,12 @@ use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_1p6_pro::handle_artcraft_kling_1p6_pro; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_2p1_master::handle_artcraft_kling_2p1_master; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_2p1_pro::handle_artcraft_kling_2p1_pro; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_2p5_turbo_pro::handle_artcraft_kling_2p5_turbo_pro; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_kling_2p6_pro::handle_artcraft_kling_2p6_pro; -use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_video_via_router::handle_artcraft_video_via_router; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_seedance_1_lite::handle_artcraft_seedance_1_lite; -use crate::core::events::generation_events::common::GenerationModel; -use artcraft_router::api::common_video_model::CommonVideoModel; -use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_seedance_2p0::handle_artcraft_seedance_2p0; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_sora2::handle_artcraft_sora2; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_sora2_pro::handle_artcraft_sora2_pro; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_veo2::handle_artcraft_veo2; @@ -17,11 +14,13 @@ use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_v use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_veo3_fast::handle_artcraft_veo3_fast; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_veo3p1::handle_artcraft_veo3p1; use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_veo3p1_fast::handle_artcraft_veo3p1_fast; +use crate::core::commands::generate::generate_video::artcraft::handle_artcraft_video_via_router::handle_artcraft_video_via_router; use crate::core::commands::generate::generate_video::request::{TauriGenerateVideoRequest, TauriVideoModel}; -use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::events::generation_events::common::GenerationModel; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; use anyhow::anyhow; +use artcraft_router::api::common_video_model::CommonVideoModel; use tauri::AppHandle; pub async fn handle_video_artcraft( @@ -38,12 +37,12 @@ pub async fn handle_video_artcraft( Err(GenerateError::no_model_specified()) }, // Handled via router - Some(TauriVideoModel::Kling3p0Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Kling3p0Pro, GenerationModel::Kling3p0Pro).await, - Some(TauriVideoModel::Kling3p0Standard) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Kling3p0Standard, GenerationModel::Kling3p0Standard).await, - Some(TauriVideoModel::Seedance1p5Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Seedance1p5Pro, GenerationModel::Seedance1p5Pro).await, - Some(TauriVideoModel::Seedance2p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Seedance2p0, GenerationModel::Seedance2p0).await, - Some(TauriVideoModel::Seedance2p0Fast) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::Seedance2p0Fast, GenerationModel::Seedance2p0Fast).await, - Some(TauriVideoModel::HappyHorse1p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonTauriVideoModel::HappyHorse1p0, GenerationModel::HappyHorse1p0).await, + Some(TauriVideoModel::Kling3p0Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Pro, GenerationModel::Kling3p0Pro).await, + Some(TauriVideoModel::Kling3p0Standard) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Kling3p0Standard, GenerationModel::Kling3p0Standard).await, + Some(TauriVideoModel::Seedance1p5Pro) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance1p5Pro, GenerationModel::Seedance1p5Pro).await, + Some(TauriVideoModel::Seedance2p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance2p0, GenerationModel::Seedance2p0).await, + Some(TauriVideoModel::Seedance2p0Fast) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::Seedance2p0Fast, GenerationModel::Seedance2p0Fast).await, + Some(TauriVideoModel::HappyHorse1p0) => handle_artcraft_video_via_router(request, app_env_configs, &creds, CommonVideoModel::HappyHorse1p0, GenerationModel::HappyHorse1p0).await, // Legacy endpoints Some(TauriVideoModel::Kling16Pro) => handle_artcraft_kling_1p6_pro(request, app_env_configs, &creds).await, Some(TauriVideoModel::Kling21Pro) => handle_artcraft_kling_2p1_pro(request, app_env_configs, &creds).await, diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_video/generate_video_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/generate_video_command.rs index b3dd4e954b..882d07a15d 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_video/generate_video_command.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/generate_video_command.rs @@ -29,7 +29,7 @@ use tauri::{AppHandle, State}; #[tauri::command] pub async fn generate_video_command( - request: TauriGenerateVideoRequest, + mut request: TauriGenerateVideoRequest, app: AppHandle, app_env_configs: State<'_, AppEnvConfigs>, app_data_root: State<'_, AppDataRoot>, @@ -44,6 +44,18 @@ pub async fn generate_video_command( info!("generate_video_command called, request: {:?}", request); + if request.image_media_token.is_none() + && request.start_frame_image_media_token.is_some() { + // NB: This is to fix the legacy handlers + request.image_media_token = request.start_frame_image_media_token.clone(); + } + + if request.image_media_token.is_some() + && request.start_frame_image_media_token.is_none() { + // NB: This is to fix the modern handlers + request.start_frame_image_media_token = request.image_media_token.clone(); + } + let result = handle_request( request, &app, diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_video/request.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/request.rs index eb3ae148e5..1e2412f8cc 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_video/request.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/request.rs @@ -74,37 +74,51 @@ pub enum TauriVideoModel { Veo3p1Fast, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone)] pub struct TauriGenerateVideoRequest { pub provider: Option, pub model: Option, + + pub prompt: Option, + pub negative_prompt: Option, + + #[deprecated(note = "Use start_frame_image_media_token instead")] pub image_media_token: Option, + + pub start_frame_image_media_token: Option, pub end_frame_image_media_token: Option, + pub reference_image_media_tokens: Option>, pub reference_video_media_tokens: Option>, pub reference_audio_media_tokens: Option>, pub reference_character_tokens: Option>, - pub prompt: Option, - pub generate_audio: Option, + pub aspect_ratio: Option, pub resolution: Option, + pub duration_seconds: Option, + pub generate_audio: Option, pub video_batch_count: Option, + + #[deprecated(note = "Use start_frame_image_media_token instead")] pub sora_orientation: Option, + + #[deprecated(note = "Use start_frame_image_media_token instead")] pub grok_aspect_ratio: Option, + pub frontend_caller: Option, pub frontend_subscriber_id: Option, pub frontend_subscriber_payload: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone, Copy)] #[serde(rename_all = "snake_case")] pub enum SoraOrientation { Portrait, Landscape, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Clone, Copy)] #[serde(rename_all = "snake_case")] pub enum GrokAspectRatio { Portrait, From b73d0b1af68ff7fd20ef998d2defa50aa2a52f6f Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 29 Apr 2026 16:47:14 -0400 Subject: [PATCH 03/14] fix compile error, remove dead code --- .../artcraft/handle_artcraft_seedance_2p0.rs | 81 ------------------- .../generate/generate_video/artcraft/mod.rs | 1 - 2 files changed, 82 deletions(-) delete mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_2p0.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_2p0.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_2p0.rs deleted file mode 100644 index fb948c8436..0000000000 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/handle_artcraft_seedance_2p0.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::generate::generate_video::request::TauriGenerateVideoRequest; -use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::events::generation_events::common::GenerationModel; -use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; -use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; -use artcraft_router::api::audio_list_ref::AudioListRef; -use artcraft_router::api::character_list_ref::CharacterListRef; -use artcraft_router::api::common_video_model::CommonVideoModel; -use artcraft_router::api::image_list_ref::ImageListRef; -use artcraft_router::api::image_ref::ImageRef; -use artcraft_router::api::provider::Provider; -use artcraft_router::api::video_list_ref::VideoListRef; -use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; -use artcraft_router::client::router_artcraft_client::RouterArtcraftClient; -use artcraft_router::client::router_client::RouterClient; -use artcraft_router::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use log::{error, info}; - -pub(super) async fn handle_artcraft_seedance_2p0( - request: &TauriGenerateVideoRequest, - app_env_configs: &AppEnvConfigs, - creds: &StorytellerCredentialSet, -) -> Result { - let client = RouterClient::Artcraft(RouterArtcraftClient::new( - app_env_configs.storyteller_host.clone(), - creds.clone(), - )); - - let start_frame = request.image_media_token.clone().map(ImageRef::MediaFileToken); - let end_frame = request.end_frame_image_media_token.clone().map(ImageRef::MediaFileToken); - let reference_images = request.reference_image_media_tokens.clone().map(ImageListRef::MediaFileTokens); - - let router_request = GenerateVideoRequestBuilder { - model: CommonTauriVideoModel::Seedance2p0, - provider: Provider::Artcraft, - prompt: request.prompt.clone(), - negative_prompt: None, - start_frame, - end_frame, - reference_images, - reference_videos: request.reference_video_media_tokens.clone().map(VideoListRef::MediaFileTokens), - reference_audio: request.reference_audio_media_tokens.clone().map(AudioListRef::MediaFileTokens), - reference_character_tokens: request.reference_character_tokens.clone().map(CharacterListRef::CharacterTokens), - resolution: None, - aspect_ratio: request.aspect_ratio, - duration_seconds: request.duration_seconds, - video_batch_count: request.video_batch_count, - generate_audio: None, - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, - idempotency_token: None, - }; - - let plan = router_request.build()?; - - info!("Video Generation Plan: {:?}", plan); - - let response = match plan.generate_video(&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::VideoGeneration, - model: Some(GenerationModel::Seedance2p0), - provider: GenerationProvider::Artcraft, - provider_job_id: Some(job_id), - }) -} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/mod.rs index 8e87cf4f2e..af73548dee 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_video/artcraft/mod.rs @@ -6,7 +6,6 @@ mod handle_artcraft_kling_2p5_turbo_pro; mod handle_artcraft_kling_2p6_pro; mod handle_artcraft_video_via_router; mod handle_artcraft_seedance_1_lite; -mod handle_artcraft_seedance_2p0; mod handle_artcraft_sora2; mod handle_artcraft_sora2_pro; mod handle_artcraft_veo2; From 62cb63b8dcb16c20e0980629103e5da35bbb8323 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 29 Apr 2026 17:40:33 -0400 Subject: [PATCH 04/14] stub "generate_image" endpoint --- .../generate_image/generate_image_command.rs | 17 ++ .../commands/generate/generate_image/mod.rs | 2 + .../generate/generate_image/request.rs | 215 ++++++++++++++++++ .../src/core/commands/generate/mod.rs | 1 + crates/desktop/artcraft/src/lib.rs | 2 + 5 files changed, 237 insertions(+) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/request.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs new file mode 100644 index 0000000000..e88253aeec --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs @@ -0,0 +1,17 @@ +use crate::core::commands::generate::generate_image::request::{TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse}; +use crate::core::commands::response::shorthand::Response; +use log::info; +use tauri::AppHandle; + +#[tauri::command] +pub async fn generate_image_command( + request: TauriGenerateImageRequest, + app: AppHandle, +) -> Response { + + info!("generate_image_command called, request: {:?}", request); + + // TODO: Handle request. + + Ok(TauriGenerateImageResponse {}.into()) +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs new file mode 100644 index 0000000000..c4ab767b7f --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs @@ -0,0 +1,2 @@ +pub mod generate_image_command; +pub mod request; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/request.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/request.rs new file mode 100644 index 0000000000..f30a4cd400 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/request.rs @@ -0,0 +1,215 @@ +use artcraft_router::api::common_aspect_ratio::CommonAspectRatio; +use artcraft_router::api::common_resolution::CommonResolution; +use enums::common::generation::common_model_type::CommonModelType; +use enums::common::generation::common_quality::CommonQuality; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; +use serde_derive::{Deserialize, Serialize}; +use tokens::tokens::media_files::MediaFileToken; + +use crate::core::commands::response::success_response_wrapper::SerializeMarker; + +// ── Model ── + +/// Unified image model enum covering text-to-image, image edit, and inpainting. +/// +/// 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 TauriImageModel { + // Text-to-image models + + #[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, + #[serde(rename = "midjourney")] + Midjourney, + + // Image edit models + + #[serde(rename = "flux_pro_kontext_max")] + FluxProKontextMax, + #[serde(rename = "qwen_edit_2511_angles")] + QwenEdit2511Angles, + #[serde(rename = "flux_2_lora_angles")] + Flux2LoraAngles, + + // Inpainting models + + #[serde(rename = "flux_dev_juggernaut")] + FluxDevJuggernaut, + #[serde(rename = "flux_pro_1")] + FluxPro1, +} + +impl TauriImageModel { + pub fn to_common_model_type(&self) -> CommonModelType { + match self { + Self::Flux1Dev => CommonModelType::Flux1Dev, + Self::Flux1Schnell => CommonModelType::Flux1Schnell, + Self::FluxPro11 => CommonModelType::FluxPro11, + Self::FluxPro11Ultra => CommonModelType::FluxPro11Ultra, + Self::GrokImage => CommonModelType::GrokImage, + Self::Recraft3 => CommonModelType::Recraft3, + Self::GptImage1 => CommonModelType::GptImage1, + Self::GptImage1p5 => CommonModelType::GptImage1p5, + Self::GptImage2 => CommonModelType::GptImage2, + Self::Gemini25Flash => CommonModelType::NanoBanana, + Self::NanoBanana => CommonModelType::NanoBanana, + Self::NanoBanana2 => CommonModelType::NanoBanana2, + Self::NanoBananaPro => CommonModelType::NanoBananaPro, + Self::Seedream4 => CommonModelType::Seedream4, + Self::Seedream4p5 => CommonModelType::Seedream4p5, + Self::Seedream5Lite => CommonModelType::Seedream5Lite, + Self::Midjourney => CommonModelType::Midjourney, + Self::FluxProKontextMax => CommonModelType::FluxProKontextMax, + Self::QwenEdit2511Angles => CommonModelType::QwenEdit2511Angles, + Self::Flux2LoraAngles => CommonModelType::Flux2LoraAngles, + Self::FluxDevJuggernaut => CommonModelType::FluxDevJuggernaut, + Self::FluxPro1 => CommonModelType::FluxPro1, + } + } +} + +// ── Request ── + +#[derive(Deserialize, Debug)] +pub struct TauriGenerateImageRequest { + /// 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. + pub prompt: Option, + + /// Aspect ratio. + pub aspect_ratio: Option, + + /// Resolution. + pub resolution: Option, + + /// Quality (used by OpenAI models). + pub quality: Option, + + /// The number of images to generate. + pub batch_size: Option, + + /// Reference images (without semantics). + /// The purpose varies on a model-by-model basis. + pub image_media_tokens: Option>, + + // ── Canvas / scene images ── + + /// Supply this *XOR* `canvas_image_raw_bytes`. + /// Becomes the first image reference (pushing back `image_media_tokens` by one). + pub canvas_image_media_token: Option, + + /// Supply this *XOR* `canvas_image_media_token`. + /// Raw bytes of a canvas image. + pub canvas_image_raw_bytes: Option>, + + /// Supply this *XOR* `scene_image_raw_bytes`. + /// Becomes a scene reference image. + pub scene_image_media_token: Option, + + /// Supply this *XOR* `scene_image_media_token`. + /// Raw bytes of a scene image. + pub scene_image_raw_bytes: Option>, + + // ── Inpainting ── + + /// Supply this *XOR* `inpainting_mask_image_raw_bytes`. + /// The mask to focus the edit (already uploaded). + pub inpainting_mask_image_media_token: Option, + + /// Supply this *XOR* `inpainting_mask_image_media_token`. + /// The mask to focus the edit (raw bytes). + pub inpainting_mask_image_raw_bytes: Option>, + + // ── Angle adjustment (for edit models like QwenEdit, Flux2LoraAngles) ── + + /// Horizontal angle adjustment. + pub adjust_horizontal_angle: Option, + + /// Vertical angle adjustment. + pub adjust_vertical_angle: Option, + + /// Zoom adjustment. + pub adjust_zoom: Option, + + /// Turn on the system prompt. + pub enable_system_prompt: Option, + + // ── Frontend metadata ── + + /// Name of the frontend caller. + pub frontend_caller: Option, + + /// A frontend-defined identifier sent back as a Tauri event on task completion. + pub frontend_subscriber_id: Option, + + /// A frontend-defined payload sent back as a Tauri event on task completion. + pub frontend_subscriber_payload: Option, +} + +// ── Response ── + +#[derive(Serialize)] +pub struct TauriGenerateImageResponse { +} + +impl SerializeMarker for TauriGenerateImageResponse {} + +// ── Error ── + +#[derive(Serialize, Debug)] +#[serde(rename_all = "snake_case")] +pub enum TauriGenerateImageErrorType { + /// Caller didn't specify a model + ModelNotSpecified, + /// Bad input + BadInput, + /// No provider available + NoProviderAvailable, + /// Generic server error + ServerError, + /// Needs to be logged into Artcraft + NeedsStorytellerCredentials, + /// Needs Grok credentials + NeedsGrokCredentials, + /// Billing issue + BillingIssue, +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/mod.rs index 2492e000e2..295de01e41 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/mod.rs @@ -1 +1,2 @@ +pub mod generate_image; pub mod generate_video; diff --git a/crates/desktop/artcraft/src/lib.rs b/crates/desktop/artcraft/src/lib.rs index e29856dc90..94c84411e5 100644 --- a/crates/desktop/artcraft/src/lib.rs +++ b/crates/desktop/artcraft/src/lib.rs @@ -18,6 +18,7 @@ use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command use crate::core::commands::enqueue::image_to_gaussian::enqueue_image_to_gaussian_command::enqueue_image_to_gaussian_command; use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::enqueue_image_to_3d_object_command; use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::enqueue_text_to_image_command; +use crate::core::commands::generate::generate_image::generate_image_command::generate_image_command; use crate::core::commands::generate::generate_video::generate_video_command::generate_video_command; use crate::core::commands::flip_image::flip_image; use crate::core::commands::get_app_info_command::get_app_info_command; @@ -217,6 +218,7 @@ pub fn run() { estimate_splat_cost_command, estimate_video_cost_command, flip_image, + generate_image_command, generate_video_command, get_app_info_command, get_app_preferences_command, From 837a8d417a76702e6736b8b2cbe999a426251d1f Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 30 Apr 2026 20:18:12 -0400 Subject: [PATCH 05/14] proxy to other endpoints --- .../generate_image/generate_image_command.rs | 306 +++++++++++++++++- 1 file changed, 301 insertions(+), 5 deletions(-) diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs index e88253aeec..6874764b4c 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs @@ -1,17 +1,313 @@ -use crate::core::commands::generate::generate_image::request::{TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse}; +use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{ + enqueue_edit_image_command, EnqueueEditImageCommand, ImageEditModel, +}; +use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::{ + enqueue_image_inpaint_command, EnqueueInpaintImageCommand, ImageInpaintModel, +}; +use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{ + enqueue_text_to_image_command, EnqueueTextToImageRequest, TextToImageModel, +}; +use crate::core::commands::generate::generate_image::request::{ + TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse, + TauriImageModel, +}; +use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; use crate::core::commands::response::shorthand::Response; -use log::info; -use tauri::AppHandle; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; +use crate::core::state::data_dir::app_data_root::AppDataRoot; +use crate::core::state::provider_priority::ProviderPriorityStore; +use crate::core::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 log::{info, warn}; +use serde::Serialize; +use tauri::{AppHandle, State}; #[tauri::command] pub async fn generate_image_command( request: TauriGenerateImageRequest, 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>, + grok_creds_manager: State<'_, GrokCredentialManager>, + grok_image_prompt_queue: State<'_, GrokImagePromptQueue>, + mj_creds_manager: State<'_, MidjourneyCredentialManager>, + storyteller_creds_manager: State<'_, StorytellerCredentialManager>, + sora_creds_manager: State<'_, SoraCredentialManager>, + sora_task_queue: State<'_, SoraTaskQueue>, ) -> Response { info!("generate_image_command called, request: {:?}", request); - // TODO: Handle request. + let model = request.model; - Ok(TauriGenerateImageResponse {}.into()) + // Determine which legacy command to proxy to based on request shape + model. + let has_mask = request.inpainting_mask_image_media_token.is_some() + || request.inpainting_mask_image_raw_bytes.is_some(); + let has_image_refs = request.image_media_tokens.as_ref().is_some_and(|t| !t.is_empty()); + let has_canvas = request.canvas_image_media_token.is_some() + || request.canvas_image_raw_bytes.is_some(); + let has_scene = request.scene_image_media_token.is_some() + || request.scene_image_raw_bytes.is_some(); + let has_angles = request.adjust_horizontal_angle.is_some() + || request.adjust_vertical_angle.is_some() + || request.adjust_zoom.is_some(); + + let is_inpaint_model = matches!( + model, + Some(TauriImageModel::FluxDevJuggernaut) | Some(TauriImageModel::FluxPro1) + ); + + let is_edit_only_model = matches!( + model, + Some(TauriImageModel::FluxProKontextMax) + | Some(TauriImageModel::QwenEdit2511Angles) + | Some(TauriImageModel::Flux2LoraAngles) + ); + + if has_mask || is_inpaint_model { + proxy_to_image_inpaint_command( + 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 + } else if has_image_refs || has_canvas || has_scene || has_angles || is_edit_only_model { + proxy_to_edit_image_command( + 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 + } else { + proxy_to_text_to_image_command( + request, app, app_data_root, app_env_configs, artcraft_usage_tracker, + provider_priority_store, task_database, mj_creds_manager, + grok_creds_manager, grok_image_prompt_queue, storyteller_creds_manager, + sora_creds_manager, sora_task_queue, + ).await + } +} + +// ── Proxy to text-to-image ── + +async fn proxy_to_text_to_image_command( + request: TauriGenerateImageRequest, + 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 { + let legacy_model = request.model.and_then(map_to_text_to_image_model); + + let legacy_request = EnqueueTextToImageRequest { + provider: request.provider, + model: legacy_model, + prompt: request.prompt, + aspect_ratio: None, + common_aspect_ratio: None, + image_resolution: None, + common_resolution: None, + quality: None, // TODO: Convert enums::CommonQuality to app's CommonQuality + number_images: request.batch_size, + image_media_tokens: request.image_media_tokens, + frontend_caller: request.frontend_caller, + frontend_subscriber_id: request.frontend_subscriber_id, + frontend_subscriber_payload: request.frontend_subscriber_payload, + }; + + let result = enqueue_text_to_image_command( + legacy_request, app, app_data_root, app_env_configs, artcraft_usage_tracker, + provider_priority_store, task_database, mj_creds_manager, + grok_creds_manager, grok_image_prompt_queue, storyteller_creds_manager, + sora_creds_manager, sora_task_queue, + ).await; + + map_legacy_result(result) +} + +// ── Proxy to edit image ── + +async fn proxy_to_edit_image_command( + request: TauriGenerateImageRequest, + 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>, + storyteller_creds_manager: State<'_, StorytellerCredentialManager>, + sora_creds_manager: State<'_, SoraCredentialManager>, + sora_task_queue: State<'_, SoraTaskQueue>, +) -> Response { + let legacy_model = request.model.and_then(map_to_image_edit_model); + + // Build image_media_tokens: prepend canvas image if present. + let mut image_tokens = request.image_media_tokens.unwrap_or_default(); + if let Some(canvas_token) = request.canvas_image_media_token { + image_tokens.insert(0, canvas_token); + } + let image_media_tokens = if image_tokens.is_empty() { None } else { Some(image_tokens) }; + + let legacy_request = EnqueueEditImageCommand { + provider: request.provider, + model: legacy_model, + image_media_tokens, + scene_image_media_token: request.scene_image_media_token, + prompt: request.prompt.unwrap_or_default(), + disable_system_prompt: request.enable_system_prompt.map(|e| !e), + image_count: request.batch_size, + aspect_ratio: None, + common_aspect_ratio: None, + image_quality: None, + image_resolution: None, + quality: None, // TODO: Convert enums::CommonQuality to app's CommonQuality + horizontal_angle: request.adjust_horizontal_angle, + vertical_angle: request.adjust_vertical_angle, + zoom: request.adjust_zoom, + frontend_caller: request.frontend_caller, + frontend_subscriber_id: request.frontend_subscriber_id, + frontend_subscriber_payload: request.frontend_subscriber_payload, + }; + + let result = enqueue_edit_image_command( + app, legacy_request, app_data_root, app_env_configs, artcraft_usage_tracker, + provider_priority_store, task_database, storyteller_creds_manager, + sora_creds_manager, sora_task_queue, + ).await; + + map_legacy_result(result) +} + +// ── Proxy to inpaint ── + +async fn proxy_to_image_inpaint_command( + request: TauriGenerateImageRequest, + 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>, + storyteller_creds_manager: State<'_, StorytellerCredentialManager>, + sora_creds_manager: State<'_, SoraCredentialManager>, + sora_task_queue: State<'_, SoraTaskQueue>, +) -> Response { + let legacy_model = request.model.and_then(map_to_inpaint_model); + + // Source image: canvas_image_media_token or first image_media_token. + let source_image = request.canvas_image_media_token + .or_else(|| request.image_media_tokens.as_ref().and_then(|t| t.first().cloned())); + + let legacy_request = EnqueueInpaintImageCommand { + provider: request.provider, + model: legacy_model, + image_media_token: source_image, + mask_image_media_token: request.inpainting_mask_image_media_token, + mask_image_raw_bytes: request.inpainting_mask_image_raw_bytes, + prompt: request.prompt.unwrap_or_default(), + image_count: request.batch_size, + quality: None, // TODO: Convert enums::CommonQuality to app's CommonQuality + require_matching_dimensions: None, + frontend_caller: request.frontend_caller, + frontend_subscriber_id: request.frontend_subscriber_id, + frontend_subscriber_payload: request.frontend_subscriber_payload, + }; + + let result = enqueue_image_inpaint_command( + app, legacy_request, app_data_root, app_env_configs, artcraft_usage_tracker, + provider_priority_store, task_database, storyteller_creds_manager, + sora_creds_manager, sora_task_queue, + ).await; + + map_legacy_result(result) +} + +// ── Model mapping helpers ── + +fn map_to_text_to_image_model(model: TauriImageModel) -> Option { + match model { + TauriImageModel::Flux1Dev => Some(TextToImageModel::Flux1Dev), + TauriImageModel::Flux1Schnell => Some(TextToImageModel::Flux1Schnell), + TauriImageModel::FluxPro11 => Some(TextToImageModel::FluxPro11), + TauriImageModel::FluxPro11Ultra => Some(TextToImageModel::FluxPro11Ultra), + TauriImageModel::GrokImage => Some(TextToImageModel::GrokImage), + TauriImageModel::Recraft3 => Some(TextToImageModel::Recraft3), + TauriImageModel::GptImage1 => Some(TextToImageModel::GptImage1), + TauriImageModel::GptImage1p5 => Some(TextToImageModel::GptImage1p5), + TauriImageModel::GptImage2 => Some(TextToImageModel::GptImage2), + TauriImageModel::Gemini25Flash => Some(TextToImageModel::Gemini25Flash), + TauriImageModel::NanoBanana => Some(TextToImageModel::NanoBanana), + TauriImageModel::NanoBanana2 => Some(TextToImageModel::NanoBanana2), + TauriImageModel::NanoBananaPro => Some(TextToImageModel::NanoBananaPro), + TauriImageModel::Seedream4 => Some(TextToImageModel::Seedream4), + TauriImageModel::Seedream4p5 => Some(TextToImageModel::Seedream4p5), + TauriImageModel::Seedream5Lite => Some(TextToImageModel::Seedream5Lite), + TauriImageModel::Midjourney => Some(TextToImageModel::Midjourney), + _ => None, + } +} + +fn map_to_image_edit_model(model: TauriImageModel) -> Option { + match model { + TauriImageModel::FluxProKontextMax => Some(ImageEditModel::FluxProKontextMax), + TauriImageModel::Gemini25Flash => Some(ImageEditModel::Gemini25Flash), + TauriImageModel::NanoBanana => Some(ImageEditModel::NanoBanana), + TauriImageModel::NanoBanana2 => Some(ImageEditModel::NanoBanana2), + TauriImageModel::NanoBananaPro => Some(ImageEditModel::NanoBananaPro), + TauriImageModel::GptImage1 => Some(ImageEditModel::GptImage1), + TauriImageModel::GptImage1p5 => Some(ImageEditModel::GptImage1p5), + TauriImageModel::Seedream4 => Some(ImageEditModel::Seedream4), + TauriImageModel::Seedream4p5 => Some(ImageEditModel::Seedream4p5), + TauriImageModel::Seedream5Lite => Some(ImageEditModel::Seedream5Lite), + TauriImageModel::QwenEdit2511Angles => Some(ImageEditModel::QwenEdit2511Angles), + TauriImageModel::Flux2LoraAngles => Some(ImageEditModel::Flux2LoraAngles), + _ => None, + } +} + +fn map_to_inpaint_model(model: TauriImageModel) -> Option { + match model { + TauriImageModel::FluxDevJuggernaut => Some(ImageInpaintModel::FluxDevJuggernaut), + TauriImageModel::FluxPro1 => Some(ImageInpaintModel::FluxPro1), + TauriImageModel::FluxProKontextMax => Some(ImageInpaintModel::FluxProKontextMax), + TauriImageModel::Gemini25Flash => Some(ImageInpaintModel::Gemini25Flash), + _ => None, + } +} + +// ── Result mapping ── + +/// Map any legacy command result to our unified response type. +/// All three legacy commands return different error types, but we just +/// check success/failure and map accordingly. +fn map_legacy_result( + result: Result>, +) -> Response { + match result { + Ok(_) => Ok(TauriGenerateImageResponse {}.into()), + Err(legacy_err) => { + warn!("Legacy command proxy failed: {:?}", legacy_err.error_message); + Err(CommandErrorResponseWrapper { + status: legacy_err.status, + error_message: legacy_err.error_message, + error_type: Some(TauriGenerateImageErrorType::ServerError), + error_details: None, + }) + } + } } From 9060ff8099a9aeb95ac5f63fb7a71e21625ab5e9 Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Thu, 30 Apr 2026 17:54:51 -0700 Subject: [PATCH 06/14] artcraft: migrate frontend image/video calls to unified generate_* tauri commands Add GenerateImage / GenerateVideo wrappers in @storyteller/tauri-api that mirror the unified TauriGenerateImageRequest / TauriGenerateVideoRequest shapes and invoke generate_image_command / generate_video_command. Migrate PromptBoxImage (text-to-image), PromptBoxVideo (image-to-video), PageDraw (edit + inpaint), and Angles off the per-flow Enqueue* wrappers onto the unified ones. Old wrappers stay; Rust proxy handles routing. --- .../app/src/pages/PageAngles/Angles.tsx | 12 +- .../app/src/pages/PageDraw/PageDraw.tsx | 50 ++--- .../promptbox/src/lib/PromptBoxImage.tsx | 48 +---- .../promptbox/src/lib/PromptBoxVideo.tsx | 12 +- frontend/libs/tauri-api/src/index.ts | 2 + .../src/lib/generate/GenerateImage.ts | 185 ++++++++++++++++++ .../src/lib/generate/GenerateVideo.ts | 167 ++++++++++++++++ 7 files changed, 398 insertions(+), 78 deletions(-) create mode 100644 frontend/libs/tauri-api/src/lib/generate/GenerateImage.ts create mode 100644 frontend/libs/tauri-api/src/lib/generate/GenerateVideo.ts diff --git a/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx b/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx index 6d0179ab0d..3adeb72403 100644 --- a/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx +++ b/frontend/apps/artcraft/app/src/pages/PageAngles/Angles.tsx @@ -30,7 +30,7 @@ import { twMerge } from "tailwind-merge"; import { LoadingSpinner } from "@storyteller/ui-loading-spinner"; import { SliderV2 } from "@storyteller/ui-sliderv2"; // import { Switch } from "@headlessui/react"; -import { EnqueueEditImage } from "@storyteller/tauri-api"; +import { GenerateImage } from "@storyteller/tauri-api"; import { ClassyModelSelector, ANGLES_PAGE_MODEL_LIST, @@ -278,14 +278,14 @@ export const Angles = () => { }); try { - await EnqueueEditImage({ - model: selectedModel ?? ("flux_2_lora_angles" as any), + await GenerateImage({ + model: selectedModel ?? "flux_2_lora_angles", provider: selectedProvider ?? undefined, image_media_tokens: [state.sourceMediaToken], prompt: "", - horizontal_angle: state.angleConfig.rotation, - vertical_angle: state.angleConfig.tilt, - zoom: state.angleConfig.zoom, + adjust_horizontal_angle: state.angleConfig.rotation, + adjust_vertical_angle: state.angleConfig.tilt, + adjust_zoom: state.angleConfig.zoom, frontend_subscriber_id: subscriberId, }); diff --git a/frontend/apps/artcraft/app/src/pages/PageDraw/PageDraw.tsx b/frontend/apps/artcraft/app/src/pages/PageDraw/PageDraw.tsx index 27bd1767eb..a1db32c8f7 100644 --- a/frontend/apps/artcraft/app/src/pages/PageDraw/PageDraw.tsx +++ b/frontend/apps/artcraft/app/src/pages/PageDraw/PageDraw.tsx @@ -7,35 +7,32 @@ import { type ImageBundle, } from "@storyteller/ui-pagedraw"; import { - EnqueueEditImage, - EnqueueEditImageRequest, - EnqueueEditImageSize, - EnqueueEditImageResolution, - EnqueueImageInpaint, - EnqueueImageInpaintRequest, + GenerateImage, + GenerateImageRequest, EnqueueImageBgRemoval, useCanvasBgRemovedEvent, } from "@storyteller/tauri-api"; +import { CommonAspectRatio, CommonResolution } from "@storyteller/model-list"; import { useImageEditCompleteEvent } from "@storyteller/tauri-events"; import { UploadImageMedia } from "@storyteller/api"; import { BaseImageSelector } from "./BaseImageSelector"; // ─── Aspect ratio / resolution mappers ──────────────────────────────────────── -const mapAspectRatio = (ratio?: string): EnqueueEditImageSize | undefined => { +const mapAspectRatio = (ratio?: string): CommonAspectRatio | undefined => { switch (ratio) { - case "auto": return EnqueueEditImageSize.Auto; - case "wide": return EnqueueEditImageSize.Wide; - case "tall": return EnqueueEditImageSize.Tall; - case "square": return EnqueueEditImageSize.Square; + case "auto": return CommonAspectRatio.Auto; + case "wide": return CommonAspectRatio.Wide; + case "tall": return CommonAspectRatio.Tall; + case "square": return CommonAspectRatio.Square; default: return undefined; } }; -const mapResolution = (res?: string): EnqueueEditImageResolution | undefined => { +const mapResolution = (res?: string): CommonResolution | undefined => { switch (res) { - case "1k": return EnqueueEditImageResolution.OneK; - case "2k": return EnqueueEditImageResolution.TwoK; - case "4k": return EnqueueEditImageResolution.FourK; + case "1k": return CommonResolution.OneK; + case "2k": return CommonResolution.TwoK; + case "4k": return CommonResolution.FourK; default: return undefined; } }; @@ -84,34 +81,37 @@ const useTauriAdapter = (): PageDrawAdapter => { return useMemo( () => ({ enqueueEditImage: async (req) => { - const request: EnqueueEditImageRequest = { + const request: GenerateImageRequest = { model: req.model, scene_image_media_token: req.sceneImageMediaToken, image_media_tokens: req.imageMediaTokens, prompt: req.prompt, - disable_system_prompt: req.disableSystemPrompt, - image_count: req.imageCount, + enable_system_prompt: + typeof req.disableSystemPrompt === "boolean" + ? !req.disableSystemPrompt + : undefined, + batch_size: req.imageCount, aspect_ratio: mapAspectRatio(req.aspectRatio), - image_resolution: mapResolution(req.imageResolution), + resolution: mapResolution(req.imageResolution), frontend_caller: req.frontendCaller, frontend_subscriber_id: req.frontendSubscriberId, }; if (req.provider) request.provider = req.provider; - return EnqueueEditImage(request); + return GenerateImage(request); }, enqueueInpaint: async (req) => { - const request: EnqueueImageInpaintRequest = { + const request: GenerateImageRequest = { model: req.model, - image_media_token: req.imageMediaToken, - mask_image_raw_bytes: req.maskImageRawBytes, + image_media_tokens: req.imageMediaToken ? [req.imageMediaToken] : undefined, + inpainting_mask_image_raw_bytes: req.maskImageRawBytes, prompt: req.prompt, - image_count: req.imageCount, + batch_size: req.imageCount, frontend_caller: req.frontendCaller, frontend_subscriber_id: req.frontendSubscriberId, }; if (req.provider) request.provider = req.provider; - return EnqueueImageInpaint(request); + return GenerateImage(request); }, enqueueBgRemoval: async (base64Image, nodeId) => { diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx index 5f5daa4f72..9fb6ddd084 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBoxImage.tsx @@ -7,10 +7,8 @@ import { Tooltip } from "@storyteller/ui-tooltip"; import { Button, ToggleButton, GenerateButton } from "@storyteller/ui-button"; import { Modal } from "@storyteller/ui-modal"; import { - EnqueueTextToImage, - EnqueueTextToImageRequest, - EnqueueTextToImageSize, - EnqueueTextToImageResolution, + GenerateImage, + GenerateImageRequest, } from "@storyteller/tauri-api"; import { faMessageXmark, @@ -296,15 +294,10 @@ export const PromptBoxImage = ({ }, 10000); try { - const aspectRatio = getCurrentLegacyAspectRatio(); - const resolution = getCurrentResolution(); - - const request: EnqueueTextToImageRequest = { + const request: GenerateImageRequest = { prompt: prompt, model: selectedModel, - aspect_ratio: aspectRatio, - image_resolution: resolution, - number_images: generationCount, + batch_size: generationCount, frontend_caller: "text_to_image", frontend_subscriber_id: subscriberId, }; @@ -314,11 +307,11 @@ export const PromptBoxImage = ({ } if (selectedModel?.supportsNewAspectRatio()) { - request.common_aspect_ratio = commonAspectRatio; + request.aspect_ratio = commonAspectRatio; } if (selectedModel?.supportsNewResolution()) { - request.common_resolution = commonResolution; + request.resolution = commonResolution; } if (selectedModel?.supportsQuality()) { @@ -342,7 +335,7 @@ export const PromptBoxImage = ({ timestamp: Date.now(), }); - const generateResponse = await EnqueueTextToImage(request); + const generateResponse = await GenerateImage(request); console.debug("PromptBoxImage - generateResponse", generateResponse); await onEnqueuePressed?.(prompt, generationCount, subscriberId); @@ -368,33 +361,6 @@ export const PromptBoxImage = ({ } }; - const getCurrentLegacyAspectRatio = (): EnqueueTextToImageSize => { - const selected = aspectRatioList.find((item) => item.selected); - switch (selected?.label.toLowerCase()) { - case "wide": - return EnqueueTextToImageSize.Wide; - case "tall": - return EnqueueTextToImageSize.Tall; - case "square": - default: - return EnqueueTextToImageSize.Square; - } - }; - - const getCurrentResolution = (): EnqueueTextToImageResolution | undefined => { - const selected = resolutionList.find((item) => item.selected); - switch (selected?.label) { - case "1k": - return EnqueueTextToImageResolution.OneK; - case "2k": - return EnqueueTextToImageResolution.TwoK; - case "4k": - return EnqueueTextToImageResolution.FourK; - default: - return undefined; - } - }; - const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key !== "Enter") return; const isSubmitCombo = enterToGenerate && !e.shiftKey; diff --git a/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx b/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx index 9e0b42b3df..c275a3f7a9 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBoxVideo.tsx @@ -8,8 +8,8 @@ import { Tooltip } from "@storyteller/ui-tooltip"; import { ToggleButton, GenerateButton } from "@storyteller/ui-button"; import { Modal } from "@storyteller/ui-modal"; import { - EnqueueImageToVideo, - EnqueueImageToVideoRequest, + GenerateVideo, + GenerateVideoRequest, } from "@storyteller/tauri-api"; import { faWaveformLines, @@ -703,10 +703,10 @@ export const PromptBoxVideo = ({ setIsEnqueueing(false); }, 10000); - const buildRequest = (subscriberId: string): EnqueueImageToVideoRequest => { - let request: EnqueueImageToVideoRequest = { + const buildRequest = (subscriberId: string): GenerateVideoRequest => { + let request: GenerateVideoRequest = { model: selectedModel, - image_media_token: imageMediaToken, + start_frame_image_media_token: imageMediaToken, prompt: prompt, end_frame_image_media_token: isRefMode ? undefined @@ -816,7 +816,7 @@ export const PromptBoxVideo = ({ ? crypto.randomUUID() : Math.random().toString(36).slice(2); subscriberIds.push(subscriberId); - enqueuePromises.push(EnqueueImageToVideo(buildRequest(subscriberId))); + enqueuePromises.push(GenerateVideo(buildRequest(subscriberId))); } try { diff --git a/frontend/libs/tauri-api/src/index.ts b/frontend/libs/tauri-api/src/index.ts index 29b5ef0f6b..73fbaae175 100644 --- a/frontend/libs/tauri-api/src/index.ts +++ b/frontend/libs/tauri-api/src/index.ts @@ -14,6 +14,8 @@ export * from "./lib/enqueue/EnqueueImageTo3dWorld.js"; export * from "./lib/enqueue/EnqueueImageToGaussian.js"; export * from "./lib/enqueue/EnqueueImageToVideo.js"; export * from "./lib/enqueue/EnqueueTextToImage.js"; +export * from "./lib/generate/GenerateImage.js"; +export * from "./lib/generate/GenerateVideo.js"; export * from "./lib/events/BasicEventWrapper.js"; export * from "./lib/events/CanvasBgRemovedEvent.js"; export * from "./lib/fal/FalBackgroundRemoval.js"; diff --git a/frontend/libs/tauri-api/src/lib/generate/GenerateImage.ts b/frontend/libs/tauri-api/src/lib/generate/GenerateImage.ts new file mode 100644 index 0000000000..5ce3dbeeb7 --- /dev/null +++ b/frontend/libs/tauri-api/src/lib/generate/GenerateImage.ts @@ -0,0 +1,185 @@ +import { invoke } from "@tauri-apps/api/core"; +import { CommandResult } from "../common/CommandStatus"; +import { + CommonAspectRatio, + CommonResolution, + CommonQuality, + ImageModel, +} from "@storyteller/model-list"; +import { GenerationProvider } from "@storyteller/api-enums"; + +export interface GenerateImageRequest { + // The provider to use (defaults to Artcraft/Storyteller). + provider?: GenerationProvider; + + // The model to use. + model?: ImageModel | string; + + // Text prompt for the image generation. + prompt?: string; + + // Aspect ratio. + aspect_ratio?: CommonAspectRatio; + + // Resolution. + resolution?: CommonResolution; + + // Quality (used by OpenAI models). + quality?: CommonQuality; + + // Number of images to generate. + batch_size?: number; + + // Reference images (without semantics — purpose varies per model). + image_media_tokens?: string[]; + + // Canvas image — supply this XOR canvas_image_raw_bytes. + // Becomes the first image reference (pushing back image_media_tokens by one). + canvas_image_media_token?: string; + canvas_image_raw_bytes?: Uint8Array; + + // Scene image — supply this XOR scene_image_raw_bytes. + scene_image_media_token?: string; + scene_image_raw_bytes?: Uint8Array; + + // Inpainting mask — supply this XOR inpainting_mask_image_raw_bytes. + inpainting_mask_image_media_token?: string; + inpainting_mask_image_raw_bytes?: Uint8Array; + + // Angle adjustments (for edit models like QwenEdit, Flux2LoraAngles). + adjust_horizontal_angle?: number; + adjust_vertical_angle?: number; + adjust_zoom?: number; + + // Turn on the system prompt. + enable_system_prompt?: boolean; + + // Frontend metadata. + frontend_caller?: string; + frontend_subscriber_id?: string; + frontend_subscriber_payload?: string; +} + +interface RawGenerateImageRequest { + provider?: GenerationProvider; + model?: string; + prompt?: string; + aspect_ratio?: CommonAspectRatio; + resolution?: CommonResolution; + quality?: CommonQuality; + batch_size?: number; + image_media_tokens?: string[]; + canvas_image_media_token?: string; + canvas_image_raw_bytes?: Uint8Array; + scene_image_media_token?: string; + scene_image_raw_bytes?: Uint8Array; + inpainting_mask_image_media_token?: string; + inpainting_mask_image_raw_bytes?: Uint8Array; + adjust_horizontal_angle?: number; + adjust_vertical_angle?: number; + adjust_zoom?: number; + enable_system_prompt?: boolean; + frontend_caller?: string; + frontend_subscriber_id?: string; + frontend_subscriber_payload?: string; +} + +export enum GenerateImageErrorType { + ModelNotSpecified = "model_not_specified", + BadInput = "bad_input", + NoProviderAvailable = "no_provider_available", + ServerError = "server_error", + NeedsStorytellerCredentials = "needs_storyteller_credentials", + NeedsGrokCredentials = "needs_grok_credentials", + BillingIssue = "billing_issue", +} + +export interface GenerateImageError extends CommandResult { + error_type: GenerateImageErrorType; + error_message?: string; +} + +export type GenerateImagePayload = Record; + +export interface GenerateImageSuccess extends CommandResult { + payload: GenerateImagePayload; +} + +export type GenerateImageResult = GenerateImageSuccess | GenerateImageError; + +export const GenerateImage = async ( + request: GenerateImageRequest, +): Promise => { + let modelName: string | undefined; + + if (!!request.model) { + if (typeof request.model === "string") { + modelName = request.model; + } else if (typeof request.model.tauriId === "string") { + modelName = request.model.tauriId; + } + } + + if (!modelName) { + throw new Error( + "No model specified in request: " + JSON.stringify(request), + ); + } + + const mutableRequest: RawGenerateImageRequest = { + model: modelName, + }; + + if (!!request.provider) mutableRequest.provider = request.provider; + if (!!request.prompt) mutableRequest.prompt = request.prompt; + if (!!request.aspect_ratio) mutableRequest.aspect_ratio = request.aspect_ratio; + if (!!request.resolution) mutableRequest.resolution = request.resolution; + if (!!request.quality) mutableRequest.quality = request.quality; + if (typeof request.batch_size === "number") mutableRequest.batch_size = request.batch_size; + if (!!request.image_media_tokens && request.image_media_tokens.length > 0) { + mutableRequest.image_media_tokens = request.image_media_tokens; + } + if (!!request.canvas_image_media_token) { + mutableRequest.canvas_image_media_token = request.canvas_image_media_token; + } + if (!!request.canvas_image_raw_bytes) { + mutableRequest.canvas_image_raw_bytes = request.canvas_image_raw_bytes; + } + if (!!request.scene_image_media_token) { + mutableRequest.scene_image_media_token = request.scene_image_media_token; + } + if (!!request.scene_image_raw_bytes) { + mutableRequest.scene_image_raw_bytes = request.scene_image_raw_bytes; + } + if (!!request.inpainting_mask_image_media_token) { + mutableRequest.inpainting_mask_image_media_token = request.inpainting_mask_image_media_token; + } + if (!!request.inpainting_mask_image_raw_bytes) { + mutableRequest.inpainting_mask_image_raw_bytes = request.inpainting_mask_image_raw_bytes; + } + if (typeof request.adjust_horizontal_angle === "number") { + mutableRequest.adjust_horizontal_angle = request.adjust_horizontal_angle; + } + if (typeof request.adjust_vertical_angle === "number") { + mutableRequest.adjust_vertical_angle = request.adjust_vertical_angle; + } + if (typeof request.adjust_zoom === "number") { + mutableRequest.adjust_zoom = request.adjust_zoom; + } + if (typeof request.enable_system_prompt === "boolean") { + mutableRequest.enable_system_prompt = request.enable_system_prompt; + } + if (!!request.frontend_caller) mutableRequest.frontend_caller = request.frontend_caller; + if (!!request.frontend_subscriber_id) { + mutableRequest.frontend_subscriber_id = request.frontend_subscriber_id; + } + if (!!request.frontend_subscriber_payload) { + mutableRequest.frontend_subscriber_payload = request.frontend_subscriber_payload; + } + + const result = await invoke("generate_image_command", { + request: mutableRequest, + }); + + return result as GenerateImageResult; +}; diff --git a/frontend/libs/tauri-api/src/lib/generate/GenerateVideo.ts b/frontend/libs/tauri-api/src/lib/generate/GenerateVideo.ts new file mode 100644 index 0000000000..0269208395 --- /dev/null +++ b/frontend/libs/tauri-api/src/lib/generate/GenerateVideo.ts @@ -0,0 +1,167 @@ +import { invoke } from "@tauri-apps/api/core"; +import { CommandResult } from "../common/CommandStatus"; +import { + CommonAspectRatio, + CommonResolution, + VideoModel, +} from "@storyteller/model-list"; +import { GenerationProvider } from "@storyteller/api-enums"; + +export interface GenerateVideoRequest { + // The provider to use (defaults to Artcraft/Storyteller). + provider?: GenerationProvider; + + // The model to use. + model?: VideoModel | string; + + // Text prompt. + prompt?: string; + + // Negative prompt. + negative_prompt?: string; + + // Starting frame. + start_frame_image_media_token?: string; + + // Ending frame. + end_frame_image_media_token?: string; + + // Reference media tokens. + reference_image_media_tokens?: string[]; + reference_video_media_tokens?: string[]; + reference_audio_media_tokens?: string[]; + reference_character_tokens?: string[]; + + aspect_ratio?: CommonAspectRatio; + resolution?: CommonResolution; + + duration_seconds?: number; + generate_audio?: boolean; + video_batch_count?: number; + + // Deprecated on the Rust side (still read by some legacy handlers). + sora_orientation?: "portrait" | "landscape"; + grok_aspect_ratio?: "portrait" | "landscape" | "square"; + + // Frontend metadata. + frontend_caller?: string; + frontend_subscriber_id?: string; + frontend_subscriber_payload?: string; +} + +interface RawGenerateVideoRequest { + provider?: GenerationProvider; + model?: string; + prompt?: string; + negative_prompt?: string; + start_frame_image_media_token?: string; + end_frame_image_media_token?: string; + reference_image_media_tokens?: string[]; + reference_video_media_tokens?: string[]; + reference_audio_media_tokens?: string[]; + reference_character_tokens?: string[]; + aspect_ratio?: CommonAspectRatio; + resolution?: CommonResolution; + duration_seconds?: number; + generate_audio?: boolean; + video_batch_count?: number; + sora_orientation?: "portrait" | "landscape"; + grok_aspect_ratio?: "portrait" | "landscape" | "square"; + frontend_caller?: string; + frontend_subscriber_id?: string; + frontend_subscriber_payload?: string; +} + +export enum GenerateVideoErrorType { + ModelNotSpecified = "model_not_specified", + NoProviderAvailable = "no_provider_available", + ServerError = "server_error", + NeedsFalApiKey = "needs_fal_api_key", + FalError = "fal_error", + NeedsStorytellerCredentials = "needs_storyteller_credentials", +} + +export interface GenerateVideoError extends CommandResult { + error_type: GenerateVideoErrorType; + error_message?: string; +} + +export type GenerateVideoPayload = Record; + +export interface GenerateVideoSuccess extends CommandResult { + payload: GenerateVideoPayload; +} + +export type GenerateVideoResult = GenerateVideoSuccess | GenerateVideoError; + +export const GenerateVideo = async ( + request: GenerateVideoRequest, +): Promise => { + let modelName: string | undefined; + + if (!!request.model) { + if (typeof request.model === "string") { + modelName = request.model; + } else if (typeof request.model.tauriId === "string") { + modelName = request.model.tauriId; + } + } + + if (!modelName) { + throw new Error( + "No model specified in request: " + JSON.stringify(request), + ); + } + + const mutableRequest: RawGenerateVideoRequest = { + model: modelName, + }; + + if (!!request.provider) mutableRequest.provider = request.provider; + if (!!request.prompt) mutableRequest.prompt = request.prompt; + if (!!request.negative_prompt) mutableRequest.negative_prompt = request.negative_prompt; + if (!!request.start_frame_image_media_token) { + mutableRequest.start_frame_image_media_token = request.start_frame_image_media_token; + } + if (!!request.end_frame_image_media_token) { + mutableRequest.end_frame_image_media_token = request.end_frame_image_media_token; + } + if (!!request.reference_image_media_tokens && request.reference_image_media_tokens.length > 0) { + mutableRequest.reference_image_media_tokens = request.reference_image_media_tokens; + } + if (!!request.reference_video_media_tokens && request.reference_video_media_tokens.length > 0) { + mutableRequest.reference_video_media_tokens = request.reference_video_media_tokens; + } + if (!!request.reference_audio_media_tokens && request.reference_audio_media_tokens.length > 0) { + mutableRequest.reference_audio_media_tokens = request.reference_audio_media_tokens; + } + if (!!request.reference_character_tokens && request.reference_character_tokens.length > 0) { + mutableRequest.reference_character_tokens = request.reference_character_tokens; + } + if (!!request.aspect_ratio) mutableRequest.aspect_ratio = request.aspect_ratio; + if (!!request.resolution) mutableRequest.resolution = request.resolution; + if (typeof request.duration_seconds === "number") { + mutableRequest.duration_seconds = request.duration_seconds; + } + if (typeof request.generate_audio === "boolean") { + mutableRequest.generate_audio = request.generate_audio; + } + if (typeof request.video_batch_count === "number") { + mutableRequest.video_batch_count = request.video_batch_count; + } + if (!!request.sora_orientation) mutableRequest.sora_orientation = request.sora_orientation; + if (!!request.grok_aspect_ratio) mutableRequest.grok_aspect_ratio = request.grok_aspect_ratio; + if (!!request.frontend_caller) mutableRequest.frontend_caller = request.frontend_caller; + if (!!request.frontend_subscriber_id) { + mutableRequest.frontend_subscriber_id = request.frontend_subscriber_id; + } + if (!!request.frontend_subscriber_payload) { + mutableRequest.frontend_subscriber_payload = request.frontend_subscriber_payload; + } + + const result = await invoke("generate_video_command", { + request: mutableRequest, + }); + + return result as GenerateVideoResult; +}; From 23084a765abe9fed800e895eb789733f9de80a2b Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 30 Apr 2026 22:06:15 -0400 Subject: [PATCH 07/14] refactor wip --- ...ndle_artcraft_gpt_image_1_text_to_image.rs | 112 ++++++++ .../artcraft/handle_text_to_image_artcraft.rs | 46 ++++ ...andle_text_to_image_artcraft_via_router.rs | 159 +++++++++++ .../generate/generate_image/artcraft/mod.rs | 4 + .../generate_image/generate_image_command.rs | 4 +- .../generate_image/grok/handle_grok.rs | 92 +++++++ .../generate/generate_image/grok/mod.rs | 1 + .../midjourney/handle_midjourney.rs | 140 ++++++++++ .../generate/generate_image/midjourney/mod.rs | 1 + .../commands/generate/generate_image/mod.rs | 3 +- .../handle_gpt_image_1_sora_text_to_image.rs | 135 ++++++++++ .../sora/handle_text_to_image_sora.rs | 21 ++ .../generate/generate_image/sora/mod.rs | 2 + ...est.rs => tauri_generate_image_request.rs} | 94 +------ .../generate_image/tauri_image_model.rs | 92 +++++++ frontend/libs/tauri-api/src/index.ts | 1 + .../src/lib/enqueue/GenerateVideoCommand.ts | 192 +++++++++++++ frontend/package-lock.json | 253 ++++++------------ 18 files changed, 1088 insertions(+), 264 deletions(-) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft_via_router.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/handle_grok.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/mod.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/handle_midjourney.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/mod.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_gpt_image_1_sora_text_to_image.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_text_to_image_sora.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/mod.rs rename crates/desktop/artcraft/src/core/commands/generate/generate_image/{request.rs => tauri_generate_image_request.rs} (55%) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_image_model.rs create mode 100644 frontend/libs/tauri-api/src/lib/enqueue/GenerateVideoCommand.ts diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs new file mode 100644 index 0000000000..4265016619 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs @@ -0,0 +1,112 @@ +use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize}; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; +use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; +use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; +use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::core::state::data_dir::app_data_root::AppDataRoot; +use crate::core::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_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, + }) +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft.rs new file mode 100644 index 0000000000..f23c44292e --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft.rs @@ -0,0 +1,46 @@ +use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::events::generation_events::common::GenerationModel; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::core::state::data_dir::app_data_root::AppDataRoot; +use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; +use artcraft_router::api::common_image_model::CommonImageModel; +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, CommonImageModel::Flux1Dev, GenerationModel::Flux1Dev).await, + TextToImageModel::Flux1Schnell => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Flux1Schnell, GenerationModel::Flux1Schnell).await, + TextToImageModel::FluxPro11 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::FluxPro11, GenerationModel::FluxPro11).await, + TextToImageModel::FluxPro11Ultra => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::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, CommonImageModel::GptImage1p5, GenerationModel::GptImage1p5).await, + TextToImageModel::GptImage2 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::GptImage2, GenerationModel::GptImage2).await, + TextToImageModel::Gemini25Flash | TextToImageModel::NanoBanana => { + handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanoBanana, GenerationModel::NanoBanana).await + }, + TextToImageModel::NanoBanana2 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanoBanana2, GenerationModel::NanoBanana2).await, + TextToImageModel::NanoBananaPro => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanoBananaPro, GenerationModel::NanoBananaPro).await, + TextToImageModel::Seedream4 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream4, GenerationModel::Seedream4).await, + TextToImageModel::Seedream4p5 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream4p5, GenerationModel::Seedream4p5).await, + TextToImageModel::Seedream5Lite => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream5Lite, GenerationModel::Seedream5Lite).await, + } +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft_via_router.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft_via_router.rs new file mode 100644 index 0000000000..9094a42422 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft_via_router.rs @@ -0,0 +1,159 @@ +use crate::core::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio as CommonAspectRatio2; +use crate::core::api_adapters::resolution::common_resolution::CommonResolution as CommonResolution2; +use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{ + EnqueueTextToImageRequest, TextToImageResolution, TextToImageSize, +}; +use crate::core::events::generation_events::common::GenerationModel; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; +use artcraft_router::api::common_aspect_ratio::CommonAspectRatio as RouterCommonAspectRatio; +use artcraft_router::api::common_image_model::CommonImageModel; +use artcraft_router::api::common_resolution::CommonResolution as RouterCommonResolution; +use artcraft_router::api::common_quality::CommonQuality as RouterCommonQuality; +use artcraft_router::api::image_list_ref::ImageListRef; +use artcraft_router::api::provider::Provider; +use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use artcraft_router::client::router_artcraft_client::RouterArtcraftClient; +use artcraft_router::client::router_client::RouterClient; +use artcraft_router::generate::generate_image::generate_image_request::GenerateImageRequest; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::tasks::task_type::TaskType; +use log::{error, info}; + +pub(super) async fn handle_text_to_image_artcraft_via_router( + request: &EnqueueTextToImageRequest, + app_env_configs: &AppEnvConfigs, + storyteller_creds_manager: &StorytellerCredentialManager, + model: CommonImageModel, + 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 = GenerateImageRequest { + model, + provider: Provider::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 plan = router_request.build()?; + + info!("Image Generation Plan: {:?}", plan); + + let response = match plan.generate_image(&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), + }) +} + +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 => RouterCommonAspectRatio::Auto, + TextToImageSize::Square => RouterCommonAspectRatio::Square, + TextToImageSize::Wide => RouterCommonAspectRatio::Wide, + TextToImageSize::Tall => RouterCommonAspectRatio::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 => RouterCommonResolution::OneK, + TextToImageResolution::TwoK => RouterCommonResolution::TwoK, + TextToImageResolution::FourK => RouterCommonResolution::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) -> RouterCommonAspectRatio { + match ar { + CommonAspectRatio2::Auto => RouterCommonAspectRatio::Auto, + CommonAspectRatio2::Square => RouterCommonAspectRatio::Square, + CommonAspectRatio2::WideThreeByTwo => RouterCommonAspectRatio::WideThreeByTwo, + CommonAspectRatio2::WideFourByThree => RouterCommonAspectRatio::WideFourByThree, + CommonAspectRatio2::WideFiveByFour => RouterCommonAspectRatio::WideFiveByFour, + CommonAspectRatio2::WideSixteenByNine => RouterCommonAspectRatio::WideSixteenByNine, + CommonAspectRatio2::WideTwentyOneByNine => RouterCommonAspectRatio::WideTwentyOneByNine, + CommonAspectRatio2::TallTwoByThree => RouterCommonAspectRatio::TallTwoByThree, + CommonAspectRatio2::TallThreeByFour => RouterCommonAspectRatio::TallThreeByFour, + CommonAspectRatio2::TallFourByFive => RouterCommonAspectRatio::TallFourByFive, + CommonAspectRatio2::TallNineBySixteen => RouterCommonAspectRatio::TallNineBySixteen, + CommonAspectRatio2::TallNineByTwentyOne => RouterCommonAspectRatio::TallNineByTwentyOne, + CommonAspectRatio2::Wide => RouterCommonAspectRatio::Wide, + CommonAspectRatio2::Tall => RouterCommonAspectRatio::Tall, + CommonAspectRatio2::Auto2k => RouterCommonAspectRatio::Auto2k, + CommonAspectRatio2::Auto4k => RouterCommonAspectRatio::Auto4k, + CommonAspectRatio2::SquareHd => RouterCommonAspectRatio::SquareHd, + } +} + +fn convert_desktop_resolution(res: CommonResolution2) -> RouterCommonResolution { + match res { + CommonResolution2::OneK => RouterCommonResolution::OneK, + CommonResolution2::TwoK => RouterCommonResolution::TwoK, + CommonResolution2::ThreeK => RouterCommonResolution::ThreeK, + CommonResolution2::FourK => RouterCommonResolution::FourK, + CommonResolution2::HalfK => RouterCommonResolution::HalfK, + CommonResolution2::FourEightyP => RouterCommonResolution::FourEightyP, + CommonResolution2::SevenTwentyP => RouterCommonResolution::SevenTwentyP, + CommonResolution2::TenEightyP => RouterCommonResolution::TenEightyP, + } +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs new file mode 100644 index 0000000000..572b124965 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs @@ -0,0 +1,4 @@ +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/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs index 6874764b4c..52a7c2e308 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs @@ -7,10 +7,10 @@ use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{ enqueue_text_to_image_command, EnqueueTextToImageRequest, TextToImageModel, }; -use crate::core::commands::generate::generate_image::request::{ +use crate::core::commands::generate::generate_image::tauri_generate_image_request::{ TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse, - TauriImageModel, }; +use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; use crate::core::commands::response::shorthand::Response; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/handle_grok.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/handle_grok.rs new file mode 100644 index 0000000000..8318f5f6e6 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/handle_grok.rs @@ -0,0 +1,92 @@ +use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image; +use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; +use crate::core::events::generation_events::common::GenerationModel; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::services::grok::state::grok_credential_manager::GrokCredentialManager; +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_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/desktop/artcraft/src/core/commands/generate/generate_image/grok/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/mod.rs new file mode 100644 index 0000000000..181e756f1a --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/mod.rs @@ -0,0 +1 @@ +pub (super) mod handle_grok; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/handle_midjourney.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/handle_midjourney.rs new file mode 100644 index 0000000000..848ebe8105 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/handle_midjourney.rs @@ -0,0 +1,140 @@ +use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason, ProviderFailureReason}; +use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize}; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; +use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; +use crate::core::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent; +use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; +use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; +use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; +use crate::core::events::warning_events::flash_user_input_error_event::FlashUserInputErrorEvent; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::core::state::data_dir::app_data_root::AppDataRoot; +use crate::core::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/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/mod.rs new file mode 100644 index 0000000000..18048875b6 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/mod.rs @@ -0,0 +1 @@ +pub (super) mod handle_midjourney; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs index c4ab767b7f..ccf608c644 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs @@ -1,2 +1,3 @@ pub mod generate_image_command; -pub mod request; +pub mod tauri_generate_image_request; +pub mod tauri_image_model; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_gpt_image_1_sora_text_to_image.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_gpt_image_1_sora_text_to_image.rs new file mode 100644 index 0000000000..82a1abedbf --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_gpt_image_1_sora_text_to_image.rs @@ -0,0 +1,135 @@ +use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image; +use crate::core::api_adapters::aspect_ratio::convert::aspect_ratio_to_sora_native_gpt_image_1::aspect_ratio_to_sora_native_gpt_image_1; +use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; +use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; +use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent; +use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; +use crate::core::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_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/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_text_to_image_sora.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_text_to_image_sora.rs new file mode 100644 index 0000000000..a632ed8eca --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_text_to_image_sora.rs @@ -0,0 +1,21 @@ +use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::EnqueueTextToImageRequest; +use crate::core::commands::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/desktop/artcraft/src/core/commands/generate/generate_image/sora/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/mod.rs new file mode 100644 index 0000000000..9a9529d300 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/mod.rs @@ -0,0 +1,2 @@ +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/desktop/artcraft/src/core/commands/generate/generate_image/request.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_generate_image_request.rs similarity index 55% rename from crates/desktop/artcraft/src/core/commands/generate/generate_image/request.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_generate_image_request.rs index f30a4cd400..759eb4274e 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/request.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_generate_image_request.rs @@ -1,106 +1,14 @@ use artcraft_router::api::common_aspect_ratio::CommonAspectRatio; use artcraft_router::api::common_resolution::CommonResolution; -use enums::common::generation::common_model_type::CommonModelType; use enums::common::generation::common_quality::CommonQuality; use enums::common::generation_provider::GenerationProvider; use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; use serde_derive::{Deserialize, Serialize}; use tokens::tokens::media_files::MediaFileToken; +use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; use crate::core::commands::response::success_response_wrapper::SerializeMarker; -// ── Model ── - -/// Unified image model enum covering text-to-image, image edit, and inpainting. -/// -/// 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 TauriImageModel { - // Text-to-image models - - #[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, - #[serde(rename = "midjourney")] - Midjourney, - - // Image edit models - - #[serde(rename = "flux_pro_kontext_max")] - FluxProKontextMax, - #[serde(rename = "qwen_edit_2511_angles")] - QwenEdit2511Angles, - #[serde(rename = "flux_2_lora_angles")] - Flux2LoraAngles, - - // Inpainting models - - #[serde(rename = "flux_dev_juggernaut")] - FluxDevJuggernaut, - #[serde(rename = "flux_pro_1")] - FluxPro1, -} - -impl TauriImageModel { - pub fn to_common_model_type(&self) -> CommonModelType { - match self { - Self::Flux1Dev => CommonModelType::Flux1Dev, - Self::Flux1Schnell => CommonModelType::Flux1Schnell, - Self::FluxPro11 => CommonModelType::FluxPro11, - Self::FluxPro11Ultra => CommonModelType::FluxPro11Ultra, - Self::GrokImage => CommonModelType::GrokImage, - Self::Recraft3 => CommonModelType::Recraft3, - Self::GptImage1 => CommonModelType::GptImage1, - Self::GptImage1p5 => CommonModelType::GptImage1p5, - Self::GptImage2 => CommonModelType::GptImage2, - Self::Gemini25Flash => CommonModelType::NanoBanana, - Self::NanoBanana => CommonModelType::NanoBanana, - Self::NanoBanana2 => CommonModelType::NanoBanana2, - Self::NanoBananaPro => CommonModelType::NanoBananaPro, - Self::Seedream4 => CommonModelType::Seedream4, - Self::Seedream4p5 => CommonModelType::Seedream4p5, - Self::Seedream5Lite => CommonModelType::Seedream5Lite, - Self::Midjourney => CommonModelType::Midjourney, - Self::FluxProKontextMax => CommonModelType::FluxProKontextMax, - Self::QwenEdit2511Angles => CommonModelType::QwenEdit2511Angles, - Self::Flux2LoraAngles => CommonModelType::Flux2LoraAngles, - Self::FluxDevJuggernaut => CommonModelType::FluxDevJuggernaut, - Self::FluxPro1 => CommonModelType::FluxPro1, - } - } -} - // ── Request ── #[derive(Deserialize, Debug)] diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_image_model.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_image_model.rs new file mode 100644 index 0000000000..c8fb4dd99b --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_image_model.rs @@ -0,0 +1,92 @@ +use enums::common::generation::common_model_type::CommonModelType; +use serde_derive::Deserialize; + +/// Unified image model enum covering text-to-image, image edit, and inpainting. +/// +/// 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 TauriImageModel { + // Text-to-image models + + #[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, + #[serde(rename = "midjourney")] + Midjourney, + + // Image edit models + + #[serde(rename = "flux_pro_kontext_max")] + FluxProKontextMax, + #[serde(rename = "qwen_edit_2511_angles")] + QwenEdit2511Angles, + #[serde(rename = "flux_2_lora_angles")] + Flux2LoraAngles, + + // Inpainting models + + #[serde(rename = "flux_dev_juggernaut")] + FluxDevJuggernaut, + #[serde(rename = "flux_pro_1")] + FluxPro1, +} + +impl TauriImageModel { + pub fn to_common_model_type(&self) -> CommonModelType { + match self { + Self::Flux1Dev => CommonModelType::Flux1Dev, + Self::Flux1Schnell => CommonModelType::Flux1Schnell, + Self::FluxPro11 => CommonModelType::FluxPro11, + Self::FluxPro11Ultra => CommonModelType::FluxPro11Ultra, + Self::GrokImage => CommonModelType::GrokImage, + Self::Recraft3 => CommonModelType::Recraft3, + Self::GptImage1 => CommonModelType::GptImage1, + Self::GptImage1p5 => CommonModelType::GptImage1p5, + Self::GptImage2 => CommonModelType::GptImage2, + Self::Gemini25Flash => CommonModelType::NanoBanana, + Self::NanoBanana => CommonModelType::NanoBanana, + Self::NanoBanana2 => CommonModelType::NanoBanana2, + Self::NanoBananaPro => CommonModelType::NanoBananaPro, + Self::Seedream4 => CommonModelType::Seedream4, + Self::Seedream4p5 => CommonModelType::Seedream4p5, + Self::Seedream5Lite => CommonModelType::Seedream5Lite, + Self::Midjourney => CommonModelType::Midjourney, + Self::FluxProKontextMax => CommonModelType::FluxProKontextMax, + Self::QwenEdit2511Angles => CommonModelType::QwenEdit2511Angles, + Self::Flux2LoraAngles => CommonModelType::Flux2LoraAngles, + Self::FluxDevJuggernaut => CommonModelType::FluxDevJuggernaut, + Self::FluxPro1 => CommonModelType::FluxPro1, + } + } +} diff --git a/frontend/libs/tauri-api/src/index.ts b/frontend/libs/tauri-api/src/index.ts index 73fbaae175..0cefd775f6 100644 --- a/frontend/libs/tauri-api/src/index.ts +++ b/frontend/libs/tauri-api/src/index.ts @@ -13,6 +13,7 @@ export * from "./lib/enqueue/EnqueueImageTo3dObject.js"; export * from "./lib/enqueue/EnqueueImageTo3dWorld.js"; export * from "./lib/enqueue/EnqueueImageToGaussian.js"; export * from "./lib/enqueue/EnqueueImageToVideo.js"; +export * from "./lib/enqueue/GenerateVideoCommand.js"; export * from "./lib/enqueue/EnqueueTextToImage.js"; export * from "./lib/generate/GenerateImage.js"; export * from "./lib/generate/GenerateVideo.js"; diff --git a/frontend/libs/tauri-api/src/lib/enqueue/GenerateVideoCommand.ts b/frontend/libs/tauri-api/src/lib/enqueue/GenerateVideoCommand.ts new file mode 100644 index 0000000000..f07aa9c6d1 --- /dev/null +++ b/frontend/libs/tauri-api/src/lib/enqueue/GenerateVideoCommand.ts @@ -0,0 +1,192 @@ +import { invoke } from "@tauri-apps/api/core"; +import { CommandResult } from "../common/CommandStatus"; +import { Model } from "@storyteller/model-list"; +import { GenerationProvider } from "@storyteller/api-enums"; + +export enum GenerateVideoErrorType { + /// Caller didn't specify a model + ModelNotSpecified = "model_not_specified", + /// Generic server error + ServerError = "server_error", + /// No Fal API key available + NeedsFalApiKey = "needs_fal_api_key", + /// Fal had an API error + FalError = "fal_error", +} + +export interface GenerateVideoRequest { + // Required. The model to use. + model?: Model; + + // The provider to use. + provider?: GenerationProvider; + + // Optional. Starting frame. + start_frame_image_media_token?: string; + + // Optional. Starting frame (legacy field name). + image_media_token?: string; + + // Optional. Ending frame. + end_frame_image_media_token?: string; + + // Optional. Text prompt to direct the video. + prompt?: string; + + // Optional frontend state to return later. + frontend_caller?: string; + + // Optional frontend state to return later. + frontend_subscriber_id?: string; + + // Optional. Orientation of the video for Sora 2. + sora_orientation?: "portrait" | "landscape"; + + // Optional. Aspect Ratio for the video for Grok Video. + grok_aspect_ratio?: "portrait" | "landscape" | "square"; + + // Optional. Whether to generate audio alongside the video (used by some models like Veo2) + generate_audio?: boolean; + + // Optional. Common aspect ratio for other models. + // Not all are supported by all models, but Artcraft will compensate. + aspect_ratio?: string; // TODO: Typesafety. + + // Optional. Duration in seconds. + duration_seconds?: number; + + // Optional. Reference image media tokens (for reference mode). + reference_image_media_tokens?: string[]; + + // Optional. Reference video media tokens (for video-to-video reference). + reference_video_media_tokens?: string[]; + + // Optional. Reference audio media tokens (for audio reference). + reference_audio_media_tokens?: string[]; + + // Optional. Character tokens for character consistency (from @-mentions). + reference_character_tokens?: string[]; +} + +interface RawGenerateVideoRequest { + model?: string; + provider?: GenerationProvider; + start_frame_image_media_token?: string; + image_media_token?: string; + end_frame_image_media_token?: string; + prompt?: string; + frontend_caller?: string; + frontend_subscriber_id?: string; + sora_orientation?: "portrait" | "landscape"; + grok_aspect_ratio?: "portrait" | "landscape" | "square"; + generate_audio?: boolean; + aspect_ratio?: string; + duration_seconds?: number; + reference_image_media_tokens?: string[]; + reference_video_media_tokens?: string[]; + reference_audio_media_tokens?: string[]; + reference_character_tokens?: string[]; +} + +export interface GenerateVideoError extends CommandResult { + error_type: GenerateVideoErrorType; + error_message?: string; +} + +export type GenerateVideoPayload = Record; + +export interface GenerateVideoSuccess extends CommandResult { + payload: GenerateVideoPayload; +} + +export type GenerateVideoResult = + | GenerateVideoSuccess + | GenerateVideoError; + +export const GenerateVideoCommand = async ( + request: GenerateVideoRequest, +): Promise => { + const mutableRequest: RawGenerateVideoRequest = { + model: request.model?.tauriId, + start_frame_image_media_token: request.start_frame_image_media_token, + image_media_token: request.image_media_token, + }; + + if (request.provider) { + mutableRequest.provider = request.provider; + } + + if (request.prompt) { + mutableRequest.prompt = request.prompt; + } + + if (request.end_frame_image_media_token) { + mutableRequest.end_frame_image_media_token = + request.end_frame_image_media_token; + } + + if (request.frontend_caller) { + mutableRequest.frontend_caller = request.frontend_caller; + } + + if (request.frontend_subscriber_id) { + mutableRequest.frontend_subscriber_id = request.frontend_subscriber_id; + } + + if (request.sora_orientation) { + mutableRequest.sora_orientation = request.sora_orientation; + } + + if (request.grok_aspect_ratio) { + mutableRequest.grok_aspect_ratio = request.grok_aspect_ratio; + } + + if (typeof request.generate_audio === "boolean") { + mutableRequest.generate_audio = request.generate_audio; + } + + if (request.aspect_ratio) { + mutableRequest.aspect_ratio = request.aspect_ratio; + } + + if (typeof request.duration_seconds === "number") { + mutableRequest.duration_seconds = request.duration_seconds; + } + + if ( + request.reference_image_media_tokens && + request.reference_image_media_tokens.length > 0 + ) { + mutableRequest.reference_image_media_tokens = + request.reference_image_media_tokens; + } + + if ( + request.reference_video_media_tokens && + request.reference_video_media_tokens.length > 0 + ) { + mutableRequest.reference_video_media_tokens = + request.reference_video_media_tokens; + } + + if ( + request.reference_audio_media_tokens && + request.reference_audio_media_tokens.length > 0 + ) { + mutableRequest.reference_audio_media_tokens = + request.reference_audio_media_tokens; + } + + if ( + request.reference_character_tokens && + request.reference_character_tokens.length > 0 + ) { + mutableRequest.reference_character_tokens = request.reference_character_tokens; + } + + const result = await invoke("generate_video_command", { + request: mutableRequest, + }); + + return result as GenerateVideoResult; +}; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6275dc9c8e..2e5bbc8123 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -349,6 +349,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -364,6 +365,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -823,6 +825,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2582,6 +2585,7 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -2655,6 +2659,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2799,6 +2804,7 @@ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", "license": "MIT", + "peer": true, "dependencies": { "@emotion/memoize": "^0.8.1" } @@ -2828,7 +2834,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=12" } @@ -2846,7 +2851,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } @@ -2864,7 +2868,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } @@ -2882,7 +2885,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } @@ -2900,7 +2902,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=12" } @@ -2918,7 +2919,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=12" } @@ -2936,7 +2936,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -2954,7 +2953,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -2972,7 +2970,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -2990,7 +2987,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3008,7 +3004,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3026,7 +3021,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3044,7 +3038,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3062,7 +3055,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3080,7 +3072,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3098,7 +3089,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3116,7 +3106,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3150,7 +3139,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -3184,7 +3172,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -3218,7 +3205,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=12" } @@ -3236,7 +3222,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } @@ -3254,7 +3239,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } @@ -3272,7 +3256,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } @@ -3500,6 +3483,7 @@ "resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -3869,7 +3853,6 @@ "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3914,6 +3897,7 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -5414,6 +5398,7 @@ "integrity": "sha512-fnP+ZOZTFeBGiTAnxve+axGmiYn2D60h86nUISXjXClK3LUY1krUfPgf6MaD4YDJ4i51OGXZWPekeMe16pkd8Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@module-federation/runtime": "0.21.6", "@module-federation/webpack-bundler-runtime": "0.21.6" @@ -5607,6 +5592,7 @@ "integrity": "sha512-kzFn3ObUeBp5vaEtN1WMxhTYBuYEErxugu1RzFUERD21X3BZ+b4cWwdFJuBDlsmVjctIg/QSOoZoPXRKAO0foA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@module-federation/runtime": "0.15.0", "@module-federation/webpack-bundler-runtime": "0.15.0" @@ -5721,6 +5707,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -8089,7 +8076,6 @@ "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.85.2.tgz", "integrity": "sha512-kauC/oPaxklU4Y+u9gBfCBJm51qX6WBZq4xx0USCdimtp+G8+554kpygfSWIjoqCJa2o06bWxBEjesiuCv+LzA==", "license": "MIT", - "peer": true, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } @@ -8099,7 +8085,6 @@ "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.2.tgz", "integrity": "sha512-XCginmxh0//++EXVOEJHBVZxHla294FzLCFF6jXwAUjvXVhqyIKyxhABfz+r4OOmaiuWk4Rtd4arqdAzeHeprg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", @@ -8121,7 +8106,6 @@ "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.85.2.tgz", "integrity": "sha512-3KLgSg1kHvBpr93zMaQhvfYTgnCw7yZRED+3J4dMcYjfSjtD0Wf8SofU6uBmAw9JaVYvP43lpdwUpI4p0+ABsg==", "license": "MIT", - "peer": true, "dependencies": { "@react-native/dev-middleware": "0.85.2", "debug": "^4.4.0", @@ -8152,7 +8136,6 @@ "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.85.2.tgz", "integrity": "sha512-j+0b9H5f5hGTLQxHIhJU/b/W6ijuxJF+ZTLHB0se2kzUBNxFKd7DkIc6753qk3CJdiv55vxG3XDgmlpbHxOpmA==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } @@ -8162,7 +8145,6 @@ "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.85.2.tgz", "integrity": "sha512-r5BkhqPMfg3LmaZS5zadHmBNVH5h4bhSpv4BEPGfK4gat9HABAMzUzybi+2wpgU3SoHxnyKGdExEJvoqVcjeRg==", "license": "MIT", - "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "debug": "^4.4.0", @@ -8177,7 +8159,6 @@ "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.85.2.tgz", "integrity": "sha512-3J+NaDUg+QEfDeLAUzgaWhpaxEg78g+KwbydlDCewh2G6WnHpsty8XooruxNHzyAsqVWywZMrzmbn78Ctc1O9Q==", "license": "MIT", - "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.85.2", @@ -8201,7 +8182,6 @@ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "license": "MIT", - "peer": true, "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -8218,7 +8198,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -8240,7 +8219,6 @@ "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.85.2.tgz", "integrity": "sha512-YXBOLeAqFrv7XwUeBPTKZeOV1FIxn4AW7UAEitScf3ibC8bu8+6NpJu4HWgbNQHg7vDbbTZVbcOl8EwGxsSq2w==", "license": "MIT", - "peer": true, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } @@ -8250,7 +8228,6 @@ "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.85.2.tgz", "integrity": "sha512-esGEAmKVM40DV/yVmNljCKZTIeUo7qXqc+Hwffkv3TG+b3E24xyFovHrbP98gGxZr2ZsEyx+2sKLdXF5asY5nw==", "license": "MIT", - "peer": true, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } @@ -8259,8 +8236,7 @@ "version": "0.85.2", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.85.2.tgz", "integrity": "sha512-svuOLtjbFGXDdHsriHXuND5FgHg7XlkOXCbH/8+X4t76YLH6qSTffSIQQrKLDL5mn4EFU+Oh/PNO0/FfpnTOTg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@react-oauth/google": { "version": "0.13.4", @@ -8418,6 +8394,7 @@ "integrity": "sha512-gfc4Hu2Sysr+GyU/F12d2uvEfQwUwWvsOjBtiemhdN1xGOn1+FyYzlLl/aJ7AL9oYko8sDqOPyJCiApWJJGCCw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.8", "@babel/generator": "^7.21.5", @@ -9246,6 +9223,7 @@ "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.2.tgz", "integrity": "sha512-dTrAG1SgOLgz1DFBDsLHN0V34YqLsHEReVHYOI4UV/p+ALbn/BRQMw1MaUuqGXmX21ZTuCzzPegtTLNEOc8ixA==", "license": "MIT", + "peer": true, "dependencies": { "@remix-run/router": "1.23.0", "@types/cookie": "^0.6.0", @@ -9838,6 +9816,7 @@ "integrity": "sha512-2mR+2YBydlgZ7Q0Rpd6bCC3MBnV9TS0x857K0zIhbDj4BQOqaWVy1n7fx/B3MrS8TR0QCuzKfyDAjNz+XTyJVQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@module-federation/runtime-tools": "0.21.6", "@rspack/binding": "1.6.6", @@ -10552,6 +10531,7 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -10734,6 +10714,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.8" @@ -10948,6 +10929,7 @@ "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -11289,6 +11271,7 @@ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -11784,6 +11767,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -11806,6 +11790,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.0.tgz", "integrity": "sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -12145,6 +12130,7 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -14354,6 +14340,7 @@ "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -14415,6 +14402,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -14497,6 +14485,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -14570,8 +14559,7 @@ "version": "1.4.10", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ansi-colors": { "version": "4.1.3", @@ -14864,8 +14852,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/assertion-error": { "version": "2.0.1", @@ -15166,7 +15153,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.33.3.tgz", "integrity": "sha512-/Z9xYdaJ1lC0pT9do6TqCqhOSLfZ5Ot8D5za1p+feEfWYupCOfGbhhEXN9r2ZgJtDNUNRw/Z+T2CvAGKBqtqWA==", "license": "MIT", - "peer": true, "dependencies": { "hermes-parser": "0.33.3" } @@ -15386,6 +15372,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -15418,7 +15405,6 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "node-int64": "^0.4.0" } @@ -15820,7 +15806,6 @@ "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -15849,7 +15834,6 @@ "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -16258,7 +16242,6 @@ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", @@ -16274,7 +16257,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -16284,7 +16266,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -16294,7 +16275,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -16312,15 +16292,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/connect/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "license": "MIT", - "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -16333,7 +16311,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -16669,7 +16646,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -17372,7 +17350,8 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -17428,6 +17407,7 @@ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -17520,7 +17500,6 @@ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "license": "MIT", - "peer": true, "dependencies": { "stackframe": "^1.3.4" } @@ -17732,6 +17711,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -18156,6 +18136,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -18212,6 +18193,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -18313,6 +18295,7 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -19048,14 +19031,14 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -19229,7 +19212,6 @@ "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", "license": "(MIT OR Apache-2.0)", - "peer": true, "bin": { "dotslash": "bin/dotslash" }, @@ -19242,7 +19224,6 @@ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "bser": "2.1.1" } @@ -19356,6 +19337,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -19549,8 +19531,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/follow-redirects": { "version": "1.15.11", @@ -20421,22 +20402,19 @@ "version": "250829098.0.10", "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.10.tgz", "integrity": "sha512-TcRlZ0/TlyfJqquRFAWoyElVNnkdYRi/sEp4/Qy8/GYxjg8j2cS9D4MjuaQ+qimkmLN7AmO+44IznRf06mAr0w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/hermes-estree": { "version": "0.33.3", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/hermes-parser": { "version": "0.33.3", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", "license": "MIT", - "peer": true, "dependencies": { "hermes-estree": "0.33.3" } @@ -20458,6 +20436,7 @@ "version": "4.12.7", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz", "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -20775,7 +20754,6 @@ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", "license": "MIT", - "peer": true, "dependencies": { "queue": "6.0.2" }, @@ -21948,6 +21926,7 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -21977,7 +21956,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -21995,7 +21973,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -22008,7 +21985,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -22022,15 +21998,13 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -22046,7 +22020,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -22104,8 +22077,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/jsdom": { "version": "22.1.0", @@ -22113,6 +22085,7 @@ "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "abab": "^2.0.6", "cssstyle": "^3.0.0", @@ -22422,7 +22395,8 @@ "url": "https://github.com/sponsors/lavrton" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/language-subtag-registry": { "version": "0.3.23", @@ -22479,7 +22453,6 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -22503,7 +22476,6 @@ "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" @@ -22514,7 +22486,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -22523,13 +22494,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lightningcss": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -23197,8 +23168,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -23521,7 +23491,6 @@ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -23540,8 +23509,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -23789,8 +23757,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/merge-descriptors": { "version": "1.0.3", @@ -23837,7 +23804,6 @@ "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.3.tgz", "integrity": "sha512-1h3lbVrE6hGf1e/764HfhPGg/bGrWMJDDh7G2rc4gFYZboVuI40BlG/y+UhtbhQDNlO/csMvrcnK0YrTlHUVew==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", @@ -23892,7 +23858,6 @@ "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.3.tgz", "integrity": "sha512-svAA+yMLpeMiGcz/jKJs4oHpIGEx4nBqNEJ5AGj4CYIg1efvK+A0TjR6tgIuc6tKO5e8JmN/1lglpN2+f3/z/w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", @@ -23908,15 +23873,13 @@ "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", - "peer": true, "dependencies": { "hermes-estree": "0.35.0" } @@ -23926,7 +23889,6 @@ "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.3.tgz", "integrity": "sha512-0QElxwLaHqLZf+Xqio8QrjVbuXP/8sJfQBGSPiITlKDVXrVLefuzYVSH9Sj+QL6lrPj2gYZd/iwQh1yZuVKnLA==", "license": "MIT", - "peer": true, "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", @@ -23942,7 +23904,6 @@ "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.3.tgz", "integrity": "sha512-TnSL1Fdvrw+2glTdBSRmA5TL8l/i16ECjsrUdf3E5HncA+sNx8KcwDG8r+3ct1UhfYcusJypzZqTN55FZZcwGg==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, @@ -23955,7 +23916,6 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 14" } @@ -23965,7 +23925,6 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", - "peer": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -23979,7 +23938,6 @@ "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.3.tgz", "integrity": "sha512-JmCzZWOETR+O22q8oPBWyQppx3roU9EbkbGzD8Gf1jukQ4b5T1fTzqqHruu6K4sTiNq5zVQySmKF6bp4kVARew==", "license": "MIT", - "peer": true, "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", @@ -23999,7 +23957,6 @@ "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.3.tgz", "integrity": "sha512-cc0pvAa80ai1nDmqqz0P59a+0ZqCZ/YHU/3jEekZL6spFnYDfX8iDLdn9FR6kX+67rmzKxHNrbrSRFLX2AYocw==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", @@ -24014,7 +23971,6 @@ "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.3.tgz", "integrity": "sha512-1cL4m4Jv1yRUt9RJExZQLfccscdlMNOcRG6LHLtmJhf3BG9j3MujPVc7CIpKYdFl+KUl+sdjge6oO3+meKCHQA==", "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", @@ -24035,7 +23991,6 @@ "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.3.tgz", "integrity": "sha512-3ofrG2OQyJbO9RNhCfOcl8QU7EE2WrSsnN5dFkuZaJO5+4Imujr9bUXmspeNlXRsOVk0F/rVRbEFH98lFSCkBQ==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" @@ -24049,7 +24004,6 @@ "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.3.tgz", "integrity": "sha512-pjEzGDtoM8DTHAIPK/9u9ZxszEiuRohYUVImWvgbnB91V4gqYJpQcoEYUugf2NIm1lrX5HNu0OvNqWmPBnGYjA==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, @@ -24062,7 +24016,6 @@ "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.3.tgz", "integrity": "sha512-o7HLRfMyVk9N2dUZ9VjQfB6xxUItL9Pi9WcqxURE7MEKOH6wbGt9/E92YdYLluTOtkzYAEVfdC6h6lcxqA+hMQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" @@ -24076,7 +24029,6 @@ "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.3.tgz", "integrity": "sha512-jS48CeSzw78M8y6VE0f9uy3lVmfbOS677j2VCxnlmlYmnahcXuC6IhoN9K6LynNvos9517yUadcfgioju38xYQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", @@ -24097,7 +24049,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -24107,7 +24058,6 @@ "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.3.tgz", "integrity": "sha512-J9Tpo8NCycYrozRvBIUyOwGAu4xkawOsAppmTscFiaegK0WvuDGwIM53GbzVSnytCHjVAF0io5GQxpkrKTuc7g==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", @@ -24128,7 +24078,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -24138,7 +24087,6 @@ "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.3.tgz", "integrity": "sha512-8S3baq2XhBaafHEH5Q8sJW6tmzsEJk80qKc3RU/nZV1MsnYq94RdjTUR6AyKjQd6Rfsk1BtBxhtiNnk7mgslCg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", @@ -24156,7 +24104,6 @@ "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.3.tgz", "integrity": "sha512-Wjba7PyYktNRsHbPmkx2J2UX32rAzcDXjCu49zPHeF/viJlYJhwRaNePQcHaCRqQ+kmgQT4ThprsnJfDj71ZMA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", @@ -24181,7 +24128,6 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", - "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -24194,22 +24140,19 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro/node_modules/hermes-estree": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro/node_modules/hermes-parser": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", - "peer": true, "dependencies": { "hermes-estree": "0.35.0" } @@ -24219,7 +24162,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -24236,7 +24178,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -24246,7 +24187,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -24256,7 +24196,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -25585,8 +25524,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-machine-id": { "version": "1.1.12", @@ -25770,8 +25708,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/nwsapi": { "version": "2.2.22", @@ -25786,6 +25723,7 @@ "integrity": "sha512-2wL/2fSmIbRWn6zXaQ/g3kj5DfEaTw/aJkPr6ozJh8BUq5iYKE+tS9oh0PjsVVwN6Pybe80Lu+mn9RgWyeV3xw==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -25943,7 +25881,6 @@ "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.3.tgz", "integrity": "sha512-J7554Ef8bzmKaDY365Afq6PF+qtdnY/d5PKUQFrsKlZHV/N3OGZewVrvDrQDyX5V5NJjTpcAKtlrFZcDr+HvpQ==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, @@ -26721,6 +26658,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -27026,6 +26964,7 @@ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -27109,6 +27048,7 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -27260,7 +27200,6 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", "license": "MIT", - "peer": true, "dependencies": { "asap": "~2.0.6" } @@ -27443,7 +27382,6 @@ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", "license": "MIT", - "peer": true, "dependencies": { "inherits": "~2.0.3" } @@ -27602,6 +27540,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -27650,7 +27589,6 @@ "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "license": "MIT", - "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -27661,7 +27599,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -27683,6 +27620,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -28036,7 +27974,6 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.0.tgz", "integrity": "sha512-90abYK2q5/qDM+GACs9zRvc5KhEEpEWqWlHSd64zTPNxg+9wCJvTfyD9x2so7hlQhjRYO1Fa6flR3BC/kpTFkA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -28085,7 +28022,6 @@ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", "license": "MIT", - "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.9" }, @@ -28108,7 +28044,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -28135,7 +28070,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -28146,7 +28080,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -28156,7 +28089,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -28170,8 +28102,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-spring/node_modules/react-konva": { "version": "19.2.3", @@ -28192,7 +28123,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@types/react-reconciler": "^0.33.0", "its-fine": "^2.0.0", @@ -28210,7 +28140,6 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.33.0.tgz", "integrity": "sha512-HZOXsKT0tGI9LlUw2LuedXsVeB88wFa536vVL0M6vE8zN63nI+sSr1ByxmPToP5K5bukaVscyeCJcF9guVNJ1g==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "*" } @@ -28220,7 +28149,6 @@ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", "license": "MIT", - "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.9" }, @@ -28233,7 +28161,6 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "*" } @@ -28243,7 +28170,6 @@ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -28319,7 +28245,6 @@ "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.85.2.tgz", "integrity": "sha512-wmVKpAlcr+UB0L5SpbrV865EdleUP7I5+X+48e1aRsQK8q+wsTRBXeUwWVip/1l+HZwlZFeO8iOILJ16VRu0Cw==", "license": "MIT", - "peer": true, "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -28342,15 +28267,13 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-spring/node_modules/ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -28394,7 +28317,6 @@ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" @@ -28576,8 +28498,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", @@ -28766,8 +28687,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.8", @@ -29315,7 +29235,6 @@ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -30023,7 +29942,6 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -30359,15 +30277,13 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/stacktrace-parser": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.7.1" }, @@ -30968,7 +30884,6 @@ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=17.0" } @@ -31279,6 +31194,7 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -31448,14 +31364,14 @@ "version": "0.171.0", "resolved": "https://registry.npmjs.org/three/-/three-0.171.0.tgz", "integrity": "sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/through2": { "version": "2.0.5", @@ -31614,8 +31530,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -31918,7 +31833,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=8" } @@ -32020,6 +31934,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -32684,6 +32599,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -33577,6 +33493,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -33655,8 +33572,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/vscode-uri": { "version": "3.1.0", @@ -33683,7 +33599,6 @@ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "makeerror": "1.0.12" } @@ -33771,6 +33686,7 @@ "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -33872,8 +33788,7 @@ "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/whatwg-mimetype": { "version": "3.0.0", @@ -34158,6 +34073,7 @@ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -34372,6 +34288,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From f2d327481b9506a0d6c6e1465d96f466c7f3ba3a Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 30 Apr 2026 22:26:27 -0400 Subject: [PATCH 08/14] note --- .../commands/generate/generate_image/generate_image_command.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs index 52a7c2e308..83824891f0 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs @@ -47,6 +47,9 @@ pub async fn generate_image_command( info!("generate_image_command called, request: {:?}", request); + + // Figure out which legacy command to route to... + let model = request.model; // Determine which legacy command to proxy to based on request shape + model. From 5beac6c3686cb6b8589fee8bbdd5d397b25d4562 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 30 Apr 2026 22:36:58 -0400 Subject: [PATCH 09/14] artcraft provider wip --- ...ndle_artcraft_gpt_image_1_text_to_image.rs | 112 ------------ .../handle_artcraft_via_omni_endpoint.rs | 129 ++++++++++++++ .../artcraft/handle_text_to_image_artcraft.rs | 46 ----- ...andle_text_to_image_artcraft_via_router.rs | 159 ------------------ .../generate/generate_image/artcraft/mod.rs | 5 +- .../generate_image/generate_image_command.rs | 77 ++++++++- .../commands/generate/generate_image/mod.rs | 1 + 7 files changed, 203 insertions(+), 326 deletions(-) delete mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_via_omni_endpoint.rs delete mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft.rs delete mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft_via_router.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs deleted file mode 100644 index 4265016619..0000000000 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_gpt_image_1_text_to_image.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize}; -use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize}; -use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; -use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; -use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent; -use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::core::state::data_dir::app_data_root::AppDataRoot; -use crate::core::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_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, - }) -} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_via_omni_endpoint.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_via_omni_endpoint.rs new file mode 100644 index 0000000000..e32264a07c --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_via_omni_endpoint.rs @@ -0,0 +1,129 @@ +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_image_cost_and_generate_request::OmniGenImageCostAndGenerateRequest; +use artcraft_client::endpoints::omni_gen::generate::image::omni_gen_image::omni_gen_image_generate; +use enums::common::generation::common_image_model::CommonImageModel; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::tasks::task_type::TaskType; +use log::{error, info}; + +use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; +use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; +use crate::core::events::generation_events::common::GenerationModel; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; + +pub async fn handle_artcraft_via_omni_endpoint( + request: &TauriGenerateImageRequest, + app_env_configs: &AppEnvConfigs, + storyteller_creds_manager: &StorytellerCredentialManager, +) -> Result { + let model = request.model.ok_or(GenerateError::no_model_specified())?; + + let omni_model = map_to_common_image_model(model) + .ok_or(GenerateError::NotYetImplemented( + format!("Model {:?} is not supported via the omni endpoint", model), + ))?; + + let generation_model = map_to_generation_model(model); + + let creds = match storyteller_creds_manager.get_credentials()? { + Some(creds) => creds, + None => return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials)), + }; + + // Build the omni gen request. + let mut image_media_tokens = request.image_media_tokens.clone().unwrap_or_default(); + + // Prepend canvas image if present. + if let Some(canvas_token) = &request.canvas_image_media_token { + image_media_tokens.insert(0, canvas_token.clone()); + } + + let image_media_tokens = if image_media_tokens.is_empty() { None } else { Some(image_media_tokens) }; + + let omni_request = OmniGenImageCostAndGenerateRequest { + idempotency_token: None, + model: Some(omni_model), + prompt: request.prompt.clone(), + image_media_tokens, + resolution: None, // TODO: Convert from request.resolution + aspect_ratio: None, // TODO: Convert from request.aspect_ratio + quality: request.quality, + image_batch_count: request.batch_size.map(|n| n as u16), + adjust_horizontal_angle: request.adjust_horizontal_angle, + adjust_vertical_angle: request.adjust_vertical_angle, + adjust_zoom: request.adjust_zoom, + }; + + info!("Sending image generation via omni endpoint: model={:?}", omni_model); + + let response = omni_gen_image_generate( + &app_env_configs.storyteller_host, + Some(&creds), + omni_request, + ).await.map_err(|err| { + error!("Omni image generation failed: {:?}", err); + GenerateError::from(err) + })?; + + info!("Omni image generation succeeded: job_token={}", response.inference_job_token.as_str()); + + Ok(TaskEnqueueSuccess { + task_type: TaskType::ImageGeneration, + model: Some(generation_model), + provider: GenerationProvider::Artcraft, + provider_job_id: Some(response.inference_job_token.to_string()), + }) +} + +// ── Model mapping helpers ── + +fn map_to_common_image_model(model: TauriImageModel) -> Option { + match model { + TauriImageModel::Flux1Dev => Some(CommonImageModel::Flux1Dev), + TauriImageModel::Flux1Schnell => Some(CommonImageModel::Flux1Schnell), + TauriImageModel::FluxPro11 => Some(CommonImageModel::FluxPro11), + TauriImageModel::FluxPro11Ultra => Some(CommonImageModel::FluxPro11Ultra), + TauriImageModel::Gemini25Flash => Some(CommonImageModel::NanoBanana), // NB: Alias for Nano Banana + TauriImageModel::GptImage1 => Some(CommonImageModel::GptImage1), + TauriImageModel::GptImage1p5 => Some(CommonImageModel::GptImage1p5), + TauriImageModel::GptImage2 => Some(CommonImageModel::GptImage2), + TauriImageModel::NanoBanana => Some(CommonImageModel::NanoBanana), + TauriImageModel::NanoBanana2 => Some(CommonImageModel::NanoBanana2), + TauriImageModel::NanoBananaPro => Some(CommonImageModel::NanoBananaPro), + TauriImageModel::Seedream4 => Some(CommonImageModel::Seedream4), + TauriImageModel::Seedream4p5 => Some(CommonImageModel::Seedream4p5), + TauriImageModel::Seedream5Lite => Some(CommonImageModel::Seedream5Lite), + // Models not in CommonImageModel (Grok, Recraft, Midjourney, edit-only, inpaint-only) + _ => None, + } +} + +fn map_to_generation_model(model: TauriImageModel) -> GenerationModel { + match model { + TauriImageModel::Flux1Dev => GenerationModel::Flux1Dev, + TauriImageModel::Flux1Schnell => GenerationModel::Flux1Schnell, + TauriImageModel::Flux2LoraAngles => GenerationModel::Flux2LoraAngles, + TauriImageModel::FluxDevJuggernaut => GenerationModel::FluxDevJuggernaut, + TauriImageModel::FluxPro1 => GenerationModel::FluxPro1, + TauriImageModel::FluxPro11 => GenerationModel::FluxPro11, + TauriImageModel::FluxPro11Ultra => GenerationModel::FluxPro11Ultra, + TauriImageModel::FluxProKontextMax => GenerationModel::FluxProKontextMax, + TauriImageModel::Gemini25Flash => GenerationModel::NanoBanana, // NB: Alias for Nano Banana + TauriImageModel::GptImage1 => GenerationModel::GptImage1, + TauriImageModel::GptImage1p5 => GenerationModel::GptImage1p5, + TauriImageModel::GptImage2 => GenerationModel::GptImage2, + TauriImageModel::GrokImage => GenerationModel::GrokImage, + TauriImageModel::NanoBanana => GenerationModel::NanoBanana, + TauriImageModel::NanoBanana2 => GenerationModel::NanoBanana2, + TauriImageModel::NanoBananaPro => GenerationModel::NanoBananaPro, + TauriImageModel::QwenEdit2511Angles => GenerationModel::QwenEdit2511Angles, + TauriImageModel::Seedream4 => GenerationModel::Seedream4, + TauriImageModel::Seedream4p5 => GenerationModel::Seedream4p5, + TauriImageModel::Seedream5Lite => GenerationModel::Seedream5Lite, + // TODO: + TauriImageModel::Midjourney => GenerationModel::Flux1Dev, // Fallback + TauriImageModel::Recraft3 => GenerationModel::Flux1Dev, // Fallback + } +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft.rs deleted file mode 100644 index f23c44292e..0000000000 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::events::generation_events::common::GenerationModel; -use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::core::state::data_dir::app_data_root::AppDataRoot; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use artcraft_router::api::common_image_model::CommonImageModel; -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, CommonImageModel::Flux1Dev, GenerationModel::Flux1Dev).await, - TextToImageModel::Flux1Schnell => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Flux1Schnell, GenerationModel::Flux1Schnell).await, - TextToImageModel::FluxPro11 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::FluxPro11, GenerationModel::FluxPro11).await, - TextToImageModel::FluxPro11Ultra => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::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, CommonImageModel::GptImage1p5, GenerationModel::GptImage1p5).await, - TextToImageModel::GptImage2 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::GptImage2, GenerationModel::GptImage2).await, - TextToImageModel::Gemini25Flash | TextToImageModel::NanoBanana => { - handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanoBanana, GenerationModel::NanoBanana).await - }, - TextToImageModel::NanoBanana2 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanoBanana2, GenerationModel::NanoBanana2).await, - TextToImageModel::NanoBananaPro => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::NanoBananaPro, GenerationModel::NanoBananaPro).await, - TextToImageModel::Seedream4 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream4, GenerationModel::Seedream4).await, - TextToImageModel::Seedream4p5 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream4p5, GenerationModel::Seedream4p5).await, - TextToImageModel::Seedream5Lite => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, CommonImageModel::Seedream5Lite, GenerationModel::Seedream5Lite).await, - } -} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft_via_router.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft_via_router.rs deleted file mode 100644 index 9094a42422..0000000000 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_text_to_image_artcraft_via_router.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::core::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio as CommonAspectRatio2; -use crate::core::api_adapters::resolution::common_resolution::CommonResolution as CommonResolution2; -use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; -use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{ - EnqueueTextToImageRequest, TextToImageResolution, TextToImageSize, -}; -use crate::core::events::generation_events::common::GenerationModel; -use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; -use artcraft_router::api::common_aspect_ratio::CommonAspectRatio as RouterCommonAspectRatio; -use artcraft_router::api::common_image_model::CommonImageModel; -use artcraft_router::api::common_resolution::CommonResolution as RouterCommonResolution; -use artcraft_router::api::common_quality::CommonQuality as RouterCommonQuality; -use artcraft_router::api::image_list_ref::ImageListRef; -use artcraft_router::api::provider::Provider; -use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; -use artcraft_router::client::router_artcraft_client::RouterArtcraftClient; -use artcraft_router::client::router_client::RouterClient; -use artcraft_router::generate::generate_image::generate_image_request::GenerateImageRequest; -use enums::common::generation_provider::GenerationProvider; -use enums::tauri::tasks::task_type::TaskType; -use log::{error, info}; - -pub(super) async fn handle_text_to_image_artcraft_via_router( - request: &EnqueueTextToImageRequest, - app_env_configs: &AppEnvConfigs, - storyteller_creds_manager: &StorytellerCredentialManager, - model: CommonImageModel, - 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 = GenerateImageRequest { - model, - provider: Provider::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 plan = router_request.build()?; - - info!("Image Generation Plan: {:?}", plan); - - let response = match plan.generate_image(&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), - }) -} - -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 => RouterCommonAspectRatio::Auto, - TextToImageSize::Square => RouterCommonAspectRatio::Square, - TextToImageSize::Wide => RouterCommonAspectRatio::Wide, - TextToImageSize::Tall => RouterCommonAspectRatio::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 => RouterCommonResolution::OneK, - TextToImageResolution::TwoK => RouterCommonResolution::TwoK, - TextToImageResolution::FourK => RouterCommonResolution::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) -> RouterCommonAspectRatio { - match ar { - CommonAspectRatio2::Auto => RouterCommonAspectRatio::Auto, - CommonAspectRatio2::Square => RouterCommonAspectRatio::Square, - CommonAspectRatio2::WideThreeByTwo => RouterCommonAspectRatio::WideThreeByTwo, - CommonAspectRatio2::WideFourByThree => RouterCommonAspectRatio::WideFourByThree, - CommonAspectRatio2::WideFiveByFour => RouterCommonAspectRatio::WideFiveByFour, - CommonAspectRatio2::WideSixteenByNine => RouterCommonAspectRatio::WideSixteenByNine, - CommonAspectRatio2::WideTwentyOneByNine => RouterCommonAspectRatio::WideTwentyOneByNine, - CommonAspectRatio2::TallTwoByThree => RouterCommonAspectRatio::TallTwoByThree, - CommonAspectRatio2::TallThreeByFour => RouterCommonAspectRatio::TallThreeByFour, - CommonAspectRatio2::TallFourByFive => RouterCommonAspectRatio::TallFourByFive, - CommonAspectRatio2::TallNineBySixteen => RouterCommonAspectRatio::TallNineBySixteen, - CommonAspectRatio2::TallNineByTwentyOne => RouterCommonAspectRatio::TallNineByTwentyOne, - CommonAspectRatio2::Wide => RouterCommonAspectRatio::Wide, - CommonAspectRatio2::Tall => RouterCommonAspectRatio::Tall, - CommonAspectRatio2::Auto2k => RouterCommonAspectRatio::Auto2k, - CommonAspectRatio2::Auto4k => RouterCommonAspectRatio::Auto4k, - CommonAspectRatio2::SquareHd => RouterCommonAspectRatio::SquareHd, - } -} - -fn convert_desktop_resolution(res: CommonResolution2) -> RouterCommonResolution { - match res { - CommonResolution2::OneK => RouterCommonResolution::OneK, - CommonResolution2::TwoK => RouterCommonResolution::TwoK, - CommonResolution2::ThreeK => RouterCommonResolution::ThreeK, - CommonResolution2::FourK => RouterCommonResolution::FourK, - CommonResolution2::HalfK => RouterCommonResolution::HalfK, - CommonResolution2::FourEightyP => RouterCommonResolution::FourEightyP, - CommonResolution2::SevenTwentyP => RouterCommonResolution::SevenTwentyP, - CommonResolution2::TenEightyP => RouterCommonResolution::TenEightyP, - } -} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs index 572b124965..4738550231 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs @@ -1,4 +1 @@ -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; +pub(super) mod handle_artcraft_via_omni_endpoint; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs index 83824891f0..d6c26dc5ec 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs @@ -1,18 +1,25 @@ +use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; +use crate::core::commands::enqueue::generate_error::GenerateError; use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{ enqueue_edit_image_command, EnqueueEditImageCommand, ImageEditModel, }; use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::{ enqueue_image_inpaint_command, EnqueueInpaintImageCommand, ImageInpaintModel, }; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{ enqueue_text_to_image_command, EnqueueTextToImageRequest, TextToImageModel, }; +use crate::core::commands::generate::generate_image::artcraft::handle_artcraft_via_omni_endpoint::handle_artcraft_via_omni_endpoint; use crate::core::commands::generate::generate_image::tauri_generate_image_request::{ TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse, }; use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; use crate::core::commands::response::shorthand::Response; +use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; +use crate::core::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent; +use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; use crate::core::state::data_dir::app_data_root::AppDataRoot; @@ -24,7 +31,8 @@ use crate::services::midjourney::state::midjourney_credential_manager::Midjourne 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 log::{info, warn}; +use enums::common::generation_provider::GenerationProvider; +use log::{error, info, warn}; use serde::Serialize; use tauri::{AppHandle, State}; @@ -47,12 +55,41 @@ pub async fn generate_image_command( info!("generate_image_command called, request: {:?}", request); + // ── Try the Artcraft omni endpoint first ── + // + // If the provider is Artcraft (or unspecified, which defaults to Artcraft), + // and the model is supported by the omni endpoint, send it directly. + // This bypasses the legacy command proxy entirely. - // Figure out which legacy command to route to... + let is_artcraft_provider = matches!( + request.provider, + None | Some(GenerationProvider::Artcraft) + ); + + if is_artcraft_provider { + let omni_result = handle_artcraft_via_omni_endpoint( + &request, + &app_env_configs, + &storyteller_creds_manager, + ).await; + + match omni_result { + Ok(success) => return map_success_to_response(success, &app), + Err(GenerateError::NotYetImplemented(_)) => { + // Model not supported by omni endpoint — fall through to legacy commands. + info!("Model not supported by omni endpoint, falling back to legacy commands."); + } + Err(err) => { + // Real error — return it. + return map_error_to_response(err); + } + } + } + + // ── Fall back to legacy command proxies ── let model = request.model; - // Determine which legacy command to proxy to based on request shape + model. let has_mask = request.inpainting_mask_image_media_token.is_some() || request.inpainting_mask_image_raw_bytes.is_some(); let has_image_refs = request.image_media_tokens.as_ref().is_some_and(|t| !t.is_empty()); @@ -295,9 +332,39 @@ fn map_to_inpaint_model(model: TauriImageModel) -> Option { // ── Result mapping ── +fn map_success_to_response( + success: TaskEnqueueSuccess, + app: &AppHandle, +) -> Response { + let event = GenerationEnqueueSuccessEvent { + action: success.to_frontend_event_action(), + service: success.to_frontend_event_service(), + model: success.model, + }; + + if let Err(err) = event.send(app) { + error!("Failed to emit event: {:?}", err); + } + + CreditsBalanceChangedEvent{}.send_infallible(app); + + Ok(TauriGenerateImageResponse {}.into()) +} + +fn map_error_to_response( + err: GenerateError, +) -> Response { + error!("generate_image_command error: {:?}", err); + + Err(CommandErrorResponseWrapper { + status: CommandErrorStatus::ServerError, + error_message: Some(format!("{:?}", err)), + error_type: Some(TauriGenerateImageErrorType::ServerError), + error_details: None, + }) +} + /// Map any legacy command result to our unified response type. -/// All three legacy commands return different error types, but we just -/// check success/failure and map accordingly. fn map_legacy_result( result: Result>, ) -> Response { diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs index ccf608c644..432b9931d7 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs @@ -1,3 +1,4 @@ +mod artcraft; pub mod generate_image_command; pub mod tauri_generate_image_request; pub mod tauri_image_model; \ No newline at end of file From f192e2c07719fdcbcb58f223ce3c13d642899ea8 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 1 May 2026 00:32:29 -0400 Subject: [PATCH 10/14] refactor, move handlers --- .../src/core/commands/generate/generate_image/artcraft/mod.rs | 1 - .../commands/generate/generate_image/generate_image_command.rs | 2 +- .../artcraft/src/core/commands/generate/generate_image/mod.rs | 2 +- .../artcraft/handle_artcraft_via_omni_endpoint.rs | 0 .../commands/generate/generate_image/providers/artcraft/mod.rs | 1 + .../generate/generate_image/{ => providers}/grok/handle_grok.rs | 0 .../generate/generate_image/{ => providers}/grok/mod.rs | 0 .../{ => providers}/midjourney/handle_midjourney.rs | 0 .../generate/generate_image/{ => providers}/midjourney/mod.rs | 0 .../src/core/commands/generate/generate_image/providers/mod.rs | 1 + .../sora/handle_gpt_image_1_sora_text_to_image.rs | 0 .../{ => providers}/sora/handle_text_to_image_sora.rs | 0 .../generate/generate_image/{ => providers}/sora/mod.rs | 0 13 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs rename crates/desktop/artcraft/src/core/commands/generate/generate_image/{ => providers}/artcraft/handle_artcraft_via_omni_endpoint.rs (100%) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/mod.rs rename crates/desktop/artcraft/src/core/commands/generate/generate_image/{ => providers}/grok/handle_grok.rs (100%) rename crates/desktop/artcraft/src/core/commands/generate/generate_image/{ => providers}/grok/mod.rs (100%) rename crates/desktop/artcraft/src/core/commands/generate/generate_image/{ => providers}/midjourney/handle_midjourney.rs (100%) rename crates/desktop/artcraft/src/core/commands/generate/generate_image/{ => providers}/midjourney/mod.rs (100%) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/mod.rs rename crates/desktop/artcraft/src/core/commands/generate/generate_image/{ => providers}/sora/handle_gpt_image_1_sora_text_to_image.rs (100%) rename crates/desktop/artcraft/src/core/commands/generate/generate_image/{ => providers}/sora/handle_text_to_image_sora.rs (100%) rename crates/desktop/artcraft/src/core/commands/generate/generate_image/{ => providers}/sora/mod.rs (100%) diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs deleted file mode 100644 index 4738550231..0000000000 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(super) mod handle_artcraft_via_omni_endpoint; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs index d6c26dc5ec..611046e178 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs @@ -10,7 +10,7 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{ enqueue_text_to_image_command, EnqueueTextToImageRequest, TextToImageModel, }; -use crate::core::commands::generate::generate_image::artcraft::handle_artcraft_via_omni_endpoint::handle_artcraft_via_omni_endpoint; +use crate::core::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_omni_endpoint::handle_artcraft_via_omni_endpoint; use crate::core::commands::generate::generate_image::tauri_generate_image_request::{ TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse, }; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs index 432b9931d7..c3fc5a9b7b 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/mod.rs @@ -1,4 +1,4 @@ -mod artcraft; pub mod generate_image_command; +pub mod providers; pub mod tauri_generate_image_request; pub mod tauri_image_model; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_via_omni_endpoint.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/generate/generate_image/artcraft/handle_artcraft_via_omni_endpoint.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/mod.rs new file mode 100644 index 0000000000..af712f0cb7 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/mod.rs @@ -0,0 +1 @@ +pub mod handle_artcraft_via_omni_endpoint; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/handle_grok.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/grok/handle_grok.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/handle_grok.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/grok/handle_grok.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/grok/mod.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/generate/generate_image/grok/mod.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/grok/mod.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/handle_midjourney.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/midjourney/handle_midjourney.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/handle_midjourney.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/midjourney/handle_midjourney.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/midjourney/mod.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/generate/generate_image/midjourney/mod.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/midjourney/mod.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/mod.rs new file mode 100644 index 0000000000..8a4247a99f --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/mod.rs @@ -0,0 +1 @@ +pub mod artcraft; \ No newline at end of file diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_gpt_image_1_sora_text_to_image.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_gpt_image_1_sora_text_to_image.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_gpt_image_1_sora_text_to_image.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_gpt_image_1_sora_text_to_image.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_text_to_image_sora.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_text_to_image_sora.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/handle_text_to_image_sora.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/handle_text_to_image_sora.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/mod.rs similarity index 100% rename from crates/desktop/artcraft/src/core/commands/generate/generate_image/sora/mod.rs rename to crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/sora/mod.rs From 874705f6911b33683fd8cf77498be994fa6f35f8 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 1 May 2026 00:49:38 -0400 Subject: [PATCH 11/14] get rid of older providers for now --- .../generate_image/generate_image_command.rs | 354 +++--------------- 1 file changed, 43 insertions(+), 311 deletions(-) diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs index 611046e178..4a6a962654 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs @@ -1,332 +1,74 @@ -use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; use crate::core::commands::enqueue::generate_error::GenerateError; -use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{ - enqueue_edit_image_command, EnqueueEditImageCommand, ImageEditModel, -}; -use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::{ - enqueue_image_inpaint_command, EnqueueInpaintImageCommand, ImageInpaintModel, -}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{ - enqueue_text_to_image_command, EnqueueTextToImageRequest, TextToImageModel, -}; use crate::core::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_omni_endpoint::handle_artcraft_via_omni_endpoint; use crate::core::commands::generate::generate_image::tauri_generate_image_request::{ TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse, }; -use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus}; use crate::core::commands::response::shorthand::Response; use crate::core::events::basic_sendable_event_trait::BasicSendableEvent; use crate::core::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent; use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; -use crate::core::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker; -use crate::core::state::data_dir::app_data_root::AppDataRoot; -use crate::core::state::provider_priority::ProviderPriorityStore; use crate::core::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 log::{error, info, warn}; -use serde::Serialize; +use log::{error, info}; use tauri::{AppHandle, State}; #[tauri::command] pub async fn generate_image_command( request: TauriGenerateImageRequest, 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>, - grok_creds_manager: State<'_, GrokCredentialManager>, - grok_image_prompt_queue: State<'_, GrokImagePromptQueue>, - mj_creds_manager: State<'_, MidjourneyCredentialManager>, storyteller_creds_manager: State<'_, StorytellerCredentialManager>, - sora_creds_manager: State<'_, SoraCredentialManager>, - sora_task_queue: State<'_, SoraTaskQueue>, ) -> Response { info!("generate_image_command called, request: {:?}", request); - // ── Try the Artcraft omni endpoint first ── - // - // If the provider is Artcraft (or unspecified, which defaults to Artcraft), - // and the model is supported by the omni endpoint, send it directly. - // This bypasses the legacy command proxy entirely. + // Only Artcraft provider is supported via this command. + // Other providers (Grok, Sora, Midjourney) should use the legacy commands. let is_artcraft_provider = matches!( request.provider, None | Some(GenerationProvider::Artcraft) ); - if is_artcraft_provider { - let omni_result = handle_artcraft_via_omni_endpoint( - &request, - &app_env_configs, - &storyteller_creds_manager, - ).await; + if !is_artcraft_provider { + return Err(CommandErrorResponseWrapper { + status: CommandErrorStatus::BadRequest, + error_message: Some("Only Artcraft is supported.".to_string()), + error_type: Some(TauriGenerateImageErrorType::NoProviderAvailable), + error_details: None, + }); + } - match omni_result { - Ok(success) => return map_success_to_response(success, &app), - Err(GenerateError::NotYetImplemented(_)) => { - // Model not supported by omni endpoint — fall through to legacy commands. - info!("Model not supported by omni endpoint, falling back to legacy commands."); - } - Err(err) => { - // Real error — return it. - return map_error_to_response(err); + let result = handle_artcraft_via_omni_endpoint( + &request, + &app_env_configs, + &storyteller_creds_manager, + ).await; + + match result { + Ok(success) => { + // Insert into task database + let db_result = success + .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) = db_result { + error!("Failed to create task in database: {:?}", err); } + + map_success_to_response(success, &app) } - } - - // ── Fall back to legacy command proxies ── - - let model = request.model; - - let has_mask = request.inpainting_mask_image_media_token.is_some() - || request.inpainting_mask_image_raw_bytes.is_some(); - let has_image_refs = request.image_media_tokens.as_ref().is_some_and(|t| !t.is_empty()); - let has_canvas = request.canvas_image_media_token.is_some() - || request.canvas_image_raw_bytes.is_some(); - let has_scene = request.scene_image_media_token.is_some() - || request.scene_image_raw_bytes.is_some(); - let has_angles = request.adjust_horizontal_angle.is_some() - || request.adjust_vertical_angle.is_some() - || request.adjust_zoom.is_some(); - - let is_inpaint_model = matches!( - model, - Some(TauriImageModel::FluxDevJuggernaut) | Some(TauriImageModel::FluxPro1) - ); - - let is_edit_only_model = matches!( - model, - Some(TauriImageModel::FluxProKontextMax) - | Some(TauriImageModel::QwenEdit2511Angles) - | Some(TauriImageModel::Flux2LoraAngles) - ); - - if has_mask || is_inpaint_model { - proxy_to_image_inpaint_command( - 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 - } else if has_image_refs || has_canvas || has_scene || has_angles || is_edit_only_model { - proxy_to_edit_image_command( - 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 - } else { - proxy_to_text_to_image_command( - request, app, app_data_root, app_env_configs, artcraft_usage_tracker, - provider_priority_store, task_database, mj_creds_manager, - grok_creds_manager, grok_image_prompt_queue, storyteller_creds_manager, - sora_creds_manager, sora_task_queue, - ).await - } -} - -// ── Proxy to text-to-image ── - -async fn proxy_to_text_to_image_command( - request: TauriGenerateImageRequest, - 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 { - let legacy_model = request.model.and_then(map_to_text_to_image_model); - - let legacy_request = EnqueueTextToImageRequest { - provider: request.provider, - model: legacy_model, - prompt: request.prompt, - aspect_ratio: None, - common_aspect_ratio: None, - image_resolution: None, - common_resolution: None, - quality: None, // TODO: Convert enums::CommonQuality to app's CommonQuality - number_images: request.batch_size, - image_media_tokens: request.image_media_tokens, - frontend_caller: request.frontend_caller, - frontend_subscriber_id: request.frontend_subscriber_id, - frontend_subscriber_payload: request.frontend_subscriber_payload, - }; - - let result = enqueue_text_to_image_command( - legacy_request, app, app_data_root, app_env_configs, artcraft_usage_tracker, - provider_priority_store, task_database, mj_creds_manager, - grok_creds_manager, grok_image_prompt_queue, storyteller_creds_manager, - sora_creds_manager, sora_task_queue, - ).await; - - map_legacy_result(result) -} - -// ── Proxy to edit image ── - -async fn proxy_to_edit_image_command( - request: TauriGenerateImageRequest, - 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>, - storyteller_creds_manager: State<'_, StorytellerCredentialManager>, - sora_creds_manager: State<'_, SoraCredentialManager>, - sora_task_queue: State<'_, SoraTaskQueue>, -) -> Response { - let legacy_model = request.model.and_then(map_to_image_edit_model); - - // Build image_media_tokens: prepend canvas image if present. - let mut image_tokens = request.image_media_tokens.unwrap_or_default(); - if let Some(canvas_token) = request.canvas_image_media_token { - image_tokens.insert(0, canvas_token); - } - let image_media_tokens = if image_tokens.is_empty() { None } else { Some(image_tokens) }; - - let legacy_request = EnqueueEditImageCommand { - provider: request.provider, - model: legacy_model, - image_media_tokens, - scene_image_media_token: request.scene_image_media_token, - prompt: request.prompt.unwrap_or_default(), - disable_system_prompt: request.enable_system_prompt.map(|e| !e), - image_count: request.batch_size, - aspect_ratio: None, - common_aspect_ratio: None, - image_quality: None, - image_resolution: None, - quality: None, // TODO: Convert enums::CommonQuality to app's CommonQuality - horizontal_angle: request.adjust_horizontal_angle, - vertical_angle: request.adjust_vertical_angle, - zoom: request.adjust_zoom, - frontend_caller: request.frontend_caller, - frontend_subscriber_id: request.frontend_subscriber_id, - frontend_subscriber_payload: request.frontend_subscriber_payload, - }; - - let result = enqueue_edit_image_command( - app, legacy_request, app_data_root, app_env_configs, artcraft_usage_tracker, - provider_priority_store, task_database, storyteller_creds_manager, - sora_creds_manager, sora_task_queue, - ).await; - - map_legacy_result(result) -} - -// ── Proxy to inpaint ── - -async fn proxy_to_image_inpaint_command( - request: TauriGenerateImageRequest, - 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>, - storyteller_creds_manager: State<'_, StorytellerCredentialManager>, - sora_creds_manager: State<'_, SoraCredentialManager>, - sora_task_queue: State<'_, SoraTaskQueue>, -) -> Response { - let legacy_model = request.model.and_then(map_to_inpaint_model); - - // Source image: canvas_image_media_token or first image_media_token. - let source_image = request.canvas_image_media_token - .or_else(|| request.image_media_tokens.as_ref().and_then(|t| t.first().cloned())); - - let legacy_request = EnqueueInpaintImageCommand { - provider: request.provider, - model: legacy_model, - image_media_token: source_image, - mask_image_media_token: request.inpainting_mask_image_media_token, - mask_image_raw_bytes: request.inpainting_mask_image_raw_bytes, - prompt: request.prompt.unwrap_or_default(), - image_count: request.batch_size, - quality: None, // TODO: Convert enums::CommonQuality to app's CommonQuality - require_matching_dimensions: None, - frontend_caller: request.frontend_caller, - frontend_subscriber_id: request.frontend_subscriber_id, - frontend_subscriber_payload: request.frontend_subscriber_payload, - }; - - let result = enqueue_image_inpaint_command( - app, legacy_request, app_data_root, app_env_configs, artcraft_usage_tracker, - provider_priority_store, task_database, storyteller_creds_manager, - sora_creds_manager, sora_task_queue, - ).await; - - map_legacy_result(result) -} - -// ── Model mapping helpers ── - -fn map_to_text_to_image_model(model: TauriImageModel) -> Option { - match model { - TauriImageModel::Flux1Dev => Some(TextToImageModel::Flux1Dev), - TauriImageModel::Flux1Schnell => Some(TextToImageModel::Flux1Schnell), - TauriImageModel::FluxPro11 => Some(TextToImageModel::FluxPro11), - TauriImageModel::FluxPro11Ultra => Some(TextToImageModel::FluxPro11Ultra), - TauriImageModel::GrokImage => Some(TextToImageModel::GrokImage), - TauriImageModel::Recraft3 => Some(TextToImageModel::Recraft3), - TauriImageModel::GptImage1 => Some(TextToImageModel::GptImage1), - TauriImageModel::GptImage1p5 => Some(TextToImageModel::GptImage1p5), - TauriImageModel::GptImage2 => Some(TextToImageModel::GptImage2), - TauriImageModel::Gemini25Flash => Some(TextToImageModel::Gemini25Flash), - TauriImageModel::NanoBanana => Some(TextToImageModel::NanoBanana), - TauriImageModel::NanoBanana2 => Some(TextToImageModel::NanoBanana2), - TauriImageModel::NanoBananaPro => Some(TextToImageModel::NanoBananaPro), - TauriImageModel::Seedream4 => Some(TextToImageModel::Seedream4), - TauriImageModel::Seedream4p5 => Some(TextToImageModel::Seedream4p5), - TauriImageModel::Seedream5Lite => Some(TextToImageModel::Seedream5Lite), - TauriImageModel::Midjourney => Some(TextToImageModel::Midjourney), - _ => None, - } -} - -fn map_to_image_edit_model(model: TauriImageModel) -> Option { - match model { - TauriImageModel::FluxProKontextMax => Some(ImageEditModel::FluxProKontextMax), - TauriImageModel::Gemini25Flash => Some(ImageEditModel::Gemini25Flash), - TauriImageModel::NanoBanana => Some(ImageEditModel::NanoBanana), - TauriImageModel::NanoBanana2 => Some(ImageEditModel::NanoBanana2), - TauriImageModel::NanoBananaPro => Some(ImageEditModel::NanoBananaPro), - TauriImageModel::GptImage1 => Some(ImageEditModel::GptImage1), - TauriImageModel::GptImage1p5 => Some(ImageEditModel::GptImage1p5), - TauriImageModel::Seedream4 => Some(ImageEditModel::Seedream4), - TauriImageModel::Seedream4p5 => Some(ImageEditModel::Seedream4p5), - TauriImageModel::Seedream5Lite => Some(ImageEditModel::Seedream5Lite), - TauriImageModel::QwenEdit2511Angles => Some(ImageEditModel::QwenEdit2511Angles), - TauriImageModel::Flux2LoraAngles => Some(ImageEditModel::Flux2LoraAngles), - _ => None, - } -} - -fn map_to_inpaint_model(model: TauriImageModel) -> Option { - match model { - TauriImageModel::FluxDevJuggernaut => Some(ImageInpaintModel::FluxDevJuggernaut), - TauriImageModel::FluxPro1 => Some(ImageInpaintModel::FluxPro1), - TauriImageModel::FluxProKontextMax => Some(ImageInpaintModel::FluxProKontextMax), - TauriImageModel::Gemini25Flash => Some(ImageInpaintModel::Gemini25Flash), - _ => None, + Err(err) => map_error_to_response(err), } } @@ -356,28 +98,18 @@ fn map_error_to_response( ) -> Response { error!("generate_image_command error: {:?}", err); + let error_type = match &err { + GenerateError::BadInput(_) => TauriGenerateImageErrorType::BadInput, + GenerateError::MissingCredentials(_) => TauriGenerateImageErrorType::NeedsStorytellerCredentials, + GenerateError::NoProviderAvailable => TauriGenerateImageErrorType::NoProviderAvailable, + GenerateError::BillingIssue(_) => TauriGenerateImageErrorType::BillingIssue, + _ => TauriGenerateImageErrorType::ServerError, + }; + Err(CommandErrorResponseWrapper { status: CommandErrorStatus::ServerError, error_message: Some(format!("{:?}", err)), - error_type: Some(TauriGenerateImageErrorType::ServerError), + error_type: Some(error_type), error_details: None, }) } - -/// Map any legacy command result to our unified response type. -fn map_legacy_result( - result: Result>, -) -> Response { - match result { - Ok(_) => Ok(TauriGenerateImageResponse {}.into()), - Err(legacy_err) => { - warn!("Legacy command proxy failed: {:?}", legacy_err.error_message); - Err(CommandErrorResponseWrapper { - status: legacy_err.status, - error_message: legacy_err.error_message, - error_type: Some(TauriGenerateImageErrorType::ServerError), - error_details: None, - }) - } - } -} From 13896d9306167a1c8f14b558fc4e26532ee79944 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 1 May 2026 01:22:58 -0400 Subject: [PATCH 12/14] cleanup --- .../generate_image/generate_image_command.rs | 50 +++++----- .../providers/artcraft/handle_artcraft.rs | 40 ++++++++ .../artcraft/handle_artcraft_via_legacy.rs | 95 +++++++++++++++++++ .../handle_artcraft_via_omni_endpoint.rs | 68 ++----------- .../generate_image/providers/artcraft/mod.rs | 7 +- .../providers/artcraft/model_mapping.rs | 79 +++++++++++++++ 6 files changed, 249 insertions(+), 90 deletions(-) create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs create mode 100644 crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs index 4a6a962654..b514035c48 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs @@ -1,6 +1,6 @@ use crate::core::commands::enqueue::generate_error::GenerateError; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; -use crate::core::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_omni_endpoint::handle_artcraft_via_omni_endpoint; +use crate::core::commands::generate::generate_image::providers::artcraft::handle_artcraft; use crate::core::commands::generate::generate_image::tauri_generate_image_request::{ TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse, }; @@ -44,55 +44,49 @@ pub async fn generate_image_command( }); } - let result = handle_artcraft_via_omni_endpoint( + let result = handle_artcraft( &request, &app_env_configs, &storyteller_creds_manager, ).await; - match result { - Ok(success) => { - // Insert into task database - let db_result = success - .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; + let success = match result { + Ok(success) => success, + Err(err) => return map_error_to_response(err), + }; - if let Err(err) = db_result { - error!("Failed to create task in database: {:?}", err); - } + // Insert into task database + let db_result = success + .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; - map_success_to_response(success, &app) - } - Err(err) => map_error_to_response(err), + if let Err(err) = db_result { + error!("Failed to create task in database: {:?}", err); } -} -// ── Result mapping ── - -fn map_success_to_response( - success: TaskEnqueueSuccess, - app: &AppHandle, -) -> Response { let event = GenerationEnqueueSuccessEvent { action: success.to_frontend_event_action(), service: success.to_frontend_event_service(), model: success.model, }; - if let Err(err) = event.send(app) { + if let Err(err) = event.send(&app) { error!("Failed to emit event: {:?}", err); } - CreditsBalanceChangedEvent{}.send_infallible(app); + CreditsBalanceChangedEvent{}.send_infallible(&app); Ok(TauriGenerateImageResponse {}.into()) } +// ── Result mapping ── + + fn map_error_to_response( err: GenerateError, ) -> Response { diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs new file mode 100644 index 0000000000..0e10e5765d --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft.rs @@ -0,0 +1,40 @@ +use log::info; + +use crate::core::commands::enqueue::generate_error::GenerateError; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_legacy::handle_artcraft_via_legacy; +use crate::core::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_omni_endpoint::handle_artcraft_via_omni_endpoint; +use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; + +/// Dispatch an image generation request to Artcraft. +/// +/// Tries the omni endpoint first. If the model isn't supported there, +/// falls back to the legacy artcraft_router path. +pub async fn handle_artcraft( + request: &TauriGenerateImageRequest, + app_env_configs: &AppEnvConfigs, + storyteller_creds_manager: &StorytellerCredentialManager, +) -> Result { + // Try the omni endpoint first. + let omni_result = handle_artcraft_via_omni_endpoint( + request, + app_env_configs, + storyteller_creds_manager, + ).await; + + match omni_result { + Ok(success) => Ok(success), + Err(GenerateError::NotYetImplemented(_)) => { + // Model not supported by omni endpoint — fall back to legacy. + info!("Model not supported by omni endpoint, falling back to legacy artcraft_router path."); + handle_artcraft_via_legacy( + request, + app_env_configs, + storyteller_creds_manager, + ).await + } + Err(err) => Err(err), + } +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs new file mode 100644 index 0000000000..d45c8f2bba --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_legacy.rs @@ -0,0 +1,95 @@ +use artcraft_router::api::common_image_model::CommonImageModel; +use artcraft_router::api::image_list_ref::ImageListRef; +use artcraft_router::api::provider::Provider; +use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use artcraft_router::client::router_artcraft_client::RouterArtcraftClient; +use artcraft_router::client::router_client::RouterClient; +use artcraft_router::generate::generate_image::generate_image_request::GenerateImageRequest; +use enums::common::generation_provider::GenerationProvider; +use enums::tauri::tasks::task_type::TaskType; +use log::{error, info}; + +use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; +use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::generate::generate_image::providers::artcraft::model_mapping::{ + map_to_generation_model, map_to_router_image_model, +}; +use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; +use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; +use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; + +/// Handle image generation via the legacy artcraft_router path. +/// +/// This is used for models not yet supported by the omni endpoint +/// (eg. Recraft, FluxProKontextMax, edit/inpaint models). +pub async fn handle_artcraft_via_legacy( + request: &TauriGenerateImageRequest, + app_env_configs: &AppEnvConfigs, + storyteller_creds_manager: &StorytellerCredentialManager, +) -> Result { + let model = request.model.ok_or(GenerateError::no_model_specified())?; + + let router_model = map_to_router_image_model(model) + .ok_or(GenerateError::NotYetImplemented( + format!("Model {:?} is not supported via the legacy router path", model), + ))?; + + let generation_model = map_to_generation_model(model); + + let creds = match storyteller_creds_manager.get_credentials()? { + Some(creds) => creds, + None => return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials)), + }; + + let client = RouterClient::Artcraft(RouterArtcraftClient::new( + app_env_configs.storyteller_host.clone(), + creds, + )); + + // Build image inputs: prepend canvas image if present. + let mut image_tokens = request.image_media_tokens.clone().unwrap_or_default(); + if let Some(canvas_token) = &request.canvas_image_media_token { + image_tokens.insert(0, canvas_token.clone()); + } + let image_inputs = if image_tokens.is_empty() { None } else { Some(ImageListRef::MediaFileTokens(image_tokens)) }; + + let router_request = GenerateImageRequest { + model: router_model, + provider: Provider::Artcraft, + prompt: request.prompt.clone(), + image_inputs, + resolution: None, // TODO: Convert from request.resolution + aspect_ratio: None, // TODO: Convert from request.aspect_ratio + quality: None, // TODO: Convert from request.quality + image_batch_count: request.batch_size.map(|n| n as u16), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, + generation_mode_mismatch_strategy: None, + idempotency_token: None, + horizontal_angle: request.adjust_horizontal_angle, + vertical_angle: request.adjust_vertical_angle, + zoom: request.adjust_zoom, + }; + + let plan = router_request.build()?; + + info!("Legacy image generation plan: {:?}", plan); + + let response = plan.generate_image(&client).await.map_err(|err| { + error!("Legacy image generation failed: {:?}", err); + GenerateError::from(err) + })?; + + let job_id = response + .get_artcraft_payload() + .map(|p| p.inference_job_token.to_string()) + .ok_or(GenerateError::ResponseHadNoJobTokens)?; + + info!("Legacy image generation succeeded: job_id={}", job_id); + + Ok(TaskEnqueueSuccess { + task_type: TaskType::ImageGeneration, + model: Some(generation_model), + provider: GenerationProvider::Artcraft, + provider_job_id: Some(job_id), + }) +} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs index e32264a07c..c02fb1a586 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs @@ -1,15 +1,15 @@ use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_image_cost_and_generate_request::OmniGenImageCostAndGenerateRequest; use artcraft_client::endpoints::omni_gen::generate::image::omni_gen_image::omni_gen_image_generate; -use enums::common::generation::common_image_model::CommonImageModel; use enums::common::generation_provider::GenerationProvider; use enums::tauri::tasks::task_type::TaskType; use log::{error, info}; use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; +use crate::core::commands::generate::generate_image::providers::artcraft::model_mapping::{ + map_to_generation_model, map_to_omni_image_model, +}; use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; -use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; -use crate::core::events::generation_events::common::GenerationModel; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager; @@ -20,7 +20,7 @@ pub async fn handle_artcraft_via_omni_endpoint( ) -> Result { let model = request.model.ok_or(GenerateError::no_model_specified())?; - let omni_model = map_to_common_image_model(model) + let omni_model = map_to_omni_image_model(model) .ok_or(GenerateError::NotYetImplemented( format!("Model {:?} is not supported via the omni endpoint", model), ))?; @@ -32,14 +32,11 @@ pub async fn handle_artcraft_via_omni_endpoint( None => return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials)), }; - // Build the omni gen request. + // Build image_media_tokens: prepend canvas image if present. let mut image_media_tokens = request.image_media_tokens.clone().unwrap_or_default(); - - // Prepend canvas image if present. if let Some(canvas_token) = &request.canvas_image_media_token { image_media_tokens.insert(0, canvas_token.clone()); } - let image_media_tokens = if image_media_tokens.is_empty() { None } else { Some(image_media_tokens) }; let omni_request = OmniGenImageCostAndGenerateRequest { @@ -47,8 +44,8 @@ pub async fn handle_artcraft_via_omni_endpoint( model: Some(omni_model), prompt: request.prompt.clone(), image_media_tokens, - resolution: None, // TODO: Convert from request.resolution - aspect_ratio: None, // TODO: Convert from request.aspect_ratio + resolution: None, + aspect_ratio: None, quality: request.quality, image_batch_count: request.batch_size.map(|n| n as u16), adjust_horizontal_angle: request.adjust_horizontal_angle, @@ -76,54 +73,3 @@ pub async fn handle_artcraft_via_omni_endpoint( provider_job_id: Some(response.inference_job_token.to_string()), }) } - -// ── Model mapping helpers ── - -fn map_to_common_image_model(model: TauriImageModel) -> Option { - match model { - TauriImageModel::Flux1Dev => Some(CommonImageModel::Flux1Dev), - TauriImageModel::Flux1Schnell => Some(CommonImageModel::Flux1Schnell), - TauriImageModel::FluxPro11 => Some(CommonImageModel::FluxPro11), - TauriImageModel::FluxPro11Ultra => Some(CommonImageModel::FluxPro11Ultra), - TauriImageModel::Gemini25Flash => Some(CommonImageModel::NanoBanana), // NB: Alias for Nano Banana - TauriImageModel::GptImage1 => Some(CommonImageModel::GptImage1), - TauriImageModel::GptImage1p5 => Some(CommonImageModel::GptImage1p5), - TauriImageModel::GptImage2 => Some(CommonImageModel::GptImage2), - TauriImageModel::NanoBanana => Some(CommonImageModel::NanoBanana), - TauriImageModel::NanoBanana2 => Some(CommonImageModel::NanoBanana2), - TauriImageModel::NanoBananaPro => Some(CommonImageModel::NanoBananaPro), - TauriImageModel::Seedream4 => Some(CommonImageModel::Seedream4), - TauriImageModel::Seedream4p5 => Some(CommonImageModel::Seedream4p5), - TauriImageModel::Seedream5Lite => Some(CommonImageModel::Seedream5Lite), - // Models not in CommonImageModel (Grok, Recraft, Midjourney, edit-only, inpaint-only) - _ => None, - } -} - -fn map_to_generation_model(model: TauriImageModel) -> GenerationModel { - match model { - TauriImageModel::Flux1Dev => GenerationModel::Flux1Dev, - TauriImageModel::Flux1Schnell => GenerationModel::Flux1Schnell, - TauriImageModel::Flux2LoraAngles => GenerationModel::Flux2LoraAngles, - TauriImageModel::FluxDevJuggernaut => GenerationModel::FluxDevJuggernaut, - TauriImageModel::FluxPro1 => GenerationModel::FluxPro1, - TauriImageModel::FluxPro11 => GenerationModel::FluxPro11, - TauriImageModel::FluxPro11Ultra => GenerationModel::FluxPro11Ultra, - TauriImageModel::FluxProKontextMax => GenerationModel::FluxProKontextMax, - TauriImageModel::Gemini25Flash => GenerationModel::NanoBanana, // NB: Alias for Nano Banana - TauriImageModel::GptImage1 => GenerationModel::GptImage1, - TauriImageModel::GptImage1p5 => GenerationModel::GptImage1p5, - TauriImageModel::GptImage2 => GenerationModel::GptImage2, - TauriImageModel::GrokImage => GenerationModel::GrokImage, - TauriImageModel::NanoBanana => GenerationModel::NanoBanana, - TauriImageModel::NanoBanana2 => GenerationModel::NanoBanana2, - TauriImageModel::NanoBananaPro => GenerationModel::NanoBananaPro, - TauriImageModel::QwenEdit2511Angles => GenerationModel::QwenEdit2511Angles, - TauriImageModel::Seedream4 => GenerationModel::Seedream4, - TauriImageModel::Seedream4p5 => GenerationModel::Seedream4p5, - TauriImageModel::Seedream5Lite => GenerationModel::Seedream5Lite, - // TODO: - TauriImageModel::Midjourney => GenerationModel::Flux1Dev, // Fallback - TauriImageModel::Recraft3 => GenerationModel::Flux1Dev, // Fallback - } -} diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/mod.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/mod.rs index af712f0cb7..657bbe3d3f 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/mod.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/mod.rs @@ -1 +1,6 @@ -pub mod handle_artcraft_via_omni_endpoint; +mod handle_artcraft; +mod handle_artcraft_via_legacy; +mod handle_artcraft_via_omni_endpoint; +mod model_mapping; + +pub use handle_artcraft::handle_artcraft; diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs new file mode 100644 index 0000000000..fa6fee7e07 --- /dev/null +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs @@ -0,0 +1,79 @@ +use artcraft_router::api::common_image_model::CommonImageModel as RouterCommonImageModel; +use enums::common::generation::common_image_model::CommonImageModel as OmniCommonImageModel; + +use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; +use crate::core::events::generation_events::common::GenerationModel; + +/// Map TauriImageModel to the omni endpoint's CommonImageModel. +/// Returns None for models not supported by the omni endpoint. +pub fn map_to_omni_image_model(model: TauriImageModel) -> Option { + match model { + TauriImageModel::Flux1Dev => Some(OmniCommonImageModel::Flux1Dev), + TauriImageModel::Flux1Schnell => Some(OmniCommonImageModel::Flux1Schnell), + TauriImageModel::FluxPro11 => Some(OmniCommonImageModel::FluxPro11), + TauriImageModel::FluxPro11Ultra => Some(OmniCommonImageModel::FluxPro11Ultra), + TauriImageModel::GptImage1 => Some(OmniCommonImageModel::GptImage1), + TauriImageModel::GptImage1p5 => Some(OmniCommonImageModel::GptImage1p5), + TauriImageModel::GptImage2 => Some(OmniCommonImageModel::GptImage2), + TauriImageModel::NanoBanana => Some(OmniCommonImageModel::NanoBanana), + TauriImageModel::NanoBanana2 => Some(OmniCommonImageModel::NanoBanana2), + TauriImageModel::NanoBananaPro => Some(OmniCommonImageModel::NanoBananaPro), + TauriImageModel::Gemini25Flash => Some(OmniCommonImageModel::NanoBanana), + TauriImageModel::Seedream4 => Some(OmniCommonImageModel::Seedream4), + TauriImageModel::Seedream4p5 => Some(OmniCommonImageModel::Seedream4p5), + TauriImageModel::Seedream5Lite => Some(OmniCommonImageModel::Seedream5Lite), + _ => None, + } +} + +/// Map TauriImageModel to the artcraft_router's CommonImageModel. +/// Returns None for models not supported by the router (Grok, Midjourney). +pub fn map_to_router_image_model(model: TauriImageModel) -> Option { + match model { + TauriImageModel::Flux1Dev => Some(RouterCommonImageModel::Flux1Dev), + TauriImageModel::Flux1Schnell => Some(RouterCommonImageModel::Flux1Schnell), + TauriImageModel::FluxPro11 => Some(RouterCommonImageModel::FluxPro11), + TauriImageModel::FluxPro11Ultra => Some(RouterCommonImageModel::FluxPro11Ultra), + TauriImageModel::GptImage1 => Some(RouterCommonImageModel::GptImage1), + TauriImageModel::GptImage1p5 => Some(RouterCommonImageModel::GptImage1p5), + TauriImageModel::GptImage2 => Some(RouterCommonImageModel::GptImage2), + TauriImageModel::NanoBanana => Some(RouterCommonImageModel::NanoBanana), + TauriImageModel::NanoBanana2 => Some(RouterCommonImageModel::NanoBanana2), + TauriImageModel::NanoBananaPro => Some(RouterCommonImageModel::NanoBananaPro), + TauriImageModel::Gemini25Flash => Some(RouterCommonImageModel::NanoBanana), + TauriImageModel::Seedream4 => Some(RouterCommonImageModel::Seedream4), + TauriImageModel::Seedream4p5 => Some(RouterCommonImageModel::Seedream4p5), + TauriImageModel::Seedream5Lite => Some(RouterCommonImageModel::Seedream5Lite), + TauriImageModel::QwenEdit2511Angles => Some(RouterCommonImageModel::QwenEdit2511Angles), + TauriImageModel::Flux2LoraAngles => Some(RouterCommonImageModel::Flux2LoraAngles), + _ => None, + } +} + +/// Map TauriImageModel to GenerationModel for frontend events. +pub fn map_to_generation_model(model: TauriImageModel) -> GenerationModel { + match model { + TauriImageModel::Flux1Dev => GenerationModel::Flux1Dev, + TauriImageModel::Flux1Schnell => GenerationModel::Flux1Schnell, + TauriImageModel::FluxPro11 => GenerationModel::FluxPro11, + TauriImageModel::FluxPro11Ultra => GenerationModel::FluxPro11Ultra, + TauriImageModel::GrokImage => GenerationModel::GrokImage, + TauriImageModel::Recraft3 => GenerationModel::Flux1Dev, // Fallback + TauriImageModel::GptImage1 => GenerationModel::GptImage1, + TauriImageModel::GptImage1p5 => GenerationModel::GptImage1p5, + TauriImageModel::GptImage2 => GenerationModel::GptImage2, + TauriImageModel::Gemini25Flash => GenerationModel::NanoBanana, + TauriImageModel::NanoBanana => GenerationModel::NanoBanana, + TauriImageModel::NanoBanana2 => GenerationModel::NanoBanana2, + TauriImageModel::NanoBananaPro => GenerationModel::NanoBananaPro, + TauriImageModel::Seedream4 => GenerationModel::Seedream4, + TauriImageModel::Seedream4p5 => GenerationModel::Seedream4p5, + TauriImageModel::Seedream5Lite => GenerationModel::Seedream5Lite, + TauriImageModel::Midjourney => GenerationModel::Flux1Dev, // Fallback + TauriImageModel::FluxProKontextMax => GenerationModel::FluxProKontextMax, + TauriImageModel::QwenEdit2511Angles => GenerationModel::QwenEdit2511Angles, + TauriImageModel::Flux2LoraAngles => GenerationModel::Flux2LoraAngles, + TauriImageModel::FluxDevJuggernaut => GenerationModel::FluxDevJuggernaut, + TauriImageModel::FluxPro1 => GenerationModel::FluxPro1, + } +} From d937a9db5fb2b5a20602a1cbf783c9738e6f2889 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Fri, 1 May 2026 01:44:41 -0400 Subject: [PATCH 13/14] wip --- .../generate_image/generate_image_command.rs | 74 ++++++++++++++-- .../handle_artcraft_via_omni_endpoint.rs | 20 +++-- .../providers/artcraft/model_mapping.rs | 88 +++++++++++++++---- .../tauri_generate_image_request.rs | 4 +- 4 files changed, 151 insertions(+), 35 deletions(-) diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs index b514035c48..96976339e4 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs @@ -15,6 +15,7 @@ use crate::services::storyteller::state::storyteller_credential_manager::Storyte use enums::common::generation_provider::GenerationProvider; use log::{error, info}; use tauri::{AppHandle, State}; +use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors; #[tauri::command] pub async fn generate_image_command( @@ -50,10 +51,21 @@ pub async fn generate_image_command( &storyteller_creds_manager, ).await; - let success = match result { - Ok(success) => success, - Err(err) => return map_error_to_response(err), - }; + match result { + Ok(success) => handle_success_behavior(&app, &task_database, &request, success).await, + Err(err) => handle_error_behavior(&app, err).await, + } +} + +// ── Result mapping ── + +async fn handle_success_behavior( + app: &AppHandle, + task_database: &TaskDatabase, + request: &TauriGenerateImageRequest, + success: TaskEnqueueSuccess, +) -> Response { + // Insert into task database let db_result = success @@ -69,6 +81,19 @@ pub async fn generate_image_command( error!("Failed to create task in database: {:?}", err); } + /* + 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); + } + */ + let event = GenerationEnqueueSuccessEvent { action: success.to_frontend_event_action(), service: success.to_frontend_event_service(), @@ -84,14 +109,14 @@ pub async fn generate_image_command( Ok(TauriGenerateImageResponse {}.into()) } -// ── Result mapping ── - - -fn map_error_to_response( +async fn handle_error_behavior( + app: &AppHandle, err: GenerateError, ) -> Response { error!("generate_image_command error: {:?}", err); + notify_frontend_of_errors(&app, &err).await; + let error_type = match &err { GenerateError::BadInput(_) => TauriGenerateImageErrorType::BadInput, GenerateError::MissingCredentials(_) => TauriGenerateImageErrorType::NeedsStorytellerCredentials, @@ -100,6 +125,39 @@ fn map_error_to_response( _ => TauriGenerateImageErrorType::ServerError, }; + // TODO: + /* + 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, + }) + */ + Err(CommandErrorResponseWrapper { status: CommandErrorStatus::ServerError, error_message: Some(format!("{:?}", err)), diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs index c02fb1a586..1071bbf99d 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/handle_artcraft_via_omni_endpoint.rs @@ -3,11 +3,11 @@ use artcraft_client::endpoints::omni_gen::generate::image::omni_gen_image::omni_ use enums::common::generation_provider::GenerationProvider; use enums::tauri::tasks::task_type::TaskType; use log::{error, info}; - +use uuid_utils::uuid::generate_random_uuid; use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason}; use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess; use crate::core::commands::generate::generate_image::providers::artcraft::model_mapping::{ - map_to_generation_model, map_to_omni_image_model, + map_to_generation_model, map_to_omni_aspect_ratio, map_to_omni_image_model, map_to_omni_resolution, }; use crate::core::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest; use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs; @@ -18,20 +18,22 @@ pub async fn handle_artcraft_via_omni_endpoint( app_env_configs: &AppEnvConfigs, storyteller_creds_manager: &StorytellerCredentialManager, ) -> Result { - let model = request.model.ok_or(GenerateError::no_model_specified())?; + let tauri_model = request.model.ok_or(GenerateError::no_model_specified())?; - let omni_model = map_to_omni_image_model(model) + let omni_model = map_to_omni_image_model(tauri_model) .ok_or(GenerateError::NotYetImplemented( - format!("Model {:?} is not supported via the omni endpoint", model), + format!("Model {:?} is not supported via the omni endpoint", tauri_model), ))?; - let generation_model = map_to_generation_model(model); + let generation_model = map_to_generation_model(tauri_model); let creds = match storyteller_creds_manager.get_credentials()? { Some(creds) => creds, None => return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials)), }; + let uuid_idempotency_token = generate_random_uuid(); + // Build image_media_tokens: prepend canvas image if present. let mut image_media_tokens = request.image_media_tokens.clone().unwrap_or_default(); if let Some(canvas_token) = &request.canvas_image_media_token { @@ -40,12 +42,12 @@ pub async fn handle_artcraft_via_omni_endpoint( let image_media_tokens = if image_media_tokens.is_empty() { None } else { Some(image_media_tokens) }; let omni_request = OmniGenImageCostAndGenerateRequest { - idempotency_token: None, + idempotency_token: Some(uuid_idempotency_token), model: Some(omni_model), prompt: request.prompt.clone(), image_media_tokens, - resolution: None, - aspect_ratio: None, + resolution: request.resolution, + aspect_ratio: request.aspect_ratio, quality: request.quality, image_batch_count: request.batch_size.map(|n| n as u16), adjust_horizontal_angle: request.adjust_horizontal_angle, diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs index fa6fee7e07..1e26f8e5da 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/providers/artcraft/model_mapping.rs @@ -1,5 +1,9 @@ +use artcraft_router::api::common_aspect_ratio::CommonAspectRatio as RouterCommonAspectRatio; use artcraft_router::api::common_image_model::CommonImageModel as RouterCommonImageModel; +use artcraft_router::api::common_resolution::CommonResolution as RouterCommonResolution; +use enums::common::generation::common_aspect_ratio::CommonAspectRatio as OmniCommonAspectRatio; use enums::common::generation::common_image_model::CommonImageModel as OmniCommonImageModel; +use enums::common::generation::common_resolution::CommonResolution as OmniCommonResolution; use crate::core::commands::generate::generate_image::tauri_image_model::TauriImageModel; use crate::core::events::generation_events::common::GenerationModel; @@ -10,6 +14,7 @@ pub fn map_to_omni_image_model(model: TauriImageModel) -> Option Some(OmniCommonImageModel::Flux1Dev), TauriImageModel::Flux1Schnell => Some(OmniCommonImageModel::Flux1Schnell), + TauriImageModel::FluxPro1 => Some(OmniCommonImageModel::FluxPro11), // TODO: Might be a slight mismatch TauriImageModel::FluxPro11 => Some(OmniCommonImageModel::FluxPro11), TauriImageModel::FluxPro11Ultra => Some(OmniCommonImageModel::FluxPro11Ultra), TauriImageModel::GptImage1 => Some(OmniCommonImageModel::GptImage1), @@ -22,7 +27,14 @@ pub fn map_to_omni_image_model(model: TauriImageModel) -> Option Some(OmniCommonImageModel::Seedream4), TauriImageModel::Seedream4p5 => Some(OmniCommonImageModel::Seedream4p5), TauriImageModel::Seedream5Lite => Some(OmniCommonImageModel::Seedream5Lite), - _ => None, + // Not accounted for yet + TauriImageModel::GrokImage => None, + TauriImageModel::Recraft3 => None, + TauriImageModel::Midjourney => None, + TauriImageModel::FluxProKontextMax => None, + TauriImageModel::QwenEdit2511Angles => None, + TauriImageModel::Flux2LoraAngles => None, + TauriImageModel::FluxDevJuggernaut => None, } } @@ -30,23 +42,67 @@ pub fn map_to_omni_image_model(model: TauriImageModel) -> Option Option { match model { - TauriImageModel::Flux1Dev => Some(RouterCommonImageModel::Flux1Dev), - TauriImageModel::Flux1Schnell => Some(RouterCommonImageModel::Flux1Schnell), - TauriImageModel::FluxPro11 => Some(RouterCommonImageModel::FluxPro11), - TauriImageModel::FluxPro11Ultra => Some(RouterCommonImageModel::FluxPro11Ultra), - TauriImageModel::GptImage1 => Some(RouterCommonImageModel::GptImage1), - TauriImageModel::GptImage1p5 => Some(RouterCommonImageModel::GptImage1p5), - TauriImageModel::GptImage2 => Some(RouterCommonImageModel::GptImage2), - TauriImageModel::NanoBanana => Some(RouterCommonImageModel::NanoBanana), - TauriImageModel::NanoBanana2 => Some(RouterCommonImageModel::NanoBanana2), - TauriImageModel::NanoBananaPro => Some(RouterCommonImageModel::NanoBananaPro), - TauriImageModel::Gemini25Flash => Some(RouterCommonImageModel::NanoBanana), - TauriImageModel::Seedream4 => Some(RouterCommonImageModel::Seedream4), - TauriImageModel::Seedream4p5 => Some(RouterCommonImageModel::Seedream4p5), - TauriImageModel::Seedream5Lite => Some(RouterCommonImageModel::Seedream5Lite), + TauriImageModel::Flux1Dev => Some(RouterCommonImageModel::Flux1Dev), // Text-to-Image + TauriImageModel::Flux1Schnell => Some(RouterCommonImageModel::Flux1Schnell), // Text-to-Image + TauriImageModel::FluxPro1 => Some(RouterCommonImageModel::FluxPro11), // TODO: Might be a slight mismatch + TauriImageModel::FluxPro11 => Some(RouterCommonImageModel::FluxPro11), // Text-to-Image + TauriImageModel::FluxPro11Ultra => Some(RouterCommonImageModel::FluxPro11Ultra), // Text-to-Image + TauriImageModel::GptImage1 => Some(RouterCommonImageModel::GptImage1), // Text-to-Image + TauriImageModel::GptImage1p5 => Some(RouterCommonImageModel::GptImage1p5), // Text-to-Image + TauriImageModel::GptImage2 => Some(RouterCommonImageModel::GptImage2), // Text-to-Image + TauriImageModel::NanoBanana => Some(RouterCommonImageModel::NanoBanana), // Text-to-Image + TauriImageModel::NanoBanana2 => Some(RouterCommonImageModel::NanoBanana2), // Text-to-Image + TauriImageModel::NanoBananaPro => Some(RouterCommonImageModel::NanoBananaPro), // Text-to-Image + TauriImageModel::Gemini25Flash => Some(RouterCommonImageModel::NanoBanana), // Text-to-Image + TauriImageModel::Seedream4 => Some(RouterCommonImageModel::Seedream4), // Text-to-Image + TauriImageModel::Seedream4p5 => Some(RouterCommonImageModel::Seedream4p5), // Text-to-Image + TauriImageModel::Seedream5Lite => Some(RouterCommonImageModel::Seedream5Lite), // Text-to-Image TauriImageModel::QwenEdit2511Angles => Some(RouterCommonImageModel::QwenEdit2511Angles), TauriImageModel::Flux2LoraAngles => Some(RouterCommonImageModel::Flux2LoraAngles), - _ => None, + // Not accounted for yet + TauriImageModel::GrokImage => None, + TauriImageModel::Recraft3 => None, + TauriImageModel::Midjourney => None, + TauriImageModel::FluxProKontextMax => None, + TauriImageModel::FluxDevJuggernaut => None, + } +} + +/// Map from the router's CommonAspectRatio to the omni endpoint's CommonAspectRatio. +pub fn map_to_omni_aspect_ratio(ratio: RouterCommonAspectRatio) -> OmniCommonAspectRatio { + match ratio { + RouterCommonAspectRatio::Auto => OmniCommonAspectRatio::Auto, + RouterCommonAspectRatio::Square => OmniCommonAspectRatio::Square, + RouterCommonAspectRatio::WideThreeByTwo => OmniCommonAspectRatio::WideThreeByTwo, + RouterCommonAspectRatio::WideFourByThree => OmniCommonAspectRatio::WideFourByThree, + RouterCommonAspectRatio::WideFiveByFour => OmniCommonAspectRatio::WideFiveByFour, + RouterCommonAspectRatio::WideSixteenByNine => OmniCommonAspectRatio::WideSixteenByNine, + RouterCommonAspectRatio::WideTwentyOneByNine => OmniCommonAspectRatio::WideTwentyOneByNine, + RouterCommonAspectRatio::TallTwoByThree => OmniCommonAspectRatio::TallTwoByThree, + RouterCommonAspectRatio::TallThreeByFour => OmniCommonAspectRatio::TallThreeByFour, + RouterCommonAspectRatio::TallFourByFive => OmniCommonAspectRatio::TallFourByFive, + RouterCommonAspectRatio::TallNineBySixteen => OmniCommonAspectRatio::TallNineBySixteen, + RouterCommonAspectRatio::TallNineByTwentyOne => OmniCommonAspectRatio::TallNineByTwentyOne, + RouterCommonAspectRatio::Wide => OmniCommonAspectRatio::Wide, + RouterCommonAspectRatio::Tall => OmniCommonAspectRatio::Tall, + RouterCommonAspectRatio::Auto2k => OmniCommonAspectRatio::Auto2k, + RouterCommonAspectRatio::Auto3k => OmniCommonAspectRatio::Auto3k, + RouterCommonAspectRatio::Auto4k => OmniCommonAspectRatio::Auto4k, + RouterCommonAspectRatio::SquareHd => OmniCommonAspectRatio::SquareHd, + } +} + +/// Map from the router's CommonResolution to the omni endpoint's CommonResolution. +pub fn map_to_omni_resolution(res: RouterCommonResolution) -> OmniCommonResolution { + match res { + RouterCommonResolution::OneK => OmniCommonResolution::OneK, + RouterCommonResolution::TwoK => OmniCommonResolution::TwoK, + RouterCommonResolution::ThreeK => OmniCommonResolution::ThreeK, + RouterCommonResolution::FourK => OmniCommonResolution::FourK, + RouterCommonResolution::HalfK => OmniCommonResolution::HalfK, + RouterCommonResolution::FourEightyP => OmniCommonResolution::FourEightyP, + RouterCommonResolution::SevenTwentyP => OmniCommonResolution::SevenTwentyP, + RouterCommonResolution::TenEightyP => OmniCommonResolution::TenEightyP, } } diff --git a/crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_generate_image_request.rs b/crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_generate_image_request.rs index 759eb4274e..1a4efafaea 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_generate_image_request.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/generate_image/tauri_generate_image_request.rs @@ -1,5 +1,5 @@ -use artcraft_router::api::common_aspect_ratio::CommonAspectRatio; -use artcraft_router::api::common_resolution::CommonResolution; +use enums::common::generation::common_aspect_ratio::CommonAspectRatio; +use enums::common::generation::common_resolution::CommonResolution; use enums::common::generation::common_quality::CommonQuality; use enums::common::generation_provider::GenerationProvider; use enums::tauri::ux::tauri_command_caller::TauriCommandCaller; From ccd7b3876022a49889075e86da01e5cb06e2cb30 Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Fri, 1 May 2026 10:00:11 -0700 Subject: [PATCH 14/14] artcraft: migrate PromptBox3D off EnqueueEditImage onto unified GenerateImage Last remaining legacy `Enqueue*` consumer in the artcraft Tauri app's generation surface. Maps `image_count` -> `batch_size`, `disable_system_prompt` -> `enable_system_prompt`, legacy aspect_ratio/image_resolution enums onto CommonAspectRatio/CommonResolution. Folds the conditional common_aspect_ratio branch into a single aspect_ratio field that prefers commonAspectRatio when the model supports it. --- .../promptbox/src/lib/PromptBox3D.tsx | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx b/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx index a8ec89f767..f7db0e2844 100644 --- a/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx +++ b/frontend/libs/components/promptbox/src/lib/PromptBox3D.tsx @@ -39,14 +39,12 @@ import { Modal } from "@storyteller/ui-modal"; import { Signal } from "@preact/signals-react"; import { CommandSuccessStatus, - EnqueueEditImage, - EnqueueEditImageSize, - EnqueueEditImageResolution, - EnqueueEditImageRequest, + GenerateImage, + GenerateImageRequest, } from "@storyteller/tauri-api"; import { usePrompt3DStore, useEnterToGenerateStore } from "./promptStore"; import { gtagEvent } from "@storyteller/google-analytics"; -import { CommonAspectRatio, ImageModel } from "@storyteller/model-list"; +import { CommonAspectRatio, CommonResolution, ImageModel } from "@storyteller/model-list"; import { GenerationProvider } from "@storyteller/api-enums"; import { ImagePromptRow } from "./ImagePromptRow"; import { AspectRatioPicker } from "./common/AspectRatioPicker"; @@ -393,28 +391,26 @@ export const PromptBox3D = ({ console.log("snapshotResult", snapshotResult); - const aspectRatio = getCurrentAspectRatio(); - const resolution = getCurrentResolution(); + const aspectRatio = + selectedImageModel?.supportsNewAspectRatio() && commonAspectRatio + ? commonAspectRatio + : getCurrentAspectRatio(); - let request: EnqueueEditImageRequest = { + const request: GenerateImageRequest = { model: selectedImageModel, scene_image_media_token: snapshotResult.data!, image_media_tokens: referenceImages.map((image) => image.mediaToken), - disable_system_prompt: !useSystemPrompt, + enable_system_prompt: useSystemPrompt, prompt: prompt, - image_count: 1, + batch_size: 1, aspect_ratio: aspectRatio, - image_resolution: resolution, + resolution: getCurrentResolution(), }; if (!!selectedProvider) { request.provider = selectedProvider; } - if (selectedImageModel?.supportsNewAspectRatio()) { - request.common_aspect_ratio = commonAspectRatio; - } - window.__storeTaskEnqueueMeta?.({ prompt, refImageUrls: referenceImages @@ -426,7 +422,7 @@ export const PromptBox3D = ({ timestamp: Date.now(), }); - const generateResponse = await EnqueueEditImage(request); + const generateResponse = await GenerateImage(request); console.log("generateResponse", generateResponse); @@ -484,28 +480,28 @@ export const PromptBox3D = ({ } }; - const getCurrentAspectRatio = (): EnqueueEditImageSize => { + const getCurrentAspectRatio = (): CommonAspectRatio => { switch (cameraAspectRatio.value) { case CameraAspectRatio.HORIZONTAL_3_2: - return EnqueueEditImageSize.Wide; + return CommonAspectRatio.Wide; case CameraAspectRatio.VERTICAL_2_3: case CameraAspectRatio.VERTICAL_9_16: - return EnqueueEditImageSize.Tall; + return CommonAspectRatio.Tall; case CameraAspectRatio.SQUARE_1_1: default: - return EnqueueEditImageSize.Square; + return CommonAspectRatio.Square; } }; - const getCurrentResolution = (): EnqueueEditImageResolution | undefined => { + const getCurrentResolution = (): CommonResolution | undefined => { const selected = resolutionList.find((item) => item.selected); switch (selected?.label) { case "1k": - return EnqueueEditImageResolution.OneK; + return CommonResolution.OneK; case "2k": - return EnqueueEditImageResolution.TwoK; + return CommonResolution.TwoK; case "4k": - return EnqueueEditImageResolution.FourK; + return CommonResolution.FourK; default: return undefined; }