Compare commits

...
Author SHA1 Message Date
Brandon Thomas 5da97bc531 billing modal component 2025-08-30 20:32:51 -04:00
Brandon Thomas 7082cf2830 billing events (wip) 2025-08-30 20:13:40 -04:00
Brandon Thomas de4a70009d notification hook 2025-08-30 19:01:25 -04:00
Brandon Thomas 77ec1fb43a remove final bespoke errors 2025-08-30 17:56:42 -04:00
Brandon Thomas 12be88789e remove error 2025-08-30 17:20:05 -04:00
Brandon Thomas b7ce347f14 get rid of inpaint error 2025-08-30 17:18:13 -04:00
Brandon Thomas def1d6afda errors for object gen 2025-08-30 16:44:28 -04:00
Brandon Thomas 507e53a5da update errors for video gen 2025-08-30 16:36:15 -04:00
Brandon Thomas f313fa4bd8 remove unused type 2025-08-30 16:23:03 -04:00
Brandon Thomas de055856e2 adjust error messages for text-to-image 2025-08-30 16:06:43 -04:00
Brandon Thomas c6830a892b refactor error type 2025-08-30 11:36:37 -04:00
Brandon Thomas 889feec4be handle error in client lib 2025-08-30 10:05:17 -04:00
Brandon Thomas aea9b32507 note 2025-08-30 09:49:00 -04:00
Brandon Thomas 1cdf5b1118 server payments error codes 2025-08-30 09:47:16 -04:00
107 changed files with 1281 additions and 1123 deletions
@@ -11,6 +11,9 @@ pub enum ApiError {
/// 401. The request was not authorized.
Unauthorized(String),
// 402. Payment required.
PaymentRequired(String),
/// 403. The request was forbidden.
Forbidden(String),
@@ -56,6 +59,7 @@ impl Display for ApiError {
match self {
// Server response code errors
ApiError::InvalidRequest(msg) => write!(f, "Invalid request: {}", msg),
ApiError::PaymentRequired(msg) => write!(f, "Payment required: {}", msg),
ApiError::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
ApiError::Forbidden(msg) => write!(f, "Forbidden: {}", msg),
ApiError::NotFound(msg) => write!(f, "Not found: {}", msg),
@@ -80,6 +84,13 @@ impl Display for ApiError {
impl From<reqwest::Error> for ApiError {
fn from(error: reqwest::Error) -> Self {
if let Some(status) = error.status() {
let status = status.as_u16();
match status {
402 => return ApiError::PaymentRequired(error.to_string()),
_ => {} // NB: Fallthrough.
}
}
if error.is_timeout() {
ApiError::Timeout(error.to_string())
} else if error.is_connect() {
@@ -38,6 +38,7 @@ pub async fn filter_bad_response(response: reqwest::Response) -> Result<reqwest:
match status_code {
STATUS_401_UNAUTHORIZED => Err(ApiError::Unauthorized(response_body)),
STATUS_402_PAYMENT_REQUIRED => Err(ApiError::PaymentRequired(response_body)),
STATUS_403_FORBIDDEN => Err(ApiError::Forbidden(response_body)),
STATUS_404_NOT_FOUND => Err(ApiError::NotFound(response_body)),
STATUS_429_TOO_MANY_REQUESTS => Err(ApiError::TooManyRequests(response_body)),
@@ -1,5 +1,6 @@
pub const STATUS_401_UNAUTHORIZED : u16 = 401u16;
pub const STATUS_402_PAYMENT_REQUIRED : u16 = 402u16;
pub const STATUS_403_FORBIDDEN : u16 = 403u16;
pub const STATUS_404_NOT_FOUND : u16 = 404u16;
@@ -15,6 +16,7 @@ mod tests {
#[test]
pub fn test_codes() {
assert_eq!(STATUS_401_UNAUTHORIZED, 401);
assert_eq!(STATUS_402_PAYMENT_REQUIRED, 402);
assert_eq!(STATUS_403_FORBIDDEN, 403);
assert_eq!(STATUS_404_NOT_FOUND, 404);
assert_eq!(STATUS_429_TOO_MANY_REQUESTS, 429);
@@ -0,0 +1,57 @@
use crate::core::commands::enqueue::generate_error::{BillingIssueReason, BillingProvider, GenerateError, ProviderFailureReason};
use crate::core::events::functional_events::show_provider_billing_modal_event::ShowProviderBillingModalEvent;
use enums::common::generation_provider::GenerationProvider;
use log::warn;
use storyteller_client::error::api_error::ApiError;
use storyteller_client::error::storyteller_error::StorytellerError;
use tauri::AppHandle;
pub async fn maybe_notify_frontend_of_billing_errors(
app: &AppHandle,
error: &GenerateError,
) {
match error {
GenerateError::BillingIssue(reason) => {
billing_error(app, reason);
}
GenerateError::ProviderFailure(reason) => {
provider_billing_error(app, reason);
}
_ => {
// Do nothing for other types of errors
}
}
}
fn billing_error(
app: &AppHandle,
reason: &BillingIssueReason,
) {
let provider = match reason.provider {
BillingProvider::Fal => GenerationProvider::Fal,
BillingProvider::Midjourney => GenerationProvider::Midjourney,
BillingProvider::Sora => GenerationProvider::Sora,
BillingProvider::Storyteller => GenerationProvider::Artcraft,
};
warn!("Billing issue with: {:?}", provider);
ShowProviderBillingModalEvent::send_for_provider(provider, app);
}
fn provider_billing_error(
app: &AppHandle,
error: &ProviderFailureReason,
) {
let provider;
match error {
ProviderFailureReason::StorytellerError(StorytellerError::Api(ApiError::PaymentRequired(reason))) => {
warn!("Billing issue with Artcraft/Storyteller: {}", reason);
provider = GenerationProvider::Artcraft;
}
_ => {
return;
}
}
ShowProviderBillingModalEvent::send_for_provider(provider, app);
}
@@ -0,0 +1,2 @@
pub mod notify_frontend_of_errors;
mod maybe_notify_frontend_of_billing_errors;
@@ -0,0 +1,10 @@
use crate::core::commands::enqueue::common::maybe_notify_frontend_of_billing_errors::maybe_notify_frontend_of_billing_errors;
use crate::core::commands::enqueue::generate_error::GenerateError;
use tauri::AppHandle;
pub async fn notify_frontend_of_errors(
app: &AppHandle,
errors: &GenerateError,
) {
maybe_notify_frontend_of_billing_errors(app, errors).await;
}
@@ -0,0 +1,166 @@
use crate::core::artcraft_error::ArtcraftError;
use base64::DecodeError;
use errors::AnyhowError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use midjourney_client::error::midjourney_error::MidjourneyError;
use openai_sora_client::sora_error::SoraError;
use storyteller_client::error::storyteller_error::StorytellerError;
#[derive(Debug)]
pub enum GenerateError {
BadInput(BadInputReason),
MissingCredentials(MissingCredentialsReason),
ProviderFailure(ProviderFailureReason),
/// We couldn't find a provider to dispatch the request to.
NoProviderAvailable,
/// There was a billing, credits, or payments issue.
BillingIssue(BillingIssueReason),
/// The feature is not yet implemented.
NotYetImplemented(String),
// Misc error buckets
AnyhowError(AnyhowError),
DecodeError(DecodeError),
IoError(std::io::Error),
}
#[derive(Debug)]
pub enum BadInputReason {
Base64DecodeError,
BothImageMaskMediaTokenAndBytesSupplied,
CannotDetermineImageMimeType,
InvalidNumberOfInputImages {
provided: u32,
min: u32,
max: u32,
},
InvalidNumberOfRequestedImages {
requested: u32,
min: u32,
max: u32,
},
NoModelSpecified,
RequiredSourceImageMaskNotProvided,
RequiredSourceImageNotProvided,
WrongImageArguments(String),
}
#[derive(Debug)]
pub enum MissingCredentialsReason {
NeedsFalApiKey,
NeedsMidjourneyCredentials,
NeedsMidjourneyUserId,
NeedsMidjourneyUserInfo,
NeedsSoraCredentials,
NeedsStorytellerCredentials,
}
#[derive(Debug)]
pub struct BillingIssueReason {
pub provider: BillingProvider,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum BillingProvider {
Fal,
Midjourney,
Sora,
Storyteller,
}
#[derive(Debug)]
pub enum ProviderFailureReason {
Fal(FalErrorPlus),
MidjourneyError(MidjourneyError),
/// NB: The midjourney client doesn't categorize all errors, so we have to do so on our end.
MidjourneyJobEnqueueFailed,
SoraError(SoraError),
StorytellerError(StorytellerError),
}
impl GenerateError {
//
// Bad input
//
pub fn both_image_mask_media_token_and_bytes_supplied() -> Self {
Self::BadInput(BadInputReason::BothImageMaskMediaTokenAndBytesSupplied)
}
pub fn no_model_specified() -> Self {
Self::BadInput(BadInputReason::NoModelSpecified)
}
pub fn required_source_image_mask_not_provided() -> Self {
Self::BadInput(BadInputReason::RequiredSourceImageMaskNotProvided)
}
pub fn required_source_image_not_provided() -> Self {
Self::BadInput(BadInputReason::RequiredSourceImageNotProvided)
}
//
// Missing credentials
//
pub fn needs_fal_api_key() -> Self {
Self::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey)
}
pub fn needs_midjourney_credentials() -> Self {
Self::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyCredentials)
}
pub fn needs_sora_credentials() -> Self {
Self::MissingCredentials(MissingCredentialsReason::NeedsSoraCredentials)
}
pub fn needs_storyteller_credentials() -> Self {
Self::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials)
}
}
impl From<AnyhowError> for GenerateError {
fn from(value: AnyhowError) -> Self {
Self::AnyhowError(value)
}
}
impl From<ArtcraftError> for GenerateError {
fn from(value: ArtcraftError) -> Self {
match value {
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
ArtcraftError::IoError(e) => Self::IoError(e),
ArtcraftError::StorytellerError(e) => Self::ProviderFailure(ProviderFailureReason::StorytellerError(e)),
}
}
}
impl From<FalErrorPlus> for GenerateError {
fn from(value: FalErrorPlus) -> Self {
Self::ProviderFailure(ProviderFailureReason::Fal(value))
}
}
impl From<MidjourneyError> for GenerateError {
fn from(value: MidjourneyError) -> Self {
Self::ProviderFailure(ProviderFailureReason::MidjourneyError(value))
}
}
impl From<SoraError> for GenerateError {
fn from(value: SoraError) -> Self {
Self::ProviderFailure(ProviderFailureReason::SoraError(value))
}
}
impl From<StorytellerError> for GenerateError {
fn from(value: StorytellerError) -> Self {
Self::ProviderFailure(ProviderFailureReason::StorytellerError(value))
}
}
@@ -1,6 +1,6 @@
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal::handle_generic_bg_removal;
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit::handle_gpt_image_1_edit;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
@@ -124,6 +124,8 @@ pub async fn enqueue_image_bg_removal_command(
match result {
Err(err) => {
error!("Error removing background: {:?}", err);
notify_frontend_of_errors(&app, &err).await;
// TODO: Derive from err. Make service provider optional.
let event = GenerationEnqueueFailureEvent {
@@ -137,7 +139,7 @@ pub async fn enqueue_image_bg_removal_command(
error!("Failed to emit event: {:?}", err); // Fail open.
}
Err(err.to_tauri_response())
Err(error_to_tauri_response(err))
}
Ok(event) => {
let event = GenerationEnqueueSuccessEvent {
@@ -165,7 +167,7 @@ pub async fn handle_request(
storyteller_creds_manager: &StorytellerCredentialManager,
fal_creds_manager: &FalCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
// TODO(bt,2025-07-07): Other model/provider routing...
@@ -196,3 +198,35 @@ pub async fn handle_request(
Ok(success_event)
}
fn error_to_tauri_response(error: GenerateError) -> CommandErrorResponseWrapper<EnqueueImageBgRemovalErrorType, ()> {
let mut status = CommandErrorStatus::ServerError;
let mut error_type = EnqueueImageBgRemovalErrorType::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::NoProviderAvailable => {
status = CommandErrorStatus::ServerError;
error_type = EnqueueImageBgRemovalErrorType::NoProviderAvailable;
error_message = "No configured provider available for background removal".to_string();
}
GenerateError::BadInput(BadInputReason::RequiredSourceImageNotProvided) => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueImageBgRemovalErrorType::MissingImage;
error_message = "No image provided for background removal".to_string();
}
GenerateError::BadInput(BadInputReason::Base64DecodeError) => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueImageBgRemovalErrorType::Base64DecodeError;
error_message = "Failed to decode base64 image data".to_string();
}
_ => {}, // Other cases fall through.
}
CommandErrorResponseWrapper {
status,
error_message: Some(error_message.to_string()),
error_type: Some(error_type),
error_details: None,
}
}
@@ -1,71 +0,0 @@
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalErrorType;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
use errors::AnyhowError;
use serde::Serialize;
use storyteller_client::error::storyteller_error::StorytellerError;
#[derive(Debug)]
pub enum InternalBgRemovalError {
/// No provider is configured for the model type.
NoProviderAvailable,
/// No storyteller credentials are set.
NeedsStorytellerCredentials,
/// No image was provided for background removal.
MissingImage,
/// Could not decode the base64 image data.
Base64DecodeError,
AnyhowError(AnyhowError),
StorytellerError(StorytellerError),
}
impl From<AnyhowError> for InternalBgRemovalError {
fn from(value: AnyhowError) -> Self {
Self::AnyhowError(value)
}
}
impl From<StorytellerError> for InternalBgRemovalError {
fn from(value: StorytellerError) -> Self {
Self::StorytellerError(value)
}
}
impl InternalBgRemovalError {
pub fn to_tauri_response<E: Serialize>(&self) -> CommandErrorResponseWrapper<EnqueueImageBgRemovalErrorType, E> {
let mut status = CommandErrorStatus::ServerError;
let mut error_type = EnqueueImageBgRemovalErrorType::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 self {
InternalBgRemovalError::NoProviderAvailable => {
status = CommandErrorStatus::ServerError;
error_type = EnqueueImageBgRemovalErrorType::NoProviderAvailable;
error_message = "No configured provider available for background removal".to_string();
}
InternalBgRemovalError::MissingImage => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueImageBgRemovalErrorType::MissingImage;
error_message = "No image provided for background removal".to_string();
}
InternalBgRemovalError::Base64DecodeError => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueImageBgRemovalErrorType::Base64DecodeError;
error_message = "Failed to decode base64 image data".to_string();
}
InternalBgRemovalError::AnyhowError(_) => {}
InternalBgRemovalError::StorytellerError(_) => {}
InternalBgRemovalError::NeedsStorytellerCredentials => {}
}
CommandErrorResponseWrapper {
status,
error_message: Some(error_message.to_string()),
error_type: Some(error_type),
error_details: None,
}
}
}
@@ -1,9 +1,8 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal_artcraft::handle_generic_bg_removal_artcraft;
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal_fal::handle_generic_bg_removal_fal;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
@@ -27,7 +26,7 @@ pub async fn handle_generic_bg_removal(
storyteller_creds_manager: &StorytellerCredentialManager,
fal_creds_manager: &FalCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -65,5 +64,5 @@ pub async fn handle_generic_bg_removal(
}
}
Err(InternalBgRemovalError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,7 +1,6 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason};
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
@@ -44,12 +43,12 @@ pub async fn handle_generic_bg_removal_artcraft(
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalBgRemovalError::NeedsStorytellerCredentials);
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials));
},
};
@@ -84,7 +83,7 @@ pub async fn handle_generic_bg_removal_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft background removal: {:?}", err);
return Err(InternalBgRemovalError::StorytellerError(err));
return Err(GenerateError::ProviderFailure(ProviderFailureReason::StorytellerError(err)));
}
};
@@ -101,7 +100,7 @@ async fn upload_image_from_base64_bytes(
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
creds: &StorytellerCredentialSet
) -> Result<MediaFileToken, InternalBgRemovalError> {
) -> Result<MediaFileToken, GenerateError> {
let temp_file;
if let Some(base64_bytes) = &request.base64_image {
@@ -110,10 +109,10 @@ async fn upload_image_from_base64_bytes(
.await
.map_err(|err| {
error!("Failed to save base64 image to temp dir: {:?}", err);
InternalBgRemovalError::Base64DecodeError
GenerateError::BadInput(BadInputReason::Base64DecodeError)
})?;
} else {
return Err(InternalBgRemovalError::MissingImage);
return Err(GenerateError::BadInput(BadInputReason::RequiredSourceImageNotProvided));
};
info!("Uploading image media file from temp file: {:?}", temp_file.path());
@@ -129,7 +128,7 @@ async fn upload_image_from_base64_bytes(
}).await
.map_err(|err| {
error!("Failed to upload image media file: {:?}", err);
InternalBgRemovalError::StorytellerError(err)
GenerateError::ProviderFailure(ProviderFailureReason::StorytellerError(err))
})?;
Ok(result.media_file_token)
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
@@ -14,6 +14,6 @@ pub async fn handle_generic_bg_removal_fal(
app_env_configs: &AppEnvConfigs,
fal_creds_manager: &FalCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
unimplemented!("TODO: Implement fal background removal handling");
) -> Result<TaskEnqueueSuccess, GenerateError> {
Err(GenerateError::NotYetImplemented("TODO: Implement fal background removal handling".to_string()))
}
@@ -1,3 +1,2 @@
pub mod enqueue_image_bg_removal_command;
pub mod errors;
pub mod generic;
@@ -1,4 +1,5 @@
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::flux_kontext::handle_flux_kontext_edit::handle_flux_kontext_edit;
use crate::core::commands::enqueue::image_edit::gemini_25_flash::handle_gemini_25_flash_edit::handle_gemini_25_flash_edit;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit::handle_gpt_image_1_edit;
@@ -43,7 +44,7 @@ use storyteller_client::utils::api_host::ApiHost;
use tauri::{AppHandle, Manager, State};
use tokens::tokens::media_files::MediaFileToken;
/// This is used in the Tauri command bridge.
/// 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")]
@@ -194,6 +195,8 @@ pub async fn enqueue_contextual_edit_image_command(
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 {
@@ -207,7 +210,7 @@ pub async fn enqueue_contextual_edit_image_command(
error!("Failed to emit event: {:?}", err); // Fail open.
}
Err(err.to_tauri_response())
Err(error_to_tauri_response(err))
}
Ok(event) => {
let event = GenerationEnqueueSuccessEvent {
@@ -237,10 +240,10 @@ pub async fn handle_request(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let success_event= match request.model {
None => {
return Err(InternalContextualEditImageError::NoModelSpecified)
return Err(GenerateError::no_model_specified())
}
Some(ContextualImageEditModel::FluxProKontextMax) => {
handle_flux_kontext_edit(
@@ -302,3 +305,41 @@ pub async fn handle_request(
Ok(success_event)
}
fn error_to_tauri_response(error: GenerateError) -> CommandErrorResponseWrapper<EnqueueContextualEditImageErrorType, ()> {
let mut status = CommandErrorStatus::ServerError;
let mut error_type = EnqueueContextualEditImageErrorType::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 = EnqueueContextualEditImageErrorType::ModelNotSpecified;
error_message = "No model specified for image generation".to_string();
}
GenerateError::NoProviderAvailable => {
status = CommandErrorStatus::ServerError;
error_type = EnqueueContextualEditImageErrorType::NoProviderAvailable;
error_message = "No configured provider available for image generation".to_string();
}
GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages { min, max, requested }) => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueContextualEditImageErrorType::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 = EnqueueContextualEditImageErrorType::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,112 +0,0 @@
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageErrorType;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
use errors::AnyhowError;
use openai_sora_client::sora_error::SoraError;
use serde::Serialize;
use storyteller_client::error::storyteller_error::StorytellerError;
#[derive(Debug)]
pub enum InternalContextualEditImageError {
/// Request did not specify a model.
NoModelSpecified,
/// No provider is configured for the model type.
NoProviderAvailable,
/// No storyteller credentials are set.
NeedsStorytellerCredentials,
/// An invalid number of images was requested.
InvalidNumberOfRequestedImages {
min: u32,
max: u32,
requested: u32,
},
/// An invalid number of input images was requested.
InvalidNumberOfInputImages {
min: u32,
max: u32,
requested: u32,
},
// TODO: Maybe just reuse the other error enum variant
/// An invalid number of input images was requested for Flux Kontext.
InvalidNumberOfInputImagesForFluxKontext {
message: String,
},
/// No sora credentials are set.
NeedsSoraCredentials,
SoraError(SoraError),
AnyhowError(AnyhowError),
StorytellerError(StorytellerError),
}
impl From<AnyhowError> for InternalContextualEditImageError {
fn from(value: AnyhowError) -> Self {
Self::AnyhowError(value)
}
}
impl From<SoraError> for InternalContextualEditImageError {
fn from(value: SoraError) -> Self {
Self::SoraError(value)
}
}
impl From<StorytellerError> for InternalContextualEditImageError {
fn from(value: StorytellerError) -> Self {
Self::StorytellerError(value)
}
}
impl InternalContextualEditImageError {
pub fn to_tauri_response<E: Serialize>(&self) -> CommandErrorResponseWrapper<EnqueueContextualEditImageErrorType, E> {
let mut status = CommandErrorStatus::ServerError;
let mut error_type = EnqueueContextualEditImageErrorType::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 self {
InternalContextualEditImageError::NoModelSpecified => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueContextualEditImageErrorType::ModelNotSpecified;
error_message = "No model specified for image generation".to_string();
}
InternalContextualEditImageError::NoProviderAvailable => {
status = CommandErrorStatus::ServerError;
error_type = EnqueueContextualEditImageErrorType::NoProviderAvailable;
error_message = "No configured provider available for image generation".to_string();
}
InternalContextualEditImageError::InvalidNumberOfRequestedImages { min, max, requested } => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueContextualEditImageErrorType::BadRequest;
error_message = format!("Invalid number of images requested ({}). Must be between {} and {}", requested, min, max);
}
InternalContextualEditImageError::InvalidNumberOfInputImages { min, max, requested } => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueContextualEditImageErrorType::BadRequest;
error_message = format!("Invalid number of input images ({}). Must be between {} and {}", requested, min, max);
}
InternalContextualEditImageError::InvalidNumberOfInputImagesForFluxKontext { message } => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueContextualEditImageErrorType::BadRequest;
error_message = message.clone();
}
InternalContextualEditImageError::SoraError(_) => {}
InternalContextualEditImageError::AnyhowError(_) => {}
InternalContextualEditImageError::StorytellerError(_) => {}
InternalContextualEditImageError::NeedsStorytellerCredentials => {}
InternalContextualEditImageError::NeedsSoraCredentials => {}
}
CommandErrorResponseWrapper {
status,
error_message: Some(error_message.to_string()),
error_type: Some(error_type),
error_details: None,
}
}
}
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_edit::flux_kontext::handle_flux_kontext_edit_artcraft::handle_flux_kontext_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
@@ -26,7 +26,7 @@ pub async fn handle_flux_kontext_edit(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -55,5 +55,5 @@ pub async fn handle_flux_kontext_edit(
}
}
Err(InternalContextualEditImageError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -39,12 +39,12 @@ pub async fn handle_flux_kontext_edit_artcraft(
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalContextualEditImageError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
@@ -59,11 +59,11 @@ pub async fn handle_flux_kontext_edit_artcraft(
Some(3) => Some(FluxProKontextMaxEditImageNumImages::Three),
Some(4) => Some(FluxProKontextMaxEditImageNumImages::Four),
Some(other) => {
return Err(InternalContextualEditImageError::InvalidNumberOfRequestedImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
min: 1,
max: 4,
requested: other,
});
}));
},
};
@@ -75,15 +75,13 @@ pub async fn handle_flux_kontext_edit_artcraft(
.unwrap_or(0);
if request.scene_image_media_token.is_some() && list_count > 0 {
return Err(InternalContextualEditImageError::InvalidNumberOfInputImagesForFluxKontext {
message: "Cannot specify both scene_image_media_token and image_media_tokens for Flux Kontext Max".to_string()
});
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(InternalContextualEditImageError::InvalidNumberOfInputImagesForFluxKontext {
message: "Cannot specify more than one image in image_media_tokens for Flux Kontext Max".to_string()
});
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 {
@@ -102,9 +100,8 @@ pub async fn handle_flux_kontext_edit_artcraft(
let media_token = match maybe_media_token {
Some(token) => token,
None => {
return Err(InternalContextualEditImageError::InvalidNumberOfInputImagesForFluxKontext {
message: "No input image specified for Flux Kontext Max edit".to_string(),
});
return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments(
"No input image specified for Flux Kontext Max edit".to_string())));
},
};
@@ -130,7 +127,7 @@ pub async fn handle_flux_kontext_edit_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
return Err(InternalContextualEditImageError::StorytellerError(err));
return Err(GenerateError::from(err));
}
};
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_edit::gemini_25_flash::handle_gemini_25_flash_edit_artcraft::handle_gemini_25_flash_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
@@ -26,7 +26,7 @@ pub async fn handle_gemini_25_flash_edit(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -55,5 +55,5 @@ pub async fn handle_gemini_25_flash_edit(
}
}
Err(InternalContextualEditImageError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -37,12 +37,12 @@ pub async fn handle_gemini_25_flash_edit_artcraft(
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalContextualEditImageError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
@@ -57,11 +57,11 @@ pub async fn handle_gemini_25_flash_edit_artcraft(
Some(3) => Some(Gemini25FlashEditImageNumImages::Three),
Some(4) => Some(Gemini25FlashEditImageNumImages::Four),
Some(other) => {
return Err(InternalContextualEditImageError::InvalidNumberOfRequestedImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
min: 1,
max: 4,
requested: other,
});
}));
},
};
@@ -76,11 +76,11 @@ pub async fn handle_gemini_25_flash_edit_artcraft(
}
if media_tokens.len() > MAX_IMAGES {
return Err(InternalContextualEditImageError::InvalidNumberOfInputImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
min: 1,
max: MAX_IMAGES as u32,
requested: media_tokens.len() as u32,
});
provided: media_tokens.len() as u32,
}));
}
let request = Gemini25FlashEditImageRequest {
@@ -106,7 +106,7 @@ pub async fn handle_gemini_25_flash_edit_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
return Err(InternalContextualEditImageError::StorytellerError(err));
return Err(GenerateError::from(err));
}
};
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
@@ -27,7 +27,7 @@ pub async fn handle_gpt_image_1_edit(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -67,5 +67,5 @@ pub async fn handle_gpt_image_1_edit(
}
}
Err(InternalContextualEditImageError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit::MAX_IMAGES;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
@@ -38,12 +38,12 @@ pub async fn handle_gpt_image_1_edit_artcraft(
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalContextualEditImageError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
@@ -74,11 +74,11 @@ pub async fn handle_gpt_image_1_edit_artcraft(
Some(3) => Some(GptImage1EditImageNumImages::Three),
Some(4) => Some(GptImage1EditImageNumImages::Four),
Some(other) => {
return Err(InternalContextualEditImageError::InvalidNumberOfRequestedImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
min: 1,
max: 4,
requested: other,
});
}));
},
};
@@ -93,11 +93,11 @@ pub async fn handle_gpt_image_1_edit_artcraft(
}
if media_tokens.len() > MAX_IMAGES {
return Err(InternalContextualEditImageError::InvalidNumberOfInputImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
min: 1,
max: MAX_IMAGES as u32,
requested: media_tokens.len() as u32,
});
provided: media_tokens.len() as u32,
}));
}
let request = GptImage1EditImageRequest {
@@ -124,7 +124,7 @@ pub async fn handle_gpt_image_1_edit_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
return Err(InternalContextualEditImageError::StorytellerError(err));
return Err(GenerateError::from(err));
}
};
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageSize, EnqueueContextualEditImageCommand, EnqueueContextualEditImageErrorType};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit::MAX_IMAGES;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
@@ -52,19 +52,19 @@ pub async fn handle_gpt_image_1_edit_sora(
app_env_configs: &AppEnvConfigs,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
) -> 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(InternalContextualEditImageError::NeedsSoraCredentials);
return Err(GenerateError::needs_sora_credentials());
}
Err(err) => {
error!("Failed to get Sora credentials: {:?}", err);
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
return Err(InternalContextualEditImageError::AnyhowError(err));
return Err(GenerateError::needs_sora_credentials());
}
};
@@ -79,11 +79,11 @@ pub async fn handle_gpt_image_1_edit_sora(
}
if media_tokens.len() > MAX_IMAGES {
return Err(InternalContextualEditImageError::InvalidNumberOfInputImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
min: 1,
max: MAX_IMAGES as u32,
requested: media_tokens.len() as u32,
});
provided: media_tokens.len() as u32,
}));
}
info!("Calling get media file API: {:?}", app_env_configs.storyteller_host);
@@ -1,5 +1,4 @@
pub mod enqueue_contextual_edit_image_command;
pub mod errors;
pub mod gpt_image_1;
mod flux_kontext;
mod gemini_25_flash;
@@ -1,6 +1,6 @@
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit::handle_gpt_image_1_edit;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::image_inpaint::flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint;
use crate::core::commands::enqueue::image_inpaint::flux_pro_1_inpaint::handle_flux_pro_1_inpaint::handle_flux_pro_1_inpaint;
use crate::core::commands::enqueue::image_inpaint::flux_pro_kontext_inpaint::handle_flux_pro_kontext_inpaint::handle_flux_pro_kontext_inpaint;
@@ -184,6 +184,8 @@ pub async fn enqueue_image_inpaint_command(
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 {
@@ -197,7 +199,7 @@ pub async fn enqueue_image_inpaint_command(
error!("Failed to emit event: {:?}", err); // Fail open.
}
Err(err.to_tauri_response())
Err(error_to_tauri_response(err))
}
Ok(event) => {
let event = GenerationEnqueueSuccessEvent {
@@ -227,10 +229,10 @@ pub async fn handle_request(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let success_event= match request.model {
None => {
return Err(InternalImageInpaintError::NoModelSpecified)
return Err(GenerateError::no_model_specified())
}
Some(ImageInpaintModel::FluxDevJuggernaut) => {
handle_flux_dev_juggernaut_inpaint(
@@ -308,3 +310,60 @@ pub async fn handle_request(
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,128 +0,0 @@
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageErrorType;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
use errors::AnyhowError;
use serde::Serialize;
use storyteller_client::error::storyteller_error::StorytellerError;
#[derive(Debug)]
pub enum InternalImageInpaintError {
/// Request did not specify a model.
NoModelSpecified,
/// No provider is configured for the model type.
NoProviderAvailable,
/// A source image was not provided
NoSourceImageSpecified,
/// A mask image was not provided
NoMaskImageSpecified,
/// Both mask image media token and raw bytes were supplied.
MaskMediaTokenAndBytesSupplied,
/// Mask image has an unknown MIME type.
CouldNotDetermineMaskMimeType,
/// Could not encode the mask image to PNG.
CouldNotEncodeMask,
/// No storyteller credentials are set.
NeedsStorytellerCredentials,
/// An invalid number of images was requested.
InvalidNumberOfRequestedImages {
min: u32,
max: u32,
requested: u32,
},
/// An invalid number of input images was requested.
InvalidNumberOfInputImages {
min: u32,
max: u32,
requested: u32,
},
AnyhowError(AnyhowError),
StorytellerError(StorytellerError),
}
impl From<AnyhowError> for InternalImageInpaintError {
fn from(value: AnyhowError) -> Self {
Self::AnyhowError(value)
}
}
impl From<StorytellerError> for InternalImageInpaintError {
fn from(value: StorytellerError) -> Self {
Self::StorytellerError(value)
}
}
impl InternalImageInpaintError {
pub fn to_tauri_response<E: Serialize>(&self) -> CommandErrorResponseWrapper<EnqueueInpaintImageErrorType, E> {
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 self {
InternalImageInpaintError::NoModelSpecified => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueInpaintImageErrorType::ModelNotSpecified;
error_message = "No model specified for image generation".to_string();
}
InternalImageInpaintError::NoProviderAvailable => {
status = CommandErrorStatus::ServerError;
error_type = EnqueueInpaintImageErrorType::NoProviderAvailable;
error_message = "No configured provider available for image generation".to_string();
}
InternalImageInpaintError::NoSourceImageSpecified => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueInpaintImageErrorType::NoSourceImageSpecified;
error_message = "No source image was provided".to_string();
}
InternalImageInpaintError::NoMaskImageSpecified => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueInpaintImageErrorType::NoMaskImageSpecified;
error_message = "No mask image was provided".to_string();
}
InternalImageInpaintError::MaskMediaTokenAndBytesSupplied => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueInpaintImageErrorType::MultipleMaskImagesSpecified;
error_message = "multiple mask images provided".to_string();
}
InternalImageInpaintError::CouldNotDetermineMaskMimeType => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueInpaintImageErrorType::BadMaskImage;
error_message = "bad mask image (mime)".to_string();
}
InternalImageInpaintError::CouldNotEncodeMask => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueInpaintImageErrorType::BadMaskImage;
error_message = "bad mask image (encode)".to_string();
}
InternalImageInpaintError::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);
}
InternalImageInpaintError::InvalidNumberOfInputImages { min, max, requested } => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueInpaintImageErrorType::BadRequest;
error_message = format!("Invalid number of input images ({}). Must be between {} and {}", requested, min, max);
}
InternalImageInpaintError::AnyhowError(_) => {}
InternalImageInpaintError::StorytellerError(_) => {}
InternalImageInpaintError::NeedsStorytellerCredentials => {}
}
CommandErrorResponseWrapper {
status,
error_message: Some(error_message.to_string()),
error_type: Some(error_type),
error_details: None,
}
}
}
@@ -1,9 +1,8 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::image_inpaint::flux_dev_juggernaut_inpaint::handle_flux_dev_juggernaut_inpaint_artcraft::handle_flux_dev_juggernaut_inpaint_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
@@ -28,7 +27,7 @@ pub async fn handle_flux_dev_juggernaut_inpaint(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -57,5 +56,5 @@ pub async fn handle_flux_dev_juggernaut_inpaint(
}
}
Err(InternalImageInpaintError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,8 +1,7 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -52,30 +51,22 @@ pub async fn handle_flux_dev_juggernaut_inpaint_artcraft(
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalImageInpaintError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
let image_media_token = match &request.image_media_token {
Some(token) => token.clone(),
None => {
return Err(InternalImageInpaintError::NoSourceImageSpecified);
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)
//})?;
let mask_media_token = get_mask(
request,
app_env_configs,
@@ -93,11 +84,11 @@ pub async fn handle_flux_dev_juggernaut_inpaint_artcraft(
Some(3) => Some(FluxDevJuggernautInpaintImageNumImages::Three),
Some(4) => Some(FluxDevJuggernautInpaintImageNumImages::Four),
Some(other) => {
return Err(InternalImageInpaintError::InvalidNumberOfRequestedImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
min: 1,
max: 4,
requested: other,
});
}));
},
};
@@ -124,7 +115,7 @@ pub async fn handle_flux_dev_juggernaut_inpaint_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft flux dev juggernaut inpaint: {:?}", err);
return Err(InternalImageInpaintError::StorytellerError(err));
return Err(GenerateError::from(err));
}
};
@@ -140,10 +131,10 @@ async fn get_mask(
request: &EnqueueInpaintImageCommand,
app_env_configs: &AppEnvConfigs,
storyteller_creds: &StorytellerCredentialSet,
) -> Result<MediaFileToken, InternalImageInpaintError> {
) -> Result<MediaFileToken, GenerateError> {
if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() {
return Err(InternalImageInpaintError::MaskMediaTokenAndBytesSupplied);
return Err(GenerateError::both_image_mask_media_token_and_bytes_supplied());
}
if let Some(token) = request.mask_image_media_token.as_ref() {
@@ -151,12 +142,12 @@ async fn get_mask(
};
let image_bytes = request.mask_image_raw_bytes.as_ref()
.ok_or(InternalImageInpaintError::NoMaskImageSpecified)?;
.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);
InternalImageInpaintError::CouldNotEncodeMask
GenerateError::AnyhowError(anyhow!("Failed to convert image bytes to png mask"))
})?;
info!("Uploading image media file from bytes...");
@@ -170,7 +161,7 @@ async fn get_mask(
}).await
.map_err(|err| {
error!("Failed to upload image media file: {:?}", err);
InternalImageInpaintError::StorytellerError(err)
GenerateError::from(err)
})?;
Ok(result.media_file_token)
@@ -1,9 +1,8 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::image_inpaint::flux_pro_1_inpaint::handle_flux_pro_1_inpaint_artcraft::handle_flux_pro_1_inpaint_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
@@ -28,7 +27,7 @@ pub async fn handle_flux_pro_1_inpaint(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -57,5 +56,5 @@ pub async fn handle_flux_pro_1_inpaint(
}
}
Err(InternalImageInpaintError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,8 +1,7 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -51,30 +50,22 @@ pub async fn handle_flux_pro_1_inpaint_artcraft(
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalImageInpaintError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
let image_media_token = match &request.image_media_token {
Some(token) => token.clone(),
None => {
return Err(InternalImageInpaintError::NoSourceImageSpecified);
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)
//})?;
let mask_media_token = get_mask(
request,
app_env_configs,
@@ -92,11 +83,11 @@ pub async fn handle_flux_pro_1_inpaint_artcraft(
Some(3) => Some(FluxPro1InpaintImageNumImages::Three),
Some(4) => Some(FluxPro1InpaintImageNumImages::Four),
Some(other) => {
return Err(InternalImageInpaintError::InvalidNumberOfRequestedImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
min: 1,
max: 4,
requested: other,
});
}));
},
};
@@ -123,7 +114,7 @@ pub async fn handle_flux_pro_1_inpaint_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft flux pro 1 inpaint: {:?}", err);
return Err(InternalImageInpaintError::StorytellerError(err));
return Err(GenerateError::from(err));
}
};
@@ -139,10 +130,10 @@ async fn get_mask(
request: &EnqueueInpaintImageCommand,
app_env_configs: &AppEnvConfigs,
storyteller_creds: &StorytellerCredentialSet,
) -> Result<MediaFileToken, InternalImageInpaintError> {
) -> Result<MediaFileToken, GenerateError> {
if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() {
return Err(InternalImageInpaintError::MaskMediaTokenAndBytesSupplied);
return Err(GenerateError::both_image_mask_media_token_and_bytes_supplied());
}
if let Some(token) = request.mask_image_media_token.as_ref() {
@@ -150,12 +141,12 @@ async fn get_mask(
};
let image_bytes = request.mask_image_raw_bytes.as_ref()
.ok_or(InternalImageInpaintError::NoMaskImageSpecified)?;
.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);
InternalImageInpaintError::CouldNotEncodeMask
GenerateError::AnyhowError(anyhow!("could not encode image mask to png"))
})?;
info!("Uploading image media file from bytes...");
@@ -169,7 +160,7 @@ async fn get_mask(
}).await
.map_err(|err| {
error!("Failed to upload image media file: {:?}", err);
InternalImageInpaintError::StorytellerError(err)
GenerateError::from(err)
})?;
Ok(result.media_file_token)
@@ -1,7 +1,6 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::image_inpaint::flux_pro_kontext_inpaint::handle_flux_pro_kontext_inpaint_artcraft::handle_flux_pro_kontext_inpaint_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
@@ -26,7 +25,7 @@ pub async fn handle_flux_pro_kontext_inpaint(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -55,5 +54,5 @@ pub async fn handle_flux_pro_kontext_inpaint(
}
}
Err(InternalImageInpaintError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,9 +1,7 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -54,19 +52,19 @@ pub async fn handle_flux_pro_kontext_inpaint_artcraft(
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalImageInpaintError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
let image_media_token = match &request.image_media_token {
Some(token) => token.clone(),
None => {
return Err(InternalImageInpaintError::NoSourceImageSpecified);
return Err(GenerateError::required_source_image_not_provided());
},
};
@@ -97,11 +95,11 @@ pub async fn handle_flux_pro_kontext_inpaint_artcraft(
Some(3) => Some(FluxProKontextMaxEditImageNumImages::Three),
Some(4) => Some(FluxProKontextMaxEditImageNumImages::Four),
Some(other) => {
return Err(InternalImageInpaintError::InvalidNumberOfRequestedImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
min: 1,
max: 4,
requested: other,
});
}));
},
};
@@ -129,7 +127,7 @@ pub async fn handle_flux_pro_kontext_inpaint_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft flux pro kontext inpaint: {:?}", err);
return Err(InternalImageInpaintError::StorytellerError(err));
return Err(GenerateError::from(err));
}
};
@@ -140,43 +138,3 @@ pub async fn handle_flux_pro_kontext_inpaint_artcraft(
task_type: TaskType::ImageInpaintEdit,
})
}
// async fn get_mask(
// request: &EnqueueInpaintImageCommand,
// app_env_configs: &AppEnvConfigs,
// storyteller_creds: &StorytellerCredentialSet,
// ) -> Result<MediaFileToken, InternalImageInpaintError> {
//
// if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() {
// return Err(InternalImageInpaintError::MaskMediaTokenAndBytesSupplied);
// }
//
// 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(InternalImageInpaintError::NoMaskImageSpecified)?;
//
// let image_bytes = normalize_image_bytes_to_flux_mask(image_bytes)
// .map_err(|err| {
// error!("Failed to convert image bytes to png: {:?}", err);
// InternalImageInpaintError::CouldNotEncodeMask
// })?;
//
// 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,
// }).await
// .map_err(|err| {
// error!("Failed to upload image media file: {:?}", err);
// InternalImageInpaintError::StorytellerError(err)
// })?;
//
// Ok(result.media_file_token)
// }
@@ -1,7 +1,6 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::image_inpaint::flux_pro_kontext_inpaint::handle_flux_pro_kontext_inpaint_artcraft::handle_flux_pro_kontext_inpaint_artcraft;
use crate::core::commands::enqueue::image_inpaint::gemini_25_flash_inpaint::handle_gemini_25_flash_inpaint_artcraft::handle_gemini_25_flash_inpaint_artcraft;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
@@ -27,7 +26,7 @@ pub async fn handle_gemini_25_flash_inpaint(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -56,5 +55,5 @@ pub async fn handle_gemini_25_flash_inpaint(
}
}
Err(InternalImageInpaintError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,9 +1,7 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -56,19 +54,19 @@ pub async fn handle_gemini_25_flash_inpaint_artcraft(
app_data_root: &AppDataRoot,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalImageInpaintError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
let image_media_token = match &request.image_media_token {
Some(token) => token.clone(),
None => {
return Err(InternalImageInpaintError::NoSourceImageSpecified);
return Err(GenerateError::required_source_image_not_provided());
},
};
@@ -83,11 +81,11 @@ pub async fn handle_gemini_25_flash_inpaint_artcraft(
Some(3) => Some(Gemini25FlashEditImageNumImages::Three),
Some(4) => Some(Gemini25FlashEditImageNumImages::Four),
Some(other) => {
return Err(InternalImageInpaintError::InvalidNumberOfRequestedImages {
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
min: 1,
max: 4,
requested: other,
});
}));
},
};
@@ -114,7 +112,7 @@ pub async fn handle_gemini_25_flash_inpaint_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft gemini 2.5 flash inpaint: {:?}", err);
return Err(InternalImageInpaintError::StorytellerError(err));
return Err(GenerateError::from(err));
}
};
@@ -125,43 +123,3 @@ pub async fn handle_gemini_25_flash_inpaint_artcraft(
task_type: TaskType::ImageInpaintEdit,
})
}
// async fn get_mask(
// request: &EnqueueInpaintImageCommand,
// app_env_configs: &AppEnvConfigs,
// storyteller_creds: &StorytellerCredentialSet,
// ) -> Result<MediaFileToken, InternalImageInpaintError> {
//
// if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() {
// return Err(InternalImageInpaintError::MaskMediaTokenAndBytesSupplied);
// }
//
// 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(InternalImageInpaintError::NoMaskImageSpecified)?;
//
// let image_bytes = normalize_image_bytes_to_flux_mask(image_bytes)
// .map_err(|err| {
// error!("Failed to convert image bytes to png: {:?}", err);
// InternalImageInpaintError::CouldNotEncodeMask
// })?;
//
// 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,
// }).await
// .map_err(|err| {
// error!("Failed to upload image media file: {:?}", err);
// InternalImageInpaintError::StorytellerError(err)
// })?;
//
// Ok(result.media_file_token)
// }
@@ -2,5 +2,4 @@ mod flux_dev_juggernaut_inpaint;
mod flux_pro_1_inpaint;
mod flux_pro_kontext_inpaint;
mod gemini_25_flash_inpaint;
pub mod enqueue_image_inpaint_command;
pub mod errors;
pub mod enqueue_image_inpaint_command;
@@ -1,5 +1,6 @@
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
use crate::core::commands::enqueue::image_to_object::generic::handle_object::handle_object;
use crate::core::commands::enqueue::image_to_object::internal_object_error::InternalObjectError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
use crate::core::commands::response::shorthand::Response;
@@ -96,28 +97,30 @@ pub async fn enqueue_image_to_3d_object_command(
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 {
InternalObjectError::NoModelSpecified => {
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueImageTo3dObjectErrorType::ModelNotSpecified;
error_message = "No model specified for object generation";
}
InternalObjectError::NoProviderAvailable => {
GenerateError::NoProviderAvailable => {
status = CommandErrorStatus::ServerError;
error_type = EnqueueImageTo3dObjectErrorType::NoProviderAvailable;
error_message = "No configured provider available for object generation";
}
InternalObjectError::NeedsFalApiKey => {
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => {
status = CommandErrorStatus::Unauthorized;
error_type = EnqueueImageTo3dObjectErrorType::NeedsFalApiKey;
error_message = "You need to set a FAL api key";
},
InternalObjectError::NeedsStorytellerCredentials => {
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials) => {
status = CommandErrorStatus::Unauthorized;
error_type = EnqueueImageTo3dObjectErrorType::NeedsStorytellerCredentials;
error_message = "You need to be logged into Artcraft.";
@@ -159,7 +162,7 @@ pub async fn handle_request(
fal_creds_manager: &FalCredentialManager,
storyteller_creds_manager: &StorytellerCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let result = handle_object(
request,
@@ -1,7 +1,6 @@
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::EnqueueImageTo3dObjectRequest;
use crate::core::commands::enqueue::image_to_object::generic::handle_object_artcraft::handle_object_artcraft;
use crate::core::commands::enqueue::image_to_object::generic::handle_object_fal::handle_object_fal;
use crate::core::commands::enqueue::image_to_object::internal_object_error::InternalObjectError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
@@ -10,6 +9,7 @@ use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use tauri::AppHandle;
use crate::core::commands::enqueue::generate_error::GenerateError;
pub async fn handle_object(
request: EnqueueImageTo3dObjectRequest,
@@ -20,7 +20,7 @@ pub async fn handle_object(
fal_creds_manager: &FalCredentialManager,
storyteller_creds_manager: &StorytellerCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -51,5 +51,5 @@ pub async fn handle_object(
}
}
Err(InternalObjectError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
use crate::core::commands::enqueue::image_to_object::internal_object_error::InternalObjectError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -25,7 +25,7 @@ pub async fn handle_object_artcraft(
app_env_configs: &AppEnvConfigs,
app_data_root: &AppDataRoot,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
@@ -38,7 +38,7 @@ pub async fn handle_object_artcraft(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalObjectError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
@@ -50,7 +50,7 @@ pub async fn handle_object_artcraft(
let job_token = match request.model {
None => {
return Err(InternalObjectError::NoModelSpecified);
return Err(GenerateError::no_model_specified());
}
Some(
EnqueueImageTo3dObjectModel::Hunyuan3d2 |
@@ -74,7 +74,7 @@ pub async fn handle_object_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Hunyuan 3D 2.0: {:?}", err);
return Err(InternalObjectError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -97,7 +97,7 @@ pub async fn handle_object_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Hunyuan 3D 2.1: {:?}", err);
return Err(InternalObjectError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
use crate::core::commands::enqueue::image_to_object::internal_object_error::InternalObjectError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -25,7 +25,7 @@ pub async fn handle_object_fal(
request: EnqueueImageTo3dObjectRequest,
fal_creds_manager: &FalCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let api_key = match fal_creds_manager.get_key()? {
Some(key) => key,
@@ -38,7 +38,7 @@ pub async fn handle_object_fal(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalObjectError::NeedsFalApiKey);
return Err(GenerateError::needs_fal_api_key());
},
};
@@ -57,7 +57,7 @@ pub async fn handle_object_fal(
&media_token
).await?;
} else {
return Err(InternalObjectError::AnyhowError(anyhow!("No image media token provided")));
return Err(GenerateError::required_source_image_not_provided());
}
info!("Calling FAL image to 3d ...");
@@ -68,7 +68,7 @@ pub async fn handle_object_fal(
let result = match request.model {
None => {
return Err(InternalObjectError::NoModelSpecified);
return Err(GenerateError::no_model_specified());
}
Some(
EnqueueImageTo3dObjectModel::Hunyuan3d2_0 |
@@ -82,7 +82,7 @@ pub async fn handle_object_fal(
}).await
}
_ => {
return Err(InternalObjectError::AnyhowError(anyhow!("Wrong model specified: {:?}", request.model)));
return Err(GenerateError::AnyhowError(anyhow!("Wrong model specified: {:?}", request.model)));
}
};
@@ -106,7 +106,7 @@ pub async fn handle_object_fal(
if let Err(err) = fal_task_queue.insert(&enqueued) {
error!("Failed to enqueue task: {:?}", err);
return Err(InternalObjectError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
return Err(GenerateError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
}
}
Err(err) => {
@@ -123,7 +123,7 @@ pub async fn handle_object_fal(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalObjectError::FalError(err));
return Err(GenerateError::from(err));
}
}
@@ -1,62 +0,0 @@
use crate::core::artcraft_error::ArtcraftError;
use base64::DecodeError;
use errors::AnyhowError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use storyteller_client::error::storyteller_error::StorytellerError;
#[derive(Debug)]
pub enum InternalObjectError {
NoModelSpecified,
NoProviderAvailable,
NeedsFalApiKey,
NeedsSoraCredentials,
NeedsStorytellerCredentials,
FalError(FalErrorPlus),
AnyhowError(AnyhowError),
StorytellerError(StorytellerError),
DecodeError(DecodeError),
IoError(std::io::Error),
ArtcraftError(ArtcraftError),
}
impl From<AnyhowError> for InternalObjectError {
fn from(value: AnyhowError) -> Self {
Self::AnyhowError(value)
}
}
impl From<FalErrorPlus> for InternalObjectError {
fn from(value: FalErrorPlus) -> Self {
Self::FalError(value)
}
}
impl From<StorytellerError> for InternalObjectError {
fn from(value: StorytellerError) -> Self {
Self::StorytellerError(value)
}
}
impl From<DecodeError> for InternalObjectError {
fn from(value: DecodeError) -> Self {
Self::DecodeError(value)
}
}
impl From<std::io::Error> for InternalObjectError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
impl From<ArtcraftError> for InternalObjectError {
fn from(value: ArtcraftError) -> Self {
match value {
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
ArtcraftError::IoError(e) => Self::IoError(e),
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
_ => Self::ArtcraftError(value),
}
}
}
@@ -1,3 +1,2 @@
pub mod enqueue_image_to_3d_object_command;
pub mod internal_object_error;
mod generic;
@@ -1,5 +1,6 @@
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason};
use crate::core::commands::enqueue::image_to_video::generic::handle_video::handle_video;
use crate::core::commands::enqueue::image_to_video::internal_video_error::InternalVideoError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
use crate::core::commands::response::shorthand::Response;
@@ -20,7 +21,7 @@ use serde_derive::{Deserialize, Serialize};
use tauri::{AppHandle, State};
use tokens::tokens::media_files::MediaFileToken;
/// This is used in the Tauri command bridge.
/// 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")]
@@ -116,28 +117,30 @@ pub async fn enqueue_image_to_video_command(
match result {
Err(err) => {
error!("error: {:?}", err);
notify_frontend_of_errors(&app, &err).await;
let mut status = CommandErrorStatus::ServerError;
let mut error_type = EnqueueImageToVideoErrorType::ServerError;
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
match err {
InternalVideoError::NoModelSpecified => {
GenerateError::BadInput(BadInputReason::NoModelSpecified)=> {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueImageToVideoErrorType::ModelNotSpecified;
error_message = "No model specified for video generation";
}
InternalVideoError::NoProviderAvailable => {
GenerateError::NoProviderAvailable => {
status = CommandErrorStatus::ServerError;
error_type = EnqueueImageToVideoErrorType::NoProviderAvailable;
error_message = "No configured provider available for video generation";
}
InternalVideoError::NeedsFalApiKey => {
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => {
status = CommandErrorStatus::Unauthorized;
error_type = EnqueueImageToVideoErrorType::NeedsFalApiKey;
error_message = "You need to set a FAL api key";
},
InternalVideoError::NeedsStorytellerCredentials => {
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials) => {
status = CommandErrorStatus::Unauthorized;
error_type = EnqueueImageToVideoErrorType::NeedsStorytellerCredentials;
error_message = "You need to be logged into Artcraft.";
@@ -179,7 +182,7 @@ pub async fn handle_request(
fal_creds_manager: &FalCredentialManager,
storyteller_creds_manager: &StorytellerCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let result = handle_video(
request,
@@ -1,7 +1,7 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
use crate::core::commands::enqueue::image_to_video::generic::handle_video_artcraft::handle_video_artcraft;
use crate::core::commands::enqueue::image_to_video::generic::handle_video_fal::handle_video_fal;
use crate::core::commands::enqueue::image_to_video::internal_video_error::InternalVideoError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
@@ -20,7 +20,7 @@ pub async fn handle_video(
fal_creds_manager: &FalCredentialManager,
storyteller_creds_manager: &StorytellerCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -51,5 +51,5 @@ pub async fn handle_video(
}
}
Err(InternalVideoError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, VideoModel};
use crate::core::commands::enqueue::image_to_video::internal_video_error::InternalVideoError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -34,7 +34,7 @@ pub async fn handle_video_artcraft(
app_env_configs: &AppEnvConfigs,
app_data_root: &AppDataRoot,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
@@ -47,7 +47,7 @@ pub async fn handle_video_artcraft(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalVideoError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
@@ -67,7 +67,7 @@ pub async fn handle_video_artcraft(
let job_token = match request.model {
None => {
return Err(InternalVideoError::NoModelSpecified);
return Err(GenerateError::no_model_specified());
}
Some(VideoModel::Kling16Pro) => {
info!("enqueue Kling 1.6 Pro with Artcraft API");
@@ -92,7 +92,7 @@ pub async fn handle_video_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Kling 1.6 video generation: {:?}", err);
return Err(InternalVideoError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -118,7 +118,7 @@ pub async fn handle_video_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Kling 2.1 Master video generation: {:?}", err);
return Err(InternalVideoError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -145,7 +145,7 @@ pub async fn handle_video_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Kling 2.1 Pro video generation: {:?}", err);
return Err(InternalVideoError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -172,7 +172,7 @@ pub async fn handle_video_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Seedance 1.0 Lite video generation: {:?}", err);
return Err(InternalVideoError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -198,7 +198,7 @@ pub async fn handle_video_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Veo 2 video generation: {:?}", err);
return Err(InternalVideoError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -1,5 +1,5 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, VideoModel};
use crate::core::commands::enqueue::image_to_video::internal_video_error::InternalVideoError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -25,7 +25,7 @@ pub async fn handle_video_fal(
request: EnqueueImageToVideoRequest,
fal_creds_manager: &FalCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let api_key = match fal_creds_manager.get_key()? {
Some(key) => key,
@@ -38,7 +38,7 @@ pub async fn handle_video_fal(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalVideoError::NeedsFalApiKey);
return Err(GenerateError::needs_fal_api_key());
},
};
@@ -57,7 +57,7 @@ pub async fn handle_video_fal(
&media_token
).await?;
} else {
return Err(InternalVideoError::AnyhowError(anyhow!("No image media token provided")));
return Err(GenerateError::BadInput(BadInputReason::RequiredSourceImageNotProvided));
}
let filename = temp_download.path().to_path_buf();
@@ -66,7 +66,7 @@ pub async fn handle_video_fal(
let result = match request.model {
None => {
return Err(InternalVideoError::NoModelSpecified);
return Err(GenerateError::no_model_specified());
}
Some(VideoModel::Kling16Pro) => {
info!("enqueue Kling 1.6 image to video with Kling API");
@@ -80,7 +80,7 @@ pub async fn handle_video_fal(
}).await
}
_ => {
return Err(InternalVideoError::AnyhowError(anyhow!("Unsupported model: {:?}", request.model)));
return Err(GenerateError::AnyhowError(anyhow!("Unsupported model: {:?}", request.model)));
}
};
@@ -94,7 +94,7 @@ pub async fn handle_video_fal(
if let Err(err) = fal_task_queue.insert(&enqueued) {
error!("Failed to enqueue task: {:?}", err);
return Err(InternalVideoError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
return Err(GenerateError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
}
}
Err(err) => {
@@ -111,7 +111,7 @@ pub async fn handle_video_fal(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalVideoError::FalError(err));
return Err(GenerateError::from(err));
}
}
@@ -1,62 +0,0 @@
use crate::core::artcraft_error::ArtcraftError;
use base64::DecodeError;
use errors::AnyhowError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use storyteller_client::error::storyteller_error::StorytellerError;
#[derive(Debug)]
pub enum InternalVideoError {
NoModelSpecified,
NoProviderAvailable,
NeedsFalApiKey,
NeedsSoraCredentials,
NeedsStorytellerCredentials,
FalError(FalErrorPlus),
AnyhowError(AnyhowError),
StorytellerError(StorytellerError),
DecodeError(DecodeError),
IoError(std::io::Error),
ArtcraftError(ArtcraftError),
}
impl From<AnyhowError> for InternalVideoError {
fn from(value: AnyhowError) -> Self {
Self::AnyhowError(value)
}
}
impl From<FalErrorPlus> for InternalVideoError {
fn from(value: FalErrorPlus) -> Self {
Self::FalError(value)
}
}
impl From<StorytellerError> for InternalVideoError {
fn from(value: StorytellerError) -> Self {
Self::StorytellerError(value)
}
}
impl From<DecodeError> for InternalVideoError {
fn from(value: DecodeError) -> Self {
Self::DecodeError(value)
}
}
impl From<std::io::Error> for InternalVideoError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
impl From<ArtcraftError> for InternalVideoError {
fn from(value: ArtcraftError) -> Self {
match value {
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
ArtcraftError::IoError(e) => Self::IoError(e),
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
_ => Self::ArtcraftError(value),
}
}
}
@@ -1,3 +1,2 @@
pub mod enqueue_image_to_video_command;
pub mod internal_video_error;
mod generic;
@@ -1,3 +1,5 @@
pub mod common;
pub mod generate_error;
pub mod image_bg_removal;
pub mod image_edit;
pub mod image_inpaint;
@@ -1,10 +1,11 @@
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::gemini_25_flash::handle_gemini_25_flash::handle_gemini_25_flash;
use crate::core::commands::enqueue::text_to_image::generic::handle_image_artcraft::handle_image_artcraft;
use crate::core::commands::enqueue::text_to_image::generic::handle_image_fal::handle_image_fal;
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1::handle_gpt_image_1;
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1_sora::handle_gpt_image_1_sora;
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
use crate::core::commands::enqueue::text_to_image::midjourney::handle_midjourney::handle_midjourney;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
use crate::core::commands::response::shorthand::Response;
@@ -173,22 +174,24 @@ pub async fn enqueue_text_to_image_command(
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 {
InternalImageError::NoModelSpecified => {
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
status = CommandErrorStatus::BadRequest;
error_type = EnqueueTextToImageErrorType::ModelNotSpecified;
error_message = "No model specified for image generation";
}
InternalImageError::NoProviderAvailable => {
GenerateError::NoProviderAvailable => {
status = CommandErrorStatus::ServerError;
error_type = EnqueueTextToImageErrorType::NoProviderAvailable;
error_message = "No configured provider available for image generation";
}
InternalImageError::NeedsFalApiKey => {
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => {
status = CommandErrorStatus::Unauthorized;
error_type = EnqueueTextToImageErrorType::NeedsFalApiKey;
error_message = "You need to set a FAL api key";
@@ -233,7 +236,7 @@ pub async fn handle_request(
fal_task_queue: &FalTaskQueue,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let result = dispatch_request(
&request,
@@ -283,11 +286,11 @@ pub async fn dispatch_request(
mj_creds_manager: &MidjourneyCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
match request.model {
None => {
return Err(InternalImageError::NoModelSpecified);
return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified));
}
Some(ImageModel::Gemini25Flash) => {
return handle_gemini_25_flash(
@@ -343,5 +346,5 @@ pub async fn dispatch_request(
}
}
Err(InternalImageError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,4 +1,4 @@
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
@@ -6,7 +6,6 @@ use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command
use crate::core::commands::enqueue::text_to_image::gemini_25_flash::handle_gemini_25_flash_artcraft::handle_gemini_25_flash_artcraft;
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1_artcraft::handle_gpt_image_1_artcraft;
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1_sora::handle_gpt_image_1_sora;
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
@@ -35,7 +34,7 @@ pub async fn handle_gemini_25_flash(
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -62,5 +61,5 @@ pub async fn handle_gemini_25_flash(
}
}
Err(InternalImageError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,9 +1,7 @@
use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason, ProviderFailureReason};
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::image_inpaint::errors::InternalImageInpaintError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
@@ -33,12 +31,12 @@ pub async fn handle_gemini_25_flash_artcraft(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalImageError::NeedsStorytellerCredentials);
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials));
},
};
@@ -78,7 +76,7 @@ pub async fn handle_gemini_25_flash_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft gemini 2.5 flash: {:?}", err);
return Err(InternalImageError::StorytellerError(err));
return Err(GenerateError::ProviderFailure(ProviderFailureReason::StorytellerError(err)));
}
};
@@ -1,6 +1,6 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, ImageModel, TextToImageSize};
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
@@ -29,7 +29,7 @@ pub async fn handle_image_artcraft(
app_env_configs: &AppEnvConfigs,
app_data_root: &AppDataRoot,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
@@ -42,7 +42,7 @@ pub async fn handle_image_artcraft(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalImageError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
@@ -54,17 +54,17 @@ pub async fn handle_image_artcraft(
let job_token = match request.model {
None => {
return Err(InternalImageError::NoModelSpecified);
return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified));
}
Some(
ImageModel::Gemini25Flash |
ImageModel::GptImage1 |
ImageModel::Midjourney
) => {
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
return Err(GenerateError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
}
Some(ImageModel::Recraft3) => {
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented in Artcraft")));
return Err(GenerateError::NotYetImplemented(format!("not yet implemented in Artcraft")));
}
Some(ImageModel::Flux1Dev) => {
info!("enqueue Flux 1 Dev");
@@ -101,7 +101,7 @@ pub async fn handle_image_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Flux 1 Dev text to image generation: {:?}", err);
return Err(InternalImageError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -140,7 +140,7 @@ pub async fn handle_image_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Flux 1 Schnell text to image generation: {:?}", err);
return Err(InternalImageError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -179,7 +179,7 @@ pub async fn handle_image_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Flux Pro 1.1 text to image generation: {:?}", err);
return Err(InternalImageError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -218,7 +218,7 @@ pub async fn handle_image_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft Flux Pro 1.1 Ultra text to image generation: {:?}", err);
return Err(InternalImageError::StorytellerError(err));
return Err(GenerateError::from(err));
}
}
}
@@ -1,6 +1,6 @@
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, ImageModel};
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
@@ -19,7 +19,7 @@ pub async fn handle_image_fal(
request: &EnqueueTextToImageRequest,
fal_creds_manager: &FalCredentialManager,
fal_task_queue: &FalTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let api_key = match fal_creds_manager.get_key()? {
Some(key) => key,
@@ -32,14 +32,14 @@ pub async fn handle_image_fal(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalImageError::NeedsFalApiKey);
return Err(GenerateError::needs_fal_api_key());
},
};
let api_key = fal_creds_manager.get_key_required()
.map_err(|err| {
error!("EnqueueTextToImage FAL api key required: {:?}", err);
InternalImageError::NeedsFalApiKey
GenerateError::needs_fal_api_key()
})?;
let prompt = request.prompt.as_deref().unwrap_or("");
@@ -48,13 +48,13 @@ pub async fn handle_image_fal(
let result = match request.model {
None => {
return Err(InternalImageError::NoModelSpecified);
return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified));
}
Some(
ImageModel::GptImage1 |
ImageModel::Midjourney
) => {
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
return Err(GenerateError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
}
Some(
ImageModel::Gemini25Flash |
@@ -62,7 +62,7 @@ pub async fn handle_image_fal(
ImageModel::Flux1Schnell |
ImageModel::FluxPro11
) => {
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented: {:?}", request.model)));
return Err(GenerateError::NotYetImplemented(format!("not yet implemented: {:?}", request.model)));
}
Some(ImageModel::FluxPro11Ultra) => {
model = GenerationModel::FluxPro11Ultra;
@@ -98,7 +98,7 @@ pub async fn handle_image_fal(
if let Err(err) = fal_task_queue.insert(&enqueued) {
error!("Failed to enqueue task: {:?}", err);
return Err(InternalImageError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
return Err(GenerateError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
}
enqueued
@@ -117,7 +117,7 @@ pub async fn handle_image_fal(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalImageError::FalError(err));
return Err(GenerateError::from(err));
}
};
@@ -1,11 +1,10 @@
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_artcraft::handle_gpt_image_1_edit_artcraft;
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_edit_sora::handle_gpt_image_1_edit_sora;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1_artcraft::handle_gpt_image_1_artcraft;
use crate::core::commands::enqueue::text_to_image::gpt_image_1::handle_gpt_image_1_sora::handle_gpt_image_1_sora;
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
@@ -34,7 +33,7 @@ pub async fn handle_gpt_image_1(
storyteller_creds_manager: &StorytellerCredentialManager,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let priority = provider_priority_store.get_priority()?;
@@ -70,5 +69,5 @@ pub async fn handle_gpt_image_1(
}
}
Err(InternalImageError::NoProviderAvailable)
Err(GenerateError::NoProviderAvailable)
}
@@ -1,8 +1,7 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
@@ -30,12 +29,12 @@ pub async fn handle_gpt_image_1_artcraft(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
storyteller_creds_manager: &StorytellerCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
let creds = match storyteller_creds_manager.get_credentials()? {
Some(creds) => creds,
None => {
return Err(InternalImageError::NeedsStorytellerCredentials);
return Err(GenerateError::needs_storyteller_credentials());
},
};
@@ -101,7 +100,7 @@ pub async fn handle_gpt_image_1_artcraft(
}
Err(err) => {
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
return Err(InternalImageError::StorytellerError(err));
return Err(GenerateError::from(err));
}
};
@@ -1,6 +1,6 @@
use crate::core::commands::enqueue::generate_error::GenerateError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
@@ -25,19 +25,19 @@ pub async fn handle_gpt_image_1_sora(
app: &AppHandle,
sora_creds_manager: &SoraCredentialManager,
sora_task_queue: &SoraTaskQueue,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> 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(InternalImageError::NeedsSoraCredentials);
return Err(GenerateError::needs_sora_credentials());
}
Err(err) => {
error!("Error reading Sora credentials: {:?}", err);
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
return Err(InternalImageError::NeedsSoraCredentials);
return Err(GenerateError::needs_sora_credentials());
}
};
@@ -96,7 +96,7 @@ pub async fn handle_gpt_image_1_sora(
error!("Failed to emit event: {:?}", err); // Fail open.
}
return Err(InternalImageError::SoraError(err));
return Err(GenerateError::from(err));
}
};
@@ -1,80 +0,0 @@
use crate::core::artcraft_error::ArtcraftError;
use base64::DecodeError;
use errors::AnyhowError;
use fal_client::error::fal_error_plus::FalErrorPlus;
use midjourney_client::error::midjourney_error::MidjourneyError;
use openai_sora_client::sora_error::SoraError;
use storyteller_client::error::storyteller_error::StorytellerError;
#[derive(Debug)]
pub enum InternalImageError {
NoModelSpecified,
NoProviderAvailable,
NeedsFalApiKey,
NeedsMidjourneyCredentials,
NeedsMidjourneyUserInfo,
NeedsMidjourneyUserId,
/// Generic - possibly this came with an error message that we sent as an event.
MidjourneyJobEnqueueFailed,
NeedsSoraCredentials,
NeedsStorytellerCredentials,
FalError(FalErrorPlus),
MidjourneyError(MidjourneyError),
SoraError(SoraError),
AnyhowError(AnyhowError),
StorytellerError(StorytellerError),
DecodeError(DecodeError),
IoError(std::io::Error),
ArtcraftError(ArtcraftError),
}
impl From<AnyhowError> for InternalImageError {
fn from(value: AnyhowError) -> Self {
Self::AnyhowError(value)
}
}
impl From<FalErrorPlus> for InternalImageError {
fn from(value: FalErrorPlus) -> Self {
Self::FalError(value)
}
}
impl From<SoraError> for InternalImageError {
fn from(value: SoraError) -> Self {
Self::SoraError(value)
}
}
impl From<StorytellerError> for InternalImageError {
fn from(value: StorytellerError) -> Self {
Self::StorytellerError(value)
}
}
impl From<DecodeError> for InternalImageError {
fn from(value: DecodeError) -> Self {
Self::DecodeError(value)
}
}
impl From<std::io::Error> for InternalImageError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
impl From<ArtcraftError> for InternalImageError {
fn from(value: ArtcraftError) -> Self {
match value {
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
ArtcraftError::IoError(e) => Self::IoError(e),
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
_ => Self::ArtcraftError(value),
}
}
}
@@ -1,8 +1,7 @@
use crate::core::commands::enqueue::generate_error::{GenerateError, MissingCredentialsReason, ProviderFailureReason};
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize};
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
use crate::core::commands::enqueue::text_to_image::internal_image_error::InternalImageError;
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent;
use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
@@ -41,19 +40,19 @@ pub async fn handle_midjourney(
request: &EnqueueTextToImageRequest,
app_env_configs: &AppEnvConfigs,
mj_creds_manager: &MidjourneyCredentialManager,
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> 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(InternalImageError::NeedsMidjourneyCredentials);
return Err(GenerateError::needs_midjourney_credentials());
}
Err(err) => {
error!("Error reading Midjourney credentials: {:?}", err);
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app);
return Err(InternalImageError::NeedsMidjourneyCredentials);
return Err(GenerateError::needs_midjourney_credentials());
},
};
@@ -62,11 +61,11 @@ pub async fn handle_midjourney(
let user_info = match mj_creds_manager.maybe_copy_user_info() {
Ok(Some(user_info)) => user_info,
Ok(None) => {
return Err(InternalImageError::NeedsMidjourneyUserInfo);
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo));
}
Err(err) => {
error!("Error reading Midjourney user info: {:?}", err);
return Err(InternalImageError::NeedsMidjourneyUserInfo);
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo));
},
};
@@ -74,7 +73,7 @@ pub async fn handle_midjourney(
Some(user_id) => ChannelId::UserId(user_id),
None => {
error!("Midjourney user info does not contain a user ID.");
return Err(InternalImageError::NeedsMidjourneyUserId);
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserId));
}
};
@@ -97,7 +96,7 @@ pub async fn handle_midjourney(
Ok(result) => result,
Err(err) => {
error!("Failed to use MidJourney: {:?}", err);
return Err(InternalImageError::MidjourneyError(err));
return Err(GenerateError::from(err));
}
};
@@ -122,7 +121,7 @@ pub async fn handle_midjourney(
fn handle_midjourney_errors(
app: &AppHandle,
maybe_errors: Option<Vec<TextToImageError>>
) -> Result<TaskEnqueueSuccess, InternalImageError> {
) -> Result<TaskEnqueueSuccess, GenerateError> {
if let Some(errors) = maybe_errors {
if !errors.is_empty() {
let messages: Vec<String> = errors.iter()
@@ -141,5 +140,5 @@ fn handle_midjourney_errors(
}
}
Err(InternalImageError::MidjourneyJobEnqueueFailed)
Err(GenerateError::ProviderFailure(ProviderFailureReason::MidjourneyJobEnqueueFailed))
}
@@ -1,6 +1,5 @@
pub mod enqueue_text_to_image_command;
pub mod generic;
pub mod internal_image_error;
mod gemini_25_flash;
mod gpt_image_1;
mod midjourney;
@@ -1,6 +1,6 @@
use crate::core::events::sendable_event_error::SendableEventError;
use enums::tauri::ux::tauri_event_name::TauriEventName;
use log::info;
use log::{error, info};
use serde::Serialize;
use std::fmt::Debug;
use tauri::{AppHandle, Emitter};
@@ -49,4 +49,11 @@ pub trait BasicSendableEvent : Clone + Debug + Serialize {
}
Ok(())
}
fn send_infallible(&self, app: &AppHandle) {
if let Err(err) = self.send(app) {
// Fail open
error!("Failed to send event `{}`: {:?}", Self::FRONTEND_EVENT_NAME.to_str(), err);
}
}
}
@@ -1,5 +1,6 @@
pub mod canvas_background_removal_complete_event;
pub mod image_edit_complete_event;
pub mod refresh_account_state_event;
pub mod show_provider_billing_modal_event;
pub mod show_provider_login_modal_event;
pub mod text_to_image_generation_complete_event;
@@ -0,0 +1,25 @@
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
use enums::common::generation_provider::GenerationProvider;
use enums::tauri::ux::tauri_event_name::TauriEventName;
use serde_derive::Serialize;
use tauri::AppHandle;
/// Send a signal to the frontend to show a modal that suggests fixing or setting up
/// service billing requirements.
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ShowProviderBillingModalEvent {
pub provider: GenerationProvider,
}
impl ShowProviderBillingModalEvent {
pub fn send_for_provider(provider: GenerationProvider, app: &AppHandle) {
let event = Self { provider };
event.send_infallible(&app);
}
}
impl BasicSendableEvent for ShowProviderBillingModalEvent {
const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::ShowProviderBillingModalEvent;
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Failure;
}
@@ -1,8 +1,6 @@
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
use crate::core::events::sendable_event_error::SendableEventError;
use enums::common::generation_provider::GenerationProvider;
use enums::tauri::ux::tauri_event_name::TauriEventName;
use log::error;
use serde_derive::Serialize;
use tauri::AppHandle;
@@ -16,9 +14,7 @@ pub struct ShowProviderLoginModalEvent {
impl ShowProviderLoginModalEvent {
pub fn send_for_provider(provider: GenerationProvider, app: &AppHandle) {
let event = Self { provider };
if let Err(err) = event.send(&app) {
error!("Failed to send ShowProviderLoginModalEvent: {:?}", err); // Fail open
}
event.send_infallible(&app);
}
}
@@ -58,6 +58,11 @@ pub enum TauriEventName {
#[serde(rename = "refresh_account_state_event")]
RefreshAccountStateEvent,
/// Special event:
/// Show a billing modal (a suggestion to pay, etc. for a specific provider)
#[serde(rename = "show_provider_billing_modal_event")]
ShowProviderBillingModalEvent,
/// Special event:
/// Show a login modal (or a suggestion to login)
#[serde(rename = "show_provider_login_modal_event")]
@@ -86,6 +91,7 @@ impl TauriEventName {
Self::TextToImageGenerationCompleteEvent => "text_to_image_generation_complete_event",
Self::ImageEditCompleteEvent => "image_edit_complete_event",
Self::RefreshAccountStateEvent => "refresh_account_state_event",
Self::ShowProviderBillingModalEvent => "show_provider_billing_modal_event",
Self::ShowProviderLoginModalEvent => "show_provider_login_modal_event",
Self::FlashUserInputErrorEvent => "flash_user_input_error_event",
}
@@ -101,6 +107,7 @@ impl TauriEventName {
"text_to_image_generation_complete_event" => Ok(Self::TextToImageGenerationCompleteEvent),
"image_edit_complete_event" => Ok(Self::ImageEditCompleteEvent),
"refresh_account_state_event" => Ok(Self::RefreshAccountStateEvent),
"show_provider_billing_modal_event" => Ok(Self::ShowProviderBillingModalEvent),
"show_provider_login_modal_event" => Ok(Self::ShowProviderLoginModalEvent),
"flash_user_input_error_event" => Ok(Self::FlashUserInputErrorEvent),
_ => Err(format!("invalid tauri_event_name: {:?}", job_status)),
@@ -119,6 +126,7 @@ impl TauriEventName {
Self::TextToImageGenerationCompleteEvent,
Self::ImageEditCompleteEvent,
Self::RefreshAccountStateEvent,
Self::ShowProviderBillingModalEvent,
Self::ShowProviderLoginModalEvent,
Self::FlashUserInputErrorEvent,
])
@@ -143,6 +151,7 @@ mod tests {
assert_serialization(TauriEventName::TextToImageGenerationCompleteEvent, "text_to_image_generation_complete_event");
assert_serialization(TauriEventName::ImageEditCompleteEvent, "image_edit_complete_event");
assert_serialization(TauriEventName::RefreshAccountStateEvent, "refresh_account_state_event");
assert_serialization(TauriEventName::ShowProviderBillingModalEvent, "show_provider_billing_modal_event");
assert_serialization(TauriEventName::ShowProviderLoginModalEvent, "show_provider_login_modal_event");
assert_serialization(TauriEventName::FlashUserInputErrorEvent, "flash_user_input_error_event");
}
@@ -157,6 +166,7 @@ mod tests {
assert_eq!(TauriEventName::TextToImageGenerationCompleteEvent.to_str(), "text_to_image_generation_complete_event");
assert_eq!(TauriEventName::ImageEditCompleteEvent.to_str(), "image_edit_complete_event");
assert_eq!(TauriEventName::RefreshAccountStateEvent.to_str(), "refresh_account_state_event");
assert_eq!(TauriEventName::ShowProviderBillingModalEvent.to_str(), "show_provider_billing_modal_event");
assert_eq!(TauriEventName::ShowProviderLoginModalEvent.to_str(), "show_provider_login_modal_event");
assert_eq!(TauriEventName::FlashUserInputErrorEvent.to_str(), "flash_user_input_error_event");
}
@@ -171,6 +181,7 @@ mod tests {
assert_eq!(TauriEventName::from_str("text_to_image_generation_complete_event").unwrap(), TauriEventName::TextToImageGenerationCompleteEvent);
assert_eq!(TauriEventName::from_str("image_edit_complete_event").unwrap(), TauriEventName::ImageEditCompleteEvent);
assert_eq!(TauriEventName::from_str("refresh_account_state_event").unwrap(), TauriEventName::RefreshAccountStateEvent);
assert_eq!(TauriEventName::from_str("show_provider_billing_modal_event").unwrap(), TauriEventName::ShowProviderBillingModalEvent);
assert_eq!(TauriEventName::from_str("show_provider_login_modal_event").unwrap(), TauriEventName::ShowProviderLoginModalEvent);
assert_eq!(TauriEventName::from_str("flash_user_input_error_event").unwrap(), TauriEventName::FlashUserInputErrorEvent);
}
@@ -178,7 +189,7 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = TauriEventName::all_variants();
assert_eq!(variants.len(), 10);
assert_eq!(variants.len(), 11);
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueSuccessEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueFailureEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationCompleteEvent));
@@ -187,6 +198,7 @@ mod tests {
assert_eq!(variants.pop_first(), Some(TauriEventName::TextToImageGenerationCompleteEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::ImageEditCompleteEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::RefreshAccountStateEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::ShowProviderBillingModalEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::ShowProviderLoginModalEvent));
assert_eq!(variants.pop_first(), Some(TauriEventName::FlashUserInputErrorEvent));
assert_eq!(variants.pop_first(), None);
@@ -15,6 +15,8 @@ pub enum CommonWebError {
NotAuthorized,
/// Server error with no reason.
ServerError,
/// Payment required with no extra info
PaymentRequired,
}
impl Error for CommonWebError {}
@@ -48,6 +50,7 @@ impl ResponseError for CommonWebError {
CommonWebError::NotFound => StatusCode::NOT_FOUND,
CommonWebError::NotAuthorized => StatusCode::UNAUTHORIZED,
CommonWebError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
CommonWebError::PaymentRequired => StatusCode::PAYMENT_REQUIRED,
}
}
@@ -0,0 +1 @@
pub mod payments_error_test;
@@ -0,0 +1,35 @@
use fal_client::export::prelude::rundiffusion_fal::juggernaut_flux::pro::pro;
use crate::http_server::common_responses::common_web_error::CommonWebError;
pub fn payments_error_test(prompt: &str) -> Result<(), CommonWebError> {
let prompt = prompt.trim().to_ascii_lowercase();
if prompt == "trigger_payment_failure" {
return Err(CommonWebError::PaymentRequired);
}
// TODO(bt,2025-08-30): Remove these later.
if prompt.contains("test") {
if prompt.contains("payment") {
return Err(CommonWebError::PaymentRequired)
}
if prompt.contains("stripe") {
return Err(CommonWebError::PaymentRequired)
}
if prompt.contains("error") {
return Err(CommonWebError::PaymentRequired)
}
}
// TODO(bt,2025-08-30): Remove these later.
if prompt.contains("payment") {
if prompt.contains("error") {
return Err(CommonWebError::PaymentRequired)
}
if prompt.contains("failure") {
return Err(CommonWebError::PaymentRequired)
}
}
Ok(())
}
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -45,6 +46,9 @@ pub async fn flux_pro_kontext_max_edit_image_handler(
request: Json<FluxProKontextMaxEditImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<FluxProKontextMaxEditImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -48,6 +49,9 @@ pub async fn gemini_25_flash_edit_image_handler(
request: Json<Gemini25FlashEditImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<Gemini25FlashEditImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -47,6 +48,9 @@ pub async fn gpt_image_1_edit_image_handler(
request: Json<GptImage1EditImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GptImage1EditImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -45,6 +46,9 @@ pub async fn qwen_edit_image_handler(
request: Json<QwenEditImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<QwenEditImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -45,6 +46,9 @@ pub async fn seededit_3_edit_image_handler(
request: Json<SeedEdit3EditImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<SeedEdit3EditImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -1,6 +1,7 @@
use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
use actix_web::web::Json;
@@ -39,6 +40,9 @@ pub async fn generate_flux_1_dev_text_to_image_handler(
request: Json<GenerateFlux1DevTextToImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateFlux1DevTextToImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -1,6 +1,7 @@
use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
use actix_web::web::Json;
@@ -39,6 +40,9 @@ pub async fn generate_flux_1_schnell_text_to_image_handler(
request: Json<GenerateFlux1SchnellTextToImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateFlux1SchnellTextToImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -1,6 +1,7 @@
use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
use actix_web::web::Json;
@@ -40,6 +41,9 @@ pub async fn generate_flux_pro_11_text_to_image_handler(
request: Json<GenerateFluxPro11TextToImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateFluxPro11TextToImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -1,6 +1,7 @@
use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
use actix_web::web::Json;
@@ -40,6 +41,9 @@ pub async fn generate_flux_pro_11_ultra_text_to_image_handler(
request: Json<GenerateFluxPro11UltraTextToImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateFluxPro11UltraTextToImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -45,6 +46,9 @@ pub async fn generate_gpt_image_1_text_to_image_handler(
request: Json<GenerateGptImage1TextToImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateGptImage1TextToImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -45,6 +46,9 @@ pub async fn flux_dev_juggernaut_inpaint_image_handler(
request: Json<FluxDevJuggernautInpaintImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<FluxDevJuggernautInpaintImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -45,6 +46,9 @@ pub async fn flux_pro_1_inpaint_image_handler(
request: Json<FluxPro1InpaintImageRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<FluxPro1InpaintImageResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -1,3 +1,4 @@
pub mod image;
pub mod object;
pub mod video;
pub mod video;
pub mod common;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -49,6 +50,9 @@ pub async fn generate_kling_1_6_pro_video_handler(
request: Json<GenerateKling16ProImageToVideoRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateKling16ProImageToVideoResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -46,6 +47,9 @@ pub async fn generate_kling_2_1_master_video_handler(
request: Json<GenerateKling21MasterImageToVideoRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateKling21MasterImageToVideoResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,9 +2,12 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
use crate::util::lookup::fetch_all_required_media_files::fetch_all_required_media_files;
use crate::util::traits::into_media_links_trait::IntoMediaLinks;
use actix_web::web::Json;
use actix_web::{web, HttpRequest};
use artcraft_api_defs::generate::video::generate_kling_2_1_pro_image_to_video::{GenerateKling21ProAspectRatio, GenerateKling21ProImageToVideoRequest};
@@ -28,8 +31,6 @@ use mysql_queries::queries::media_files::get::get_media_file::{get_media_file, g
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use sqlx::Acquire;
use utoipa::ToSchema;
use crate::util::lookup::fetch_all_required_media_files::fetch_all_required_media_files;
use crate::util::traits::into_media_links_trait::IntoMediaLinks;
/// Kling 2.1 Pro Image to Video
#[utoipa::path(
@@ -48,6 +49,9 @@ pub async fn generate_kling_2_1_pro_video_handler(
request: Json<GenerateKling21ProImageToVideoRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateKling21ProImageToVideoResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -49,6 +50,9 @@ pub async fn generate_seedance_1_0_lite_image_to_video_handler(
request: Json<GenerateSeedance10LiteImageToVideoRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateSeedance10LiteImageToVideoResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -49,6 +50,9 @@ pub async fn generate_seedance_1_0_pro_image_to_video_handler(
request: Json<GenerateSeedance10ProImageToVideoRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateSeedance10ProImageToVideoResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::analytics::log_browser_session_handler::LogBrowserSessionError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -51,6 +52,9 @@ pub async fn generate_veo_2_image_to_video_handler(
request: Json<GenerateVeo2ImageToVideoRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateVeo2ImageToVideoResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::analytics::log_browser_session_handler::LogBrowserSessionError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -45,6 +46,9 @@ pub async fn generate_veo_3_fast_image_to_video_handler(
request: Json<GenerateVeo3FastImageToVideoRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateVeo3FastImageToVideoResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -3,6 +3,7 @@ use std::sync::Arc;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::endpoints::analytics::log_browser_session_handler::LogBrowserSessionError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
use crate::state::server_state::ServerState;
@@ -45,6 +46,9 @@ pub async fn generate_veo_3_image_to_video_handler(
request: Json<GenerateVeo3ImageToVideoRequest>,
server_state: web::Data<Arc<ServerState>>
) -> Result<Json<GenerateVeo3ImageToVideoResponse>, CommonWebError> {
payments_error_test(&request.prompt.as_deref().unwrap_or(""))?;
let mut mysql_connection = server_state.mysql_pool
.acquire()
.await?;
@@ -7,6 +7,7 @@ import {
faPaintbrush,
faImage,
faPenNib,
faGem,
} from "@fortawesome/pro-solid-svg-icons";
import {
faWindowRestore,
@@ -51,6 +52,9 @@ import {
} from "@storyteller/tauri-utils";
import { useEditStore } from "../../../pages/PageEdit/stores/EditState";
import { BaseSelectorImage } from "../../../pages/PageEdit/BaseImageSelector";
import { ProviderSetupModal } from "@storyteller/provider-setup-modal";
import { ProviderBillingModal } from "@storyteller/provider-billing-modal";
import { usePricingModalStore } from "@storyteller/ui-pricing-modal";
interface Props {
pageName: string;
@@ -190,7 +194,7 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
const pageTitle = getPageTitle();
// const { toggleModal } = usePricingModalStore(); - Uncomment for pricing modal - BFlat
const { toggleModal } = usePricingModalStore();
return (
<>
@@ -261,14 +265,14 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
<div className="flex justify-end gap-2" data-tauri-drag-region>
<div className="no-drag flex gap-2">
{/* - Uncomment for pricing modal - BFlat */}
{/* <Button
<Button
variant="primary"
icon={faGem}
onClick={toggleModal}
className="shadow-md shadow-primary-500/50 transition-all duration-300 hover:shadow-md hover:shadow-primary-500/75"
>
Upgrade Now
</Button> */}
</Button>
<Tooltip content="Settings" position="bottom" delay={300}>
<Button
variant="secondary"
@@ -336,6 +340,9 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
onDownloadClicked={downloadFile}
onEditClicked={handleEditFromGallery}
/>
<ProviderSetupModal />
<ProviderBillingModal />
</>
);
};
@@ -24,7 +24,6 @@ import { PageEditor } from "~/pages/PageEnigma/PageEditor";
import { GalleryDragComponent } from "@storyteller/ui-gallery-modal";
import { LoadingDots } from "@storyteller/ui-loading";
import { PricingModal } from "@storyteller/ui-pricing-modal";
import { ProviderSetupModal } from "@storyteller/provider-setup-modal";
import {
isActionReminderOpen,
actionReminderProps,
@@ -120,7 +119,6 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
/>
<GenerateModals />
<ErrorDialog />
<ProviderSetupModal />
<EditorLoadingBar />
<Toaster offsetTop={70} offsetRight={12} />
+3
View File
@@ -118,6 +118,9 @@
{
"path": "../../libs/components/provider-setup-modal"
},
{
"path": "../../libs/components/provider-billing-modal"
},
{
"path": "../../libs/components/modal"
},
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
@@ -0,0 +1,7 @@
# @frontend/provider-billing-modal
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @frontend/provider-billing-modal` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,17 @@
{
"name": "@storyteller/provider-billing-modal",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
@@ -0,0 +1,2 @@
export * from './lib/provider-billing-modal';
@@ -0,0 +1,13 @@
interface ArtcraftBillingBlockProps {
}
export function ArtcraftBillingBlock({
}: ArtcraftBillingBlockProps) {
return (
<div>
Set up ArtCraft billing!
</div>
);
}
@@ -0,0 +1,33 @@
import { GenerationProvider } from "@storyteller/common";
interface GenericProviderBillingBlockProps {
provider: GenerationProvider;
}
export function GenericProviderBillingBlock({
provider
}: GenericProviderBillingBlockProps) {
const serviceProviderName = getServiceProviderName(provider);
return (
<div>
Please set up {serviceProviderName} on their website
to use it with Artcraft.
</div>
);
}
function getServiceProviderName(provider: GenerationProvider) : string {
switch (provider) {
case GenerationProvider.Sora:
return "Sora";
case GenerationProvider.Fal:
return "Fal";
case GenerationProvider.Midjourney:
return "Midjourney";
case GenerationProvider.Artcraft:
default:
return "Artcraft";
}
}
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import FrontendProviderBillingModal from './provider-billing-modal';
describe('FrontendProviderBillingModal', () => {
it('should render successfully', () => {
const { baseElement } = render(<FrontendProviderBillingModal />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,52 @@
import { Modal } from "@storyteller/ui-modal";
import { useState } from "react";
import { useShowProviderBillingModalEvent } from "@storyteller/tauri-events";
import { GenerationProvider } from "@storyteller/common";
import { GenericProviderBillingBlock } from "./others/GenericProviderBillingBlock";
import { ArtcraftBillingBlock } from "./artcraft/ArtcraftBillingBlock";
interface ProviderBillingModalProps {
}
export function ProviderBillingModal({
}: ProviderBillingModalProps) {
const [showModal, setShowModal] = useState(false);
const [provider, setProvider] = useState<GenerationProvider>(GenerationProvider.Artcraft);
useShowProviderBillingModalEvent(async (event) => {
console.log("Show provider billing modal event received from Tauri:", event);
setProvider(event.provider);
setShowModal(true);
});
let block;
switch (provider) {
case GenerationProvider.Midjourney:
case GenerationProvider.Sora:
case GenerationProvider.Fal:
// NB: We're just going to ask users to set up billing on the provider's website.
block = <GenericProviderBillingBlock provider={provider} />;
break;
case GenerationProvider.Artcraft:
block = <ArtcraftBillingBlock />;
break;
}
return (
<Modal
isOpen={showModal}
onClose={() => {
setShowModal(false);
}}
className="max-w-2xl max-h-[500px] p-6"
showClose={true}
>
<div className="flex flex-col items-center justify-center gap-6">
{block}
</div>
</Modal>
);
}
export default ProviderBillingModal;
@@ -0,0 +1,25 @@
{
"files": [],
"include": [],
"references": [
{
"path": "../../common"
},
{
"path": "../../tauri-events"
},
{
"path": "../button"
},
{
"path": "../modal"
},
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}

Some files were not shown because too many files have changed in this diff Show More