cleanup / remove some old code that isn't used
This commit is contained in:
parent
b82fbe0147
commit
d77d243dcd
|
|
@ -1,4 +1,3 @@
|
|||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::EnqueueEditImageSuccessResponse;
|
||||
use crate::commands::response::shorthand::ResponseOrErrorMessage;
|
||||
use crate::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason};
|
||||
use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason};
|
||||
use crate::commands::background_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::enqueue::image_bg_removal::artcraft::handle_generic_bg_removal_artcraft::handle_generic_bg_removal_artcraft;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::generate::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::background_removal::artcraft::handle_generic_bg_removal_artcraft::handle_generic_bg_removal_artcraft;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::commands::response::shorthand::{Response, ResponseOrErrorType};
|
||||
use crate::commands::response::success_response_wrapper::SerializeMarker;
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_client::api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::{FluxProKontextMaxEditImageNumImages, FluxProKontextMaxEditImageRequest};
|
||||
use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use artcraft_client::endpoints::generate::image::edit::flux_pro_kontext_max_edit_image::flux_pro_kontext_max_edit_image;
|
||||
use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_artcraft_flux_kontext_edit(
|
||||
request: &EnqueueEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft flux kontext max edit (edit) ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(FluxProKontextMaxEditImageNumImages::One),
|
||||
Some(2) => Some(FluxProKontextMaxEditImageNumImages::Two),
|
||||
Some(3) => Some(FluxProKontextMaxEditImageNumImages::Three),
|
||||
Some(4) => Some(FluxProKontextMaxEditImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
let mut maybe_media_token = None;
|
||||
|
||||
let list_count = request.image_media_tokens
|
||||
.as_ref()
|
||||
.map(|tokens| tokens.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
if request.scene_image_media_token.is_some() && list_count > 0 {
|
||||
return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments(
|
||||
"Cannot specify both scene_image_media_token and image_media_tokens for Flux Kontext Max".to_string())));
|
||||
}
|
||||
|
||||
if list_count > 1 {
|
||||
return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments(
|
||||
"Cannot specify more than one image in image_media_tokens for Flux Kontext Max".to_string())));
|
||||
}
|
||||
|
||||
if let Some(media_token) = &request.scene_image_media_token {
|
||||
maybe_media_token = Some(media_token.clone());
|
||||
}
|
||||
|
||||
let maybe_list_media_token = request.image_media_tokens
|
||||
.as_ref()
|
||||
.map(|tokens| tokens.first())
|
||||
.flatten();
|
||||
|
||||
if let Some(media_token) = maybe_list_media_token {
|
||||
maybe_media_token = Some(media_token.clone());
|
||||
}
|
||||
|
||||
let media_token = match maybe_media_token {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments(
|
||||
"No input image specified for Flux Kontext Max edit".to_string())));
|
||||
},
|
||||
};
|
||||
|
||||
let request = FluxProKontextMaxEditImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_token: media_token,
|
||||
num_images,
|
||||
};
|
||||
|
||||
let result = flux_pro_kontext_max_edit_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft gpt-image-1. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::FluxProKontextMax),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const MAX_IMAGES: usize = 4;
|
||||
|
||||
pub async fn handle_artcraft_gpt_image_1_edit(
|
||||
request: &EnqueueEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft gpt-image-1 (edit) ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let image_quality = request.image_quality
|
||||
.map(|quality| match quality {
|
||||
EditImageQuality::Auto => GptImage1EditImageImageQuality::Auto,
|
||||
EditImageQuality::High => GptImage1EditImageImageQuality::High,
|
||||
EditImageQuality::Medium => GptImage1EditImageImageQuality::Medium,
|
||||
EditImageQuality::Low => GptImage1EditImageImageQuality::Low,
|
||||
});
|
||||
|
||||
let image_size = request.aspect_ratio
|
||||
.map(|size| match size {
|
||||
EditImageSize::Auto => GptImage1EditImageImageSize::Square,
|
||||
EditImageSize::Square => GptImage1EditImageImageSize::Square,
|
||||
EditImageSize::Tall => GptImage1EditImageImageSize::Vertical,
|
||||
EditImageSize::Wide => GptImage1EditImageImageSize::Horizontal,
|
||||
});
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(GptImage1EditImageNumImages::One),
|
||||
Some(2) => Some(GptImage1EditImageNumImages::Two),
|
||||
Some(3) => Some(GptImage1EditImageNumImages::Three),
|
||||
Some(4) => Some(GptImage1EditImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
let mut media_tokens = Vec::with_capacity(10);
|
||||
|
||||
if let Some(scene_image_media_token) = request.scene_image_media_token.clone() {
|
||||
media_tokens.push(scene_image_media_token);
|
||||
}
|
||||
|
||||
if let Some(image_media_tokens) = request.image_media_tokens.as_ref() {
|
||||
media_tokens.extend_from_slice(image_media_tokens);
|
||||
}
|
||||
|
||||
if media_tokens.len() > MAX_IMAGES {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
|
||||
min: 1,
|
||||
max: MAX_IMAGES as u32,
|
||||
provided: media_tokens.len() as u32,
|
||||
}));
|
||||
}
|
||||
|
||||
let request = GptImage1EditImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_tokens: Some(media_tokens),
|
||||
image_size,
|
||||
num_images,
|
||||
image_quality,
|
||||
};
|
||||
|
||||
let result = gpt_image_1_edit_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft gpt-image-1. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::GptImage1),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::deprecated::image_edit::artcraft::handle_artcraft_flux_kontext_edit::handle_artcraft_flux_kontext_edit;
|
||||
use crate::commands::deprecated::image_edit::artcraft::handle_artcraft_gpt_image_1_edit::handle_artcraft_gpt_image_1_edit;
|
||||
use crate::commands::deprecated::image_edit::artcraft::handle_image_edit_artcraft_via_router::handle_image_edit_artcraft_via_router;
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use router::api::router_image_model::RouterImageModel;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_image_edit_artcraft(
|
||||
model: ImageEditModel,
|
||||
request: &EnqueueEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<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_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBanana, GenerationModel::NanoBanana).await
|
||||
},
|
||||
ImageEditModel::NanoBanana2 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBanana2, GenerationModel::NanoBanana2).await,
|
||||
ImageEditModel::NanoBananaPro => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBananaPro, GenerationModel::NanoBananaPro).await,
|
||||
ImageEditModel::GptImage1 => handle_artcraft_gpt_image_1_edit(request, app, app_data_root, app_env_configs, storyteller_creds_manager).await,
|
||||
ImageEditModel::GptImage1p5 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::GptImage1p5, GenerationModel::GptImage1p5).await,
|
||||
ImageEditModel::Seedream4 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream4, GenerationModel::Seedream4).await,
|
||||
ImageEditModel::Seedream4p5 => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream4p5, GenerationModel::Seedream4p5).await,
|
||||
ImageEditModel::Seedream5Lite => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream5Lite, GenerationModel::Seedream5Lite).await,
|
||||
ImageEditModel::QwenEdit2511Angles => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::QwenEdit2511Angles, GenerationModel::QwenEdit2511Angles).await,
|
||||
ImageEditModel::Flux2LoraAngles => handle_image_edit_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Flux2LoraAngles, GenerationModel::Flux2LoraAngles).await,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
use crate::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio as DesktopCommonAspectRatio;
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{
|
||||
EditImageResolution, EditImageSize, EnqueueEditImageCommand,
|
||||
};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use router::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use router::api::router_image_model::RouterImageModel;
|
||||
use router::api::router_resolution::RouterResolution;
|
||||
use router::api::image_list_ref::ImageListRef;
|
||||
use router::api::router_provider::RouterProvider;
|
||||
use router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use router::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use router::client::router_client::RouterClient;
|
||||
use router::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder;
|
||||
use router::generate::generate_image::image_generation_draft_or_request::ImageGenerationDraftOrRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use log::{error, info};
|
||||
|
||||
pub(super) const MAX_EDIT_IMAGES: usize = 10;
|
||||
|
||||
pub(super) async fn handle_image_edit_artcraft_via_router(
|
||||
request: &EnqueueEditImageCommand,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
model: RouterImageModel,
|
||||
generation_model: GenerationModel,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials)),
|
||||
};
|
||||
|
||||
let client = RouterClient::Artcraft(RouterArtcraftClient::new(
|
||||
app_env_configs.storyteller_host.clone(),
|
||||
creds,
|
||||
));
|
||||
|
||||
let mut media_tokens = Vec::with_capacity(MAX_EDIT_IMAGES);
|
||||
|
||||
if let Some(scene_token) = request.scene_image_media_token.clone() {
|
||||
media_tokens.push(scene_token);
|
||||
}
|
||||
if let Some(tokens) = request.image_media_tokens.as_ref() {
|
||||
media_tokens.extend_from_slice(tokens);
|
||||
}
|
||||
|
||||
if media_tokens.len() > MAX_EDIT_IMAGES {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
|
||||
min: 1,
|
||||
max: MAX_EDIT_IMAGES as u32,
|
||||
provided: media_tokens.len() as u32,
|
||||
}));
|
||||
}
|
||||
|
||||
let image_inputs = if media_tokens.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(ImageListRef::MediaFileTokens(media_tokens))
|
||||
};
|
||||
|
||||
let aspect_ratio = get_aspect_ratio_edit(request);
|
||||
let resolution = get_resolution_edit(request);
|
||||
|
||||
let router_request = GenerateImageRequestBuilder {
|
||||
model,
|
||||
provider: RouterProvider::Artcraft,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_inputs,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
quality: None, // TODO(bt): Add quality
|
||||
image_batch_count: request.image_count.map(|n| n as u16),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
horizontal_angle: request.horizontal_angle,
|
||||
vertical_angle: request.vertical_angle,
|
||||
zoom: request.zoom,
|
||||
};
|
||||
|
||||
let dor = router_request.build2()?;
|
||||
|
||||
info!("Image Generation Request: {:?}", dor);
|
||||
|
||||
let request = match dor {
|
||||
ImageGenerationDraftOrRequest::Request(req) => req,
|
||||
// Artcraft never returns a Draft — image-token resolution happens server-side.
|
||||
// The only draft-producing models today are Kinovi-Midjourney variants,
|
||||
// which are routed to a separate provider.
|
||||
ImageGenerationDraftOrRequest::Draft(_) => unreachable!(
|
||||
"Artcraft router should never produce a draft for image edits"
|
||||
),
|
||||
};
|
||||
|
||||
let response = match request.send_request(&client).await {
|
||||
Ok(resp) => {
|
||||
info!("Successfully enqueued.");
|
||||
resp
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_id = response
|
||||
.get_artcraft_payload()
|
||||
.map(|p| p.inference_job_token.to_string())
|
||||
.ok_or(GenerateError::ResponseHadNoJobTokens)?;
|
||||
|
||||
info!("Job token: {}", job_id);
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(generation_model),
|
||||
provider: GenerationProvider::Artcraft,
|
||||
provider_job_id: Some(job_id),
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio_edit(request: &EnqueueEditImageCommand) -> Option<RouterAspectRatio> {
|
||||
if let Some(ar) = request.common_aspect_ratio {
|
||||
return Some(convert_desktop_aspect_ratio(ar));
|
||||
}
|
||||
if let Some(ar) = request.aspect_ratio {
|
||||
return Some(match ar {
|
||||
EditImageSize::Auto => RouterAspectRatio::Auto,
|
||||
EditImageSize::Square => RouterAspectRatio::Square,
|
||||
EditImageSize::Wide => RouterAspectRatio::Wide,
|
||||
EditImageSize::Tall => RouterAspectRatio::Tall,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_resolution_edit(request: &EnqueueEditImageCommand) -> Option<RouterResolution> {
|
||||
request.image_resolution.map(|res| match res {
|
||||
EditImageResolution::OneK => RouterResolution::OneK,
|
||||
EditImageResolution::TwoK => RouterResolution::TwoK,
|
||||
EditImageResolution::FourK => RouterResolution::FourK,
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_desktop_aspect_ratio(ar: DesktopCommonAspectRatio) -> RouterAspectRatio {
|
||||
match ar {
|
||||
DesktopCommonAspectRatio::Auto => RouterAspectRatio::Auto,
|
||||
DesktopCommonAspectRatio::Square => RouterAspectRatio::Square,
|
||||
DesktopCommonAspectRatio::WideThreeByTwo => RouterAspectRatio::WideThreeByTwo,
|
||||
DesktopCommonAspectRatio::WideFourByThree => RouterAspectRatio::WideFourByThree,
|
||||
DesktopCommonAspectRatio::WideFiveByFour => RouterAspectRatio::WideFiveByFour,
|
||||
DesktopCommonAspectRatio::WideSixteenByNine => RouterAspectRatio::WideSixteenByNine,
|
||||
DesktopCommonAspectRatio::WideTwentyOneByNine => RouterAspectRatio::WideTwentyOneByNine,
|
||||
DesktopCommonAspectRatio::TallTwoByThree => RouterAspectRatio::TallTwoByThree,
|
||||
DesktopCommonAspectRatio::TallThreeByFour => RouterAspectRatio::TallThreeByFour,
|
||||
DesktopCommonAspectRatio::TallFourByFive => RouterAspectRatio::TallFourByFive,
|
||||
DesktopCommonAspectRatio::TallNineBySixteen => RouterAspectRatio::TallNineBySixteen,
|
||||
DesktopCommonAspectRatio::TallNineByTwentyOne => RouterAspectRatio::TallNineByTwentyOne,
|
||||
DesktopCommonAspectRatio::Wide => RouterAspectRatio::Wide,
|
||||
DesktopCommonAspectRatio::Tall => RouterAspectRatio::Tall,
|
||||
DesktopCommonAspectRatio::Auto2k => RouterAspectRatio::Auto2k,
|
||||
DesktopCommonAspectRatio::Auto4k => RouterAspectRatio::Auto4k,
|
||||
DesktopCommonAspectRatio::SquareHd => RouterAspectRatio::SquareHd,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
mod handle_image_edit_artcraft_via_router;
|
||||
mod handle_artcraft_flux_kontext_edit;
|
||||
mod handle_artcraft_gpt_image_1_edit;
|
||||
|
||||
pub (super) mod handle_image_edit_artcraft;
|
||||
|
|
@ -1,424 +0,0 @@
|
|||
use crate::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api_adapters::quality::common_quality::CommonQuality;
|
||||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::deprecated::image_edit::artcraft::handle_image_edit_artcraft::handle_image_edit_artcraft;
|
||||
use crate::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::commands::deprecated::image_edit::sora::handle_image_edit_sora::handle_image_edit_sora;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::commands::response::shorthand::{Response, ResponseOrErrorType};
|
||||
use crate::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::events::sendable_event_trait::SendableEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::data_dir::subdirectory::trait_data_subdir::DataSubdir;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::state::task_database::TaskDatabase;
|
||||
use crate::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use artcraft_client::error::storyteller_error::StorytellerError;
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use errors::AnyhowError;
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sqlite_database::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ImageEditModel {
|
||||
#[serde(rename = "flux_pro_kontext_max")]
|
||||
FluxProKontextMax,
|
||||
|
||||
#[serde(rename = "gemini_25_flash")]
|
||||
Gemini25Flash,
|
||||
|
||||
#[serde(rename = "nano_banana")]
|
||||
NanoBanana,
|
||||
|
||||
#[serde(rename = "nano_banana_2")]
|
||||
NanoBanana2,
|
||||
|
||||
#[serde(rename = "nano_banana_pro")]
|
||||
NanoBananaPro,
|
||||
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
|
||||
#[serde(rename = "gpt_image_1p5")]
|
||||
GptImage1p5,
|
||||
|
||||
#[serde(rename = "seedream_4")]
|
||||
Seedream4,
|
||||
|
||||
#[serde(rename = "seedream_4p5")]
|
||||
Seedream4p5,
|
||||
|
||||
#[serde(rename = "seedream_5_lite")]
|
||||
Seedream5Lite,
|
||||
|
||||
#[serde(rename = "qwen_edit_2511_angles")]
|
||||
QwenEdit2511Angles,
|
||||
|
||||
#[serde(rename = "flux_2_lora_angles")]
|
||||
Flux2LoraAngles,
|
||||
|
||||
// #[serde(rename = "qwen")]
|
||||
// Qwen,
|
||||
//
|
||||
// #[serde(rename = "seededit_3")]
|
||||
// SeedEdit3,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EnqueueEditImageCommand {
|
||||
/// The provider to use (defaults to Artcraft/Storyteller).
|
||||
/// Not all (provider, model) combinations are valid.
|
||||
pub provider: Option<GenerationProvider>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<ImageEditModel>,
|
||||
|
||||
/// Images to use for the image edit.
|
||||
/// The first image is typically a 2D canvas or 3D stage, but doesn't have to be.
|
||||
/// There must be at least one image.
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// If set, this becomes the first image in the image media tokens (pushing back
|
||||
/// each of the `image_media_tokens` by one).
|
||||
/// This is useful if we want to do prompt engineering.
|
||||
pub scene_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// The user's image generation prompt.
|
||||
pub prompt: String,
|
||||
|
||||
/// Turn off the system prompt.
|
||||
pub disable_system_prompt: Option<bool>,
|
||||
|
||||
/// Number of images to generate.
|
||||
pub image_count: Option<u32>,
|
||||
|
||||
/// Aspect ratio.
|
||||
#[deprecated(note="use common_aspect_ratio")]
|
||||
pub aspect_ratio: Option<EditImageSize>,
|
||||
|
||||
/// New field for aspect ratio.
|
||||
/// Not all models support each of these aspect ratios, but we can choose or interpolate sensibly.
|
||||
pub common_aspect_ratio: Option<CommonAspectRatio>,
|
||||
|
||||
/// Image quality.
|
||||
pub image_quality: Option<EditImageQuality>,
|
||||
|
||||
/// Image resolution
|
||||
pub image_resolution: Option<EditImageResolution>,
|
||||
|
||||
/// Quality.
|
||||
/// Tends to be used by OpenAI models.
|
||||
pub quality: Option<CommonQuality>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Only for image angle edit models.
|
||||
pub horizontal_angle: Option<f64>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Only for image angle edit models.
|
||||
pub vertical_angle: Option<f64>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Only for image angle edit models.
|
||||
pub zoom: Option<f64>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Name of the frontend caller.
|
||||
/// We'll use this to selectively trigger events.
|
||||
pub frontend_caller: Option<TauriCommandCaller>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined identifier that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_id: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined payload that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EditImageSize {
|
||||
Auto,
|
||||
Square,
|
||||
Wide,
|
||||
Tall,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EditImageQuality {
|
||||
Auto,
|
||||
High,
|
||||
Medium,
|
||||
Low,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EditImageResolution {
|
||||
/// 1K - nano banana pro
|
||||
OneK,
|
||||
/// 2K - nano banana pro
|
||||
TwoK,
|
||||
/// 4K - nano banana pro
|
||||
FourK,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueEditImageErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
|
||||
/// No model available for image generation
|
||||
NoProviderAvailable,
|
||||
|
||||
/// Generic bad request error
|
||||
BadRequest,
|
||||
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
|
||||
/// The user is sending too many requests
|
||||
TooManyConcurrentTasks,
|
||||
/// User is not logged into Sora!
|
||||
SoraLoginRequired,
|
||||
/// The user needs to create a Sora account
|
||||
SoraUsernameNotYetCreated,
|
||||
/// The Sora service is having problems. Try again soon.
|
||||
SoraIsHavingProblems,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueEditImageSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueEditImageSuccessResponse {}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_edit_image_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueEditImageCommand,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> ResponseOrErrorType<EnqueueEditImageSuccessResponse, EnqueueEditImageErrorType> {
|
||||
|
||||
info!("enqueue_edit_image_command called; image media tokens : {:?}, full request: {:?}",
|
||||
&request.image_media_tokens, &request);
|
||||
|
||||
let result = handle_request(
|
||||
&request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&app_env_configs,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("Error enqueuing contextual edit image: {:?}", err);
|
||||
|
||||
notify_frontend_of_errors(&app, &err).await;
|
||||
|
||||
// TODO: Derive from err. Make service provider optional.
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Artcraft, // FIXME: This is wrong.
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Err(error_to_tauri_response(err))
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: event.model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
CreditsBalanceChangedEvent{}.send_infallible(&app);
|
||||
|
||||
Ok(EnqueueEditImageSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_request(
|
||||
request: &EnqueueEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let model = match request.model {
|
||||
Some(model) => model,
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified())
|
||||
}
|
||||
};
|
||||
|
||||
let provider = request.provider
|
||||
.unwrap_or(GenerationProvider::Artcraft);
|
||||
|
||||
info!("edit image with {:?} via provider {:?}", &model, &provider);
|
||||
|
||||
let mut success_event = match provider {
|
||||
GenerationProvider::Artcraft => {
|
||||
handle_image_edit_artcraft(
|
||||
model,
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager,
|
||||
).await?
|
||||
}
|
||||
GenerationProvider::Sora => {
|
||||
handle_image_edit_sora(
|
||||
model,
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
}
|
||||
_ => {
|
||||
return Err(GenerateError::BadProviderForModel {
|
||||
provider,
|
||||
model: image_edit_model_to_model_type(model),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
match request.frontend_caller {
|
||||
Some(TauriCommandCaller::ImageEditor) => {
|
||||
// NB: Certain image generations enqueued in the inpainting editor are classed
|
||||
// as "image_generation" and won't update the inpainting UI. This is a hack to
|
||||
// temporarily fix the UI event binding.
|
||||
// TODO: The frontend shouldn't require us to change the type to inpainting.
|
||||
success_event.task_type = TaskType::ImageInpaintEdit;
|
||||
}
|
||||
_ => {} // fall-through
|
||||
}
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database_with_frontend_payload(
|
||||
task_database,
|
||||
request.frontend_caller.clone(),
|
||||
request.frontend_subscriber_id.as_deref(),
|
||||
request.frontend_subscriber_payload.as_deref(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
// TODO: We don't know that this is strictly the edit page. It could be the stage. It might also be nice to distinguish inpainting.
|
||||
let num_images = request.image_count.map(|count| count as u16).unwrap_or(1);
|
||||
if let Err(err) = artcraft_usage_tracker.record_image_generation(num_images, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::EditPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
fn error_to_tauri_response(error: GenerateError) -> CommandErrorResponseWrapper<EnqueueEditImageErrorType, ()> {
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueEditImageErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.".to_string();
|
||||
|
||||
match error {
|
||||
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueEditImageErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation".to_string();
|
||||
}
|
||||
GenerateError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueEditImageErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for image generation".to_string();
|
||||
}
|
||||
GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { min, max, requested }) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueEditImageErrorType::BadRequest;
|
||||
error_message = format!("Invalid number of images requested ({}). Must be between {} and {}", requested, min, max);
|
||||
|
||||
}
|
||||
GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages{ min, max, provided }) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueEditImageErrorType::BadRequest;
|
||||
error_message = format!("Invalid number of input images ({}). Must be between {} and {}", provided, min, max);
|
||||
}
|
||||
_ => {} // Fall-through for now
|
||||
}
|
||||
|
||||
CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::ImageEditModel;
|
||||
|
||||
pub fn image_edit_model_to_model_type(model: ImageEditModel) -> CommonModelType {
|
||||
match model {
|
||||
ImageEditModel::FluxProKontextMax => CommonModelType::FluxProKontextMax,
|
||||
ImageEditModel::Gemini25Flash => CommonModelType::NanoBanana,
|
||||
ImageEditModel::NanoBanana => CommonModelType::NanoBanana,
|
||||
ImageEditModel::NanoBanana2 => CommonModelType::NanoBanana2,
|
||||
ImageEditModel::NanoBananaPro => CommonModelType::NanoBananaPro,
|
||||
ImageEditModel::GptImage1 => CommonModelType::GptImage1,
|
||||
ImageEditModel::GptImage1p5 => CommonModelType::GptImage1p5,
|
||||
ImageEditModel::Seedream4 => CommonModelType::Seedream4,
|
||||
ImageEditModel::Seedream4p5 => CommonModelType::Seedream4p5,
|
||||
ImageEditModel::Seedream5Lite => CommonModelType::Seedream5Lite,
|
||||
ImageEditModel::QwenEdit2511Angles => CommonModelType::QwenEdit2511Angles,
|
||||
ImageEditModel::Flux2LoraAngles => CommonModelType::Flux2LoraAngles,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
pub (super) mod artcraft;
|
||||
pub (super) mod sora;
|
||||
pub mod enqueue_edit_image_command;
|
||||
pub mod image_edit_models;
|
||||
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EnqueueEditImageCommand, ImageEditModel};
|
||||
use crate::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::commands::deprecated::image_edit::sora::handle_sora_gpt_image_1_edit::handle_sora_gpt_image_1_edit;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_image_edit_sora(
|
||||
model: ImageEditModel,
|
||||
request: &EnqueueEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<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,183 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageSize, EnqueueEditImageCommand, EnqueueEditImageErrorType};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::data_dir::subdirectory::trait_data_subdir::DataSubdir;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use errors::AnyhowResult;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use artcraft_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const MAX_IMAGES: usize = 4;
|
||||
const SORA_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
const SORA_IMAGE_REMIX_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
const DEFAULT_ASPECT_RATIO : EditImageSize = EditImageSize::Square;
|
||||
|
||||
pub async fn handle_sora_gpt_image_1_edit(
|
||||
request: &EnqueueEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let mut creds = match sora_creds_manager.get_credentials() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
warn!("No Sora credentials found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to get Sora credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
}
|
||||
};
|
||||
|
||||
let mut media_tokens = Vec::with_capacity(10);
|
||||
|
||||
if let Some(scene_image_media_token) = request.scene_image_media_token.clone() {
|
||||
media_tokens.push(scene_image_media_token);
|
||||
}
|
||||
|
||||
if let Some(image_media_tokens) = request.image_media_tokens.as_ref() {
|
||||
media_tokens.extend_from_slice(image_media_tokens);
|
||||
}
|
||||
|
||||
if media_tokens.len() > MAX_IMAGES {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
|
||||
min: 1,
|
||||
max: MAX_IMAGES as u32,
|
||||
provided: media_tokens.len() as u32,
|
||||
}));
|
||||
}
|
||||
|
||||
info!("Calling get media file API: {:?}", app_env_configs.storyteller_host);
|
||||
|
||||
let mut files_to_upload_to_sora = Vec::with_capacity(10);
|
||||
|
||||
// TODO(bt,2025-07-07): This is inefficient. Cache and parallelize this.
|
||||
for media_token in media_tokens.iter() {
|
||||
info!("Using media token: {:?}", media_token);
|
||||
|
||||
let response = get_media_file(
|
||||
&app_env_configs.storyteller_host,
|
||||
media_token
|
||||
).await?;
|
||||
|
||||
let media_file_url = &response.media_file.media_links.cdn_url;
|
||||
let extension_with_dot = get_url_file_extension(media_file_url)
|
||||
.map(|ext| format!(".{}", ext))
|
||||
.unwrap_or_else(|| ".png".to_string());
|
||||
|
||||
let filename = format!("{}{}", response.media_file.token.as_str(), extension_with_dot);
|
||||
let filename = app_data_root.downloads_dir().path().join(&filename);
|
||||
|
||||
simple_http_download(&media_file_url, &filename).await?;
|
||||
|
||||
files_to_upload_to_sora.push(filename);
|
||||
}
|
||||
|
||||
let credential_updated = maybe_upgrade_or_renew_session(&mut creds)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upgrade or renew session: {:?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
if credential_updated {
|
||||
info!("Storing updated credentials");
|
||||
sora_creds_manager.set_credentials(&creds)?;
|
||||
}
|
||||
|
||||
let mut sora_media_tokens = Vec::with_capacity(files_to_upload_to_sora.len());
|
||||
|
||||
for (i, file_path) in files_to_upload_to_sora.iter().enumerate() {
|
||||
info!("Uploading image {} of {}...", (i+1), files_to_upload_to_sora.len());
|
||||
|
||||
let (response, maybe_new_credentials) =
|
||||
image_upload_from_file_with_session_auto_renew(ImageUploadFromFileAutoRenewRequest {
|
||||
file_path,
|
||||
credentials: &creds,
|
||||
request_timeout: Some(SORA_IMAGE_UPLOAD_TIMEOUT), // TODO: Centralize and make configurable.
|
||||
}).await?;
|
||||
|
||||
if let Some(new_creds) = maybe_new_credentials {
|
||||
info!("Storing updated credentials.");
|
||||
sora_creds_manager.set_credentials(&new_creds)?;
|
||||
creds = new_creds;
|
||||
}
|
||||
|
||||
sora_media_tokens.push(response.id);
|
||||
}
|
||||
|
||||
let aspect_ratio = request.aspect_ratio.unwrap_or(DEFAULT_ASPECT_RATIO);
|
||||
|
||||
let aspect_ratio = match aspect_ratio {
|
||||
EditImageSize::Auto => ImageSize::Square,
|
||||
EditImageSize::Square => ImageSize::Square,
|
||||
EditImageSize::Wide => ImageSize::Wide,
|
||||
EditImageSize::Tall => ImageSize::Tall,
|
||||
};
|
||||
|
||||
info!("Calling Sora image generation...");
|
||||
|
||||
// TODO(bt,2025-04-21): Download media tokens.
|
||||
// Note: This is incredibly inefficient. We should keep a local cache.
|
||||
// Also, if they've already been uploaded to OpenAI, we shouldn't continue to re-upload.
|
||||
|
||||
let (response, maybe_new_creds) =
|
||||
image_remix_with_session_auto_renew(ImageRemixAutoRenewRequest {
|
||||
prompt: request.prompt.to_string(),
|
||||
num_images: NumImages::One,
|
||||
image_size: aspect_ratio,
|
||||
sora_media_tokens: sora_media_tokens.clone(),
|
||||
credentials: &creds,
|
||||
request_timeout: Some(SORA_IMAGE_REMIX_TIMEOUT),
|
||||
}).await?;
|
||||
|
||||
if let Some(new_creds) = maybe_new_creds {
|
||||
info!("Storing updated credentials.");
|
||||
sora_creds_manager.set_credentials(&new_creds)?;
|
||||
}
|
||||
|
||||
info!("New Sora Task ID: {:?} ", response.task_id);
|
||||
|
||||
sora_task_queue.insert(&response.task_id)?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(GenerationModel::GptImage1),
|
||||
provider: GenerationProvider::Sora,
|
||||
provider_job_id: Some(response.task_id.to_string()),
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
mod handle_sora_gpt_image_1_edit;
|
||||
pub (super) mod handle_image_edit_sora;
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
use artcraft_client::api_defs::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::{FluxDevJuggernautInpaintImageNumImages, FluxDevJuggernautInpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask;
|
||||
use log::{error, info};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use artcraft_client::endpoints::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::flux_dev_juggernaut_inpaint_image;
|
||||
use artcraft_client::endpoints::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image;
|
||||
use artcraft_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs};
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_artcraft_flux_dev_juggernaut_inpaint(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
let image_media_token = match &request.image_media_token {
|
||||
Some(token) => token.clone(),
|
||||
None => {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
},
|
||||
};
|
||||
|
||||
let mask_media_token = get_mask(
|
||||
request,
|
||||
app_env_configs,
|
||||
&creds,
|
||||
).await?;
|
||||
|
||||
info!("Calling Artcraft flux dev juggernaut inpaint ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(FluxDevJuggernautInpaintImageNumImages::One),
|
||||
Some(2) => Some(FluxDevJuggernautInpaintImageNumImages::Two),
|
||||
Some(3) => Some(FluxDevJuggernautInpaintImageNumImages::Three),
|
||||
Some(4) => Some(FluxDevJuggernautInpaintImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
let request = FluxDevJuggernautInpaintImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_token,
|
||||
mask_media_token,
|
||||
num_images,
|
||||
};
|
||||
|
||||
let result = flux_dev_juggernaut_inpaint_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft flux dev juggernaut inpaint. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft flux dev juggernaut inpaint: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::FluxDevJuggernaut),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageInpaintEdit,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_mask(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds: &StorytellerCredentialSet,
|
||||
) -> Result<MediaFileToken, GenerateError> {
|
||||
|
||||
if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() {
|
||||
return Err(GenerateError::both_image_mask_media_token_and_bytes_supplied());
|
||||
}
|
||||
|
||||
if let Some(token) = request.mask_image_media_token.as_ref() {
|
||||
return Ok(token.clone());
|
||||
};
|
||||
|
||||
let image_bytes = request.mask_image_raw_bytes.as_ref()
|
||||
.ok_or(GenerateError::required_source_image_mask_not_provided())?;
|
||||
|
||||
let image_bytes = normalize_image_bytes_to_flux_mask(image_bytes)
|
||||
.map_err(|err| {
|
||||
error!("Failed to convert image bytes to png: {:?}", err);
|
||||
GenerateError::AnyhowError(anyhow!("Failed to convert image bytes to png mask"))
|
||||
})?;
|
||||
|
||||
info!("Uploading image media file from bytes...");
|
||||
|
||||
let result = upload_image_media_file_from_bytes(UploadImageBytesArgs {
|
||||
api_host: &app_env_configs.storyteller_host,
|
||||
maybe_creds: Some(&storyteller_creds),
|
||||
image_bytes: image_bytes.0,
|
||||
image_type: ImageType::Png,
|
||||
is_intermediate_system_file: true,
|
||||
maybe_generation_provider: None,
|
||||
}).await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upload image media file: {:?}", err);
|
||||
GenerateError::from(err)
|
||||
})?;
|
||||
|
||||
Ok(result.media_file_token)
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
use artcraft_client::api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask;
|
||||
use log::{error, info};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use artcraft_client::endpoints::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image;
|
||||
use artcraft_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs};
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_artcraft_flux_pro_1_inpaint(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
let image_media_token = match &request.image_media_token {
|
||||
Some(token) => token.clone(),
|
||||
None => {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
},
|
||||
};
|
||||
|
||||
let mask_media_token = get_mask(
|
||||
request,
|
||||
app_env_configs,
|
||||
&creds,
|
||||
).await?;
|
||||
|
||||
info!("Calling Artcraft flux pro 1 inpaint ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(FluxPro1InpaintImageNumImages::One),
|
||||
Some(2) => Some(FluxPro1InpaintImageNumImages::Two),
|
||||
Some(3) => Some(FluxPro1InpaintImageNumImages::Three),
|
||||
Some(4) => Some(FluxPro1InpaintImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
let request = FluxPro1InpaintImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_token,
|
||||
mask_media_token,
|
||||
num_images,
|
||||
};
|
||||
|
||||
let result = flux_pro_1_inpaint_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft flux pro 1 inpaint. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft flux pro 1 inpaint: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::FluxPro1),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageInpaintEdit,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_mask(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds: &StorytellerCredentialSet,
|
||||
) -> Result<MediaFileToken, GenerateError> {
|
||||
|
||||
if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() {
|
||||
return Err(GenerateError::both_image_mask_media_token_and_bytes_supplied());
|
||||
}
|
||||
|
||||
if let Some(token) = request.mask_image_media_token.as_ref() {
|
||||
return Ok(token.clone());
|
||||
};
|
||||
|
||||
let image_bytes = request.mask_image_raw_bytes.as_ref()
|
||||
.ok_or(GenerateError::required_source_image_mask_not_provided())?;
|
||||
|
||||
let image_bytes = normalize_image_bytes_to_flux_mask(image_bytes)
|
||||
.map_err(|err| {
|
||||
error!("Failed to convert image bytes to png: {:?}", err);
|
||||
GenerateError::AnyhowError(anyhow!("could not encode image mask to png"))
|
||||
})?;
|
||||
|
||||
info!("Uploading image media file from bytes...");
|
||||
|
||||
let result = upload_image_media_file_from_bytes(UploadImageBytesArgs {
|
||||
api_host: &app_env_configs.storyteller_host,
|
||||
maybe_creds: Some(&storyteller_creds),
|
||||
image_bytes: image_bytes.0,
|
||||
image_type: ImageType::Png,
|
||||
is_intermediate_system_file: true,
|
||||
maybe_generation_provider: None,
|
||||
}).await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upload image media file: {:?}", err);
|
||||
GenerateError::from(err)
|
||||
})?;
|
||||
|
||||
Ok(result.media_file_token)
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_client::api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::{FluxProKontextMaxEditImageNumImages, FluxProKontextMaxEditImageRequest};
|
||||
use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
use artcraft_client::api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask;
|
||||
use log::{error, info};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::endpoints::generate::image::edit::flux_pro_kontext_max_edit_image::flux_pro_kontext_max_edit_image;
|
||||
use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use artcraft_client::endpoints::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image;
|
||||
use artcraft_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs};
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_artcraft_flux_pro_kontext_inpaint(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
let image_media_token = match &request.image_media_token {
|
||||
Some(token) => token.clone(),
|
||||
None => {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
},
|
||||
};
|
||||
|
||||
//let image_media_file = get_media_file(
|
||||
// &app_env_configs.storyteller_host,
|
||||
// &image_media_token,
|
||||
//).await.map_err(|err| {
|
||||
// error!("Failed to get media file for image: {:?}", err);
|
||||
// InternalImageInpaintError::StorytellerError(err)
|
||||
//})?;
|
||||
|
||||
// TODO: Experiment in seeing if we can use the mask in any useful way.
|
||||
|
||||
// let mask_media_token = get_mask(
|
||||
// request,
|
||||
// app_env_configs,
|
||||
// &creds,
|
||||
// ).await?;
|
||||
|
||||
info!("Calling Artcraft flux pro kontext inpaint ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(FluxProKontextMaxEditImageNumImages::One),
|
||||
Some(2) => Some(FluxProKontextMaxEditImageNumImages::Two),
|
||||
Some(3) => Some(FluxProKontextMaxEditImageNumImages::Three),
|
||||
Some(4) => Some(FluxProKontextMaxEditImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
let request = FluxProKontextMaxEditImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_token,
|
||||
num_images,
|
||||
};
|
||||
|
||||
let result = flux_pro_kontext_max_edit_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft flux pro kontext inpaint. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft flux pro kontext inpaint: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::FluxProKontextMax),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageInpaintEdit,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize, EnqueueEditImageCommand};
|
||||
use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_client::api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::{FluxProKontextMaxEditImageNumImages, FluxProKontextMaxEditImageRequest};
|
||||
use artcraft_client::api_defs::generate::image::edit::gemini_25_flash_edit_image::{Gemini25FlashEditImageNumImages, Gemini25FlashEditImageRequest};
|
||||
use artcraft_client::api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
use artcraft_client::api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask;
|
||||
use log::{error, info};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::endpoints::generate::image::edit::flux_pro_kontext_max_edit_image::flux_pro_kontext_max_edit_image;
|
||||
use artcraft_client::endpoints::generate::image::edit::gemini_25_flash_edit_image::gemini_25_flash_edit_image;
|
||||
use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use artcraft_client::endpoints::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image;
|
||||
use artcraft_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs};
|
||||
use artcraft_client::endpoints::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_artcraft_nano_banana_inpaint(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
let image_media_token = match &request.image_media_token {
|
||||
Some(token) => token.clone(),
|
||||
None => {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft gemini 2.5 flash inpaint ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(Gemini25FlashEditImageNumImages::One),
|
||||
Some(2) => Some(Gemini25FlashEditImageNumImages::Two),
|
||||
Some(3) => Some(Gemini25FlashEditImageNumImages::Three),
|
||||
Some(4) => Some(Gemini25FlashEditImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
let request = Gemini25FlashEditImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_tokens: Some(vec![image_media_token]),
|
||||
num_images,
|
||||
image_quality: None,
|
||||
};
|
||||
|
||||
let result = gemini_25_flash_edit_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft gemini 2.5 flash inpaint. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gemini 2.5 flash inpaint: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::Gemini25Flash),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageInpaintEdit,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::deprecated::image_inpaint::artcraft::handle_artcraft_flux_dev_juggernaut_inpaint::handle_artcraft_flux_dev_juggernaut_inpaint;
|
||||
use crate::commands::deprecated::image_inpaint::artcraft::handle_artcraft_flux_pro_1_inpaint::handle_artcraft_flux_pro_1_inpaint;
|
||||
use crate::commands::deprecated::image_inpaint::artcraft::handle_artcraft_flux_pro_kontext_inpaint::handle_artcraft_flux_pro_kontext_inpaint;
|
||||
use crate::commands::deprecated::image_inpaint::artcraft::handle_artcraft_nano_banana_inpaint::handle_artcraft_nano_banana_inpaint;
|
||||
use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::{EnqueueInpaintImageCommand, ImageInpaintModel};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_inpaint_artcraft(
|
||||
model: ImageInpaintModel,
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
pub (super) mod handle_inpaint_artcraft;
|
||||
|
||||
mod handle_artcraft_flux_pro_1_inpaint;
|
||||
mod handle_artcraft_flux_dev_juggernaut_inpaint;
|
||||
mod handle_artcraft_flux_pro_kontext_inpaint;
|
||||
mod handle_artcraft_nano_banana_inpaint;
|
||||
|
|
@ -1,348 +0,0 @@
|
|||
use crate::api_adapters::quality::common_quality::CommonQuality;
|
||||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::commands::deprecated::image_inpaint::artcraft::handle_inpaint_artcraft::handle_inpaint_artcraft;
|
||||
use crate::commands::deprecated::image_inpaint::inpaint_models::image_inpaint_model_to_model_type;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::commands::response::shorthand::{Response, ResponseOrErrorType};
|
||||
use crate::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::events::sendable_event_trait::SendableEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::data_dir::subdirectory::trait_data_subdir::DataSubdir;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::state::task_database::TaskDatabase;
|
||||
use crate::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use artcraft_client::error::storyteller_error::StorytellerError;
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use errors::AnyhowError;
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sqlite_database::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ImageInpaintModel {
|
||||
/*
|
||||
* Mask-based inpainting models
|
||||
*/
|
||||
|
||||
#[serde(rename = "flux_dev_juggernaut")]
|
||||
FluxDevJuggernaut,
|
||||
|
||||
#[serde(rename = "flux_pro_1")]
|
||||
FluxPro1,
|
||||
|
||||
/*
|
||||
* Non-inpainting, instructive editing models
|
||||
* NB: We're supporting these to keep the app simple and not over-complicate the javascript
|
||||
*/
|
||||
|
||||
#[serde(rename = "flux_pro_kontext_max")]
|
||||
FluxProKontextMax,
|
||||
|
||||
#[serde(rename = "gemini_25_flash")]
|
||||
Gemini25Flash,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EnqueueInpaintImageCommand {
|
||||
/// The provider to use (defaults to Artcraft/Storyteller).
|
||||
/// Not all (provider, model) combinations are valid.
|
||||
pub provider: Option<GenerationProvider>,
|
||||
|
||||
/// REQUIRED (Option<T> is just for error messages).
|
||||
/// The model to use.
|
||||
pub model: Option<ImageInpaintModel>,
|
||||
|
||||
/// REQUIRED (Option<T> is just for error messages).
|
||||
/// The source image to edit.
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// REQUIRED: Supply this *XOR* `mask_image_raw_bytes`.
|
||||
/// The mask to focus the edit (already uploaded).
|
||||
pub mask_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// REQUIRED: Supply this *XOR* `mask_image_media_token`.
|
||||
/// The mask to focus the edit (raw bytes).
|
||||
pub mask_image_raw_bytes: Option<Vec<u8>>,
|
||||
|
||||
/// REQUIRED.
|
||||
/// The user's image generation prompt.
|
||||
pub prompt: String,
|
||||
|
||||
/// Number of images to generate.
|
||||
pub image_count: Option<u32>,
|
||||
|
||||
/// Quality.
|
||||
/// Tends to be used by OpenAI models.
|
||||
pub quality: Option<CommonQuality>,
|
||||
|
||||
/// If true, force the dimensions of the source image and mask image to match.
|
||||
pub require_matching_dimensions: Option<bool>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Name of the frontend caller.
|
||||
/// We'll use this to selectively trigger events.
|
||||
pub frontend_caller: Option<TauriCommandCaller>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined identifier that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_id: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined payload that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueInpaintImageErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
|
||||
/// No model available for image generation
|
||||
NoProviderAvailable,
|
||||
|
||||
/// No source image was supplied.
|
||||
NoSourceImageSpecified,
|
||||
|
||||
/// No mask image was supplied.
|
||||
NoMaskImageSpecified,
|
||||
|
||||
/// Too many mask images were supplied.
|
||||
MultipleMaskImagesSpecified,
|
||||
|
||||
/// Bad mask image was supplied.
|
||||
BadMaskImage,
|
||||
|
||||
/// Generic bad request error
|
||||
BadRequest,
|
||||
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueImageInpaintSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueImageInpaintSuccessResponse {}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_image_inpaint_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueInpaintImageCommand,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> ResponseOrErrorType<EnqueueImageInpaintSuccessResponse, EnqueueInpaintImageErrorType> {
|
||||
|
||||
info!("enqueue_image_inpaint_command called; model: {:?}, image_media_token: {:?}, prompt: {:?}, image_count: {:?}",
|
||||
&request.model, &request.image_media_token, &request.prompt, &request.image_count);
|
||||
|
||||
let result = handle_request(
|
||||
&request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&app_env_configs,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("Error enqueuing inpaint image: {:?}", err);
|
||||
|
||||
notify_frontend_of_errors(&app, &err).await;
|
||||
|
||||
// TODO: Derive from err. Make service provider optional.
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Artcraft, // FIXME: This is wrong.
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Err(error_to_tauri_response(err))
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: event.model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
CreditsBalanceChangedEvent{}.send_infallible(&app);
|
||||
|
||||
Ok(EnqueueImageInpaintSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_request(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let model = match request.model {
|
||||
Some(model) => model,
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified())
|
||||
}
|
||||
};
|
||||
|
||||
let provider = request.provider
|
||||
.unwrap_or(GenerationProvider::Artcraft);
|
||||
|
||||
info!("image inpaint with {:?} via provider {:?}", &model, &provider);
|
||||
|
||||
let success_event = match provider {
|
||||
GenerationProvider::Artcraft => {
|
||||
handle_inpaint_artcraft(
|
||||
model,
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager,
|
||||
).await?
|
||||
}
|
||||
_ => {
|
||||
return Err(GenerateError::BadProviderForModel {
|
||||
provider,
|
||||
model: image_inpaint_model_to_model_type(model),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database_with_frontend_payload(
|
||||
task_database,
|
||||
request.frontend_caller,
|
||||
request.frontend_subscriber_id.as_deref(),
|
||||
request.frontend_subscriber_payload.as_deref()
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
// TODO: We don't know that this is strictly the edit page. It could be the stage. It might also be nice to distinguish inpainting.
|
||||
let num_images = request.image_count.map(|count| count as u16).unwrap_or(1);
|
||||
if let Err(err) = artcraft_usage_tracker.record_image_generation(num_images, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::EditPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
pub fn error_to_tauri_response(error: GenerateError) -> CommandErrorResponseWrapper<EnqueueInpaintImageErrorType, ()> {
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueInpaintImageErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.".to_string();
|
||||
|
||||
match error {
|
||||
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueInpaintImageErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation".to_string();
|
||||
}
|
||||
GenerateError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueInpaintImageErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for image generation".to_string();
|
||||
}
|
||||
GenerateError::BadInput(BadInputReason::RequiredSourceImageNotProvided) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueInpaintImageErrorType::NoSourceImageSpecified;
|
||||
error_message = "No source image was provided".to_string();
|
||||
}
|
||||
GenerateError::BadInput(BadInputReason::RequiredSourceImageMaskNotProvided) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueInpaintImageErrorType::NoMaskImageSpecified;
|
||||
error_message = "No mask image was provided".to_string();
|
||||
}
|
||||
GenerateError::BadInput(BadInputReason::BothImageMaskMediaTokenAndBytesSupplied) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueInpaintImageErrorType::MultipleMaskImagesSpecified;
|
||||
error_message = "multiple mask images provided".to_string();
|
||||
}
|
||||
GenerateError::BadInput(BadInputReason::CannotDetermineImageMimeType) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueInpaintImageErrorType::BadMaskImage;
|
||||
error_message = "bad mask image (mime)".to_string();
|
||||
}
|
||||
GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { min, max, requested }) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueInpaintImageErrorType::BadRequest;
|
||||
error_message = format!("Invalid number of images requested ({}). Must be between {} and {}", requested, min, max);
|
||||
}
|
||||
GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages{ min, max, provided }) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueInpaintImageErrorType::BadRequest;
|
||||
error_message = format!("Invalid number of input images ({}). Must be between {} and {}", provided, min, max);
|
||||
}
|
||||
_ => {}, // Other cases fall through.
|
||||
}
|
||||
|
||||
CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::ImageInpaintModel;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
|
||||
pub fn image_inpaint_model_to_model_type(model: ImageInpaintModel) -> CommonModelType {
|
||||
match model {
|
||||
ImageInpaintModel::FluxDevJuggernaut => CommonModelType::FluxDevJuggernaut,
|
||||
ImageInpaintModel::FluxPro1 => CommonModelType::FluxPro1,
|
||||
ImageInpaintModel::FluxProKontextMax => CommonModelType::FluxProKontextMax,
|
||||
ImageInpaintModel::Gemini25Flash => CommonModelType::NanoBanana,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
pub (super) mod inpaint_models;
|
||||
pub mod enqueue_image_inpaint_command;
|
||||
|
||||
mod artcraft;
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
pub mod image_edit;
|
||||
pub mod image_inpaint;
|
||||
pub mod text_to_image;
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_client::api_defs::generate::image::text::generate_gpt_image_1_text_to_image::{GenerateGptImage1TextToImageImageQuality, GenerateGptImage1TextToImageImageSize, GenerateGptImage1TextToImageNumImages, GenerateGptImage1TextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use artcraft_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use artcraft_client::endpoints::generate::image::text::generate_gpt_image_1_text_to_image::generate_gpt_image_1_text_to_image;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_artcraft_gpt_image_1_text_to_image(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft gpt-image-1 ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let image_quality = Some(GenerateGptImage1TextToImageImageQuality::High);
|
||||
|
||||
//let image_quality = request.image_quality
|
||||
// .map(|quality| match quality {
|
||||
// EditImageQuality::Auto => GenerateGptImage1TextToImageImageQuality::Auto,
|
||||
// EditImageQuality::High => GenerateGptImage1TextToImageImageQuality::High,
|
||||
// EditImageQuality::Medium => GenerateGptImage1TextToImageImageQuality::Medium,
|
||||
// EditImageQuality::Low => GenerateGptImage1TextToImageImageQuality::Low,
|
||||
// });
|
||||
|
||||
let image_size = request.aspect_ratio
|
||||
.map(|size| match size {
|
||||
TextToImageSize::Auto => GenerateGptImage1TextToImageImageSize::Square,
|
||||
TextToImageSize::Square => GenerateGptImage1TextToImageImageSize::Square,
|
||||
TextToImageSize::Tall => GenerateGptImage1TextToImageImageSize::Vertical,
|
||||
TextToImageSize::Wide => GenerateGptImage1TextToImageImageSize::Horizontal,
|
||||
});
|
||||
|
||||
let num_images = match request.number_images {
|
||||
None => None,
|
||||
Some(1) => Some(GenerateGptImage1TextToImageNumImages::One),
|
||||
Some(2) => Some(GenerateGptImage1TextToImageNumImages::Two),
|
||||
Some(3) => Some(GenerateGptImage1TextToImageNumImages::Three),
|
||||
Some(4) => Some(GenerateGptImage1TextToImageNumImages::Four),
|
||||
// TODO: Error
|
||||
//Some(other) => {
|
||||
// return Err(InternalImageError::InvalidNumberOfRequestedImages {
|
||||
// min: 1,
|
||||
// max: 4,
|
||||
// requested: other,
|
||||
// });
|
||||
//},
|
||||
_ => Some(GenerateGptImage1TextToImageNumImages::One), // Default to one image if invalid number
|
||||
};
|
||||
|
||||
let request = GenerateGptImage1TextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt.clone(),
|
||||
image_size,
|
||||
num_images,
|
||||
image_quality,
|
||||
};
|
||||
|
||||
let result = generate_gpt_image_1_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft gpt-image-1. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::GptImage1),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::artcraft::handle_artcraft_gpt_image_1_text_to_image::handle_artcraft_gpt_image_1_text_to_image;
|
||||
use crate::commands::deprecated::text_to_image::artcraft::handle_text_to_image_artcraft_via_router::handle_text_to_image_artcraft_via_router;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageModel};
|
||||
use crate::commands::deprecated::text_to_image::text_to_image_models::text_to_image_model_to_model_type;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use router::api::router_image_model::RouterImageModel;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_text_to_image_artcraft(
|
||||
model: TextToImageModel,
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<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_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Flux1Dev, GenerationModel::Flux1Dev).await,
|
||||
TextToImageModel::Flux1Schnell => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Flux1Schnell, GenerationModel::Flux1Schnell).await,
|
||||
TextToImageModel::FluxPro11 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::FluxPro11, GenerationModel::FluxPro11).await,
|
||||
TextToImageModel::FluxPro11Ultra => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::FluxPro11Ultra, GenerationModel::FluxPro11Ultra).await,
|
||||
TextToImageModel::GptImage1 => handle_artcraft_gpt_image_1_text_to_image(request, app_env_configs, storyteller_creds_manager).await,
|
||||
TextToImageModel::GptImage1p5 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::GptImage1p5, GenerationModel::GptImage1p5).await,
|
||||
TextToImageModel::GptImage2 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::GptImage2, GenerationModel::GptImage2).await,
|
||||
TextToImageModel::Gemini25Flash | TextToImageModel::NanoBanana => {
|
||||
handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBanana, GenerationModel::NanoBanana).await
|
||||
},
|
||||
TextToImageModel::NanoBanana2 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBanana2, GenerationModel::NanoBanana2).await,
|
||||
TextToImageModel::NanoBananaPro => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::NanoBananaPro, GenerationModel::NanoBananaPro).await,
|
||||
TextToImageModel::Seedream4 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream4, GenerationModel::Seedream4).await,
|
||||
TextToImageModel::Seedream4p5 => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream4p5, GenerationModel::Seedream4p5).await,
|
||||
TextToImageModel::Seedream5Lite => handle_text_to_image_artcraft_via_router(request, app_env_configs, storyteller_creds_manager, RouterImageModel::Seedream5Lite, GenerationModel::Seedream5Lite).await,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
use crate::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio as CommonAspectRatio2;
|
||||
use crate::api_adapters::resolution::common_resolution::CommonResolution as CommonResolution2;
|
||||
use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{
|
||||
EnqueueTextToImageRequest, TextToImageResolution, TextToImageSize,
|
||||
};
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use router::api::router_aspect_ratio::RouterAspectRatio;
|
||||
use router::api::router_image_model::RouterImageModel;
|
||||
use router::api::router_resolution::RouterResolution;
|
||||
use router::api::router_quality::RouterQuality;
|
||||
use router::api::image_list_ref::ImageListRef;
|
||||
use router::api::router_provider::RouterProvider;
|
||||
use router::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use router::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use router::client::router_client::RouterClient;
|
||||
use router::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder;
|
||||
use router::generate::generate_image::image_generation_draft_or_request::ImageGenerationDraftOrRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use log::{error, info};
|
||||
|
||||
pub(super) async fn handle_text_to_image_artcraft_via_router(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
model: RouterImageModel,
|
||||
generation_model: GenerationModel,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials)),
|
||||
};
|
||||
|
||||
let client = RouterClient::Artcraft(RouterArtcraftClient::new(
|
||||
app_env_configs.storyteller_host.clone(),
|
||||
creds,
|
||||
));
|
||||
|
||||
let image_inputs = request.image_media_tokens.clone().map(ImageListRef::MediaFileTokens);
|
||||
|
||||
let aspect_ratio = get_aspect_ratio_t2i(request);
|
||||
let resolution = get_resolution_t2i(request);
|
||||
let quality = get_quality_t2i(request);
|
||||
|
||||
let router_request = GenerateImageRequestBuilder {
|
||||
model,
|
||||
provider: RouterProvider::Artcraft,
|
||||
prompt: request.prompt.clone(),
|
||||
image_inputs,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
quality,
|
||||
image_batch_count: request.number_images.map(|n| n as u16),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
zoom: None,
|
||||
};
|
||||
|
||||
let dor = router_request.build2()?;
|
||||
|
||||
info!("Image Generation Request: {:?}", dor);
|
||||
|
||||
let request = match dor {
|
||||
ImageGenerationDraftOrRequest::Request(req) => req,
|
||||
// Artcraft never returns a Draft — image-token resolution happens server-side.
|
||||
// The only draft-producing models today are Kinovi-Midjourney variants,
|
||||
// which are routed to a separate provider.
|
||||
ImageGenerationDraftOrRequest::Draft(_) => unreachable!(
|
||||
"Artcraft router should never produce a draft for text-to-image"
|
||||
),
|
||||
};
|
||||
|
||||
let response = match request.send_request(&client).await {
|
||||
Ok(resp) => {
|
||||
info!("Successfully enqueued.");
|
||||
resp
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_id = response
|
||||
.get_artcraft_payload()
|
||||
.map(|p| p.inference_job_token.to_string())
|
||||
.ok_or(GenerateError::ResponseHadNoJobTokens)?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(generation_model),
|
||||
provider: GenerationProvider::Artcraft,
|
||||
provider_job_id: Some(job_id),
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio_t2i(request: &EnqueueTextToImageRequest) -> Option<RouterAspectRatio> {
|
||||
if let Some(ar) = request.common_aspect_ratio {
|
||||
return Some(convert_desktop_aspect_ratio(ar));
|
||||
}
|
||||
if let Some(ar) = request.aspect_ratio {
|
||||
return Some(match ar {
|
||||
TextToImageSize::Auto => RouterAspectRatio::Auto,
|
||||
TextToImageSize::Square => RouterAspectRatio::Square,
|
||||
TextToImageSize::Wide => RouterAspectRatio::Wide,
|
||||
TextToImageSize::Tall => RouterAspectRatio::Tall,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_resolution_t2i(request: &EnqueueTextToImageRequest) -> Option<RouterResolution> {
|
||||
if let Some(res) = request.common_resolution {
|
||||
return Some(convert_desktop_resolution(res));
|
||||
}
|
||||
if let Some(res) = request.image_resolution {
|
||||
return Some(match res {
|
||||
TextToImageResolution::OneK => RouterResolution::OneK,
|
||||
TextToImageResolution::TwoK => RouterResolution::TwoK,
|
||||
TextToImageResolution::FourK => RouterResolution::FourK,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_quality_t2i(request: &EnqueueTextToImageRequest) -> Option<RouterQuality> {
|
||||
request.quality.map(|quality| quality.to_artcraft_router_type())
|
||||
}
|
||||
|
||||
fn convert_desktop_aspect_ratio(ar: CommonAspectRatio2) -> RouterAspectRatio {
|
||||
match ar {
|
||||
CommonAspectRatio2::Auto => RouterAspectRatio::Auto,
|
||||
CommonAspectRatio2::Square => RouterAspectRatio::Square,
|
||||
CommonAspectRatio2::WideThreeByTwo => RouterAspectRatio::WideThreeByTwo,
|
||||
CommonAspectRatio2::WideFourByThree => RouterAspectRatio::WideFourByThree,
|
||||
CommonAspectRatio2::WideFiveByFour => RouterAspectRatio::WideFiveByFour,
|
||||
CommonAspectRatio2::WideSixteenByNine => RouterAspectRatio::WideSixteenByNine,
|
||||
CommonAspectRatio2::WideTwentyOneByNine => RouterAspectRatio::WideTwentyOneByNine,
|
||||
CommonAspectRatio2::TallTwoByThree => RouterAspectRatio::TallTwoByThree,
|
||||
CommonAspectRatio2::TallThreeByFour => RouterAspectRatio::TallThreeByFour,
|
||||
CommonAspectRatio2::TallFourByFive => RouterAspectRatio::TallFourByFive,
|
||||
CommonAspectRatio2::TallNineBySixteen => RouterAspectRatio::TallNineBySixteen,
|
||||
CommonAspectRatio2::TallNineByTwentyOne => RouterAspectRatio::TallNineByTwentyOne,
|
||||
CommonAspectRatio2::Wide => RouterAspectRatio::Wide,
|
||||
CommonAspectRatio2::Tall => RouterAspectRatio::Tall,
|
||||
CommonAspectRatio2::Auto2k => RouterAspectRatio::Auto2k,
|
||||
CommonAspectRatio2::Auto4k => RouterAspectRatio::Auto4k,
|
||||
CommonAspectRatio2::SquareHd => RouterAspectRatio::SquareHd,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_desktop_resolution(res: CommonResolution2) -> RouterResolution {
|
||||
match res {
|
||||
CommonResolution2::OneK => RouterResolution::OneK,
|
||||
CommonResolution2::TwoK => RouterResolution::TwoK,
|
||||
CommonResolution2::ThreeK => RouterResolution::ThreeK,
|
||||
CommonResolution2::FourK => RouterResolution::FourK,
|
||||
CommonResolution2::HalfK => RouterResolution::HalfK,
|
||||
CommonResolution2::FourEightyP => RouterResolution::FourEightyP,
|
||||
CommonResolution2::SevenTwentyP => RouterResolution::SevenTwentyP,
|
||||
CommonResolution2::TenEightyP => RouterResolution::TenEightyP,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
pub (super) mod handle_text_to_image_artcraft;
|
||||
|
||||
mod handle_text_to_image_artcraft_via_router;
|
||||
mod handle_artcraft_gpt_image_1_text_to_image;
|
||||
|
|
@ -1,425 +0,0 @@
|
|||
use crate::api_adapters::aspect_ratio::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api_adapters::quality::common_quality::CommonQuality;
|
||||
use crate::api_adapters::resolution::common_resolution::CommonResolution;
|
||||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::deprecated::image_edit::image_edit_models::image_edit_model_to_model_type;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::artcraft::handle_text_to_image_artcraft::handle_text_to_image_artcraft;
|
||||
use crate::commands::deprecated::text_to_image::grok::handle_grok::handle_grok;
|
||||
use crate::commands::deprecated::text_to_image::midjourney::handle_midjourney::handle_midjourney;
|
||||
use crate::commands::deprecated::text_to_image::sora::handle_text_to_image_sora::handle_text_to_image_sora;
|
||||
use crate::commands::deprecated::text_to_image::text_to_image_models::text_to_image_model_to_model_type;
|
||||
use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::commands::response::shorthand::Response;
|
||||
use crate::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::events::sendable_event_trait::SendableEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::state::task_database::TaskDatabase;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::grok::state::grok_image_prompt_queue::GrokImagePromptQueue;
|
||||
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use log::{error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlite_database::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TextToImageModel {
|
||||
#[serde(rename = "flux_1_dev")]
|
||||
Flux1Dev,
|
||||
#[serde(rename = "flux_1_schnell")]
|
||||
Flux1Schnell,
|
||||
#[serde(rename = "flux_pro_11")]
|
||||
FluxPro11,
|
||||
#[serde(rename = "flux_pro_11_ultra")]
|
||||
FluxPro11Ultra,
|
||||
#[serde(rename = "grok_image")]
|
||||
GrokImage,
|
||||
#[serde(rename = "recraft_3")]
|
||||
Recraft3,
|
||||
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
#[serde(rename = "gpt_image_1p5")]
|
||||
GptImage1p5,
|
||||
#[serde(rename = "gpt_image_2")]
|
||||
GptImage2,
|
||||
#[serde(rename = "gemini_25_flash")]
|
||||
Gemini25Flash,
|
||||
#[serde(rename = "nano_banana")]
|
||||
NanoBanana,
|
||||
#[serde(rename = "nano_banana_2")]
|
||||
NanoBanana2,
|
||||
#[serde(rename = "nano_banana_pro")]
|
||||
NanoBananaPro,
|
||||
#[serde(rename = "seedream_4")]
|
||||
Seedream4,
|
||||
#[serde(rename = "seedream_4p5")]
|
||||
Seedream4p5,
|
||||
#[serde(rename = "seedream_5_lite")]
|
||||
Seedream5Lite,
|
||||
|
||||
// Generic Midjourney model, version unknown.
|
||||
#[serde(rename = "midjourney")]
|
||||
Midjourney,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EnqueueTextToImageRequest {
|
||||
/// The provider to use (defaults to Artcraft/Storyteller).
|
||||
/// Not all (provider, model) combinations are valid.
|
||||
pub provider: Option<GenerationProvider>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<TextToImageModel>,
|
||||
|
||||
/// Text prompt for the image generation. Required.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// New field for aspect ratio.
|
||||
/// Not all models support each of these aspect ratios, but we can choose or interpolate sensibly.
|
||||
pub common_aspect_ratio: Option<CommonAspectRatio>,
|
||||
|
||||
/// New field for resolution.
|
||||
/// Not all models support each of these resolutions, but we can choose or interpolate sensibly.
|
||||
pub common_resolution: Option<CommonResolution>,
|
||||
|
||||
/// Quality.
|
||||
/// Tends to be used by OpenAI models.
|
||||
pub quality: Option<CommonQuality>,
|
||||
|
||||
/// Aspect ratio.
|
||||
#[deprecated(note="use common_aspect_ratio")]
|
||||
pub aspect_ratio: Option<TextToImageSize>,
|
||||
|
||||
/// Aspect ratio.
|
||||
#[deprecated(note="use common_resolution")]
|
||||
pub image_resolution: Option<TextToImageResolution>,
|
||||
|
||||
/// The number of images to generate.
|
||||
pub number_images: Option<u32>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Reference images (without semantics)
|
||||
/// The purpose varies on a model-by-model basis, but they
|
||||
/// are not semantically treated as "style reference",
|
||||
/// "character/object reference", etc.
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Name of the frontend caller.
|
||||
/// We'll use this to selectively trigger events.
|
||||
pub frontend_caller: Option<TauriCommandCaller>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined identifier that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_id: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined payload that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
// TODO(bt,2025-07-14): Support other aspect ratios / resolutions -
|
||||
// Flux Dev has: 4:3, 16:9, 3:4, 9:16, and custom.
|
||||
// Flux Schnell has: 4:3, 16:9, 3:4, 9:16, and custom.
|
||||
// Flux Pro has: 4:3, 16:9, 3:4, 9:16, and custom.
|
||||
// Flux Pro Ulra has: 4:3, 16:9, 3:4, 9:16, and custom.
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TextToImageSize {
|
||||
Auto,
|
||||
Square,
|
||||
Wide,
|
||||
Tall,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TextToImageResolution {
|
||||
/// 1K - nano banana pro
|
||||
OneK,
|
||||
/// 2K - nano banana pro
|
||||
TwoK,
|
||||
/// 4K - nano banana pro
|
||||
FourK,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueTextToImageSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueTextToImageSuccessResponse {}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueTextToImageErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// No model available for image generation
|
||||
NoProviderAvailable,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// No Fal API key available
|
||||
NeedsFalApiKey,
|
||||
/// Fal had an API error
|
||||
FalError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_text_to_image_command(
|
||||
request: EnqueueTextToImageRequest,
|
||||
app: AppHandle,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
mj_creds_manager: State<'_, MidjourneyCredentialManager>,
|
||||
grok_creds_manager: State<'_, GrokCredentialManager>,
|
||||
grok_image_prompt_queue: State<'_, GrokImagePromptQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueTextToImageSuccessResponse, EnqueueTextToImageErrorType, ()> {
|
||||
|
||||
info!("enqueue_text_to_image called");
|
||||
|
||||
info!("request: {:?}", request);
|
||||
|
||||
let result = handle_request(
|
||||
request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&mj_creds_manager,
|
||||
&grok_creds_manager,
|
||||
&grok_image_prompt_queue,
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
notify_frontend_of_errors(&app, &err).await;
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueTextToImageErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match err {
|
||||
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueTextToImageErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
GenerateError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueTextToImageErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for image generation";
|
||||
}
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueTextToImageErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: event.model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
CreditsBalanceChangedEvent{}.send_infallible(&app);
|
||||
|
||||
Ok(EnqueueTextToImageSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_request(
|
||||
request: EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
grok_creds_manager: &GrokCredentialManager,
|
||||
grok_image_prompt_queue: &GrokImagePromptQueue,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let result = dispatch_request(
|
||||
&request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
&mj_creds_manager,
|
||||
&grok_creds_manager,
|
||||
&grok_image_prompt_queue,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
|
||||
let success_event = match result {
|
||||
Err(err) => return Err(err),
|
||||
Ok(event) => event,
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database_with_frontend_payload(
|
||||
task_database,
|
||||
request.frontend_caller,
|
||||
request.frontend_subscriber_id.as_deref(),
|
||||
request.frontend_subscriber_payload.as_deref(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
let num_images = request.number_images.map(|n| n as u16).unwrap_or(1);
|
||||
|
||||
let is_image_to_image = request.image_media_tokens
|
||||
.as_ref()
|
||||
.map(|tokens| !tokens.is_empty())
|
||||
.unwrap_or(false);
|
||||
|
||||
let usage_type = if is_image_to_image {
|
||||
ArtcraftUsageType::ImageToResult
|
||||
} else {
|
||||
ArtcraftUsageType::TextToResult
|
||||
};
|
||||
|
||||
if let Err(err) = artcraft_usage_tracker.record_image_generation(num_images, usage_type, ArtcraftUsagePage::ImagePage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
pub async fn dispatch_request(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
grok_creds_manager: &GrokCredentialManager,
|
||||
grok_image_prompt_queue: &GrokImagePromptQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let model = match request.model {
|
||||
Some(model) => model,
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified())
|
||||
}
|
||||
};
|
||||
|
||||
let provider = match (model, request.provider) {
|
||||
(TextToImageModel::GrokImage, _) => GenerationProvider::Grok,
|
||||
(TextToImageModel::Midjourney, _) => GenerationProvider::Midjourney,
|
||||
_ => request.provider.unwrap_or(GenerationProvider::Artcraft),
|
||||
};
|
||||
|
||||
info!("generate image with {:?} via provider {:?}", &model, &provider);
|
||||
|
||||
match provider {
|
||||
GenerationProvider::Artcraft => {
|
||||
handle_text_to_image_artcraft(
|
||||
model,
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager,
|
||||
).await
|
||||
}
|
||||
GenerationProvider::Grok => {
|
||||
handle_grok(
|
||||
app,
|
||||
request,
|
||||
app_env_configs,
|
||||
grok_creds_manager,
|
||||
grok_image_prompt_queue,
|
||||
).await
|
||||
}
|
||||
GenerationProvider::Midjourney => {
|
||||
handle_midjourney(
|
||||
app,
|
||||
request,
|
||||
app_env_configs,
|
||||
mj_creds_manager,
|
||||
).await
|
||||
}
|
||||
GenerationProvider::Sora => {
|
||||
handle_text_to_image_sora(
|
||||
request,
|
||||
app,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await
|
||||
}
|
||||
_ => {
|
||||
Err(GenerateError::BadProviderForModel {
|
||||
provider,
|
||||
model: text_to_image_model_to_model_type(model),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::grok::state::grok_image_prompt_queue::{GrokImagePromptQueue, PromptItem};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use grok_consumer_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_grok(
|
||||
app: &AppHandle,
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
creds_manager: &GrokCredentialManager,
|
||||
grok_image_prompt_queue: &GrokImagePromptQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
|
||||
//// TODO: We can request population of the user info if absent or expired.
|
||||
//let websocket = get_websocket(
|
||||
// app,
|
||||
// creds_manager,
|
||||
// websocket_manager,
|
||||
//).await?;
|
||||
//
|
||||
//let _result = prompt_websocket_image(PromptWebsocketImageArgs {
|
||||
// websocket_wrapped: &websocket,
|
||||
// prompt: request.prompt.as_deref().unwrap_or_else(||""),
|
||||
//}).await?;
|
||||
//
|
||||
//let request_id = listen_for_websocket_request_id(ListenForWebsocketRequestIdArgs {
|
||||
// websocket: &websocket,
|
||||
// timeout: Duration::from_millis(10_000),
|
||||
//}).await?;
|
||||
//
|
||||
//let request_id = match request_id.request_id {
|
||||
// Some(request_id) => request_id.to_string(),
|
||||
// None => {
|
||||
// warn!("No request id from Grok image generation (websocket)");
|
||||
// return Err(GenerateError::ProviderFailure(ProviderFailureReason::GrokJobEnqueueFailed));
|
||||
// }
|
||||
//};
|
||||
//
|
||||
//info!("Successfully enqueued MidJourney. Job token: {}", request_id);
|
||||
|
||||
let job_id = generate_random_uuid();
|
||||
|
||||
let prompt = request.prompt
|
||||
.as_deref()
|
||||
.map(|prompt| prompt.trim().to_string())
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
|
||||
let aspect_ratio = get_aspect_ratio(request);
|
||||
|
||||
grok_image_prompt_queue.enqueue(PromptItem {
|
||||
task_id: job_id.clone(),
|
||||
prompt,
|
||||
aspect_ratio: aspect_ratio.unwrap_or(ClientMessageAspectRatio::Square),
|
||||
})?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Grok,
|
||||
model: Some(GenerationModel::GrokImage),
|
||||
provider_job_id: Some(job_id),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option<ClientMessageAspectRatio> {
|
||||
if let Some(common_aspect_ratio) = request.common_aspect_ratio {
|
||||
// Handle modern aspect ratio
|
||||
let aspect = aspect_ratio_to_grok_image(common_aspect_ratio);
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
if let Some(aspect_ratio) = request.aspect_ratio {
|
||||
// Handle deprecated aspect ratio
|
||||
return match aspect_ratio {
|
||||
TextToImageSize::Auto => Some(ClientMessageAspectRatio::Square),
|
||||
TextToImageSize::Square => Some(ClientMessageAspectRatio::Square),
|
||||
TextToImageSize::Wide => Some(ClientMessageAspectRatio::WideThreeByTwo),
|
||||
TextToImageSize::Tall => Some(ClientMessageAspectRatio::TallTwoByThree),
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub (super) mod handle_grok;
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason, ProviderFailureReason};
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent;
|
||||
use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::events::warning_events::flash_user_input_error_event::FlashUserInputErrorEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use midjourney_client::client::midjourney_hostname::MidjourneyHostname;
|
||||
use midjourney_client::endpoints::submit_job::{submit_job, SubmitJobRequest};
|
||||
use midjourney_client::error::midjourney_api_error::MidjourneyApiError;
|
||||
use midjourney_client::recipes::channel_id::ChannelId;
|
||||
use midjourney_client::recipes::text_to_image::{text_to_image, TextToImageError, TextToImageRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_midjourney(
|
||||
app: &AppHandle,
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match mj_creds_manager.maybe_copy_cookie_store() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
error!("Midjourney credentials not found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app);
|
||||
return Err(GenerateError::needs_midjourney_credentials());
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Midjourney credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app);
|
||||
return Err(GenerateError::needs_midjourney_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: We can request population of the user info if absent or expired.
|
||||
|
||||
let user_info = match mj_creds_manager.maybe_copy_user_info() {
|
||||
Ok(Some(user_info)) => user_info,
|
||||
Ok(None) => {
|
||||
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo));
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Midjourney user info: {:?}", err);
|
||||
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo));
|
||||
},
|
||||
};
|
||||
|
||||
let channel_id = match user_info.user_id {
|
||||
Some(user_id) => ChannelId::UserId(user_id),
|
||||
None => {
|
||||
error!("Midjourney user info does not contain a user ID.");
|
||||
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserId));
|
||||
}
|
||||
};
|
||||
|
||||
info!("Calling midjourney ...");
|
||||
|
||||
let cookie_header = creds.to_cookie_string();
|
||||
|
||||
let prompt = request.prompt
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
|
||||
let result = text_to_image(TextToImageRequest {
|
||||
prompt,
|
||||
channel_id: &channel_id,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
cookie_header,
|
||||
}).await;
|
||||
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
error!("Failed to use MidJourney: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_id = match result.maybe_job_id {
|
||||
Some(job_id) => job_id,
|
||||
None => {
|
||||
error!("Failed to enqueue MidJourney: No job ID returned.");
|
||||
return handle_midjourney_errors(app, result.maybe_errors);
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued MidJourney. Job token: {}", job_id);
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Midjourney,
|
||||
model: Some(GenerationModel::Midjourney),
|
||||
provider_job_id: Some(job_id),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_midjourney_errors(
|
||||
app: &AppHandle,
|
||||
maybe_errors: Option<Vec<TextToImageError>>
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
if let Some(errors) = maybe_errors {
|
||||
if !errors.is_empty() {
|
||||
let messages: Vec<String> = errors.iter()
|
||||
.map(|e| format!("{:?}", e))
|
||||
.collect();
|
||||
|
||||
let combined_message = messages.join("; ");
|
||||
|
||||
let event = FlashUserInputErrorEvent {
|
||||
message: format!("Midjourney Error: {}", combined_message),
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to send FlashUserInputErrorEvent: {:?}", err); // Fail open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::ProviderFailure(ProviderFailureReason::MidjourneyJobEnqueueFailed))
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub (super) mod handle_midjourney;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
pub (super) mod text_to_image_models;
|
||||
pub mod enqueue_text_to_image_command;
|
||||
|
||||
mod artcraft;
|
||||
mod grok;
|
||||
mod midjourney;
|
||||
mod sora;
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image;
|
||||
use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_sora_native_gpt_image_1::aspect_ratio_to_sora_native_gpt_image_1;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use errors::AnyhowResult;
|
||||
use grok_consumer_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const SORA_SIMPLE_IMAGE_GEN_TIMEOUT : Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
pub async fn handle_gpt_image_1_sora_text_to_image(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let mut creds = match sora_creds_manager.get_credentials() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
error!("Sora credentials not found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Sora credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
}
|
||||
};
|
||||
|
||||
let credential_updated = maybe_upgrade_or_renew_session(&mut creds)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upgrade or renew session: {:?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
if credential_updated {
|
||||
info!("Storing updated credentials");
|
||||
sora_creds_manager.set_credentials(&creds)?;
|
||||
}
|
||||
|
||||
let prompt = request.prompt.as_deref().unwrap_or("");
|
||||
|
||||
let aspect_ratio = get_aspect_ratio(request)
|
||||
.unwrap_or(ImageSize::Square);
|
||||
|
||||
let num_images = match request.number_images {
|
||||
None => NumImages::One,
|
||||
Some(1) => NumImages::One,
|
||||
Some(2) => NumImages::Two,
|
||||
Some(3) => NumImages::Two, // NB: There is no option for three images!
|
||||
Some(4) => NumImages::Four,
|
||||
_ => NumImages::One,
|
||||
};
|
||||
|
||||
let result =
|
||||
simple_image_gen_with_session_auto_renew(SimpleImageGenAutoRenewRequest {
|
||||
prompt: prompt.to_string(),
|
||||
num_images,
|
||||
image_size: aspect_ratio,
|
||||
credentials: &creds,
|
||||
request_timeout: Some(SORA_SIMPLE_IMAGE_GEN_TIMEOUT),
|
||||
}).await;
|
||||
|
||||
let (response, maybe_new_creds) =
|
||||
match result {
|
||||
Ok((response, maybe_new_creds)) => (response, maybe_new_creds),
|
||||
Err(err) => {
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(new_creds) = maybe_new_creds {
|
||||
info!("Storing updated credentials.");
|
||||
sora_creds_manager.set_credentials(&new_creds)?;
|
||||
}
|
||||
|
||||
info!("New Sora Task ID: {:?} ", response.task_id);
|
||||
|
||||
sora_task_queue.insert(&response.task_id)?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Sora,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(GenerationModel::GptImage1),
|
||||
provider_job_id: Some(response.task_id.to_string()),
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option<ImageSize> {
|
||||
if let Some(common_aspect_ratio) = request.common_aspect_ratio {
|
||||
// Handle modern aspect ratio
|
||||
let aspect = aspect_ratio_to_sora_native_gpt_image_1(common_aspect_ratio);
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
if let Some(aspect_ratio) = request.aspect_ratio {
|
||||
// Handle deprecated aspect ratio
|
||||
return match aspect_ratio {
|
||||
TextToImageSize::Auto => Some(ImageSize::Square),
|
||||
TextToImageSize::Square => Some(ImageSize::Square),
|
||||
TextToImageSize::Wide => Some(ImageSize::Wide),
|
||||
TextToImageSize::Tall => Some(ImageSize::Tall),
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::commands::deprecated::text_to_image::sora::handle_gpt_image_1_sora_text_to_image::handle_gpt_image_1_sora_text_to_image;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_text_to_image_sora(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
handle_gpt_image_1_sora_text_to_image(
|
||||
request,
|
||||
app,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
pub (super) mod handle_text_to_image_sora;
|
||||
mod handle_gpt_image_1_sora_text_to_image;
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::TextToImageModel;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
|
||||
pub fn text_to_image_model_to_model_type(model: TextToImageModel) -> CommonModelType {
|
||||
match model {
|
||||
TextToImageModel::Flux1Dev => CommonModelType::Flux1Dev,
|
||||
TextToImageModel::Flux1Schnell => CommonModelType::Flux1Schnell,
|
||||
TextToImageModel::FluxPro11 => CommonModelType::FluxPro11,
|
||||
TextToImageModel::FluxPro11Ultra => CommonModelType::FluxPro11Ultra,
|
||||
TextToImageModel::GrokImage => CommonModelType::GrokImage,
|
||||
TextToImageModel::Recraft3 => CommonModelType::Recraft3,
|
||||
TextToImageModel::GptImage1 => CommonModelType::GptImage1,
|
||||
TextToImageModel::GptImage1p5 => CommonModelType::GptImage1p5,
|
||||
TextToImageModel::GptImage2 => CommonModelType::GptImage2,
|
||||
TextToImageModel::Gemini25Flash => CommonModelType::NanoBanana,
|
||||
TextToImageModel::NanoBanana => CommonModelType::NanoBanana,
|
||||
TextToImageModel::NanoBanana2 => CommonModelType::NanoBanana2,
|
||||
TextToImageModel::NanoBananaPro => CommonModelType::NanoBananaPro,
|
||||
TextToImageModel::Seedream4 => CommonModelType::Seedream4,
|
||||
TextToImageModel::Seedream4p5 => CommonModelType::Seedream4p5,
|
||||
TextToImageModel::Seedream5Lite => CommonModelType::Seedream5Lite,
|
||||
TextToImageModel::Midjourney => CommonModelType::Midjourney,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
pub mod maybe_notify_frontend_of_billing_errors;
|
||||
pub mod maybe_notify_frontend_of_credential_errors;
|
||||
pub mod maybe_notify_frontend_of_grok_errors;
|
||||
pub mod notify_frontend_of_errors;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
use crate::commands::enqueue::common::maybe_notify_frontend_of_billing_errors::maybe_notify_frontend_of_billing_errors;
|
||||
use crate::commands::enqueue::common::maybe_notify_frontend_of_credential_errors::maybe_notify_frontend_of_credential_errors;
|
||||
use crate::commands::enqueue::common::maybe_notify_frontend_of_grok_errors::maybe_notify_frontend_of_grok_errors;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn notify_frontend_of_errors(
|
||||
app: &AppHandle,
|
||||
error: &GenerateError,
|
||||
) {
|
||||
maybe_notify_frontend_of_billing_errors(app, error).await;
|
||||
maybe_notify_frontend_of_credential_errors(app, error).await;
|
||||
maybe_notify_frontend_of_grok_errors(app, error).await;
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::GenerationAction;
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use log::error;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub(super) fn get_storyteller_creds_or_error(
|
||||
app: &AppHandle,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<StorytellerCredentialSet, GenerateError> {
|
||||
match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => Ok(creds),
|
||||
None => {
|
||||
error!("No Artcraft credentials are set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::GenerateGaussian);
|
||||
|
||||
event.send_infallible(app);
|
||||
|
||||
Err(GenerateError::needs_storyteller_credentials())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::image_to_gaussian::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error;
|
||||
use crate::commands::enqueue::image_to_gaussian::artcraft::handle_artcraft_splat_via_router::handle_artcraft_splat_via_router;
|
||||
use crate::commands::enqueue::image_to_gaussian::enqueue_image_to_gaussian_command::{EnqueueImageToGaussianRequest, GaussianModel};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use router::api::router_splat_model::RouterSplatModel;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_gaussian_artcraft(
|
||||
request: &EnqueueImageToGaussianRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let creds = get_storyteller_creds_or_error(app, storyteller_creds_manager)?;
|
||||
|
||||
match request.model {
|
||||
None => Err(GenerateError::no_model_specified()),
|
||||
Some(GaussianModel::Marble0p1Mini) => {
|
||||
handle_artcraft_splat_via_router(request, app_env_configs, &creds, RouterSplatModel::Marble0p1Mini, GenerationModel::WorldlabsMarble0p1Mini).await
|
||||
}
|
||||
Some(
|
||||
GaussianModel::Marble0p1Plus |
|
||||
GaussianModel::WorldLabsMarble // Legacy variant — route to Plus
|
||||
) => {
|
||||
handle_artcraft_splat_via_router(request, app_env_configs, &creds, RouterSplatModel::Marble0p1Plus, GenerationModel::WorldlabsMarble0p1Plus).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::image_to_gaussian::enqueue_image_to_gaussian_command::EnqueueImageToGaussianRequest;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use router::api::router_splat_model::RouterSplatModel;
|
||||
use router::api::image_list_ref::ImageListRef;
|
||||
use router::api::router_provider::RouterProvider;
|
||||
use router::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use router::client::router_client::RouterClient;
|
||||
use router::generate::generate_splat::generate_splat_request::GenerateSplatRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use log::{error, info};
|
||||
|
||||
pub(super) async fn handle_artcraft_splat_via_router(
|
||||
request: &EnqueueImageToGaussianRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
creds: &StorytellerCredentialSet,
|
||||
model: RouterSplatModel,
|
||||
generation_model: GenerationModel,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let client = RouterClient::Artcraft(RouterArtcraftClient::new(
|
||||
app_env_configs.storyteller_host.clone(),
|
||||
creds.clone(),
|
||||
));
|
||||
|
||||
let router_request = GenerateSplatRequest {
|
||||
model,
|
||||
provider: RouterProvider::Artcraft,
|
||||
prompt: request.prompt.clone(),
|
||||
reference_images: request.image_media_tokens.clone().map(ImageListRef::MediaFileTokens),
|
||||
idempotency_token: None,
|
||||
};
|
||||
|
||||
let plan = router_request.build()?;
|
||||
|
||||
info!("Splat Generation Plan: {:?}", plan);
|
||||
|
||||
let response = match plan.generate_splat(&client).await {
|
||||
Ok(resp) => {
|
||||
info!("Successfully enqueued splat generation.");
|
||||
resp
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue splat generation: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_id = response.get_artcraft_payload()
|
||||
.map(|p| p.inference_job_token.to_string())
|
||||
.ok_or(GenerateError::ResponseHadNoJobTokens)?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(generation_model),
|
||||
provider: GenerationProvider::Artcraft,
|
||||
provider_job_id: Some(job_id),
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
mod get_storyteller_creds_or_error;
|
||||
mod handle_artcraft_splat_via_router;
|
||||
pub(super) mod handle_artcraft_gaussian;
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::enqueue::image_to_gaussian::artcraft::handle_artcraft_gaussian::handle_gaussian_artcraft;
|
||||
use crate::commands::enqueue::image_to_gaussian::worldlabs::handle_worldlabs_marble::handle_worldlabs_marble;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::commands::response::shorthand::Response;
|
||||
use crate::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::events::sendable_event_trait::SendableEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::state::task_database::TaskDatabase;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::grok::state::grok_image_prompt_queue::GrokImagePromptQueue;
|
||||
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::services::worldlabs::state::worldlabs_credential_manager::WorldlabsCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use log::{error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlite_database::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GaussianModel {
|
||||
#[serde(rename = "world_labs_marble")]
|
||||
WorldLabsMarble,
|
||||
|
||||
#[serde(rename = "marble_0p1_mini")]
|
||||
Marble0p1Mini,
|
||||
|
||||
#[serde(rename = "marble_0p1_plus")]
|
||||
Marble0p1Plus,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageToGaussianRequest {
|
||||
/// REQUIRED.
|
||||
/// The model to use.
|
||||
pub model: Option<GaussianModel>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// The provider to use (defaults to Artcraft/Storyteller).
|
||||
/// Not all (provider, model) combinations are valid.
|
||||
pub provider: Option<GenerationProvider>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Text prompt for the gaussian generation. Required.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Images for the gaussian generation
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Name of the frontend caller.
|
||||
/// We'll use this to selectively trigger events.
|
||||
pub frontend_caller: Option<TauriCommandCaller>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined identifier that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_id: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined payload that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueImageToGaussianSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueImageToGaussianSuccessResponse {}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageToGaussianErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// No model available for image generation
|
||||
NoProviderAvailable,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_image_to_gaussian_command(
|
||||
request: EnqueueImageToGaussianRequest,
|
||||
app: AppHandle,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
worldlabs_creds_manager: State<'_, WorldlabsCredentialManager>,
|
||||
) -> Response<EnqueueImageToGaussianSuccessResponse, EnqueueImageToGaussianErrorType, ()> {
|
||||
|
||||
info!("enqueue_image_to_gaussian called");
|
||||
|
||||
let result = handle_request(
|
||||
request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&artcraft_usage_tracker,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
&worldlabs_creds_manager,
|
||||
&app_env_configs,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
notify_frontend_of_errors(&app, &err).await;
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueImageToGaussianErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match err {
|
||||
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueImageToGaussianErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
GenerateError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueImageToGaussianErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for image generation";
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: event.model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
CreditsBalanceChangedEvent{}.send_infallible(&app);
|
||||
|
||||
Ok(EnqueueImageToGaussianSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_request(
|
||||
request: EnqueueImageToGaussianRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
worldlabs_creds_manager: &WorldlabsCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let result = dispatch_request(
|
||||
&request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&storyteller_creds_manager,
|
||||
&worldlabs_creds_manager,
|
||||
&app_env_configs,
|
||||
).await;
|
||||
|
||||
let success_event = match result {
|
||||
Err(err) => return Err(err),
|
||||
Ok(event) => event,
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database_with_frontend_payload(
|
||||
task_database,
|
||||
request.frontend_caller,
|
||||
request.frontend_subscriber_id.as_deref(),
|
||||
request.frontend_subscriber_payload.as_deref(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
if let Err(err) = artcraft_usage_tracker.record_object_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::OtherPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
pub async fn dispatch_request(
|
||||
request: &EnqueueImageToGaussianRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
worldlabs_creds_manager: &WorldlabsCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
if request.model.is_none() {
|
||||
return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified));
|
||||
}
|
||||
|
||||
let provider = request.provider.unwrap_or(GenerationProvider::Artcraft);
|
||||
|
||||
match provider {
|
||||
GenerationProvider::WorldLabs => {
|
||||
handle_worldlabs_marble(
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
request,
|
||||
worldlabs_creds_manager,
|
||||
).await
|
||||
}
|
||||
GenerationProvider::Artcraft => {
|
||||
handle_gaussian_artcraft(
|
||||
request,
|
||||
app,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager,
|
||||
).await
|
||||
}
|
||||
_ => {
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
mod artcraft;
|
||||
pub mod enqueue_image_to_gaussian_command;
|
||||
mod worldlabs;
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::enqueue::image_to_gaussian::enqueue_image_to_gaussian_command::EnqueueImageToGaussianRequest;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::data_dir::subdirectory::trait_data_subdir::DataSubdir;
|
||||
use crate::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::services::worldlabs::state::worldlabs_credential_manager::WorldlabsCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use std::path::PathBuf;
|
||||
use artcraft_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use worldlabs_consumer_client::credentials::world_labs_cookies::WorldLabsCookies;
|
||||
use worldlabs_consumer_client::recipes::upload_image_and_create_world_with_retry::{upload_image_and_create_world_with_retry, FileBytesOrPath, UploadImageAndCreateWorldWithRetryArgs};
|
||||
|
||||
pub(super) const MAX_IMAGES: usize = 10;
|
||||
|
||||
pub async fn handle_worldlabs_marble(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
request: &EnqueueImageToGaussianRequest,
|
||||
worldlabs_creds_manager: &WorldlabsCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let world_labs_cookies = match worldlabs_creds_manager.maybe_copy_typed_cookies()? {
|
||||
Some(cookies) => cookies,
|
||||
None => {
|
||||
error!("No WorldLabs cookies!");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::WorldLabs, &app);
|
||||
return Err(GenerateError::needs_worldlabs_credentials());
|
||||
}
|
||||
};
|
||||
|
||||
let world_labs_bearer = match worldlabs_creds_manager.maybe_copy_bearer_token()? {
|
||||
Some(bearer) => bearer,
|
||||
None => {
|
||||
error!("No WorldLabs bearer token!");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::WorldLabs, &app);
|
||||
return Err(GenerateError::needs_worldlabs_credentials());
|
||||
}
|
||||
};
|
||||
|
||||
let world_labs_refresh = match worldlabs_creds_manager.maybe_copy_refresh_token()? {
|
||||
Some(bearer) => bearer,
|
||||
None => {
|
||||
error!("No WorldLabs refresh token!");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::WorldLabs, &app);
|
||||
return Err(GenerateError::needs_worldlabs_credentials());
|
||||
}
|
||||
};
|
||||
|
||||
let maybe_prompt = request.prompt
|
||||
.as_deref()
|
||||
.map(|prompt| prompt.trim().to_string());
|
||||
|
||||
info!("Downloading image file...");
|
||||
|
||||
let file_path = download_file(
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
request,
|
||||
).await?;
|
||||
|
||||
info!("Enqueueing WorldLabs request...");
|
||||
|
||||
let response = upload_image_and_create_world_with_retry(UploadImageAndCreateWorldWithRetryArgs {
|
||||
cookies: &world_labs_cookies,
|
||||
bearer_token: &world_labs_bearer,
|
||||
refresh_token: &world_labs_refresh,
|
||||
individual_request_timeout: None,
|
||||
file: FileBytesOrPath::Path(file_path),
|
||||
}).await?;
|
||||
|
||||
let run_id = response.run_id;
|
||||
let world_id = response.world_id;
|
||||
|
||||
info!("Run ID: {:?}", run_id);
|
||||
info!("World ID: {:?}", world_id);
|
||||
|
||||
if let Some(new_access) = response.maybe_new_access_tokens {
|
||||
info!("New access tokens were generated; saving.");
|
||||
worldlabs_creds_manager.replace_bearer_and_refresh_token(
|
||||
new_access.bearer_token,
|
||||
new_access.refresh_token
|
||||
)?;
|
||||
|
||||
worldlabs_creds_manager.persist_to_disk()?;
|
||||
}
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::WorldLabs,
|
||||
model: Some(GenerationModel::WorldlabsMarble),
|
||||
provider_job_id: Some(world_id.0),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn download_file(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
request: &EnqueueImageToGaussianRequest,
|
||||
) -> Result<PathBuf, GenerateError> {
|
||||
|
||||
let maybe_media_token = request.image_media_tokens
|
||||
.as_ref()
|
||||
.and_then(|tokens| tokens.get(0));
|
||||
|
||||
let media_token = match maybe_media_token {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
|
||||
min: 1,
|
||||
max: MAX_IMAGES as u32,
|
||||
provided: 0,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
info!("Calling get media file API: {:?}", app_env_configs.storyteller_host);
|
||||
|
||||
// TODO(bt,2025-12-18): Add a cache.
|
||||
info!("Using media token: {:?}", media_token);
|
||||
|
||||
let response = get_media_file(
|
||||
&app_env_configs.storyteller_host,
|
||||
media_token
|
||||
).await?;
|
||||
|
||||
let media_file_url = &response.media_file.media_links.cdn_url;
|
||||
let extension_with_dot = get_url_file_extension(media_file_url)
|
||||
.map(|ext| format!(".{}", ext))
|
||||
.unwrap_or_else(|| ".png".to_string());
|
||||
|
||||
let filename = format!("{}{}", response.media_file.token.as_str(), extension_with_dot);
|
||||
let filename = app_data_root.downloads_dir().path().join(&filename);
|
||||
|
||||
simple_http_download(&media_file_url, &filename).await?;
|
||||
|
||||
Ok(filename)
|
||||
}
|
||||
|
||||
//async fn download_files(
|
||||
// app_data_root: &AppDataRoot,
|
||||
// app_env_configs: &AppEnvConfigs,
|
||||
// request: &EnqueueImageToGaussianRequest,
|
||||
//) -> Result<Vec<PathBuf>, GenerateError> {
|
||||
//
|
||||
// let maybe_media_token = request.image_media_tokens
|
||||
// .as_ref()
|
||||
// .and_then(|tokens| tokens.get(0));
|
||||
//
|
||||
//
|
||||
// let media_tokens = match request.image_media_tokens.as_ref() {
|
||||
// Some(media_tokens) => media_tokens.to_vec(),
|
||||
// None => {
|
||||
// return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
|
||||
// min: 1,
|
||||
// max: MAX_IMAGES as u32,
|
||||
// provided: 0,
|
||||
// }));
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// if media_tokens.len() > MAX_IMAGES {
|
||||
// return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
|
||||
// min: 1,
|
||||
// max: MAX_IMAGES as u32,
|
||||
// provided: media_tokens.len() as u32,
|
||||
// }));
|
||||
// }
|
||||
//
|
||||
// info!("Calling get media file API: {:?}", app_env_configs.storyteller_host);
|
||||
//
|
||||
// let mut local_files_to_upload = Vec::with_capacity(10);
|
||||
//
|
||||
// // TODO(bt,2025-07-07): This is inefficient. Cache and parallelize this.
|
||||
// // TODO(bt,2025-12-18): Add a cache.
|
||||
// for media_token in media_tokens.iter() {
|
||||
// info!("Using media token: {:?}", media_token);
|
||||
//
|
||||
// let response = get_media_file(
|
||||
// &app_env_configs.storyteller_host,
|
||||
// media_token
|
||||
// ).await?;
|
||||
//
|
||||
// let media_file_url = &response.media_file.media_links.cdn_url;
|
||||
// let extension_with_dot = get_url_file_extension(media_file_url)
|
||||
// .map(|ext| format!(".{}", ext))
|
||||
// .unwrap_or_else(|| ".png".to_string());
|
||||
//
|
||||
// let filename = format!("{}{}", response.media_file.token.as_str(), extension_with_dot);
|
||||
// let filename = app_data_root.downloads_dir().path().join(&filename);
|
||||
//
|
||||
// simple_http_download(&media_file_url, &filename).await?;
|
||||
//
|
||||
// local_files_to_upload.push(filename);
|
||||
// }
|
||||
//
|
||||
// Ok(local_files_to_upload)
|
||||
//}
|
||||
|
||||
// TODO:
|
||||
// fn handle_midjourney_errors(
|
||||
// app: &AppHandle,
|
||||
// maybe_errors: Option<Vec<TextToImageError>>
|
||||
// ) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
// if let Some(errors) = maybe_errors {
|
||||
// if !errors.is_empty() {
|
||||
// let messages: Vec<String> = errors.iter()
|
||||
// .map(|e| format!("{:?}", e))
|
||||
// .collect();
|
||||
//
|
||||
// let combined_message = messages.join("; ");
|
||||
//
|
||||
// let event = FlashUserInputErrorEvent {
|
||||
// message: format!("Midjourney Error: {}", combined_message),
|
||||
// };
|
||||
//
|
||||
// if let Err(err) = event.send(&app) {
|
||||
// error!("Failed to send FlashUserInputErrorEvent: {:?}", err); // Fail open
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Err(GenerateError::ProviderFailure(ProviderFailureReason::MidjourneyJobEnqueueFailed))
|
||||
// }
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub (super) mod handle_worldlabs_marble;
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_client::api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::GenerateHunyuan20ImageTo3dRequest;
|
||||
use artcraft_client::api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::GenerateHunyuan21ImageTo3dRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use artcraft_client::endpoints::generate::object::generate_hunyuan_3d_2_0_image_to_3d::generate_hunyuan3d_2_0_image_to_3d;
|
||||
use artcraft_client::endpoints::generate::object::generate_hunyuan_3d_2_1_image_to_3d::generate_hunyuan3d_2_1_image_to_3d;
|
||||
use tauri::AppHandle;
|
||||
use artcraft_client::api_defs::generate::object::multi_function::hunyuan3d_v3_multi_function_object_gen::Hunyuan3dV3MultiFunctionObjectGenRequest;
|
||||
use artcraft_client::endpoints::generate::object::multi_function::hunyuan3d_v3_multi_function_object_gen::hunyuan3d_v3_multi_function_object_gen;
|
||||
|
||||
pub async fn handle_artcraft_object(
|
||||
request: &EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<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::ImageTo3d);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft image to 3d ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let mut used_model = None;
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified());
|
||||
}
|
||||
Some(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2 |
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0
|
||||
) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 2.0");
|
||||
used_model = Some(GenerationModel::Hunyuan3d2_0);
|
||||
let request = GenerateHunyuan20ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
};
|
||||
let result = generate_hunyuan3d_2_0_image_to_3d(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Hunyuan 3D 2.0");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Hunyuan 3D 2.0: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(EnqueueImageTo3dObjectModel::Hunyuan3d2_1) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 2.1");
|
||||
used_model = Some(GenerationModel::Hunyuan3d2_1);
|
||||
let request = GenerateHunyuan21ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
};
|
||||
let result = generate_hunyuan3d_2_1_image_to_3d(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Hunyuan 3D 2.1");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Hunyuan 3D 2.1: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(EnqueueImageTo3dObjectModel::Hunyuan3d3) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 3");
|
||||
used_model = Some(GenerationModel::Hunyuan3d3);
|
||||
let request = Hunyuan3dV3MultiFunctionObjectGenRequest {
|
||||
uuid_idempotency_token,
|
||||
image_media_token: request.image_media_token.clone(),
|
||||
prompt: None,
|
||||
back_image_media_token: None,
|
||||
left_image_media_token: None,
|
||||
right_image_media_token: None,
|
||||
face_count: None,
|
||||
generate_type: None,
|
||||
polygon_type: None,
|
||||
enable_pbr: None,
|
||||
};
|
||||
let result = hunyuan3d_v3_multi_function_object_gen(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Hunyuan 3D 3");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Hunyuan 3D 3: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued 3D generation");
|
||||
|
||||
//let event = GenerationEnqueueSuccessEvent {
|
||||
// action: GenerationAction::ImageTo3d,
|
||||
// service: GenerationServiceProvider::Artcraft,
|
||||
// model: None,
|
||||
//};
|
||||
//
|
||||
//if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
//}
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::ObjectGeneration,
|
||||
model: used_model,
|
||||
provider: GenerationProvider::Artcraft,
|
||||
provider_job_id: Some(job_token.to_string()),
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub (super) mod handle_artcraft_object;
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::enqueue::image_to_object::artcraft::handle_artcraft_object::handle_artcraft_object;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::commands::response::shorthand::Response;
|
||||
use crate::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent;
|
||||
use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::state::task_database::TaskDatabase;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageTo3dObjectRequest {
|
||||
/// Image media file; the image to remove the background from.
|
||||
/// TODO: In the future we may support base64 images, URLs, or file paths here.
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<EnqueueImageTo3dObjectModel>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Name of the frontend caller.
|
||||
/// We'll use this to selectively trigger events.
|
||||
pub frontend_caller: Option<TauriCommandCaller>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined identifier that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_id: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined payload that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageTo3dObjectModel {
|
||||
#[deprecated(note="Use `hunyuan_3d_2_0` instead")]
|
||||
#[serde(rename = "hunyuan_3d_2")]
|
||||
Hunyuan3d2,
|
||||
#[serde(rename = "hunyuan_3d_2_0")]
|
||||
Hunyuan3d2_0,
|
||||
#[serde(rename = "hunyuan_3d_2_1")]
|
||||
Hunyuan3d2_1,
|
||||
#[serde(rename = "hunyuan_3d_3")]
|
||||
Hunyuan3d3,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueImageTo3dObjectSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueImageTo3dObjectSuccessResponse {}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageTo3dObjectErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// No model available for object generation
|
||||
NoProviderAvailable,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// No Fal API key available
|
||||
NeedsFalApiKey,
|
||||
/// Fal had an API error
|
||||
FalError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_image_to_3d_object_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueImageTo3dObjectSuccessResponse, EnqueueImageTo3dObjectErrorType, ()> {
|
||||
|
||||
info!("enqueue_image_to_3d_object_command called");
|
||||
|
||||
let result = handle_request(
|
||||
request,
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
notify_frontend_of_errors(&app, &err).await;
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueImageTo3dObjectErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match err {
|
||||
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for object generation";
|
||||
}
|
||||
GenerateError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for object generation";
|
||||
}
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials) => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsStorytellerCredentials;
|
||||
error_message = "You need to be logged into Artcraft.";
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: event.model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
CreditsBalanceChangedEvent{}.send_infallible(&app);
|
||||
|
||||
Ok(EnqueueImageTo3dObjectSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let result = handle_artcraft_object(
|
||||
&request,
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&storyteller_creds_manager,
|
||||
).await;
|
||||
|
||||
let success_event = match result {
|
||||
Err(err) => return Err(err),
|
||||
Ok(event) => event,
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database_with_frontend_payload(
|
||||
task_database,
|
||||
request.frontend_caller.clone(),
|
||||
request.frontend_subscriber_id.as_deref(),
|
||||
request.frontend_subscriber_payload.as_deref(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
if let Err(err) = artcraft_usage_tracker.record_object_generation(1, ArtcraftUsageType::ImageToResult, ArtcraftUsagePage::ObjectPage) {
|
||||
// NB: Fail open.
|
||||
warn!("Failed to report usage: {:?}", err);
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
pub mod enqueue_image_to_3d_object_command;
|
||||
mod artcraft;
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
pub mod common;
|
||||
pub mod generate_error;
|
||||
pub mod image_bg_removal;
|
||||
pub mod image_to_gaussian;
|
||||
pub mod image_to_object;
|
||||
pub mod task_enqueue_success;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
|
||||
use crate::commands::enqueue::generate_error::{CredentialProblemReason, GenerateError};
|
||||
use crate::commands::generate::generate_error::{CredentialProblemReason, GenerateError};
|
||||
use crate::credentials::credential::Credential;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::commands::enqueue::generate_error::{BillingIssueReason, BillingProvider, GenerateError, ProviderFailureReason};
|
||||
use crate::commands::generate::generate_error::{BillingIssueReason, BillingProvider, GenerateError, ProviderFailureReason};
|
||||
use crate::events::functional_events::show_provider_billing_modal_event::ShowProviderBillingModalEvent;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use log::warn;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use tauri::AppHandle;
|
||||
|
||||
use crate::commands::enqueue::generate_error::{CredentialProblemReason, GenerateError};
|
||||
use crate::commands::generate::generate_error::{CredentialProblemReason, GenerateError};
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::show_credential_error_modal_event::ShowCredentialErrorModalEvent;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::commands::enqueue::generate_error::{GenerateError, ProviderFailureReason};
|
||||
use crate::commands::generate::generate_error::{GenerateError, ProviderFailureReason};
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
pub mod maybe_notify_frontend_of_billing_errors;
|
||||
pub mod maybe_notify_frontend_of_credential_errors;
|
||||
pub mod maybe_notify_frontend_of_grok_errors;
|
||||
pub mod notify_frontend_of_errors;
|
||||
pub mod generation_credential;
|
||||
pub mod router_image_request_to_artcraft_prompt;
|
||||
pub mod router_video_request_to_artcraft_prompt;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
use crate::commands::generate::common::maybe_notify_frontend_of_billing_errors::maybe_notify_frontend_of_billing_errors;
|
||||
use crate::commands::generate::common::maybe_notify_frontend_of_credential_errors::maybe_notify_frontend_of_credential_errors;
|
||||
use crate::commands::generate::common::maybe_notify_frontend_of_grok_errors::maybe_notify_frontend_of_grok_errors;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn notify_frontend_of_errors(
|
||||
app: &AppHandle,
|
||||
error: &GenerateError,
|
||||
) {
|
||||
maybe_notify_frontend_of_billing_errors(app, error).await;
|
||||
maybe_notify_frontend_of_credential_errors(app, error).await;
|
||||
maybe_notify_frontend_of_grok_errors(app, error).await;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_audio::handle_credential_router::handle_credential_router;
|
||||
use crate::commands::generate::generate_audio::request::{
|
||||
TauriGenerateAudioErrorType, TauriGenerateAudioRequest, TauriGenerateAudioResponse,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ use router::api::image_list_ref::ImageListRef;
|
|||
|
||||
use crate::api_adapters::models::audio::tauri_audio_model_to_generation_model::tauri_audio_model_to_generation_model;
|
||||
use crate::api_adapters::models::audio::tauri_audio_model_to_router_model::tauri_audio_model_to_router_model;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential};
|
||||
use crate::commands::generate::generate_audio::request::TauriGenerateAudioRequest;
|
||||
use crate::credentials::artcraft_api_host::maybe_artcraft_api_host_for_service;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::generate::generate_error::{GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_image::providers::router::handle_credential_router::handle_credential_router;
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::{
|
||||
TauriGenerateImageErrorType, TauriGenerateImageRequest, TauriGenerateImageResponse,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use log::info;
|
||||
|
||||
use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::{GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_legacy::handle_artcraft_via_legacy;
|
||||
use crate::commands::generate::generate_image::providers::artcraft::handle_artcraft_via_omni_endpoint::handle_artcraft_via_omni_endpoint;
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_image::providers::artcraft::legacy::artcraft_flux_2_lora_angles::handle_flux_2_lora_angles;
|
||||
use crate::commands::generate::generate_image::providers::artcraft::legacy::artcraft_flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint;
|
||||
use crate::commands::generate::generate_image::providers::artcraft::legacy::artcraft_flux_pro_kontext_edit::handle_flux_pro_kontext_edit;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use log::{error, info};
|
|||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::api_adapters::models::image::tauri_image_model_to_enums_model::tauri_image_model_to_enums_model;
|
||||
use crate::api_adapters::models::image::tauri_image_model_to_generation_model::tauri_image_model_to_generation_model;
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ use router::generate::generate_image::image_generation_draft_or_request::ImageGe
|
|||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
use crate::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ use uuid_utils::uuid::generate_random_uuid;
|
|||
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
use crate::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ use enums::tauri::tasks::task_type::TaskType;
|
|||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
use crate::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ use router::generate::generate_image::image_generation_draft_or_request::ImageGe
|
|||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
use crate::commands::generate::generate_image::utils::parse_semantic_media_files::SemanticMediaFiles;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::grok::state::grok_image_prompt_queue::{GrokImagePromptQueue, PromptItem};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use grok_consumer_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_grok(
|
||||
app: &AppHandle,
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
creds_manager: &GrokCredentialManager,
|
||||
grok_image_prompt_queue: &GrokImagePromptQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
|
||||
//// TODO: We can request population of the user info if absent or expired.
|
||||
//let websocket = get_websocket(
|
||||
// app,
|
||||
// creds_manager,
|
||||
// websocket_manager,
|
||||
//).await?;
|
||||
//
|
||||
//let _result = prompt_websocket_image(PromptWebsocketImageArgs {
|
||||
// websocket_wrapped: &websocket,
|
||||
// prompt: request.prompt.as_deref().unwrap_or_else(||""),
|
||||
//}).await?;
|
||||
//
|
||||
//let request_id = listen_for_websocket_request_id(ListenForWebsocketRequestIdArgs {
|
||||
// websocket: &websocket,
|
||||
// timeout: Duration::from_millis(10_000),
|
||||
//}).await?;
|
||||
//
|
||||
//let request_id = match request_id.request_id {
|
||||
// Some(request_id) => request_id.to_string(),
|
||||
// None => {
|
||||
// warn!("No request id from Grok image generation (websocket)");
|
||||
// return Err(GenerateError::ProviderFailure(ProviderFailureReason::GrokJobEnqueueFailed));
|
||||
// }
|
||||
//};
|
||||
//
|
||||
//info!("Successfully enqueued MidJourney. Job token: {}", request_id);
|
||||
|
||||
let job_id = generate_random_uuid();
|
||||
|
||||
let prompt = request.prompt
|
||||
.as_deref()
|
||||
.map(|prompt| prompt.trim().to_string())
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
|
||||
let aspect_ratio = get_aspect_ratio(request);
|
||||
|
||||
grok_image_prompt_queue.enqueue(PromptItem {
|
||||
task_id: job_id.clone(),
|
||||
prompt,
|
||||
aspect_ratio: aspect_ratio.unwrap_or(ClientMessageAspectRatio::Square),
|
||||
})?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Grok,
|
||||
model: Some(GenerationModel::GrokImage),
|
||||
provider_job_id: Some(job_id),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option<ClientMessageAspectRatio> {
|
||||
if let Some(common_aspect_ratio) = request.common_aspect_ratio {
|
||||
// Handle modern aspect ratio
|
||||
let aspect = aspect_ratio_to_grok_image(common_aspect_ratio);
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
if let Some(aspect_ratio) = request.aspect_ratio {
|
||||
// Handle deprecated aspect ratio
|
||||
return match aspect_ratio {
|
||||
TextToImageSize::Auto => Some(ClientMessageAspectRatio::Square),
|
||||
TextToImageSize::Square => Some(ClientMessageAspectRatio::Square),
|
||||
TextToImageSize::Wide => Some(ClientMessageAspectRatio::WideThreeByTwo),
|
||||
TextToImageSize::Tall => Some(ClientMessageAspectRatio::TallTwoByThree),
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub (super) mod handle_grok;
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason, ProviderFailureReason};
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::{EditImageQuality, EditImageSize};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent;
|
||||
use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::events::warning_events::flash_user_input_error_event::FlashUserInputErrorEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use uuid_utils::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use midjourney_client::client::midjourney_hostname::MidjourneyHostname;
|
||||
use midjourney_client::endpoints::submit_job::{submit_job, SubmitJobRequest};
|
||||
use midjourney_client::error::midjourney_api_error::MidjourneyApiError;
|
||||
use midjourney_client::recipes::channel_id::ChannelId;
|
||||
use midjourney_client::recipes::text_to_image::{text_to_image, TextToImageError, TextToImageRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_midjourney(
|
||||
app: &AppHandle,
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match mj_creds_manager.maybe_copy_cookie_store() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
error!("Midjourney credentials not found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app);
|
||||
return Err(GenerateError::needs_midjourney_credentials());
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Midjourney credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app);
|
||||
return Err(GenerateError::needs_midjourney_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: We can request population of the user info if absent or expired.
|
||||
|
||||
let user_info = match mj_creds_manager.maybe_copy_user_info() {
|
||||
Ok(Some(user_info)) => user_info,
|
||||
Ok(None) => {
|
||||
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo));
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Midjourney user info: {:?}", err);
|
||||
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo));
|
||||
},
|
||||
};
|
||||
|
||||
let channel_id = match user_info.user_id {
|
||||
Some(user_id) => ChannelId::UserId(user_id),
|
||||
None => {
|
||||
error!("Midjourney user info does not contain a user ID.");
|
||||
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserId));
|
||||
}
|
||||
};
|
||||
|
||||
info!("Calling midjourney ...");
|
||||
|
||||
let cookie_header = creds.to_cookie_string();
|
||||
|
||||
let prompt = request.prompt
|
||||
.as_deref()
|
||||
.unwrap_or("");
|
||||
|
||||
let result = text_to_image(TextToImageRequest {
|
||||
prompt,
|
||||
channel_id: &channel_id,
|
||||
hostname: MidjourneyHostname::Standard,
|
||||
cookie_header,
|
||||
}).await;
|
||||
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
error!("Failed to use MidJourney: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
let job_id = match result.maybe_job_id {
|
||||
Some(job_id) => job_id,
|
||||
None => {
|
||||
error!("Failed to enqueue MidJourney: No job ID returned.");
|
||||
return handle_midjourney_errors(app, result.maybe_errors);
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued MidJourney. Job token: {}", job_id);
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Midjourney,
|
||||
model: Some(GenerationModel::Midjourney),
|
||||
provider_job_id: Some(job_id),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_midjourney_errors(
|
||||
app: &AppHandle,
|
||||
maybe_errors: Option<Vec<TextToImageError>>
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
if let Some(errors) = maybe_errors {
|
||||
if !errors.is_empty() {
|
||||
let messages: Vec<String> = errors.iter()
|
||||
.map(|e| format!("{:?}", e))
|
||||
.collect();
|
||||
|
||||
let combined_message = messages.join("; ");
|
||||
|
||||
let event = FlashUserInputErrorEvent {
|
||||
message: format!("Midjourney Error: {}", combined_message),
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to send FlashUserInputErrorEvent: {:?}", err); // Fail open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::ProviderFailure(ProviderFailureReason::MidjourneyJobEnqueueFailed))
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub (super) mod handle_midjourney;
|
||||
|
|
@ -17,8 +17,8 @@ use tokens::tokens::prompts::PromptToken;
|
|||
|
||||
use crate::api_adapters::models::image::tauri_image_model_to_generation_model::tauri_image_model_to_generation_model;
|
||||
use crate::api_adapters::models::image::tauri_image_model_to_router_model::tauri_image_model_to_router_model;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::router_image_request_to_artcraft_prompt::router_image_request_to_artcraft_prompt;
|
||||
use crate::commands::generate::generate_image::providers::router::utils::convert_enums_to_router::{convert_aspect_ratio, convert_quality, convert_resolution};
|
||||
use crate::commands::generate::generate_image::providers::router::utils::map_media_files_to_urls::map_media_file_tokens_to_cdn_urls;
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ use tokens::tokens::media_files::MediaFileToken;
|
|||
|
||||
use crate::api_adapters::models::image::tauri_image_model_to_generation_model::tauri_image_model_to_generation_model;
|
||||
use crate::api_adapters::models::image::tauri_image_model_to_router_model::tauri_image_model_to_router_model;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential};
|
||||
use crate::commands::generate::generate_image::providers::router::handle_api_providers::handle_api_key_provider;
|
||||
use crate::commands::generate::generate_image::providers::router::utils::convert_enums_to_router::{convert_aspect_ratio, convert_quality, convert_resolution};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use enums::common::generation_provider::GenerationProvider;
|
||||
use log::info;
|
||||
|
||||
use crate::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::{GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_image::providers::router::handle_api_providers::handle_api_key_provider;
|
||||
use crate::commands::generate::generate_image::providers::router::handle_web_login_providers::handle_web_login_provider;
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use enums::common::generation_provider::GenerationProvider;
|
||||
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
use crate::providers::credentials::payload::web_login::WebLoginData;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use artcraft_client::utils::api_host::ApiHost;
|
|||
use log::info;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
|
||||
/// Resolve a list of media file tokens to their CDN URLs.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,135 +0,0 @@
|
|||
use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_grok_image::aspect_ratio_to_grok_image;
|
||||
use crate::api_adapters::aspect_ratio::convert::aspect_ratio_to_sora_native_gpt_image_1::aspect_ratio_to_sora_native_gpt_image_1;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use errors::AnyhowResult;
|
||||
use grok_consumer_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const SORA_SIMPLE_IMAGE_GEN_TIMEOUT : Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
pub async fn handle_gpt_image_1_sora_text_to_image(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let mut creds = match sora_creds_manager.get_credentials() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
error!("Sora credentials not found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Sora credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
}
|
||||
};
|
||||
|
||||
let credential_updated = maybe_upgrade_or_renew_session(&mut creds)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upgrade or renew session: {:?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
if credential_updated {
|
||||
info!("Storing updated credentials");
|
||||
sora_creds_manager.set_credentials(&creds)?;
|
||||
}
|
||||
|
||||
let prompt = request.prompt.as_deref().unwrap_or("");
|
||||
|
||||
let aspect_ratio = get_aspect_ratio(request)
|
||||
.unwrap_or(ImageSize::Square);
|
||||
|
||||
let num_images = match request.number_images {
|
||||
None => NumImages::One,
|
||||
Some(1) => NumImages::One,
|
||||
Some(2) => NumImages::Two,
|
||||
Some(3) => NumImages::Two, // NB: There is no option for three images!
|
||||
Some(4) => NumImages::Four,
|
||||
_ => NumImages::One,
|
||||
};
|
||||
|
||||
let result =
|
||||
simple_image_gen_with_session_auto_renew(SimpleImageGenAutoRenewRequest {
|
||||
prompt: prompt.to_string(),
|
||||
num_images,
|
||||
image_size: aspect_ratio,
|
||||
credentials: &creds,
|
||||
request_timeout: Some(SORA_SIMPLE_IMAGE_GEN_TIMEOUT),
|
||||
}).await;
|
||||
|
||||
let (response, maybe_new_creds) =
|
||||
match result {
|
||||
Ok((response, maybe_new_creds)) => (response, maybe_new_creds),
|
||||
Err(err) => {
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(new_creds) = maybe_new_creds {
|
||||
info!("Storing updated credentials.");
|
||||
sora_creds_manager.set_credentials(&new_creds)?;
|
||||
}
|
||||
|
||||
info!("New Sora Task ID: {:?} ", response.task_id);
|
||||
|
||||
sora_task_queue.insert(&response.task_id)?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Sora,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(GenerationModel::GptImage1),
|
||||
provider_job_id: Some(response.task_id.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_aspect_ratio(request: &EnqueueTextToImageRequest) -> Option<ImageSize> {
|
||||
if let Some(common_aspect_ratio) = request.common_aspect_ratio {
|
||||
// Handle modern aspect ratio
|
||||
let aspect = aspect_ratio_to_sora_native_gpt_image_1(common_aspect_ratio);
|
||||
return Some(aspect);
|
||||
}
|
||||
|
||||
if let Some(aspect_ratio) = request.aspect_ratio {
|
||||
// Handle deprecated aspect ratio
|
||||
return match aspect_ratio {
|
||||
TextToImageSize::Auto => Some(ImageSize::Square),
|
||||
TextToImageSize::Square => Some(ImageSize::Square),
|
||||
TextToImageSize::Wide => Some(ImageSize::Wide),
|
||||
TextToImageSize::Tall => Some(ImageSize::Tall),
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::deprecated::text_to_image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::commands::generate::generate_image::sora::handle_gpt_image_1_sora_text_to_image::handle_gpt_image_1_sora_text_to_image;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_text_to_image_sora(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
handle_gpt_image_1_sora_text_to_image(
|
||||
request,
|
||||
app,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
pub (super) mod handle_text_to_image_sora;
|
||||
mod handle_gpt_image_1_sora_text_to_image;
|
||||
|
|
@ -4,7 +4,7 @@ use artcraft_client::credentials::storyteller_credential_set::StorytellerCredent
|
|||
use artcraft_client::utils::api_host::ApiHost;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::generate::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
use crate::utils::upload_bytes_to_media_file::upload_image_bytes_as_media_file::upload_image_bytes_as_media_file;
|
||||
use crate::utils::upload_bytes_to_media_file::upload_mask_image_bytes_as_media_file::upload_mask_image_bytes_as_media_file;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_mesh::handle_credential_router::handle_credential_router;
|
||||
use crate::commands::generate::generate_mesh::request::{
|
||||
TauriGenerateMeshErrorType, TauriGenerateMeshRequest, TauriGenerateMeshResponse,
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ use router::api::mesh_ref::MeshRef;
|
|||
|
||||
use crate::api_adapters::models::mesh::tauri_mesh_model_to_generation_model::tauri_mesh_model_to_generation_model;
|
||||
use crate::api_adapters::models::mesh::tauri_mesh_model_to_router_model::tauri_mesh_model_to_router_model;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential};
|
||||
use crate::commands::generate::generate_mesh::request::TauriGenerateMeshRequest;
|
||||
use crate::credentials::artcraft_api_host::maybe_artcraft_api_host_for_service;
|
||||
|
|
|
|||
|
|
@ -71,9 +71,9 @@ pub struct TauriGenerateMeshRequest {
|
|||
/// model ids (`CommonMeshModel` serde strings).
|
||||
#[derive(Clone, Copy, Debug, Deserialize)]
|
||||
pub enum TauriMeshModel {
|
||||
#[serde(rename = "hunyuan_3d_2p0")]
|
||||
#[serde(rename = "hunyuan_3d_2p0", alias = "hunyuan_3d_2_0", alias = "hunyuan_3d_2")]
|
||||
Hunyuan3d2p0,
|
||||
#[serde(rename = "hunyuan_3d_2p1")]
|
||||
#[serde(rename = "hunyuan_3d_2p1", alias = "hunyuan_3d_2_1")]
|
||||
Hunyuan3d2p1,
|
||||
#[serde(rename = "hunyuan_3d_3")]
|
||||
Hunyuan3d3,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_splat::handle_credential_router::handle_credential_router;
|
||||
use crate::commands::generate::generate_splat::request::{
|
||||
TauriGenerateSplatErrorType, TauriGenerateSplatRequest, TauriGenerateSplatResponse,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ use router::api::video_ref::VideoRef;
|
|||
|
||||
use crate::api_adapters::models::splat::tauri_splat_model_to_generation_model::tauri_splat_model_to_generation_model;
|
||||
use crate::api_adapters::models::splat::tauri_splat_model_to_router_model::tauri_splat_model_to_router_model;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential};
|
||||
use crate::commands::generate::generate_splat::request::TauriGenerateSplatRequest;
|
||||
use crate::credentials::artcraft_api_host::maybe_artcraft_api_host_for_service;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::generation_events::common::GenerationAction;
|
||||
use crate::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::api_adapters::models::video::tauri_video_model_to_generation_model::tauri_video_model_to_generation_model;
|
||||
use crate::api_adapters::models::video::tauri_video_model_to_router_model::tauri_video_model_to_router_model;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_video::artcraft::get_storyteller_creds_or_error::get_storyteller_creds_or_error;
|
||||
use crate::commands::generate::generate_video::artcraft::handle_artcraft_video_via_router::handle_artcraft_video_via_router;
|
||||
use crate::commands::generate::generate_video::request::TauriGenerateVideoRequest;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::generate_video::request::TauriGenerateVideoRequest;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::generate::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_video::handle_credential_router::handle_credential_router;
|
||||
use crate::commands::generate::generate_video::request::{
|
||||
TauriGenerateVideoErrorType, TauriGenerateVideoRequest, TauriGenerateVideoResponse,
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::commands::deprecated::image_edit::enqueue_edit_image_command::EnqueueEditImageCommand;
|
||||
use crate::commands::deprecated::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::commands::generate::generate_video::request::{TauriGenerateVideoRequest, TauriVideoModel, GrokAspectRatio, SoraOrientation};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::grok::util::upload_image_media_file_to_grok::{upload_image_media_file_to_grok, UploadImageMediaFileToGrok};
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::sora::utils::upload_images_to_sora::{upload_images_to_sora, UploadImagesToSoraArgs};
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use grok_consumer_client::credentials::grok_cookies::GrokCookies;
|
||||
use grok_consumer_client::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use grok_consumer_client::datatypes::api::aspect_ratio::AspectRatio;
|
||||
use grok_consumer_client::datatypes::file_upload_spec::FileUploadSpec;
|
||||
use grok_consumer_client::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs};
|
||||
use grok_consumer_client::recipes::upload_image_and_generate_video::{upload_image_and_generate_video, UploadImageAndGenerateVideo};
|
||||
use grok_consumer_client::recipes::upload_image_and_generate_video_with_retry::{upload_image_and_generate_video_with_retry, UploadImageAndGenerateVideoWithRetry};
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::recipes::generate_sora2_video::generate_sora2_video_with_session_auto_renew::generate_sora2_video_with_session_auto_renew;
|
||||
use openai_sora_client::requests::generate_sora2_video::generate_sora2_video::{GenerateSora2VideoArgs, Orientation};
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const GROK_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
pub async fn handle_grok_video(
|
||||
request: &TauriGenerateVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
grok_credential_manager: &GrokCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = get_grok_creds(app, grok_credential_manager).await?;
|
||||
|
||||
let image_token = match request.image_media_token.as_ref() {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages { min: 1, max: 1, provided: 0 }))
|
||||
}
|
||||
};
|
||||
|
||||
info!("Downloading image media file...");
|
||||
|
||||
let local_image_file = download_media_file_to_temp_dir(
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
image_token,
|
||||
).await?;
|
||||
|
||||
let aspect_ratio = match request.grok_aspect_ratio {
|
||||
None => AspectRatio::WideThreeByTwo,
|
||||
Some(GrokAspectRatio::Landscape) => AspectRatio::WideThreeByTwo,
|
||||
Some(GrokAspectRatio::Portrait) => AspectRatio::TallTwoByThree,
|
||||
Some(GrokAspectRatio::Square) => AspectRatio::Square,
|
||||
};
|
||||
|
||||
info!("Calling Grok Video generate...");
|
||||
|
||||
let upload_result = upload_image_and_generate_video_with_retry(UploadImageAndGenerateVideoWithRetry {
|
||||
credentials: &creds,
|
||||
file: FileUploadSpec::Path(local_image_file.path()),
|
||||
prompt: request.prompt.as_deref(),
|
||||
aspect_ratio: Some(aspect_ratio),
|
||||
wait_for_generation: false,
|
||||
individual_request_timeout: Some(GROK_IMAGE_UPLOAD_TIMEOUT),
|
||||
mode: None,
|
||||
}).await;
|
||||
|
||||
let post_id = match upload_result {
|
||||
Err(err) => {
|
||||
error!("Failed to use Grok video: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
Ok(result) => {
|
||||
if let Some(secrets) = result.maybe_new_client_secrets {
|
||||
info!("New Grok secrets generated! Storing them in the credential manager...");
|
||||
grok_credential_manager.replace_client_secrets_only(secrets)?;
|
||||
}
|
||||
info!("Successfully enqueued Grok Video: post_id = {:?}", result.upload_result.post_id);
|
||||
result.upload_result.post_id
|
||||
}
|
||||
};
|
||||
|
||||
// NB: Grok's consumer API doesn't take a version selector; this only affects
|
||||
// which model is recorded for the task queue / events.
|
||||
let generation_model = match request.model {
|
||||
Some(TauriVideoModel::GrokImagineVideo1p5) => GenerationModel::GrokImagineVideo1p5,
|
||||
_ => GenerationModel::GrokVideo,
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Grok,
|
||||
model: Some(generation_model),
|
||||
provider_job_id: Some(post_id.to_string()),
|
||||
task_type: TaskType::VideoGeneration,
|
||||
maybe_queue_status_url: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_queue_response_url: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_grok_creds(app: &AppHandle, grok_credential_manager: &GrokCredentialManager) -> Result<GrokFullCredentials, GenerateError> {
|
||||
if let Some(creds) = grok_credential_manager.maybe_copy_full_credentials()? {
|
||||
return Ok(creds);
|
||||
}
|
||||
|
||||
let cookies = match grok_credential_manager.maybe_copy_cookie_store()? {
|
||||
Some(cookies) => cookies.to_cookie_string(),
|
||||
None => {
|
||||
warn!("No Grok Cookie stored. Must login.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Grok, &app);
|
||||
return Err(GenerateError::needs_grok_credentials())
|
||||
}
|
||||
};
|
||||
|
||||
let cookies = GrokCookies::new(cookies);
|
||||
|
||||
info!("Requesting Grok client secrets...");
|
||||
|
||||
let upgraded = request_client_secrets(RequestClientSecretsArgs {
|
||||
cookies: &cookies,
|
||||
}).await;
|
||||
|
||||
match upgraded {
|
||||
Err(err) => {
|
||||
error!("Failed to fetch Grok client secrets: {}", err); // NB: Fall-through
|
||||
}
|
||||
Ok(secrets) => {
|
||||
info!("Grok client secrets successfully upgraded...");
|
||||
let full_creds = GrokFullCredentials::from_cookies_and_client_secrets(cookies, secrets);
|
||||
grok_credential_manager.replace_full_credentials(full_creds.clone())?;
|
||||
info!("Persisting grok credentials to disk...");
|
||||
grok_credential_manager.persist_to_disk()?;
|
||||
return Ok(full_creds)
|
||||
}
|
||||
}
|
||||
|
||||
warn!("Grok upgrade failed. Try logging in again...");
|
||||
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Grok, &app);
|
||||
Err(GenerateError::needs_grok_credentials())
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub (super) mod handle_grok_video;
|
||||
|
|
@ -2,8 +2,8 @@ use log::info;
|
|||
|
||||
use crate::api_adapters::models::video::tauri_video_model_to_generation_model::tauri_video_model_to_generation_model;
|
||||
use crate::api_adapters::models::video::tauri_video_model_to_router_model::tauri_video_model_to_router_model;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential};
|
||||
use crate::commands::generate::generate_video::artcraft::handle_artcraft_video_via_router::handle_artcraft_video_via_router;
|
||||
use crate::commands::generate::generate_video::request::TauriGenerateVideoRequest;
|
||||
|
|
|
|||
|
|
@ -2,5 +2,4 @@ pub mod generate_video_command;
|
|||
mod handle_credential_router;
|
||||
pub mod request;
|
||||
pub mod artcraft;
|
||||
pub mod grok;
|
||||
pub mod sora2;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::generate::generate_error::GenerateError;
|
||||
use crate::commands::generate::generate_video::request::{TauriGenerateVideoRequest, SoraOrientation};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
|
|
|
|||
|
|
@ -4,4 +4,6 @@ pub mod generate_mesh;
|
|||
pub mod generate_splat;
|
||||
pub mod generate_video;
|
||||
pub mod common;
|
||||
pub mod generate_error;
|
||||
pub mod task_enqueue_success;
|
||||
pub mod models;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue