Compare commits

...
Author SHA1 Message Date
Brandon Thomas b71348f152 tauri inpaint cleanup 2025-12-27 19:15:31 -05:00
Brandon Thomas 5c33baca70 tauri cleanup 2025-12-27 19:01:16 -05:00
Brandon Thomas 469342ff49 cleanup image_edit module 2025-12-27 17:57:46 -05:00
Brandon Thomas a0bc38ad52 cleanup 2025-12-23 21:44:19 -05:00
Brandon Thomas af22c41519 cleanup 2025-12-23 20:29:08 -05:00
Brandon Thomas 93e8c6d668 begin cleaning up providers 2025-12-23 20:24:00 -05:00
89 changed files with 741 additions and 1390 deletions
@@ -1,6 +1,8 @@
use crate::core::artcraft_error::ArtcraftError;
use anyhow::anyhow;
use base64::DecodeError;
use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use errors::AnyhowError;
use grok_client::error::grok_error::GrokError;
use midjourney_client::error::midjourney_error::MidjourneyError;
@@ -20,6 +22,12 @@ pub enum GenerateError {
/// We pulled out Fal (for now) - it's impacting build speeds. We'll add it in the future.
FalNoLongerSupported,
/// Wrong provider for the model
BadProviderForModel {
provider: GenerationProvider,
model: ModelType,
},
/// There was a billing, credits, or payments issue.
BillingIssue(BillingIssueReason),
@@ -0,0 +1 @@
pub mod handle_generic_bg_removal_artcraft;
@@ -1,6 +1,6 @@
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal::handle_generic_bg_removal;
use crate::core::commands::enqueue::image_bg_removal::artcraft::handle_generic_bg_removal_artcraft::handle_generic_bg_removal_artcraft;
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, ResponseOrErrorType};
@@ -164,12 +164,11 @@ pub async fn handle_request(
// TODO(bt,2025-07-07): Other model/provider routing...
let success_event = handle_generic_bg_removal(
let success_event = handle_generic_bg_removal_artcraft(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
).await?;
@@ -1,60 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal_artcraft::handle_generic_bg_removal_artcraft;
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal_fal::handle_generic_bg_removal_fal;
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::EnqueueEditImageCommand;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
pub async fn handle_generic_bg_removal(
request: &EnqueueImageBgRemovalCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Sora => {
// Fallthrough
// Sora doesn't have background removal
}
Provider::Artcraft => {
info!("Removing background via Artcraft...");
return handle_generic_bg_removal_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager
).await;
}
Provider::Fal => {
info!("Removing background via Fal...");
return handle_generic_bg_removal_fal(
request,
app,
app_data_root,
app_env_configs,
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,15 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use tauri::AppHandle;
pub async fn handle_generic_bg_removal_fal(
request: &EnqueueImageBgRemovalCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
) -> Result<TaskEnqueueSuccess, GenerateError> {
Err(GenerateError::NotYetImplemented("TODO: Implement fal background removal handling".to_string()))
}
@@ -1,3 +0,0 @@
pub mod handle_generic_bg_removal;
pub mod handle_generic_bg_removal_artcraft;
pub mod handle_generic_bg_removal_fal;
@@ -1,2 +1,2 @@
pub mod enqueue_image_bg_removal_command;
pub mod generic;
pub (super) mod artcraft;
@@ -29,7 +29,7 @@ use storyteller_client::endpoints::generate::image::generate_flux_pro_11_text_to
use storyteller_client::endpoints::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
use tauri::AppHandle;
pub async fn handle_flux_kontext_edit_artcraft(
pub async fn handle_artcraft_flux_kontext_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -1,6 +1,5 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
use crate::core::commands::enqueue::image_edit::gpt_image::handle_gpt_image_1_edit::MAX_IMAGES;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -28,7 +27,9 @@ use storyteller_client::endpoints::generate::image::generate_flux_pro_11_text_to
use storyteller_client::endpoints::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
use tauri::AppHandle;
pub async fn handle_gpt_image_1_edit_artcraft(
const MAX_IMAGES: usize = 4;
pub async fn handle_artcraft_gpt_image_1_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -1,6 +1,5 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
use crate::core::commands::enqueue::image_edit::gpt_image::handle_gpt_image_1_edit::MAX_IMAGES;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -29,7 +28,9 @@ use storyteller_client::endpoints::generate::image::generate_flux_pro_11_ultra_t
use storyteller_client::endpoints::generate::image::multi_function::gpt_image_1p5_multi_function_image_gen_image::gpt_image_1p5_multi_function_image_gen;
use tauri::AppHandle;
pub async fn handle_gpt_image_1p5_edit_artcraft(
const MAX_IMAGES: usize = 4;
pub async fn handle_artcraft_gpt_image_1p5_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -29,7 +29,7 @@ use tauri::AppHandle;
pub(super) const MAX_IMAGES: usize = 10;
pub async fn handle_nano_banana_edit_artcraft(
pub async fn handle_artcraft_nano_banana_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -32,7 +32,7 @@ use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::Imag
pub(super) const MAX_IMAGES: usize = 10;
pub async fn handle_nano_banana_pro_edit_artcraft(
pub async fn handle_artcraft_nano_banana_pro_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -21,7 +21,7 @@ use tauri::AppHandle;
const MAX_IMAGES: usize = 4;
pub async fn handle_seedream_4_edit_artcraft(
pub async fn handle_artcraft_seedream_4_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -21,7 +21,7 @@ use tauri::AppHandle;
const MAX_IMAGES: usize = 4;
pub async fn handle_seedream_4p5_edit_artcraft(
pub async fn handle_artcraft_seedream_4p5_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -0,0 +1,36 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::artcraft::handle_artcraft_flux_kontext_edit::handle_artcraft_flux_kontext_edit;
use crate::core::commands::enqueue::image_edit::artcraft::handle_artcraft_gpt_image_1_edit::handle_artcraft_gpt_image_1_edit;
use crate::core::commands::enqueue::image_edit::artcraft::handle_artcraft_gpt_image_1p5_edit::handle_artcraft_gpt_image_1p5_edit;
use crate::core::commands::enqueue::image_edit::artcraft::handle_artcraft_nano_banana_edit::handle_artcraft_nano_banana_edit;
use crate::core::commands::enqueue::image_edit::artcraft::handle_artcraft_nano_banana_pro_edit::handle_artcraft_nano_banana_pro_edit;
use crate::core::commands::enqueue::image_edit::artcraft::handle_artcraft_seedream_4_edit::handle_artcraft_seedream_4_edit;
use crate::core::commands::enqueue::image_edit::artcraft::handle_artcraft_seedream_4p5_edit::handle_artcraft_seedream_4p5_edit;
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel};
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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 tauri::AppHandle;
pub async fn handle_image_edit_artcraft(
model: ImageEditModel,
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
match model {
ImageEditModel::FluxProKontextMax => handle_artcraft_flux_kontext_edit(request, app, app_data_root, app_env_configs, storyteller_creds_manager).await,
ImageEditModel::Gemini25Flash | ImageEditModel::NanoBanana => {
handle_artcraft_nano_banana_edit( request, app, app_data_root, app_env_configs, storyteller_creds_manager).await
},
ImageEditModel::NanoBananaPro => handle_artcraft_nano_banana_pro_edit(request, app, app_data_root, app_env_configs, storyteller_creds_manager).await,
ImageEditModel::GptImage1 => handle_artcraft_gpt_image_1_edit(request, app, app_data_root, app_env_configs, storyteller_creds_manager).await,
ImageEditModel::GptImage1p5 => handle_artcraft_gpt_image_1p5_edit(request, app, app_data_root, app_env_configs, storyteller_creds_manager).await,
ImageEditModel::Seedream4 => handle_artcraft_seedream_4_edit(request, app, app_data_root, app_env_configs, storyteller_creds_manager).await,
ImageEditModel::Seedream4p5 => handle_artcraft_seedream_4p5_edit(request, app, app_data_root, app_env_configs, storyteller_creds_manager).await,
}
}
@@ -0,0 +1,9 @@
mod handle_artcraft_flux_kontext_edit;
mod handle_artcraft_gpt_image_1_edit;
mod handle_artcraft_gpt_image_1p5_edit;
mod handle_artcraft_nano_banana_edit;
mod handle_artcraft_nano_banana_pro_edit;
mod handle_artcraft_seedream_4_edit;
mod handle_artcraft_seedream_4p5_edit;
pub (super) mod handle_image_edit_artcraft;
@@ -1,12 +1,8 @@
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::flux_kontext::handle_flux_kontext_edit::handle_flux_kontext_edit;
use crate::core::commands::enqueue::image_edit::gpt_image::handle_gpt_image_1_edit::handle_gpt_image_1_edit;
use crate::core::commands::enqueue::image_edit::gpt_image::handle_gpt_image_1p5_artcraft::handle_gpt_image_1p5_edit_artcraft;
use crate::core::commands::enqueue::image_edit::nano_banana::handle_nano_banana_edit::handle_nano_banana_edit;
use crate::core::commands::enqueue::image_edit::nano_banana_pro::handle_nano_banana_pro_edit::handle_nano_banana_pro_edit;
use crate::core::commands::enqueue::image_edit::seedream::handle_seedream_4_artcraft::handle_seedream_4_edit_artcraft;
use crate::core::commands::enqueue::image_edit::seedream::handle_seedream_4p5_artcraft::handle_seedream_4p5_edit_artcraft;
use crate::core::commands::enqueue::image_edit::artcraft::handle_image_edit_artcraft::handle_image_edit_artcraft;
use crate::core::commands::enqueue::image_edit::image_edit_models::image_edit_model_to_model_type;
use crate::core::commands::enqueue::image_edit::sora::handle_image_edit_sora::handle_image_edit_sora;
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, ResponseOrErrorType};
@@ -28,6 +24,7 @@ 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::common::model_type::ModelType;
use enums::tauri::tasks::task_status::TaskStatus;
use enums::tauri::tasks::task_type::TaskType;
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
@@ -84,6 +81,10 @@ pub enum ImageEditModel {
#[derive(Deserialize, Debug)]
pub struct EnqueueEditImageCommand {
/// The provider to use (defaults to Artcraft/Storyteller).
/// Not all (provider, model) combinations are valid.
pub provider: Option<GenerationProvider>,
/// The model to use.
pub model: Option<ImageEditModel>,
@@ -268,84 +269,41 @@ pub async fn handle_request(
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let mut success_event= match request.model {
let model = match request.model {
Some(model) => model,
None => {
return Err(GenerateError::no_model_specified())
}
Some(ImageEditModel::FluxProKontextMax) => {
handle_flux_kontext_edit(
};
let mut success_event = match request.provider {
None | Some(GenerationProvider::Artcraft) => {
handle_image_edit_artcraft(
model,
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
).await?
}
Some(GenerationProvider::Sora) => {
handle_image_edit_sora(
model,
request,
app,
app_data_root,
app_env_configs,
sora_creds_manager,
sora_task_queue,
).await?
}
Some(ImageEditModel::Gemini25Flash | ImageEditModel::NanoBanana) => {
handle_nano_banana_edit(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await?
}
Some(ImageEditModel::NanoBananaPro) => {
handle_nano_banana_pro_edit(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await?
}
Some(ImageEditModel::GptImage1) => {
handle_gpt_image_1_edit(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await?
}
Some(ImageEditModel::GptImage1p5) => {
handle_gpt_image_1p5_edit_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager,
).await?
}
Some(ImageEditModel::Seedream4) => {
handle_seedream_4_edit_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager,
).await?
}
Some(ImageEditModel::Seedream4p5) => {
handle_seedream_4p5_edit_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager,
).await?
_ => {
return Err(GenerateError::BadProviderForModel {
provider: request.provider.unwrap_or(GenerationProvider::Artcraft),
model: image_edit_model_to_model_type(model),
})
}
};
@@ -1,53 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::EnqueueEditImageCommand;
use crate::core::commands::enqueue::image_edit::flux_kontext::handle_flux_kontext_edit_artcraft::handle_flux_kontext_edit_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
pub async fn handle_flux_kontext_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (TODO)
}
Provider::Sora => {
// Fallthrough
}
Provider::Artcraft => {
info!("Dispatching gpt-image-1 (edit) via Artcraft...");
return handle_flux_kontext_edit_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,2 +0,0 @@
pub (super) mod handle_flux_kontext_edit;
mod handle_flux_kontext_edit_artcraft;
@@ -1,67 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::EnqueueEditImageCommand;
use crate::core::commands::enqueue::image_edit::gpt_image::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
pub(super) const MAX_IMAGES: usize = 10;
pub async fn handle_gpt_image_1_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough
// If in the future we support OpenAI API keys, it's worth considering whether we
// send those requests ourselves, or if we use FAL as an intermediary. FAL makes
// the API nicer to deal with, but the user needs an additional key.
}
Provider::Artcraft => {
info!("Dispatching gpt-image-1 (edit) via Artcraft...");
return handle_gpt_image_1_edit_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager
).await;
}
Provider::Sora => {
info!("Dispatching gpt-image-1 (edit) via Sora...");
return handle_gpt_image_1_edit_sora(
request,
app,
app_data_root,
app_env_configs,
sora_creds_manager,
sora_task_queue,
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,4 +0,0 @@
pub (super) mod handle_gpt_image_1_edit;
pub (super) mod handle_gpt_image_1_edit_artcraft;
pub (super) mod handle_gpt_image_1_edit_sora;
pub (super) mod handle_gpt_image_1p5_artcraft;
@@ -0,0 +1,15 @@
use enums::common::model_type::ModelType;
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::ImageEditModel;
pub fn image_edit_model_to_model_type(model: ImageEditModel) -> ModelType {
match model {
ImageEditModel::FluxProKontextMax => ModelType::FluxProKontextMax,
ImageEditModel::Gemini25Flash => ModelType::NanoBanana,
ImageEditModel::NanoBanana => ModelType::NanoBanana,
ImageEditModel::NanoBananaPro => ModelType::NanoBananaPro,
ImageEditModel::GptImage1 => ModelType::GptImage1,
ImageEditModel::GptImage1p5 => ModelType::GptImage1p5,
ImageEditModel::Seedream4 => ModelType::Seedream4,
ImageEditModel::Seedream4p5 => ModelType::Seedream4p5,
}
}
@@ -1,6 +1,5 @@
pub (super) mod artcraft;
pub (super) mod sora;
pub mod enqueue_edit_image_command;
pub mod gpt_image;
mod flux_kontext;
mod nano_banana;
mod nano_banana_pro;
mod seedream;
pub mod image_edit_models;
@@ -1,53 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::EnqueueEditImageCommand;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
use crate::core::commands::enqueue::image_edit::nano_banana::handle_nano_banana_edit_artcraft::handle_nano_banana_edit_artcraft;
pub async fn handle_nano_banana_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (TODO)
}
Provider::Sora => {
// Fallthrough
}
Provider::Artcraft => {
info!("Dispatching Nano Banana (edit) via Artcraft...");
return handle_nano_banana_edit_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,2 +0,0 @@
pub (super) mod handle_nano_banana_edit;
pub (super) mod handle_nano_banana_edit_artcraft;
@@ -1,53 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::EnqueueEditImageCommand;
use crate::core::commands::enqueue::image_edit::nano_banana_pro::handle_nano_banana_pro_edit_artcraft::handle_nano_banana_pro_edit_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
pub async fn handle_nano_banana_pro_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (TODO)
}
Provider::Sora => {
// Fallthrough
}
Provider::Artcraft => {
info!("Dispatching Nano Banana Pro (edit) via Artcraft...");
return handle_nano_banana_pro_edit_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,2 +0,0 @@
pub (super) mod handle_nano_banana_pro_edit;
pub (super) mod handle_nano_banana_pro_edit_artcraft;
@@ -1,2 +0,0 @@
pub (super) mod handle_seedream_4p5_artcraft;
pub (super) mod handle_seedream_4_artcraft;
@@ -0,0 +1,40 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel};
use crate::core::commands::enqueue::image_edit::image_edit_models::image_edit_model_to_model_type;
use crate::core::commands::enqueue::image_edit::sora::handle_sora_gpt_image_1_edit::handle_sora_gpt_image_1_edit;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use enums::common::generation_provider::GenerationProvider;
use tauri::AppHandle;
pub async fn handle_image_edit_sora(
model: ImageEditModel,
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
match model {
ImageEditModel::GptImage1 => {
handle_sora_gpt_image_1_edit(
request,
app,
app_data_root,
app_env_configs,
sora_creds_manager,
sora_task_queue,
).await
},
_ => {
Err(GenerateError::BadProviderForModel {
provider: GenerationProvider::Sora,
model: image_edit_model_to_model_type(model),
})
}
}
}
@@ -1,6 +1,5 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::enqueue_edit_image_command::{EditImageSize, EnqueueEditImageCommand, EnqueueEditImageErrorType};
use crate::core::commands::enqueue::image_edit::gpt_image::handle_gpt_image_1_edit::MAX_IMAGES;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
@@ -35,13 +34,14 @@ use std::time::Duration;
use storyteller_client::endpoints::media_files::get_media_file::get_media_file;
use tauri::AppHandle;
const MAX_IMAGES: usize = 4;
const SORA_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
const SORA_IMAGE_REMIX_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
const DEFAULT_ASPECT_RATIO : EditImageSize = EditImageSize::Square;
pub async fn handle_gpt_image_1_edit_sora(
pub async fn handle_sora_gpt_image_1_edit(
request: &EnqueueEditImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -0,0 +1,2 @@
mod handle_sora_gpt_image_1_edit;
pub (super) mod handle_image_edit_sora;
@@ -42,7 +42,7 @@ use storyteller_client::endpoints::media_files::upload_image_media_file_from_fil
use tauri::AppHandle;
use tokens::tokens::media_files::MediaFileToken;
pub async fn handle_flux_dev_juggernaut_inpaint_artcraft(
pub async fn handle_artcraft_flux_dev_juggernaut_inpaint(
request: &EnqueueInpaintImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -41,7 +41,7 @@ use storyteller_client::endpoints::media_files::upload_image_media_file_from_fil
use tauri::AppHandle;
use tokens::tokens::media_files::MediaFileToken;
pub async fn handle_flux_pro_1_inpaint_artcraft(
pub async fn handle_artcraft_flux_pro_1_inpaint(
request: &EnqueueInpaintImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -43,7 +43,7 @@ use storyteller_client::endpoints::media_files::upload_image_media_file_from_fil
use tauri::AppHandle;
use tokens::tokens::media_files::MediaFileToken;
pub async fn handle_flux_pro_kontext_inpaint_artcraft(
pub async fn handle_artcraft_flux_pro_kontext_inpaint(
request: &EnqueueInpaintImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -45,7 +45,7 @@ use storyteller_client::endpoints::media_files::upload_image_media_file_from_fil
use tauri::AppHandle;
use tokens::tokens::media_files::MediaFileToken;
pub async fn handle_gemini_25_flash_inpaint_artcraft(
pub async fn handle_artcraft_nano_banana_inpaint(
request: &EnqueueInpaintImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -0,0 +1,60 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_artcraft_flux_dev_juggernaut_inpaint::handle_artcraft_flux_dev_juggernaut_inpaint;
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_artcraft_flux_pro_1_inpaint::handle_artcraft_flux_pro_1_inpaint;
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_artcraft_flux_pro_kontext_inpaint::handle_artcraft_flux_pro_kontext_inpaint;
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_artcraft_nano_banana_inpaint::handle_artcraft_nano_banana_inpaint;
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::{EnqueueInpaintImageCommand, ImageInpaintModel};
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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 tauri::AppHandle;
pub async fn handle_inpaint_artcraft(
model: ImageInpaintModel,
request: &EnqueueInpaintImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
match model {
ImageInpaintModel::FluxDevJuggernaut => {
handle_artcraft_flux_dev_juggernaut_inpaint(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager,
).await
}
ImageInpaintModel::FluxPro1 => {
handle_artcraft_flux_pro_1_inpaint(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager,
).await
}
ImageInpaintModel::FluxProKontextMax => {
handle_artcraft_flux_pro_kontext_inpaint(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager,
).await
}
ImageInpaintModel::Gemini25Flash => {
handle_artcraft_nano_banana_inpaint(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager,
).await
}
}
}
@@ -0,0 +1,6 @@
pub (super) mod handle_inpaint_artcraft;
mod handle_artcraft_flux_pro_1_inpaint;
mod handle_artcraft_flux_dev_juggernaut_inpaint;
mod handle_artcraft_flux_pro_kontext_inpaint;
mod handle_artcraft_nano_banana_inpaint;
@@ -1,9 +1,8 @@
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_inpaint::flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint;
use crate::core::commands::enqueue::image_inpaint::flux_pro_1_inpaint::handle_flux_pro_1_inpaint::handle_flux_pro_1_inpaint;
use crate::core::commands::enqueue::image_inpaint::flux_pro_kontext_inpaint::handle_flux_pro_kontext_inpaint::handle_flux_pro_kontext_inpaint;
use crate::core::commands::enqueue::image_inpaint::gemini_25_flash_inpaint::handle_gemini_25_flash_inpaint::handle_gemini_25_flash_inpaint;
use crate::core::commands::enqueue::image_edit::image_edit_models::image_edit_model_to_model_type;
use crate::core::commands::enqueue::image_inpaint::artcraft::handle_inpaint_artcraft::handle_inpaint_artcraft;
use crate::core::commands::enqueue::image_inpaint::inpaint_models::image_inpaint_model_to_model_type;
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, ResponseOrErrorType};
@@ -70,6 +69,10 @@ pub enum ImageInpaintModel {
#[derive(Deserialize, Debug)]
pub struct EnqueueInpaintImageCommand {
/// The provider to use (defaults to Artcraft/Storyteller).
/// Not all (provider, model) combinations are valid.
pub provider: Option<GenerationProvider>,
/// REQUIRED (Option<T> is just for error messages).
/// The model to use.
pub model: Option<ImageInpaintModel>,
@@ -223,59 +226,30 @@ pub async fn handle_request(
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let success_event= match request.model {
let model = match request.model {
Some(model) => model,
None => {
return Err(GenerateError::no_model_specified())
}
Some(ImageInpaintModel::FluxDevJuggernaut) => {
handle_flux_dev_juggernaut_inpaint(
};
let success_event = match request.provider {
None | Some(GenerationProvider::Artcraft) => {
handle_inpaint_artcraft(
model,
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await?
}
Some(ImageInpaintModel::FluxPro1) => {
handle_flux_pro_1_inpaint(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await?
}
Some(ImageInpaintModel::FluxProKontextMax) => {
// TODO: Redirect to contextual image edit handler.
handle_flux_pro_kontext_inpaint(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await?
}
Some(ImageInpaintModel::Gemini25Flash) => {
// TODO: Redirect to contextual image edit handler.
handle_gemini_25_flash_inpaint(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await?
Some(provider) => {
return Err(GenerateError::BadProviderForModel {
provider,
model: image_inpaint_model_to_model_type(model),
})
}
};
@@ -1,54 +0,0 @@
use crate::core::commands::enqueue::generate_error::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_inpaint::flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint_artcraft::handle_flux_dev_juggernaut_inpaint_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
pub async fn handle_flux_dev_juggernaut_inpaint(
request: &EnqueueInpaintImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (for now)
}
Provider::Sora => {
// Fallthrough
}
Provider::Artcraft => {
info!("Dispatching flux dev juggernaut inpaint via Artcraft...");
return handle_flux_dev_juggernaut_inpaint_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,2 +0,0 @@
pub (super) mod handle_flux_dev_juggernaut_inpaint;
mod handle_flux_dev_juggernaut_inpaint_artcraft;
@@ -1,54 +0,0 @@
use crate::core::commands::enqueue::generate_error::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_inpaint::flux_pro_1_inpaint::handle_flux_pro_1_inpaint_artcraft::handle_flux_pro_1_inpaint_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
pub async fn handle_flux_pro_1_inpaint(
request: &EnqueueInpaintImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (for now)
}
Provider::Sora => {
// Fallthrough
}
Provider::Artcraft => {
info!("Dispatching flux pro 1 inpaint via Artcraft...");
return handle_flux_pro_1_inpaint_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,2 +0,0 @@
pub (super) mod handle_flux_pro_1_inpaint;
mod handle_flux_pro_1_inpaint_artcraft;
@@ -1,54 +0,0 @@
use crate::core::commands::enqueue::generate_error::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_inpaint::flux_pro_kontext_inpaint::handle_flux_pro_kontext_inpaint_artcraft::handle_flux_pro_kontext_inpaint_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
pub async fn handle_flux_pro_kontext_inpaint(
request: &EnqueueInpaintImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (for now)
}
Provider::Sora => {
// Fallthrough
}
Provider::Artcraft => {
info!("Dispatching flux pro kontext inpaint via Artcraft...");
return handle_flux_pro_kontext_inpaint_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,2 +0,0 @@
pub (super) mod handle_flux_pro_kontext_inpaint;
pub (super) mod handle_flux_pro_kontext_inpaint_artcraft;
@@ -1,55 +0,0 @@
use crate::core::commands::enqueue::generate_error::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_inpaint::flux_pro_kontext_inpaint::handle_flux_pro_kontext_inpaint_artcraft::handle_flux_pro_kontext_inpaint_artcraft;
use crate::core::commands::enqueue::image_inpaint::gemini_25_flash_inpaint::handle_gemini_25_flash_inpaint_artcraft::handle_gemini_25_flash_inpaint_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
pub async fn handle_gemini_25_flash_inpaint(
request: &EnqueueInpaintImageCommand,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (for now)
}
Provider::Sora => {
// Fallthrough
}
Provider::Artcraft => {
info!("Dispatching gemini 25 flash inpaint via Artcraft...");
return handle_gemini_25_flash_inpaint_artcraft(
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,2 +0,0 @@
pub (super) mod handle_gemini_25_flash_inpaint;
pub (super) mod handle_gemini_25_flash_inpaint_artcraft;
@@ -0,0 +1,11 @@
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::ImageInpaintModel;
use enums::common::model_type::ModelType;
pub fn image_inpaint_model_to_model_type(model: ImageInpaintModel) -> ModelType {
match model {
ImageInpaintModel::FluxDevJuggernaut => ModelType::FluxDevJuggernaut,
ImageInpaintModel::FluxPro1 => ModelType::FluxPro1,
ImageInpaintModel::FluxProKontextMax => ModelType::FluxProKontextMax,
ImageInpaintModel::Gemini25Flash => ModelType::NanoBanana,
}
}
@@ -1,5 +1,4 @@
mod flux_dev_juggernaut_inpaint;
mod flux_pro_1_inpaint;
mod flux_pro_kontext_inpaint;
mod gemini_25_flash_inpaint;
pub mod enqueue_image_inpaint_command;
pub (super) mod inpaint_models;
pub mod enqueue_image_inpaint_command;
mod artcraft;
@@ -18,7 +18,7 @@ use storyteller_client::endpoints::generate::object::generate_hunyuan_3d_2_0_ima
use storyteller_client::endpoints::generate::object::generate_hunyuan_3d_2_1_image_to_3d::generate_hunyuan3d_2_1_image_to_3d;
use tauri::AppHandle;
pub async fn handle_object_artcraft(
pub async fn handle_artcraft_object(
request: &EnqueueImageTo3dObjectRequest,
app: &AppHandle,
app_env_configs: &AppEnvConfigs,
@@ -0,0 +1 @@
pub (super) mod handle_artcraft_object;
@@ -1,6 +1,6 @@
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_object::generic::handle_object::handle_object;
use crate::core::commands::enqueue::image_to_object::artcraft::handle_artcraft_object::handle_artcraft_object;
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;
@@ -14,10 +14,10 @@ use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
use crate::core::state::task_database::TaskDatabase;
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
use log::{error, info, warn};
use serde_derive::{Deserialize, Serialize};
use tauri::{AppHandle, State};
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
use tokens::tokens::media_files::MediaFileToken;
#[derive(Deserialize)]
@@ -174,12 +174,11 @@ pub async fn handle_request(
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let result = handle_object(
let result = handle_artcraft_object(
&request,
&app,
&app_env_configs,
&app_data_root,
&provider_priority_store,
&storyteller_creds_manager,
).await;
@@ -1,40 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::EnqueueImageTo3dObjectRequest;
use crate::core::commands::enqueue::image_to_object::generic::handle_object_artcraft::handle_object_artcraft;
use crate::core::commands::enqueue::image_to_object::generic::handle_object_fal::handle_object_fal;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, ProviderPriorityStore};
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use tauri::AppHandle;
pub async fn handle_object(
request: &EnqueueImageTo3dObjectRequest,
app: &AppHandle,
app_env_configs: &AppEnvConfigs,
app_data_root: &AppDataRoot,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
for provider in priority.iter() {
match provider {
Provider::Fal => {} // Fallthrough
Provider::Sora => {} // Fallthrough
Provider::Artcraft => {
return Ok(handle_object_artcraft(
request,
app,
app_env_configs,
app_data_root,
storyteller_creds_manager
).await?);
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,15 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::EnqueueImageTo3dObjectRequest;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use tauri::AppHandle;
pub async fn handle_object_fal(
app: &AppHandle,
app_env_configs: &AppEnvConfigs,
app_data_root: &AppDataRoot,
request: EnqueueImageTo3dObjectRequest,
) -> Result<TaskEnqueueSuccess, GenerateError> {
Err(GenerateError::FalNoLongerSupported)
}
@@ -1,3 +0,0 @@
pub (super) mod handle_object;
pub (super) mod handle_object_artcraft;
pub (super) mod handle_object_fal;
@@ -1,2 +1,2 @@
pub mod enqueue_image_to_3d_object_command;
mod generic;
mod artcraft;
@@ -2,7 +2,7 @@ use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_fr
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason};
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_sora2_video::handle_sora2_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;
@@ -275,12 +275,11 @@ pub async fn handle_request(
}
Some(VideoModel::Sora2) => {
// TODO: Route based on forthcoming provider parameter.
handle_sora2_video(
handle_sora_sora2(
&request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
sora_creds_manager,
).await
}
@@ -1,53 +0,0 @@
use crate::core::commands::enqueue::generate_error::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;
use crate::core::commands::enqueue::image_to_video::sora2::handle_sora2_video_sora::handle_sora2_video_sora;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
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::{Provider, 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 log::info;
use tauri::AppHandle;
pub async fn handle_sora2_video(
request: &EnqueueImageToVideoRequest,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
sora_creds_manager: &SoraCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (for now)
}
Provider::Artcraft => {
// Fallthrough
}
Provider::Sora => {
info!("Dispatching Sora2 via OpenAI Sora...");
return handle_sora2_video_sora(
request,
app,
app_data_root,
app_env_configs,
sora_creds_manager,
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,6 +1,7 @@
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::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;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
@@ -16,9 +17,8 @@ use openai_sora_client::recipes::list_sora2_drafts::list_sora2_drafts_with_sessi
use openai_sora_client::requests::generate_sora2_video::generate_sora2_video::{GenerateSora2VideoArgs, Orientation};
use tauri::AppHandle;
use tokens::tokens::media_files::MediaFileToken;
use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
pub (super) async fn handle_sora2_video_sora(
pub async fn handle_sora_sora2(
request: &EnqueueImageToVideoRequest,
app: &AppHandle,
app_data_root: &AppDataRoot,
@@ -1,2 +1 @@
pub (super) mod handle_sora2_video;
pub (super) mod handle_sora2_video_sora;
pub (super) mod handle_sora_sora2;
@@ -0,0 +1,74 @@
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::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::{GenerateFlux1DevTextToImageAspectRatio, GenerateFlux1DevTextToImageNumImages, GenerateFlux1DevTextToImageRequest};
use enums::common::generation_provider::GenerationProvider;
use enums::tauri::tasks::task_type::TaskType;
use log::{error, info};
use idempotency::uuid::generate_random_uuid;
use storyteller_client::endpoints::generate::image::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
pub async fn handle_artcraft_flux_1_dev_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(GenerateError::needs_storyteller_credentials());
},
};
info!("enqueue Flux 1 Dev");
let uuid_idempotency_token = generate_random_uuid();
let request = GenerateFlux1DevTextToImageRequest {
uuid_idempotency_token,
prompt: request.prompt.clone(),
aspect_ratio: request.aspect_ratio
.map(|aspect| match aspect {
// TODO(bt,2025-07-14): Support other aspect ratios.
TextToImageSize::Tall => GenerateFlux1DevTextToImageAspectRatio::PortraitNineBySixteen,
TextToImageSize::Wide => GenerateFlux1DevTextToImageAspectRatio::LandscapeSixteenByNine,
TextToImageSize::Auto => GenerateFlux1DevTextToImageAspectRatio::SquareHd,
TextToImageSize::Square => GenerateFlux1DevTextToImageAspectRatio::SquareHd,
}),
num_images: request.number_images
.and_then(|num| match num {
1 => Some(GenerateFlux1DevTextToImageNumImages::One),
2 => Some(GenerateFlux1DevTextToImageNumImages::Two),
3 => Some(GenerateFlux1DevTextToImageNumImages::Three),
4 => Some(GenerateFlux1DevTextToImageNumImages::Four),
_ => None, // NB: use service defaults
}),
};
let result = generate_flux_1_dev_text_to_image(
&app_env_configs.storyteller_host,
Some(&creds),
request,
).await;
let job_token = match result {
Ok(enqueued) => {
info!("Successfully enqueued Artcraft Flux 1 Dev text to image generation");
enqueued.inference_job_token
}
Err(err) => {
error!("Failed to use Artcraft Flux 1 Dev text to image generation: {:?}", err);
return Err(GenerateError::from(err));
}
};
Ok(TaskEnqueueSuccess {
provider: GenerationProvider::Artcraft,
task_type: TaskType::ImageGeneration,
model: Some(GenerationModel::Flux1Dev),
provider_job_id: Some(job_token.to_string()),
})
}
@@ -0,0 +1,74 @@
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::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageAspectRatio, GenerateFlux1SchnellTextToImageNumImages, GenerateFlux1SchnellTextToImageRequest};
use enums::common::generation_provider::GenerationProvider;
use enums::tauri::tasks::task_type::TaskType;
use log::{error, info};
use idempotency::uuid::generate_random_uuid;
use storyteller_client::endpoints::generate::image::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
pub async fn handle_artcraft_flux_1_schnell_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(GenerateError::needs_storyteller_credentials());
},
};
info!("enqueue Flux 1 Schnell");
let uuid_idempotency_token = generate_random_uuid();
let request = GenerateFlux1SchnellTextToImageRequest {
uuid_idempotency_token,
prompt: request.prompt.clone(),
aspect_ratio: request.aspect_ratio
.map(|aspect| match aspect {
// TODO(bt,2025-07-14): Support other aspect ratios.
TextToImageSize::Tall => GenerateFlux1SchnellTextToImageAspectRatio::PortraitNineBySixteen,
TextToImageSize::Wide => GenerateFlux1SchnellTextToImageAspectRatio::LandscapeSixteenByNine,
TextToImageSize::Square => GenerateFlux1SchnellTextToImageAspectRatio::SquareHd,
TextToImageSize::Auto => GenerateFlux1SchnellTextToImageAspectRatio::SquareHd,
}),
num_images: request.number_images
.and_then(|num| match num {
1 => Some(GenerateFlux1SchnellTextToImageNumImages::One),
2 => Some(GenerateFlux1SchnellTextToImageNumImages::Two),
3 => Some(GenerateFlux1SchnellTextToImageNumImages::Three),
4 => Some(GenerateFlux1SchnellTextToImageNumImages::Four),
_ => None, // NB: use service defaults
}),
};
let result = generate_flux_1_schnell_text_to_image(
&app_env_configs.storyteller_host,
Some(&creds),
request,
).await;
let job_token = match result {
Ok(enqueued) => {
info!("Successfully enqueued Artcraft Flux 1 Schnell text to image generation");
enqueued.inference_job_token
}
Err(err) => {
error!("Failed to use Artcraft Flux 1 Schnell text to image generation: {:?}", err);
return Err(GenerateError::from(err));
}
};
Ok(TaskEnqueueSuccess {
provider: GenerationProvider::Artcraft,
task_type: TaskType::ImageGeneration,
model: Some(GenerationModel::Flux1Schnell),
provider_job_id: Some(job_token.to_string()),
})
}
@@ -0,0 +1,74 @@
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::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::{GenerateFluxPro11TextToImageAspectRatio, GenerateFluxPro11TextToImageNumImages, GenerateFluxPro11TextToImageRequest};
use enums::common::generation_provider::GenerationProvider;
use enums::tauri::tasks::task_type::TaskType;
use log::{error, info};
use idempotency::uuid::generate_random_uuid;
use storyteller_client::endpoints::generate::image::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
pub async fn handle_artcraft_flux_pro_1p1_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(GenerateError::needs_storyteller_credentials());
},
};
info!("enqueue Flux Pro 1.1");
let uuid_idempotency_token = generate_random_uuid();
let request = GenerateFluxPro11TextToImageRequest {
uuid_idempotency_token,
prompt: request.prompt.clone(),
aspect_ratio: request.aspect_ratio
.map(|aspect| match aspect {
// TODO(bt,2025-07-14): Support other aspect ratios.
TextToImageSize::Tall => GenerateFluxPro11TextToImageAspectRatio::PortraitNineBySixteen,
TextToImageSize::Wide => GenerateFluxPro11TextToImageAspectRatio::LandscapeSixteenByNine,
TextToImageSize::Square => GenerateFluxPro11TextToImageAspectRatio::Square,
TextToImageSize::Auto => GenerateFluxPro11TextToImageAspectRatio::Square,
}),
num_images: request.number_images
.and_then(|num| match num {
1 => Some(GenerateFluxPro11TextToImageNumImages::One),
2 => Some(GenerateFluxPro11TextToImageNumImages::Two),
3 => Some(GenerateFluxPro11TextToImageNumImages::Three),
4 => Some(GenerateFluxPro11TextToImageNumImages::Four),
_ => None, // NB: use service defaults
}),
};
let result = generate_flux_pro_11_text_to_image(
&app_env_configs.storyteller_host,
Some(&creds),
request,
).await;
let job_token = match result {
Ok(enqueued) => {
info!("Successfully enqueued Artcraft Flux Pro 1.1 text to image generation");
enqueued.inference_job_token
}
Err(err) => {
error!("Failed to use Artcraft Flux Pro 1.1 text to image generation: {:?}", err);
return Err(GenerateError::from(err));
}
};
Ok(TaskEnqueueSuccess {
provider: GenerationProvider::Artcraft,
task_type: TaskType::ImageGeneration,
model: Some(GenerationModel::FluxPro11),
provider_job_id: Some(job_token.to_string()),
})
}
@@ -0,0 +1,75 @@
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::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::{GenerateFluxPro11UltraTextToImageAspectRatio, GenerateFluxPro11UltraTextToImageNumImages, GenerateFluxPro11UltraTextToImageRequest};
use enums::common::generation_provider::GenerationProvider;
use enums::tauri::tasks::task_type::TaskType;
use log::{error, info};
use idempotency::uuid::generate_random_uuid;
use storyteller_client::endpoints::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
pub async fn handle_artcraft_flux_pro_1p1_ultra_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(GenerateError::needs_storyteller_credentials());
},
};
info!("enqueue Flux Pro 1.1 Ultra");
let uuid_idempotency_token = generate_random_uuid();
let request = GenerateFluxPro11UltraTextToImageRequest {
uuid_idempotency_token,
prompt: request.prompt.clone(),
aspect_ratio: request.aspect_ratio
.map(|aspect| match aspect {
// TODO(bt,2025-07-14): Support other aspect ratios.
TextToImageSize::Tall => GenerateFluxPro11UltraTextToImageAspectRatio::PortraitNineBySixteen,
TextToImageSize::Wide => GenerateFluxPro11UltraTextToImageAspectRatio::LandscapeSixteenByNine,
TextToImageSize::Square => GenerateFluxPro11UltraTextToImageAspectRatio::Square,
TextToImageSize::Auto => GenerateFluxPro11UltraTextToImageAspectRatio::Square,
}),
num_images: request.number_images
.and_then(|num| match num {
1 => Some(GenerateFluxPro11UltraTextToImageNumImages::One),
2 => Some(GenerateFluxPro11UltraTextToImageNumImages::Two),
3 => Some(GenerateFluxPro11UltraTextToImageNumImages::Three),
4 => Some(GenerateFluxPro11UltraTextToImageNumImages::Four),
_ => None, // NB: use service defaults
}),
};
let result = generate_flux_pro_11_ultra_text_to_image(
&app_env_configs.storyteller_host,
Some(&creds),
request,
).await;
let job_token = match result {
Ok(enqueued) => {
info!("Successfully enqueued Artcraft Flux Pro 1.1 Ultra text to image generation");
enqueued.inference_job_token
}
Err(err) => {
error!("Failed to use Artcraft Flux Pro 1.1 Ultra text to image generation: {:?}", err);
return Err(GenerateError::from(err));
}
};
Ok(TaskEnqueueSuccess {
provider: GenerationProvider::Artcraft,
task_type: TaskType::ImageGeneration,
model: Some(GenerationModel::FluxPro11Ultra),
provider_job_id: Some(job_token.to_string()),
})
}
@@ -24,7 +24,7 @@ use storyteller_client::endpoints::generate::image::edit::gpt_image_1_edit_image
use storyteller_client::endpoints::generate::image::generate_gpt_image_1_text_to_image::generate_gpt_image_1_text_to_image;
use tauri::AppHandle;
pub async fn handle_gpt_image_1_artcraft(
pub async fn handle_artcraft_gpt_image_1_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
@@ -20,7 +20,7 @@ use std::time::Duration;
use storyteller_client::endpoints::generate::image::multi_function::gpt_image_1p5_multi_function_image_gen_image::gpt_image_1p5_multi_function_image_gen;
use tauri::AppHandle;
pub async fn handle_gpt_image_1p5_artcraft(
pub async fn handle_artcraft_gpt_image_1p5_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
@@ -26,7 +26,7 @@ use storyteller_client::endpoints::generate::image::edit::gemini_25_flash_edit_i
use storyteller_client::endpoints::generate::image::multi_function::nano_banana_pro_multi_function_image_gen_image::nano_banana_pro_multi_function_image_gen;
use tauri::AppHandle;
pub async fn handle_nano_banana_pro_artcraft(
pub async fn handle_artcraft_nano_banana_pro_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
@@ -26,7 +26,7 @@ use storyteller_client::endpoints::generate::image::edit::gpt_image_1_edit_image
use storyteller_client::endpoints::generate::image::generate_gpt_image_1_text_to_image::generate_gpt_image_1_text_to_image;
use tauri::AppHandle;
pub async fn handle_nano_banana_artcraft(
pub async fn handle_artcraft_nano_banana_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
@@ -20,7 +20,7 @@ use std::time::Duration;
use storyteller_client::endpoints::generate::image::multi_function::bytedance_seedream_v4_multi_function_image_gen_image::bytedance_seedream_v4_multi_function_image_gen;
use tauri::AppHandle;
pub async fn handle_seedream_4_artcraft(
pub async fn handle_artcraft_seedream_4_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
@@ -20,7 +20,7 @@ use std::time::Duration;
use storyteller_client::endpoints::generate::image::multi_function::bytedance_seedream_v4p5_multi_function_image_gen_image::bytedance_seedream_v4p5_multi_function_image_gen;
use tauri::AppHandle;
pub async fn handle_seedream_4p5_artcraft(
pub async fn handle_artcraft_seedream_4p5_text_to_image(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
@@ -0,0 +1,53 @@
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::artcraft::handle_artcraft_flux_1_dev_text_to_image::handle_artcraft_flux_1_dev_text_to_image;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_flux_1_schnell_text_to_image::handle_artcraft_flux_1_schnell_text_to_image;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_flux_pro_1p1_text_to_image::handle_artcraft_flux_pro_1p1_text_to_image;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_flux_pro_1p1_ultra_text_to_image::handle_artcraft_flux_pro_1p1_ultra_text_to_image;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_gpt_image_1_text_to_image::handle_artcraft_gpt_image_1_text_to_image;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_gpt_image_1p5_text_to_image::handle_artcraft_gpt_image_1p5_text_to_image;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_nano_banana_pro_text_to_image::handle_artcraft_nano_banana_pro_text_to_image;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_nano_banana_text_to_image::handle_artcraft_nano_banana_text_to_image;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_seedream_4_text_to_image::handle_artcraft_seedream_4_text_to_image;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_artcraft_seedream_4p5_text_to_image::handle_artcraft_seedream_4p5_text_to_image;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageModel};
use crate::core::commands::enqueue::text_to_image::text_to_image_models::text_to_image_model_to_model_type;
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 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<TaskEnqueueSuccess, GenerateError> {
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_artcraft_flux_1_dev_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
TextToImageModel::Flux1Schnell => handle_artcraft_flux_1_schnell_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
TextToImageModel::FluxPro11 =>handle_artcraft_flux_pro_1p1_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
TextToImageModel::FluxPro11Ultra => handle_artcraft_flux_pro_1p1_ultra_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
TextToImageModel::GptImage1 => handle_artcraft_gpt_image_1_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
TextToImageModel::GptImage1p5 => handle_artcraft_gpt_image_1p5_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
TextToImageModel::Gemini25Flash | TextToImageModel::NanoBanana => {
handle_artcraft_nano_banana_text_to_image(request, app_env_configs, storyteller_creds_manager).await
},
TextToImageModel::NanoBananaPro => handle_artcraft_nano_banana_pro_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
TextToImageModel::Seedream4 => handle_artcraft_seedream_4_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
TextToImageModel::Seedream4p5 => handle_artcraft_seedream_4p5_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
}
}
@@ -0,0 +1,12 @@
pub (super) mod handle_text_to_image_artcraft;
mod handle_artcraft_gpt_image_1_text_to_image;
mod handle_artcraft_gpt_image_1p5_text_to_image;
mod handle_artcraft_nano_banana_pro_text_to_image;
mod handle_artcraft_nano_banana_text_to_image;
mod handle_artcraft_seedream_4_text_to_image;
mod handle_artcraft_seedream_4p5_text_to_image;
mod handle_artcraft_flux_pro_1p1_ultra_text_to_image;
mod handle_artcraft_flux_1_dev_text_to_image;
mod handle_artcraft_flux_pro_1p1_text_to_image;
mod handle_artcraft_flux_1_schnell_text_to_image;
@@ -1,16 +1,12 @@
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_edit::image_edit_models::image_edit_model_to_model_type;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::generic::handle_image_artcraft::handle_image_artcraft;
use crate::core::commands::enqueue::text_to_image::generic::handle_image_fal::handle_image_fal;
use crate::core::commands::enqueue::text_to_image::gpt_image::handle_gpt_image_1::handle_gpt_image_1;
use crate::core::commands::enqueue::text_to_image::gpt_image::handle_gpt_image_1p5::handle_gpt_image_1p5;
use crate::core::commands::enqueue::text_to_image::artcraft::handle_text_to_image_artcraft::handle_text_to_image_artcraft;
use crate::core::commands::enqueue::text_to_image::grok::handle_grok::handle_grok;
use crate::core::commands::enqueue::text_to_image::midjourney::handle_midjourney::handle_midjourney;
use crate::core::commands::enqueue::text_to_image::nano_banana::handle_nano_banana::handle_nano_banana;
use crate::core::commands::enqueue::text_to_image::nano_banana_pro::handle_nano_banana_pro::handle_nano_banana_pro;
use crate::core::commands::enqueue::text_to_image::seedream::handle_seedream_4_artcraft::handle_seedream_4_artcraft;
use crate::core::commands::enqueue::text_to_image::seedream::handle_seedream_4p5_artcraft::handle_seedream_4p5_artcraft;
use crate::core::commands::enqueue::text_to_image::sora::handle_text_to_image_sora::handle_text_to_image_sora;
use crate::core::commands::enqueue::text_to_image::text_to_image_models::text_to_image_model_to_model_type;
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;
@@ -43,7 +39,7 @@ use tokens::tokens::media_files::MediaFileToken;
/// Don't change the serializations without coordinating with the frontend.
#[derive(Deserialize, Debug, Copy, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ImageModel {
pub enum TextToImageModel {
#[serde(rename = "flux_1_dev")]
Flux1Dev,
#[serde(rename = "flux_1_schnell")]
@@ -79,11 +75,15 @@ pub enum ImageModel {
#[derive(Deserialize)]
pub struct EnqueueTextToImageRequest {
/// Text prompt for the image generation. Required.
pub prompt: Option<String>,
/// The provider to use (defaults to Artcraft/Storyteller).
/// Not all (provider, model) combinations are valid.
pub provider: Option<GenerationProvider>,
/// The model to use.
pub model: Option<ImageModel>,
pub model: Option<TextToImageModel>,
/// Text prompt for the image generation. Required.
pub prompt: Option<String>,
/// Aspect ratio.
pub aspect_ratio: Option<TextToImageSize>,
@@ -320,103 +320,60 @@ pub async fn dispatch_request(
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
match request.model {
let model = match request.model {
Some(model) => model,
None => {
return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified));
return Err(GenerateError::no_model_specified())
}
Some(ImageModel::NanoBanana | ImageModel::Gemini25Flash) => {
return handle_nano_banana(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await;
}
Some(ImageModel::NanoBananaPro) => {
return handle_nano_banana_pro(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await;
}
Some(ImageModel::GptImage1) => {
return handle_gpt_image_1(
request,
app,
app_data_root,
app_env_configs,
provider_priority_store,
storyteller_creds_manager,
sora_creds_manager,
sora_task_queue,
).await;
}
Some(ImageModel::GptImage1p5) => {
return handle_gpt_image_1p5(
};
let provider = match (model, request.provider) {
(TextToImageModel::GrokImage, _) => GenerationProvider::Grok,
(TextToImageModel::Midjourney, _) => GenerationProvider::Midjourney,
_ => request.provider.unwrap_or(GenerationProvider::Artcraft),
};
match provider {
GenerationProvider::Artcraft => {
handle_text_to_image_artcraft(
model,
request,
app,
app_data_root,
app_env_configs,
storyteller_creds_manager,
).await;
).await
}
Some(ImageModel::GrokImage) => {
return handle_grok(
GenerationProvider::Grok => {
handle_grok(
app,
request,
app_env_configs,
grok_creds_manager,
grok_image_prompt_queue,
).await;
).await
}
Some(ImageModel::Midjourney) => {
return handle_midjourney(
GenerationProvider::Midjourney => {
handle_midjourney(
app,
request,
app_env_configs,
mj_creds_manager,
).await;
).await
}
Some(ImageModel::Seedream4) => {
return handle_seedream_4_artcraft(
GenerationProvider::Sora => {
handle_text_to_image_sora(
request,
app_env_configs,
storyteller_creds_manager,
).await;
}
Some(ImageModel::Seedream4p5) => {
return handle_seedream_4p5_artcraft(
request,
app_env_configs,
storyteller_creds_manager,
).await;
app,
sora_creds_manager,
sora_task_queue,
).await
}
_ => {
// Fall-through
}
};
let priority = provider_priority_store.get_priority()?;
for provider in priority.iter() {
match provider {
Provider::Fal => {} // Fallthrough
Provider::Sora => {} // Fallthrough
Provider::Artcraft => {
return handle_image_artcraft(
request, &app, app_env_configs, app_data_root, storyteller_creds_manager).await;
}
Err(GenerateError::BadProviderForModel {
provider,
model: text_to_image_model_to_model_type(model),
})
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,238 +0,0 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, ImageModel, 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::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use anyhow::anyhow;
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::{GenerateFlux1DevTextToImageAspectRatio, GenerateFlux1DevTextToImageNumImages, GenerateFlux1DevTextToImageRequest};
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageAspectRatio, GenerateFlux1SchnellTextToImageNumImages, GenerateFlux1SchnellTextToImageRequest};
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::{GenerateFluxPro11TextToImageAspectRatio, GenerateFluxPro11TextToImageNumImages, GenerateFluxPro11TextToImageRequest};
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::{GenerateFluxPro11UltraTextToImageAspectRatio, GenerateFluxPro11UltraTextToImageNumImages, GenerateFluxPro11UltraTextToImageRequest};
use enums::common::generation_provider::GenerationProvider;
use enums::tauri::tasks::task_type::TaskType;
use idempotency::uuid::generate_random_uuid;
use log::{error, info};
use storyteller_client::endpoints::generate::image::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
use storyteller_client::endpoints::generate::image::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
use storyteller_client::endpoints::generate::image::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
use storyteller_client::endpoints::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
use tauri::AppHandle;
pub async fn handle_image_artcraft(
request: &EnqueueTextToImageRequest,
app: &AppHandle,
app_env_configs: &AppEnvConfigs,
app_data_root: &AppDataRoot,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
error!("No Artcraft credentials are set. Can't perform action.");
let event =
GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::GenerateImage);
if let Err(err) = event.send(&app) {
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(GenerateError::needs_storyteller_credentials());
},
};
info!("Calling Artcraft text to image...");
let uuid_idempotency_token = generate_random_uuid();
let mut selected_model;
let job_token = match request.model {
None => {
return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified));
}
Some(
ImageModel::Gemini25Flash |
ImageModel::NanoBanana |
ImageModel::NanoBananaPro |
ImageModel::GptImage1 |
ImageModel::GptImage1p5 |
ImageModel::Seedream4 |
ImageModel::Seedream4p5 |
ImageModel::GrokImage |
ImageModel::Midjourney
) => {
return Err(GenerateError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
}
Some(ImageModel::Recraft3) => {
return Err(GenerateError::NotYetImplemented(format!("not yet implemented in Artcraft")));
}
Some(ImageModel::Flux1Dev) => {
info!("enqueue Flux 1 Dev");
selected_model = GenerationModel::Flux1Dev;
let request = GenerateFlux1DevTextToImageRequest {
uuid_idempotency_token,
prompt: request.prompt.clone(),
aspect_ratio: request.aspect_ratio
.map(|aspect| match aspect {
// TODO(bt,2025-07-14): Support other aspect ratios.
TextToImageSize::Tall => GenerateFlux1DevTextToImageAspectRatio::PortraitNineBySixteen,
TextToImageSize::Wide => GenerateFlux1DevTextToImageAspectRatio::LandscapeSixteenByNine,
TextToImageSize::Auto => GenerateFlux1DevTextToImageAspectRatio::SquareHd,
TextToImageSize::Square => GenerateFlux1DevTextToImageAspectRatio::SquareHd,
}),
num_images: request.number_images
.and_then(|num| match num {
1 => Some(GenerateFlux1DevTextToImageNumImages::One),
2 => Some(GenerateFlux1DevTextToImageNumImages::Two),
3 => Some(GenerateFlux1DevTextToImageNumImages::Three),
4 => Some(GenerateFlux1DevTextToImageNumImages::Four),
_ => None, // NB: use service defaults
}),
};
let result = generate_flux_1_dev_text_to_image(
&app_env_configs.storyteller_host,
Some(&creds),
request,
).await;
match result {
Ok(enqueued) => {
info!("Successfully enqueued Artcraft Flux 1 Dev text to image generation");
enqueued.inference_job_token
}
Err(err) => {
error!("Failed to use Artcraft Flux 1 Dev text to image generation: {:?}", err);
return Err(GenerateError::from(err));
}
}
}
Some(ImageModel::Flux1Schnell) => {
info!("enqueue Flux 1 Schnell");
selected_model = GenerationModel::Flux1Schnell;
let request = GenerateFlux1SchnellTextToImageRequest {
uuid_idempotency_token,
prompt: request.prompt.clone(),
aspect_ratio: request.aspect_ratio
.map(|aspect| match aspect {
// TODO(bt,2025-07-14): Support other aspect ratios.
TextToImageSize::Tall => GenerateFlux1SchnellTextToImageAspectRatio::PortraitNineBySixteen,
TextToImageSize::Wide => GenerateFlux1SchnellTextToImageAspectRatio::LandscapeSixteenByNine,
TextToImageSize::Square => GenerateFlux1SchnellTextToImageAspectRatio::SquareHd,
TextToImageSize::Auto => GenerateFlux1SchnellTextToImageAspectRatio::SquareHd,
}),
num_images: request.number_images
.and_then(|num| match num {
1 => Some(GenerateFlux1SchnellTextToImageNumImages::One),
2 => Some(GenerateFlux1SchnellTextToImageNumImages::Two),
3 => Some(GenerateFlux1SchnellTextToImageNumImages::Three),
4 => Some(GenerateFlux1SchnellTextToImageNumImages::Four),
_ => None, // NB: use service defaults
}),
};
let result = generate_flux_1_schnell_text_to_image(
&app_env_configs.storyteller_host,
Some(&creds),
request,
).await;
match result {
Ok(enqueued) => {
info!("Successfully enqueued Artcraft Flux 1 Schnell text to image generation");
enqueued.inference_job_token
}
Err(err) => {
error!("Failed to use Artcraft Flux 1 Schnell text to image generation: {:?}", err);
return Err(GenerateError::from(err));
}
}
}
Some(ImageModel::FluxPro11) => {
info!("enqueue Flux Pro 1.1");
selected_model = GenerationModel::FluxPro11;
let request = GenerateFluxPro11TextToImageRequest {
uuid_idempotency_token,
prompt: request.prompt.clone(),
aspect_ratio: request.aspect_ratio
.map(|aspect| match aspect {
// TODO(bt,2025-07-14): Support other aspect ratios.
TextToImageSize::Tall => GenerateFluxPro11TextToImageAspectRatio::PortraitNineBySixteen,
TextToImageSize::Wide => GenerateFluxPro11TextToImageAspectRatio::LandscapeSixteenByNine,
TextToImageSize::Square => GenerateFluxPro11TextToImageAspectRatio::Square,
TextToImageSize::Auto => GenerateFluxPro11TextToImageAspectRatio::Square,
}),
num_images: request.number_images
.and_then(|num| match num {
1 => Some(GenerateFluxPro11TextToImageNumImages::One),
2 => Some(GenerateFluxPro11TextToImageNumImages::Two),
3 => Some(GenerateFluxPro11TextToImageNumImages::Three),
4 => Some(GenerateFluxPro11TextToImageNumImages::Four),
_ => None, // NB: use service defaults
}),
};
let result = generate_flux_pro_11_text_to_image(
&app_env_configs.storyteller_host,
Some(&creds),
request,
).await;
match result {
Ok(enqueued) => {
info!("Successfully enqueued Artcraft Flux Pro 1.1 text to image generation");
enqueued.inference_job_token
}
Err(err) => {
error!("Failed to use Artcraft Flux Pro 1.1 text to image generation: {:?}", err);
return Err(GenerateError::from(err));
}
}
}
Some(ImageModel::FluxPro11Ultra) => {
info!("enqueue Flux Pro 1.1 Ultra");
selected_model = GenerationModel::FluxPro11Ultra;
let request = GenerateFluxPro11UltraTextToImageRequest {
uuid_idempotency_token,
prompt: request.prompt.clone(),
aspect_ratio: request.aspect_ratio
.map(|aspect| match aspect {
// TODO(bt,2025-07-14): Support other aspect ratios.
TextToImageSize::Tall => GenerateFluxPro11UltraTextToImageAspectRatio::PortraitNineBySixteen,
TextToImageSize::Wide => GenerateFluxPro11UltraTextToImageAspectRatio::LandscapeSixteenByNine,
TextToImageSize::Square => GenerateFluxPro11UltraTextToImageAspectRatio::Square,
TextToImageSize::Auto => GenerateFluxPro11UltraTextToImageAspectRatio::Square,
}),
num_images: request.number_images
.and_then(|num| match num {
1 => Some(GenerateFluxPro11UltraTextToImageNumImages::One),
2 => Some(GenerateFluxPro11UltraTextToImageNumImages::Two),
3 => Some(GenerateFluxPro11UltraTextToImageNumImages::Three),
4 => Some(GenerateFluxPro11UltraTextToImageNumImages::Four),
_ => None, // NB: use service defaults
}),
};
let result = generate_flux_pro_11_ultra_text_to_image(
&app_env_configs.storyteller_host,
Some(&creds),
request,
).await;
match result {
Ok(enqueued) => {
info!("Successfully enqueued Artcraft Flux Pro 1.1 Ultra text to image generation");
enqueued.inference_job_token
}
Err(err) => {
error!("Failed to use Artcraft Flux Pro 1.1 Ultra text to image generation: {:?}", err);
return Err(GenerateError::from(err));
}
}
}
};
Ok(TaskEnqueueSuccess {
provider: GenerationProvider::Artcraft,
task_type: TaskType::ImageGeneration,
model: Some(selected_model),
provider_job_id: Some(job_token.to_string()),
})
}
@@ -1,11 +0,0 @@
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 tauri::AppHandle;
pub async fn handle_image_fal(
app: &AppHandle,
request: &EnqueueTextToImageRequest,
) -> Result<TaskEnqueueSuccess, GenerateError> {
Err(GenerateError::FalNoLongerSupported)
}
@@ -1,2 +0,0 @@
pub mod handle_image_artcraft;
pub mod handle_image_fal;
@@ -1,70 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
use crate::core::commands::enqueue::text_to_image::gpt_image::handle_gpt_image_1_artcraft::handle_gpt_image_1_artcraft;
use crate::core::commands::enqueue::text_to_image::gpt_image::handle_gpt_image_1_sora::handle_gpt_image_1_sora;
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::{Provider, 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 enums::common::generation_provider::GenerationProvider;
use enums::tauri::tasks::task_type::TaskType;
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;
pub async fn handle_gpt_image_1(
request: &EnqueueTextToImageRequest,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough
// If in the future we support OpenAI API keys, it's worth considering whether we
// send those requests ourselves, or if we use FAL as an intermediary. FAL makes
// the API nicer to deal with, but the user needs an additional key.
}
Provider::Artcraft => {
info!("Dispatching gpt-image-1 via Artcraft...");
return handle_gpt_image_1_artcraft(
request,
app_env_configs,
storyteller_creds_manager
).await;
}
Provider::Sora => {
info!("Dispatching gpt-image-1 via Sora...");
return handle_gpt_image_1_sora(
request,
app,
sora_creds_manager,
sora_task_queue,
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,25 +0,0 @@
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::enqueue::text_to_image::gpt_image::handle_gpt_image_1p5_artcraft::handle_gpt_image_1p5_artcraft;
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 tauri::AppHandle;
pub async fn handle_gpt_image_1p5(
request: &EnqueueTextToImageRequest,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, GenerateError> {
handle_gpt_image_1p5_artcraft(
request,
app_env_configs,
storyteller_creds_manager
).await
}
@@ -1,5 +0,0 @@
pub (crate) mod handle_gpt_image_1_artcraft;
pub (crate) mod handle_gpt_image_1_sora;
pub (super) mod handle_gpt_image_1;
pub (super) mod handle_gpt_image_1p5;
pub (super) mod handle_gpt_image_1p5_artcraft;
@@ -1,8 +1,7 @@
pub (super) mod text_to_image_models;
pub mod enqueue_text_to_image_command;
pub mod generic;
mod nano_banana;
mod nano_banana_pro;
mod gpt_image;
mod midjourney;
mod artcraft;
mod grok;
mod seedream;
mod midjourney;
mod sora;
@@ -1,60 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
use crate::core::commands::enqueue::text_to_image::nano_banana::handle_nano_banana_artcraft::handle_nano_banana_artcraft;
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::{Provider, 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 enums::common::generation_provider::GenerationProvider;
use enums::tauri::tasks::task_type::TaskType;
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;
pub async fn handle_nano_banana(
request: &EnqueueTextToImageRequest,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (for now)
}
Provider::Sora => {
// Fallthrough
}
Provider::Artcraft => {
info!("Dispatching Nano Banana via Artcraft...");
return handle_nano_banana_artcraft(
request,
app_env_configs,
storyteller_creds_manager
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,2 +0,0 @@
pub (crate) mod handle_nano_banana_artcraft;
pub (super) mod handle_nano_banana;
@@ -1,59 +0,0 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
use crate::core::commands::enqueue::text_to_image::nano_banana_pro::handle_nano_banana_pro_artcraft::handle_nano_banana_pro_artcraft;
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::{Provider, 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 enums::common::generation_provider::GenerationProvider;
use enums::tauri::tasks::task_type::TaskType;
use log::{error, info};
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;
pub async fn handle_nano_banana_pro(
request: &EnqueueTextToImageRequest,
app: &AppHandle,
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
provider_priority_store: &ProviderPriorityStore,
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
// TODO: Check if providers are available before proceeding.
info!("Providers by priority: {:?}", priority);
for provider in priority.iter() {
match provider {
Provider::Fal => {
// Fallthrough (for now)
}
Provider::Sora => {
// Fallthrough
}
Provider::Artcraft => {
info!("Dispatching Nano Banana Pro via Artcraft...");
return handle_nano_banana_pro_artcraft(
request,
app_env_configs,
storyteller_creds_manager
).await;
}
}
}
Err(GenerateError::NoProviderAvailable)
}
@@ -1,2 +0,0 @@
pub (crate) mod handle_nano_banana_pro_artcraft;
pub (super) mod handle_nano_banana_pro;
@@ -1,2 +0,0 @@
pub (super) mod handle_seedream_4_artcraft;
pub (super) mod handle_seedream_4p5_artcraft;
@@ -19,7 +19,7 @@ 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(
pub async fn handle_gpt_image_1_sora_text_to_image(
request: &EnqueueTextToImageRequest,
app: &AppHandle,
sora_creds_manager: &SoraCredentialManager,
@@ -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::enqueue::text_to_image::sora::handle_gpt_image_1_sora_text_to_image::handle_gpt_image_1_sora_text_to_image;
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use tauri::AppHandle;
pub async fn handle_text_to_image_sora(
request: &EnqueueTextToImageRequest,
app: &AppHandle,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, GenerateError> {
handle_gpt_image_1_sora_text_to_image(
request,
app,
sora_creds_manager,
sora_task_queue,
).await
}
@@ -0,0 +1,2 @@
pub (super) mod handle_text_to_image_sora;
mod handle_gpt_image_1_sora_text_to_image;
@@ -0,0 +1,21 @@
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::TextToImageModel;
use enums::common::model_type::ModelType;
pub fn text_to_image_model_to_model_type(model: TextToImageModel) -> ModelType {
match model {
TextToImageModel::Flux1Dev => ModelType::Flux1Dev,
TextToImageModel::Flux1Schnell => ModelType::Flux1Schnell,
TextToImageModel::FluxPro11 => ModelType::FluxPro11,
TextToImageModel::FluxPro11Ultra => ModelType::FluxPro11Ultra,
TextToImageModel::GrokImage => ModelType::GrokImage,
TextToImageModel::Recraft3 => ModelType::Recraft3,
TextToImageModel::GptImage1 => ModelType::GptImage1,
TextToImageModel::GptImage1p5 => ModelType::GptImage1p5,
TextToImageModel::Gemini25Flash => ModelType::NanoBanana,
TextToImageModel::NanoBanana => ModelType::NanoBanana,
TextToImageModel::NanoBananaPro => ModelType::NanoBananaPro,
TextToImageModel::Seedream4 => ModelType::Seedream4,
TextToImageModel::Seedream4p5 => ModelType::Seedream4p5,
TextToImageModel::Midjourney => ModelType::Midjourney,
}
}