mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-22 09:39:31 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de1e2431ad |
-87
@@ -1,87 +0,0 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use fal::endpoints::fal_ai::gemini_25_flash_image::edit::gemini_25_flash_image_edit;
|
||||
use fal::prelude::fal_ai::gemini_25_flash_image::edit::Gemini25FlashImageEditInput;
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Gemini25FlashEditArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub image_urls: Vec<String>,
|
||||
pub num_images: Gemini25FlashEditNumImages,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Gemini25FlashEditNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
|
||||
pub async fn enqueue_gemini_25_flash_edit_webhook<R: IntoUrl>(
|
||||
args: Gemini25FlashEditArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
Gemini25FlashEditNumImages::One => 1,
|
||||
Gemini25FlashEditNumImages::Two => 2,
|
||||
Gemini25FlashEditNumImages::Three => 3,
|
||||
Gemini25FlashEditNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let request = Gemini25FlashImageEditInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
image_urls: args.image_urls,
|
||||
// Constants
|
||||
num_images: Some(num_images),
|
||||
output_format: Some("png".to_string()),
|
||||
};
|
||||
|
||||
let result = gemini_25_flash_image_edit(request)
|
||||
.with_api_key(&args.api_key.0)
|
||||
.queue_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::image::edit::enqueue_gemini_25_flash_edit_webhook::{enqueue_gemini_25_flash_edit_webhook, Gemini25FlashEditArgs, Gemini25FlashEditNumImages};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{GHOST_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = Gemini25FlashEditArgs {
|
||||
image_urls: vec![
|
||||
GHOST_IMAGE_URL.to_string(),
|
||||
TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
],
|
||||
num_images: Gemini25FlashEditNumImages::Two,
|
||||
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly",
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_gemini_25_flash_edit_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
-9
@@ -14,13 +14,6 @@ pub struct QwenImageEditArgs<'a, U: IntoUrl, R: IntoUrl> {
|
||||
pub num_images: Option<QwenImageEditNumImages>,
|
||||
pub image_size: Option<QwenImageEditSize>,
|
||||
|
||||
pub negative_prompt: Option<String>,
|
||||
|
||||
/// Acceleration level for image generation.
|
||||
/// Options: 'none', 'regular'. Higher acceleration increases speed.
|
||||
/// 'regular' balances speed and quality. Default value: "none"
|
||||
pub acceleration: Option<String>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
@@ -116,8 +109,6 @@ mod tests {
|
||||
prompt: "put christmas lights on the tree, add snow to the mountains",
|
||||
num_images: Some(QwenImageEditNumImages::One),
|
||||
image_size: None,
|
||||
negative_prompt: None,
|
||||
acceleration: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
pub mod enqueue_flux_pro_kontext_max_edit_webhook;
|
||||
pub mod enqueue_gemini_25_flash_edit_webhook;
|
||||
pub mod enqueue_gpt_image_1_edit_image_webhook;
|
||||
pub mod enqueue_qwen_image_edit_webhook;
|
||||
pub mod enqueue_seededit_v3_edit_webhook;
|
||||
+2
-2
@@ -21,7 +21,7 @@ pub struct GptEditImageByokArgs<'a, V: IntoUrl> {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum GptEditImageSize {
|
||||
pub enum GptEditImageSize{
|
||||
Auto,
|
||||
Square,
|
||||
Horizontal,
|
||||
@@ -92,7 +92,7 @@ pub async fn enqueue_gpt_image_1_edit_image_webhook<V: IntoUrl>(
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::creds::open_ai_api_key::OpenAiApiKey;
|
||||
use crate::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::{enqueue_gpt_image_1_edit_image_webhook, GptEditImageByokArgs, GptEditImageNumImages, GptEditImageQuality, GptEditImageSize};
|
||||
use crate::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::{enqueue_gpt_image_1_edit_image_webhook, GptEditImageByokArgs, GptEditImageNumImages, GptEditImageQuality, GptEditImageSize};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, GRASSY_HILL_TRANSPARENT_IMAGE_URL};
|
||||
@@ -3,6 +3,7 @@ pub mod enqueue_flux_1_dev_text_to_image_webhook;
|
||||
pub mod enqueue_flux_1_schnell_text_to_image_webhook;
|
||||
pub mod enqueue_flux_pro_11_text_to_image_webhook;
|
||||
pub mod enqueue_flux_pro_11_ultra_text_to_image_webhook;
|
||||
pub mod enqueue_gpt_image_1_edit_image_webhook;
|
||||
pub mod enqueue_gpt_image_1_text_to_image_webhook;
|
||||
pub mod infill;
|
||||
pub mod remove_background_rembg_webhook;
|
||||
|
||||
-2
@@ -57,8 +57,6 @@ pub async fn enqueue_seedance_1_lite_image_to_video_webhook<U: IntoUrl, T: IntoU
|
||||
// TODO: Add these later
|
||||
camera_fixed: None,
|
||||
seed: -1,
|
||||
// Static
|
||||
enable_safety_checker: Some(false),
|
||||
};
|
||||
|
||||
let result = image_to_video(request)
|
||||
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use fal::endpoints::fal_ai::bytedance::seedance::v1::pro::image_to_video::{seedance_v1_pro_image_to_video, SeedanceImageToVideoProRequest};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Seedance1ProArgs<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub webhook_url: V,
|
||||
pub prompt: &'a str,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub camera_fixed: bool,
|
||||
pub duration: Seedance1ProDuration,
|
||||
pub resolution: Seedance1ProResolution,
|
||||
pub seed: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Seedance1ProDuration {
|
||||
ThreeSeconds,
|
||||
FourSeconds,
|
||||
FiveSeconds,
|
||||
SixSeconds,
|
||||
SevenSeconds,
|
||||
EightSeconds,
|
||||
NineSeconds,
|
||||
TenSeconds,
|
||||
ElevenSeconds,
|
||||
TwelveSeconds,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Seedance1ProResolution {
|
||||
FourEightyP, // 480p
|
||||
SevenTwentyP, // 720p
|
||||
TenEightyP, // 1080p
|
||||
}
|
||||
|
||||
pub async fn enqueue_seedance_1_pro_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Seedance1ProArgs<'_, U, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
let duration = match args.duration {
|
||||
Seedance1ProDuration::ThreeSeconds => Some("3".to_string()),
|
||||
Seedance1ProDuration::FourSeconds => Some("4".to_string()),
|
||||
Seedance1ProDuration::FiveSeconds => Some("5".to_string()),
|
||||
Seedance1ProDuration::SixSeconds => Some("6".to_string()),
|
||||
Seedance1ProDuration::SevenSeconds => Some("7".to_string()),
|
||||
Seedance1ProDuration::EightSeconds => Some("8".to_string()),
|
||||
Seedance1ProDuration::NineSeconds => Some("9".to_string()),
|
||||
Seedance1ProDuration::TenSeconds => Some("10".to_string()),
|
||||
Seedance1ProDuration::ElevenSeconds => Some("11".to_string()),
|
||||
Seedance1ProDuration::TwelveSeconds => Some("12".to_string()),
|
||||
};
|
||||
|
||||
let resolution = match args.resolution {
|
||||
Seedance1ProResolution::FourEightyP => Some("480p".to_string()),
|
||||
Seedance1ProResolution::SevenTwentyP => Some("720p".to_string()),
|
||||
Seedance1ProResolution::TenEightyP => Some("1080p".to_string()),
|
||||
};
|
||||
|
||||
let image_url = args.image_url.as_str().to_string();
|
||||
|
||||
let request = SeedanceImageToVideoProRequest {
|
||||
image_url,
|
||||
prompt: args.prompt.to_string(),
|
||||
duration,
|
||||
resolution,
|
||||
// TODO: Add these later
|
||||
camera_fixed: None,
|
||||
// Static
|
||||
enable_safety_checker: Some(false),
|
||||
};
|
||||
|
||||
let result = seedance_v1_pro_image_to_video(request)
|
||||
.with_api_key(&args.api_key.0)
|
||||
.queue_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::video::enqueue_seedance_1_pro_image_to_video_webhook::{enqueue_seedance_1_pro_image_to_video_webhook, Seedance1ProArgs, Seedance1ProDuration, Seedance1ProResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::TALL_MOCHI_WITH_GLASSES_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = Seedance1ProArgs {
|
||||
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
|
||||
prompt: "shiba in glasses runs to the lake and stands by the shore",
|
||||
api_key: &api_key,
|
||||
camera_fixed: false,
|
||||
duration: Seedance1ProDuration::FiveSeconds,
|
||||
resolution: Seedance1ProResolution::SevenTwentyP,
|
||||
seed: None,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_seedance_1_pro_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+9
-25
@@ -6,13 +6,12 @@ use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Veo3Args<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub prompt: &'a str,
|
||||
pub image_url: U,
|
||||
pub duration: Veo3Duration,
|
||||
pub aspect_ratio: Veo3AspectRatio,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub resolution: Veo3Resolution,
|
||||
pub generate_audio: bool,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub webhook_url: V,
|
||||
}
|
||||
|
||||
@@ -22,14 +21,6 @@ pub enum Veo3Duration {
|
||||
EightSeconds,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Veo3AspectRatio {
|
||||
Default,
|
||||
WideSixteenNine,
|
||||
TallNineSixteen,
|
||||
Square,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Veo3Resolution {
|
||||
Default,
|
||||
@@ -44,13 +35,6 @@ pub async fn enqueue_veo_3_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
Veo3Duration::Default => None,
|
||||
Veo3Duration::EightSeconds => Some("8s".to_string()),
|
||||
};
|
||||
|
||||
let aspect_ratio = match args.aspect_ratio {
|
||||
Veo3AspectRatio::Default => None,
|
||||
Veo3AspectRatio::WideSixteenNine => Some("16:9".to_string()),
|
||||
Veo3AspectRatio::TallNineSixteen => Some("9:16".to_string()),
|
||||
Veo3AspectRatio::Square => Some("1:1".to_string()),
|
||||
};
|
||||
|
||||
let resolution= match args.resolution {
|
||||
Veo3Resolution::Default => None,
|
||||
@@ -63,7 +47,6 @@ pub async fn enqueue_veo_3_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
let request = ImageToVideoInput {
|
||||
image_url,
|
||||
prompt: args.prompt.to_string(),
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
duration,
|
||||
generate_audio: Some(args.generate_audio),
|
||||
@@ -81,27 +64,28 @@ pub async fn enqueue_veo_3_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::video::enqueue_veo_3_image_to_video_webhook::{enqueue_veo_3_image_to_video_webhook, Veo3Args, Veo3AspectRatio, Veo3Duration, Veo3Resolution};
|
||||
use crate::requests::webhook::video::enqueue_veo_3_image_to_video_webhook::{enqueue_veo_3_image_to_video_webhook, Veo3Args, Veo3Duration, Veo3Resolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{JUNO_AT_LAKE_IMAGE_URL, TREX_SKELETON_IMAGE_URL};
|
||||
use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
let image_url = JUNO_AT_LAKE_IMAGE_URL;
|
||||
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = Veo3Args {
|
||||
image_url: TREX_SKELETON_IMAGE_URL,
|
||||
prompt: "the t-rex skeleton starts walking towards the camera and roars",
|
||||
image_url: image_url,
|
||||
prompt: "corgi at the lake. the corgi barks. camera pulls back to show a wider shot of the lake as the corgi jumps into the water. there are sounds of barking and splashing. cinematic, 8k, ultra wide shot",
|
||||
api_key: &api_key,
|
||||
duration: Veo3Duration::EightSeconds,
|
||||
aspect_ratio: Veo3AspectRatio::WideSixteenNine,
|
||||
resolution: Veo3Resolution::TenEightyP,
|
||||
generate_audio: true,
|
||||
resolution: Veo3Resolution::TenEightyP,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ pub mod enqueue_kling_16_pro_image_to_video_webhook;
|
||||
pub mod enqueue_kling_21_master_image_to_video_webhook;
|
||||
pub mod enqueue_kling_21_pro_image_to_video_webhook;
|
||||
pub mod enqueue_seedance_1_lite_image_to_video_webhook;
|
||||
pub mod enqueue_seedance_1_pro_image_to_video_webhook;
|
||||
pub mod enqueue_veo_2_image_to_video_webhook;
|
||||
pub mod enqueue_veo_3_fast_image_to_video_webhook;
|
||||
pub mod enqueue_veo_3_image_to_video_webhook;
|
||||
@@ -11,9 +11,6 @@ pub enum ApiError {
|
||||
|
||||
/// 401. The request was not authorized.
|
||||
Unauthorized(String),
|
||||
|
||||
// 402. Payment required.
|
||||
PaymentRequired(String),
|
||||
|
||||
/// 403. The request was forbidden.
|
||||
Forbidden(String),
|
||||
@@ -59,7 +56,6 @@ 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),
|
||||
@@ -84,13 +80,6 @@ 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() {
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::edit::gemini_25_flash_edit_image::{Gemini25FlashEditImageRequest, Gemini25FlashEditImageResponse, GEMINI_25_FLASH_EDIT_IMAGE_PATH};
|
||||
|
||||
pub async fn gemini_25_flash_edit_image(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: Gemini25FlashEditImageRequest,
|
||||
) -> Result<Gemini25FlashEditImageResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GEMINI_25_FLASH_EDIT_IMAGE_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod flux_pro_kontext_max_edit_image;
|
||||
pub mod gemini_25_flash_edit_image;
|
||||
pub mod gpt_image_1_edit_image;
|
||||
@@ -38,7 +38,6 @@ 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,6 +1,5 @@
|
||||
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;
|
||||
@@ -16,7 +15,6 @@ 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);
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod notify_frontend_of_errors;
|
||||
mod maybe_notify_frontend_of_billing_errors;
|
||||
@@ -1,10 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,166 +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 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))
|
||||
}
|
||||
}
|
||||
+4
-38
@@ -1,6 +1,6 @@
|
||||
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
|
||||
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,8 +124,6 @@ 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 {
|
||||
@@ -139,7 +137,7 @@ pub async fn enqueue_image_bg_removal_command(
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Err(error_to_tauri_response(err))
|
||||
Err(err.to_tauri_response())
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
@@ -167,7 +165,7 @@ pub async fn handle_request(
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
|
||||
|
||||
// TODO(bt,2025-07-07): Other model/provider routing...
|
||||
|
||||
@@ -198,35 +196,3 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
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;
|
||||
@@ -26,7 +27,7 @@ pub async fn handle_generic_bg_removal(
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
@@ -64,5 +65,5 @@ pub async fn handle_generic_bg_removal(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalBgRemovalError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
+10
-8
@@ -1,6 +1,7 @@
|
||||
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};
|
||||
@@ -24,6 +25,7 @@ use artcraft_api_defs::generate::image::remove_image_background::RemoveImageBack
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
@@ -43,12 +45,12 @@ pub async fn handle_generic_bg_removal_artcraft(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials));
|
||||
return Err(InternalBgRemovalError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -83,7 +85,7 @@ pub async fn handle_generic_bg_removal_artcraft(
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft background removal: {:?}", err);
|
||||
return Err(GenerateError::ProviderFailure(ProviderFailureReason::StorytellerError(err)));
|
||||
return Err(InternalBgRemovalError::StorytellerError(err));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -100,7 +102,7 @@ async fn upload_image_from_base64_bytes(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
creds: &StorytellerCredentialSet
|
||||
) -> Result<MediaFileToken, GenerateError> {
|
||||
) -> Result<MediaFileToken, InternalBgRemovalError> {
|
||||
let temp_file;
|
||||
|
||||
if let Some(base64_bytes) = &request.base64_image {
|
||||
@@ -109,10 +111,10 @@ async fn upload_image_from_base64_bytes(
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to save base64 image to temp dir: {:?}", err);
|
||||
GenerateError::BadInput(BadInputReason::Base64DecodeError)
|
||||
InternalBgRemovalError::Base64DecodeError
|
||||
})?;
|
||||
} else {
|
||||
return Err(GenerateError::BadInput(BadInputReason::RequiredSourceImageNotProvided));
|
||||
return Err(InternalBgRemovalError::MissingImage);
|
||||
};
|
||||
|
||||
info!("Uploading image media file from temp file: {:?}", temp_file.path());
|
||||
@@ -128,7 +130,7 @@ async fn upload_image_from_base64_bytes(
|
||||
}).await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upload image media file: {:?}", err);
|
||||
GenerateError::ProviderFailure(ProviderFailureReason::StorytellerError(err))
|
||||
InternalBgRemovalError::StorytellerError(err)
|
||||
})?;
|
||||
|
||||
Ok(result.media_file_token)
|
||||
|
||||
+3
-3
@@ -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, GenerateError> {
|
||||
Err(GenerateError::NotYetImplemented("TODO: Implement fal background removal handling".to_string()))
|
||||
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
|
||||
unimplemented!("TODO: Implement fal background removal handling");
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod enqueue_image_bg_removal_command;
|
||||
pub mod errors;
|
||||
pub mod generic;
|
||||
+8
-73
@@ -1,7 +1,5 @@
|
||||
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::errors::InternalContextualEditImageError;
|
||||
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;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
@@ -44,25 +42,16 @@ 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")]
|
||||
pub enum ContextualImageEditModel {
|
||||
#[serde(rename = "flux_pro_kontext_max")]
|
||||
FluxProKontextMax,
|
||||
|
||||
#[serde(rename = "gemini_25_flash")]
|
||||
Gemini25Flash,
|
||||
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
|
||||
// #[serde(rename = "qwen")]
|
||||
// Qwen,
|
||||
//
|
||||
// #[serde(rename = "seededit_3")]
|
||||
// SeedEdit3,
|
||||
|
||||
#[serde(rename = "flux_pro_kontext_max")]
|
||||
FluxProKontextMax,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -195,8 +184,6 @@ 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 {
|
||||
@@ -210,7 +197,7 @@ pub async fn enqueue_contextual_edit_image_command(
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Err(error_to_tauri_response(err))
|
||||
Err(err.to_tauri_response())
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
@@ -240,10 +227,10 @@ pub async fn handle_request(
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
let success_event= match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified())
|
||||
return Err(InternalContextualEditImageError::NoModelSpecified)
|
||||
}
|
||||
Some(ContextualImageEditModel::FluxProKontextMax) => {
|
||||
handle_flux_kontext_edit(
|
||||
@@ -259,20 +246,6 @@ pub async fn handle_request(
|
||||
sora_task_queue,
|
||||
).await?
|
||||
}
|
||||
Some(ContextualImageEditModel::Gemini25Flash) => {
|
||||
handle_gemini_25_flash_edit(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
}
|
||||
Some(ContextualImageEditModel::GptImage1) => {
|
||||
handle_gpt_image_1_edit(
|
||||
request,
|
||||
@@ -305,41 +278,3 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
@@ -55,5 +55,5 @@ pub async fn handle_flux_kontext_edit(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalContextualEditImageError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
+16
-12
@@ -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};
|
||||
@@ -23,6 +23,7 @@ use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::edit::enqueue_flux_pro_kontext_max_edit_webhook::FluxProKontextMaxNumImages;
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::generate::image::edit::flux_pro_kontext_max_edit_image::flux_pro_kontext_max_edit_image;
|
||||
@@ -39,12 +40,12 @@ pub async fn handle_flux_kontext_edit_artcraft(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
return Err(InternalContextualEditImageError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -59,11 +60,11 @@ pub async fn handle_flux_kontext_edit_artcraft(
|
||||
Some(3) => Some(FluxProKontextMaxEditImageNumImages::Three),
|
||||
Some(4) => Some(FluxProKontextMaxEditImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
return Err(InternalContextualEditImageError::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -75,13 +76,15 @@ pub async fn handle_flux_kontext_edit_artcraft(
|
||||
.unwrap_or(0);
|
||||
|
||||
if request.scene_image_media_token.is_some() && list_count > 0 {
|
||||
return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments(
|
||||
"Cannot specify both scene_image_media_token and image_media_tokens for Flux Kontext Max".to_string())));
|
||||
return Err(InternalContextualEditImageError::InvalidNumberOfInputImagesForFluxKontext {
|
||||
message: "Cannot specify both scene_image_media_token and image_media_tokens for Flux Kontext Max".to_string()
|
||||
});
|
||||
}
|
||||
|
||||
if list_count > 1 {
|
||||
return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments(
|
||||
"Cannot specify more than one image in image_media_tokens for Flux Kontext Max".to_string())));
|
||||
return Err(InternalContextualEditImageError::InvalidNumberOfInputImagesForFluxKontext {
|
||||
message: "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 {
|
||||
@@ -100,8 +103,9 @@ pub async fn handle_flux_kontext_edit_artcraft(
|
||||
let media_token = match maybe_media_token {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::WrongImageArguments(
|
||||
"No input image specified for Flux Kontext Max edit".to_string())));
|
||||
return Err(InternalContextualEditImageError::InvalidNumberOfInputImagesForFluxKontext {
|
||||
message: "No input image specified for Flux Kontext Max edit".to_string(),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -127,7 +131,7 @@ pub async fn handle_flux_kontext_edit_artcraft(
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalContextualEditImageError::StorytellerError(err));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
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::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;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use log::info;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_gemini_25_flash_edit(
|
||||
request: &EnqueueContextualEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
// TODO: Check if providers are available before proceeding.
|
||||
|
||||
info!("Providers by priority: {:?}", priority);
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Fal => {
|
||||
// Fallthrough (TODO)
|
||||
}
|
||||
Provider::Sora => {
|
||||
// Fallthrough
|
||||
}
|
||||
Provider::Artcraft => {
|
||||
info!("Dispatching gemini 2.5 flash (edit) via Artcraft...");
|
||||
return handle_gemini_25_flash_edit_artcraft(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager
|
||||
).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
}
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
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::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::{FluxProKontextMaxEditImageNumImages, FluxProKontextMaxEditImageRequest};
|
||||
use artcraft_api_defs::generate::image::edit::gemini_25_flash_edit_image::{Gemini25FlashEditImageNumImages, Gemini25FlashEditImageRequest};
|
||||
use artcraft_api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::GenerateFluxPro11TextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::generate::image::edit::flux_pro_kontext_max_edit_image::flux_pro_kontext_max_edit_image;
|
||||
use storyteller_client::generate::image::edit::gemini_25_flash_edit_image::gemini_25_flash_edit_image;
|
||||
use storyteller_client::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub(super) const MAX_IMAGES: usize = 10;
|
||||
|
||||
pub async fn handle_gemini_25_flash_edit_artcraft(
|
||||
request: &EnqueueContextualEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft gemini 2.5 flash (edit) ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(Gemini25FlashEditImageNumImages::One),
|
||||
Some(2) => Some(Gemini25FlashEditImageNumImages::Two),
|
||||
Some(3) => Some(Gemini25FlashEditImageNumImages::Three),
|
||||
Some(4) => Some(Gemini25FlashEditImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
let mut media_tokens = Vec::with_capacity(10);
|
||||
|
||||
if let Some(scene_image_media_token) = request.scene_image_media_token.clone() {
|
||||
media_tokens.push(scene_image_media_token);
|
||||
}
|
||||
|
||||
if let Some(image_media_tokens) = request.image_media_tokens.as_ref() {
|
||||
media_tokens.extend_from_slice(image_media_tokens);
|
||||
}
|
||||
|
||||
if media_tokens.len() > MAX_IMAGES {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
|
||||
min: 1,
|
||||
max: MAX_IMAGES as u32,
|
||||
provided: media_tokens.len() as u32,
|
||||
}));
|
||||
}
|
||||
|
||||
let request = Gemini25FlashEditImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_tokens: Some(media_tokens),
|
||||
num_images,
|
||||
image_quality: None,
|
||||
};
|
||||
|
||||
let result = gemini_25_flash_edit_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft gpt-image-1. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::Gemini25Flash),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
})
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub (super) mod handle_gemini_25_flash_edit;
|
||||
pub (super) mod handle_gemini_25_flash_edit_artcraft;
|
||||
+3
-3
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
@@ -67,5 +67,5 @@ pub async fn handle_gpt_image_1_edit(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalContextualEditImageError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
+10
-10
@@ -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;
|
||||
@@ -22,7 +22,7 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
return Err(InternalContextualEditImageError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
return Err(InternalContextualEditImageError::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(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
|
||||
return Err(InternalContextualEditImageError::InvalidNumberOfInputImages {
|
||||
min: 1,
|
||||
max: MAX_IMAGES as u32,
|
||||
provided: media_tokens.len() as u32,
|
||||
}));
|
||||
requested: 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(GenerateError::from(err));
|
||||
return Err(InternalContextualEditImageError::StorytellerError(err));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+7
-7
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
|
||||
let mut creds = match sora_creds_manager.get_credentials() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
warn!("No Sora credentials found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
return Err(InternalContextualEditImageError::NeedsSoraCredentials);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to get Sora credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
return Err(InternalContextualEditImageError::AnyhowError(err));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,11 +79,11 @@ pub async fn handle_gpt_image_1_edit_sora(
|
||||
}
|
||||
|
||||
if media_tokens.len() > MAX_IMAGES {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages {
|
||||
return Err(InternalContextualEditImageError::InvalidNumberOfInputImages {
|
||||
min: 1,
|
||||
max: MAX_IMAGES as u32,
|
||||
provided: media_tokens.len() as u32,
|
||||
}));
|
||||
requested: media_tokens.len() as u32,
|
||||
});
|
||||
}
|
||||
|
||||
info!("Calling get media file API: {:?}", app_env_configs.storyteller_host);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod enqueue_contextual_edit_image_command;
|
||||
pub mod errors;
|
||||
pub mod gpt_image_1;
|
||||
mod flux_kontext;
|
||||
mod gemini_25_flash;
|
||||
mod flux_kontext;
|
||||
+9
-94
@@ -1,10 +1,9 @@
|
||||
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::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::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;
|
||||
use crate::core::commands::enqueue::image_inpaint::gemini_25_flash_inpaint::handle_gemini_25_flash_inpaint::handle_gemini_25_flash_inpaint;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::{Response, ResponseOrErrorType};
|
||||
@@ -49,26 +48,16 @@ use tokens::tokens::media_files::MediaFileToken;
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ImageInpaintModel {
|
||||
/*
|
||||
* Mask-based inpainting models
|
||||
*/
|
||||
|
||||
// Mask-based inpainting models
|
||||
#[serde(rename = "flux_dev_juggernaut")]
|
||||
FluxDevJuggernaut,
|
||||
|
||||
#[serde(rename = "flux_pro_1")]
|
||||
FluxPro1,
|
||||
|
||||
/*
|
||||
* Non-inpainting, instructive editing models
|
||||
* NB: We're supporting these to keep the app simple and not over-complicate the javascript
|
||||
*/
|
||||
|
||||
// Non-inpainting, instructive editing models
|
||||
// NB: We're supporting these to keep the app simple and not over-complicate the javascript
|
||||
#[serde(rename = "flux_pro_kontext_max")]
|
||||
FluxProKontextMax,
|
||||
|
||||
#[serde(rename = "gemini_25_flash")]
|
||||
Gemini25Flash,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -184,8 +173,6 @@ 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 {
|
||||
@@ -199,7 +186,7 @@ pub async fn enqueue_image_inpaint_command(
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Err(error_to_tauri_response(err))
|
||||
Err(err.to_tauri_response())
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
@@ -229,10 +216,10 @@ pub async fn handle_request(
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
|
||||
let success_event= match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified())
|
||||
return Err(InternalImageInpaintError::NoModelSpecified)
|
||||
}
|
||||
Some(ImageInpaintModel::FluxDevJuggernaut) => {
|
||||
handle_flux_dev_juggernaut_inpaint(
|
||||
@@ -263,7 +250,7 @@ pub async fn handle_request(
|
||||
).await?
|
||||
}
|
||||
Some(ImageInpaintModel::FluxProKontextMax) => {
|
||||
// TODO: Redirect to contextual image edit handler.
|
||||
// Redirect to contextual image edit handler.
|
||||
handle_flux_pro_kontext_inpaint(
|
||||
request,
|
||||
app,
|
||||
@@ -277,21 +264,6 @@ pub async fn handle_request(
|
||||
sora_task_queue,
|
||||
).await?
|
||||
}
|
||||
Some(ImageInpaintModel::Gemini25Flash) => {
|
||||
// TODO: Redirect to contextual image edit handler.
|
||||
handle_gemini_25_flash_inpaint(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
}
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
@@ -310,60 +282,3 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
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;
|
||||
@@ -27,7 +28,7 @@ pub async fn handle_flux_dev_juggernaut_inpaint(
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
@@ -56,5 +57,5 @@ pub async fn handle_flux_dev_juggernaut_inpaint(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalImageInpaintError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
+22
-12
@@ -1,7 +1,8 @@
|
||||
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};
|
||||
@@ -25,6 +26,7 @@ use artcraft_api_defs::generate::image::inpaint::flux_dev_juggernaut_inpaint_ima
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
@@ -51,22 +53,30 @@ pub async fn handle_flux_dev_juggernaut_inpaint_artcraft(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
return Err(InternalImageInpaintError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
let image_media_token = match &request.image_media_token {
|
||||
Some(token) => token.clone(),
|
||||
None => {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
return Err(InternalImageInpaintError::NoSourceImageSpecified);
|
||||
},
|
||||
};
|
||||
|
||||
//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,
|
||||
@@ -84,11 +94,11 @@ pub async fn handle_flux_dev_juggernaut_inpaint_artcraft(
|
||||
Some(3) => Some(FluxDevJuggernautInpaintImageNumImages::Three),
|
||||
Some(4) => Some(FluxDevJuggernautInpaintImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
return Err(InternalImageInpaintError::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -115,7 +125,7 @@ pub async fn handle_flux_dev_juggernaut_inpaint_artcraft(
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft flux dev juggernaut inpaint: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalImageInpaintError::StorytellerError(err));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,10 +141,10 @@ async fn get_mask(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds: &StorytellerCredentialSet,
|
||||
) -> Result<MediaFileToken, GenerateError> {
|
||||
) -> Result<MediaFileToken, InternalImageInpaintError> {
|
||||
|
||||
if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() {
|
||||
return Err(GenerateError::both_image_mask_media_token_and_bytes_supplied());
|
||||
return Err(InternalImageInpaintError::MaskMediaTokenAndBytesSupplied);
|
||||
}
|
||||
|
||||
if let Some(token) = request.mask_image_media_token.as_ref() {
|
||||
@@ -142,12 +152,12 @@ async fn get_mask(
|
||||
};
|
||||
|
||||
let image_bytes = request.mask_image_raw_bytes.as_ref()
|
||||
.ok_or(GenerateError::required_source_image_mask_not_provided())?;
|
||||
.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);
|
||||
GenerateError::AnyhowError(anyhow!("Failed to convert image bytes to png mask"))
|
||||
InternalImageInpaintError::CouldNotEncodeMask
|
||||
})?;
|
||||
|
||||
info!("Uploading image media file from bytes...");
|
||||
@@ -161,7 +171,7 @@ async fn get_mask(
|
||||
}).await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upload image media file: {:?}", err);
|
||||
GenerateError::from(err)
|
||||
InternalImageInpaintError::StorytellerError(err)
|
||||
})?;
|
||||
|
||||
Ok(result.media_file_token)
|
||||
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
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;
|
||||
@@ -27,7 +28,7 @@ pub async fn handle_flux_pro_1_inpaint(
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
@@ -56,5 +57,5 @@ pub async fn handle_flux_pro_1_inpaint(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalImageInpaintError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
+22
-12
@@ -1,7 +1,8 @@
|
||||
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};
|
||||
@@ -25,6 +26,7 @@ use artcraft_api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{Flux
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
@@ -50,22 +52,30 @@ pub async fn handle_flux_pro_1_inpaint_artcraft(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
return Err(InternalImageInpaintError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
let image_media_token = match &request.image_media_token {
|
||||
Some(token) => token.clone(),
|
||||
None => {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
return Err(InternalImageInpaintError::NoSourceImageSpecified);
|
||||
},
|
||||
};
|
||||
|
||||
//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,
|
||||
@@ -83,11 +93,11 @@ pub async fn handle_flux_pro_1_inpaint_artcraft(
|
||||
Some(3) => Some(FluxPro1InpaintImageNumImages::Three),
|
||||
Some(4) => Some(FluxPro1InpaintImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
return Err(InternalImageInpaintError::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -114,7 +124,7 @@ pub async fn handle_flux_pro_1_inpaint_artcraft(
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft flux pro 1 inpaint: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalImageInpaintError::StorytellerError(err));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -130,10 +140,10 @@ async fn get_mask(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds: &StorytellerCredentialSet,
|
||||
) -> Result<MediaFileToken, GenerateError> {
|
||||
) -> Result<MediaFileToken, InternalImageInpaintError> {
|
||||
|
||||
if request.mask_image_media_token.is_some() && request.mask_image_raw_bytes.is_some() {
|
||||
return Err(GenerateError::both_image_mask_media_token_and_bytes_supplied());
|
||||
return Err(InternalImageInpaintError::MaskMediaTokenAndBytesSupplied);
|
||||
}
|
||||
|
||||
if let Some(token) = request.mask_image_media_token.as_ref() {
|
||||
@@ -141,12 +151,12 @@ async fn get_mask(
|
||||
};
|
||||
|
||||
let image_bytes = request.mask_image_raw_bytes.as_ref()
|
||||
.ok_or(GenerateError::required_source_image_mask_not_provided())?;
|
||||
.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);
|
||||
GenerateError::AnyhowError(anyhow!("could not encode image mask to png"))
|
||||
InternalImageInpaintError::CouldNotEncodeMask
|
||||
})?;
|
||||
|
||||
info!("Uploading image media file from bytes...");
|
||||
@@ -160,7 +170,7 @@ async fn get_mask(
|
||||
}).await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upload image media file: {:?}", err);
|
||||
GenerateError::from(err)
|
||||
InternalImageInpaintError::StorytellerError(err)
|
||||
})?;
|
||||
|
||||
Ok(result.media_file_token)
|
||||
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -25,7 +26,7 @@ pub async fn handle_flux_pro_kontext_inpaint(
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
@@ -54,5 +55,5 @@ pub async fn handle_flux_pro_kontext_inpaint(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalImageInpaintError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
+50
-7
@@ -1,7 +1,9 @@
|
||||
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};
|
||||
@@ -26,6 +28,7 @@ use artcraft_api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{Flux
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
@@ -52,19 +55,19 @@ pub async fn handle_flux_pro_kontext_inpaint_artcraft(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageInpaintError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
return Err(InternalImageInpaintError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
let image_media_token = match &request.image_media_token {
|
||||
Some(token) => token.clone(),
|
||||
None => {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
return Err(InternalImageInpaintError::NoSourceImageSpecified);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -95,11 +98,11 @@ pub async fn handle_flux_pro_kontext_inpaint_artcraft(
|
||||
Some(3) => Some(FluxProKontextMaxEditImageNumImages::Three),
|
||||
Some(4) => Some(FluxProKontextMaxEditImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
return Err(InternalImageInpaintError::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -127,7 +130,7 @@ pub async fn handle_flux_pro_kontext_inpaint_artcraft(
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft flux pro kontext inpaint: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalImageInpaintError::StorytellerError(err));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -138,3 +141,43 @@ 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)
|
||||
// }
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
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_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::image_inpaint::flux_pro_kontext_inpaint::handle_flux_pro_kontext_inpaint_artcraft::handle_flux_pro_kontext_inpaint_artcraft;
|
||||
use crate::core::commands::enqueue::image_inpaint::gemini_25_flash_inpaint::handle_gemini_25_flash_inpaint_artcraft::handle_gemini_25_flash_inpaint_artcraft;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use log::info;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_gemini_25_flash_inpaint(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
// TODO: Check if providers are available before proceeding.
|
||||
|
||||
info!("Providers by priority: {:?}", priority);
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Fal => {
|
||||
// Fallthrough (for now)
|
||||
}
|
||||
Provider::Sora => {
|
||||
// Fallthrough
|
||||
}
|
||||
Provider::Artcraft => {
|
||||
info!("Dispatching gemini 25 flash inpaint via Artcraft...");
|
||||
return handle_gemini_25_flash_inpaint_artcraft(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager
|
||||
).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
}
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
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_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::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::image::edit::flux_pro_kontext_max_edit_image::{FluxProKontextMaxEditImageNumImages, FluxProKontextMaxEditImageRequest};
|
||||
use artcraft_api_defs::generate::image::edit::gemini_25_flash_edit_image::{Gemini25FlashEditImageNumImages, Gemini25FlashEditImageRequest};
|
||||
use artcraft_api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::GenerateFluxPro11TextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
use images::mask_images::normalize_image_bytes_to_flux_mask::normalize_image_bytes_to_flux_mask;
|
||||
use log::{error, info};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::generate::image::edit::flux_pro_kontext_max_edit_image::flux_pro_kontext_max_edit_image;
|
||||
use storyteller_client::generate::image::edit::gemini_25_flash_edit_image::gemini_25_flash_edit_image;
|
||||
use storyteller_client::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use storyteller_client::generate::image::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
|
||||
use storyteller_client::generate::image::inpaint::flux_pro_1_inpaint_image::flux_pro_1_inpaint_image;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::media_files::upload_image_media_file_from_bytes::{upload_image_media_file_from_bytes, ImageType, UploadImageBytesArgs};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn handle_gemini_25_flash_inpaint_artcraft(
|
||||
request: &EnqueueInpaintImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
let image_media_token = match &request.image_media_token {
|
||||
Some(token) => token.clone(),
|
||||
None => {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft gemini 2.5 flash inpaint ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(Gemini25FlashEditImageNumImages::One),
|
||||
Some(2) => Some(Gemini25FlashEditImageNumImages::Two),
|
||||
Some(3) => Some(Gemini25FlashEditImageNumImages::Three),
|
||||
Some(4) => Some(Gemini25FlashEditImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
let request = Gemini25FlashEditImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_tokens: Some(vec![image_media_token]),
|
||||
num_images,
|
||||
image_quality: None,
|
||||
};
|
||||
|
||||
let result = gemini_25_flash_edit_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft gemini 2.5 flash inpaint. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gemini 2.5 flash inpaint: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::Gemini25Flash),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageInpaintEdit,
|
||||
})
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
pub (super) mod handle_gemini_25_flash_inpaint;
|
||||
pub (super) mod handle_gemini_25_flash_inpaint_artcraft;
|
||||
@@ -1,5 +1,5 @@
|
||||
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 enqueue_image_inpaint_command;
|
||||
pub mod errors;
|
||||
+6
-9
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -97,30 +96,28 @@ 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 {
|
||||
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
|
||||
InternalObjectError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for object generation";
|
||||
}
|
||||
GenerateError::NoProviderAvailable => {
|
||||
InternalObjectError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for object generation";
|
||||
}
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => {
|
||||
InternalObjectError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials) => {
|
||||
InternalObjectError::NeedsStorytellerCredentials => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsStorytellerCredentials;
|
||||
error_message = "You need to be logged into Artcraft.";
|
||||
@@ -162,7 +159,7 @@ pub async fn handle_request(
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
|
||||
|
||||
let result = handle_object(
|
||||
request,
|
||||
|
||||
+3
-3
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -9,7 +10,6 @@ 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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
@@ -51,5 +51,5 @@ pub async fn handle_object(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalObjectError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
+6
-6
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
|
||||
|
||||
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(GenerateError::needs_storyteller_credentials());
|
||||
return Err(InternalObjectError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ pub async fn handle_object_artcraft(
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified());
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
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(GenerateError::from(err));
|
||||
return Err(InternalObjectError::StorytellerError(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(GenerateError::from(err));
|
||||
return Err(InternalObjectError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
|
||||
|
||||
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(GenerateError::needs_fal_api_key());
|
||||
return Err(InternalObjectError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ pub async fn handle_object_fal(
|
||||
&media_token
|
||||
).await?;
|
||||
} else {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
return Err(InternalObjectError::AnyhowError(anyhow!("No image media token 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(GenerateError::no_model_specified());
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0 |
|
||||
@@ -82,7 +82,7 @@ pub async fn handle_object_fal(
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
return Err(GenerateError::AnyhowError(anyhow!("Wrong model specified: {:?}", request.model)));
|
||||
return Err(InternalObjectError::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(GenerateError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
return Err(InternalObjectError::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(GenerateError::from(err));
|
||||
return Err(InternalObjectError::FalError(err));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
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,2 +1,3 @@
|
||||
pub mod enqueue_image_to_3d_object_command;
|
||||
pub mod internal_object_error;
|
||||
mod generic;
|
||||
+7
-10
@@ -1,6 +1,5 @@
|
||||
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;
|
||||
@@ -21,7 +20,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")]
|
||||
@@ -117,30 +116,28 @@ 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 {
|
||||
GenerateError::BadInput(BadInputReason::NoModelSpecified)=> {
|
||||
InternalVideoError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueImageToVideoErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for video generation";
|
||||
}
|
||||
GenerateError::NoProviderAvailable => {
|
||||
InternalVideoError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueImageToVideoErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for video generation";
|
||||
}
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => {
|
||||
InternalVideoError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageToVideoErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials) => {
|
||||
InternalVideoError::NeedsStorytellerCredentials => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageToVideoErrorType::NeedsStorytellerCredentials;
|
||||
error_message = "You need to be logged into Artcraft.";
|
||||
@@ -182,7 +179,7 @@ pub async fn handle_request(
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
|
||||
|
||||
let result = handle_video(
|
||||
request,
|
||||
|
||||
+3
-3
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
@@ -51,5 +51,5 @@ pub async fn handle_video(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalVideoError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
+9
-9
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
|
||||
|
||||
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(GenerateError::needs_storyteller_credentials());
|
||||
return Err(InternalVideoError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ pub async fn handle_video_artcraft(
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified());
|
||||
return Err(InternalVideoError::NoModelSpecified);
|
||||
}
|
||||
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(GenerateError::from(err));
|
||||
return Err(InternalVideoError::StorytellerError(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(GenerateError::from(err));
|
||||
return Err(InternalVideoError::StorytellerError(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(GenerateError::from(err));
|
||||
return Err(InternalVideoError::StorytellerError(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(GenerateError::from(err));
|
||||
return Err(InternalVideoError::StorytellerError(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(GenerateError::from(err));
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
|
||||
|
||||
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(GenerateError::needs_fal_api_key());
|
||||
return Err(InternalVideoError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ pub async fn handle_video_fal(
|
||||
&media_token
|
||||
).await?;
|
||||
} else {
|
||||
return Err(GenerateError::BadInput(BadInputReason::RequiredSourceImageNotProvided));
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("No image media token provided")));
|
||||
}
|
||||
|
||||
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(GenerateError::no_model_specified());
|
||||
return Err(InternalVideoError::NoModelSpecified);
|
||||
}
|
||||
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(GenerateError::AnyhowError(anyhow!("Unsupported model: {:?}", request.model)));
|
||||
return Err(InternalVideoError::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(GenerateError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
return Err(InternalVideoError::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(GenerateError::from(err));
|
||||
return Err(InternalVideoError::FalError(err));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
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,2 +1,3 @@
|
||||
pub mod enqueue_image_to_video_command;
|
||||
pub mod internal_video_error;
|
||||
mod generic;
|
||||
@@ -1,5 +1,3 @@
|
||||
pub mod common;
|
||||
pub mod generate_error;
|
||||
pub mod image_bg_removal;
|
||||
pub mod image_edit;
|
||||
pub mod image_inpaint;
|
||||
|
||||
@@ -64,7 +64,6 @@ impl TaskEnqueueSuccess{
|
||||
Some(GenerationModel::FluxPro11) => Some(TaskModelType::FluxPro11),
|
||||
Some(GenerationModel::FluxPro11Ultra) => Some(TaskModelType::FluxPro11Ultra),
|
||||
Some(GenerationModel::FluxProKontextMax) => Some(TaskModelType::FluxProKontextMax),
|
||||
Some(GenerationModel::Gemini25Flash) => Some(TaskModelType::Gemini25Flash),
|
||||
Some(GenerationModel::GptImage1) => Some(TaskModelType::GptImage1),
|
||||
Some(GenerationModel::Recraft3) => Some(TaskModelType::Recraft3),
|
||||
Some(GenerationModel::Kling21Pro) => Some(TaskModelType::Kling21Pro),
|
||||
|
||||
+9
-36
@@ -1,11 +1,9 @@
|
||||
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;
|
||||
@@ -33,9 +31,8 @@ use log::{error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
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")]
|
||||
@@ -53,9 +50,6 @@ pub enum ImageModel {
|
||||
#[serde(rename = "recraft_3")]
|
||||
Recraft3,
|
||||
|
||||
#[serde(rename = "gemini_25_flash")]
|
||||
Gemini25Flash,
|
||||
|
||||
// Generic Midjourney model, version unknown.
|
||||
#[serde(rename = "midjourney")]
|
||||
Midjourney,
|
||||
@@ -75,13 +69,6 @@ pub struct EnqueueTextToImageRequest {
|
||||
/// The number of images to generate.
|
||||
pub number_images: Option<u32>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Reference images (without semantics)
|
||||
/// The purpose varies on a model-by-model basis, but they
|
||||
/// are not semantically treated as "style reference",
|
||||
/// "character/object reference", etc.
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Name of the frontend caller.
|
||||
/// We'll use this to selectively trigger events.
|
||||
@@ -174,24 +161,22 @@ 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 {
|
||||
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
|
||||
InternalImageError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueTextToImageErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
GenerateError::NoProviderAvailable => {
|
||||
InternalImageError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueTextToImageErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for image generation";
|
||||
}
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => {
|
||||
InternalImageError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueTextToImageErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
@@ -236,7 +221,7 @@ pub async fn handle_request(
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let result = dispatch_request(
|
||||
&request,
|
||||
@@ -286,23 +271,11 @@ pub async fn dispatch_request(
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified));
|
||||
}
|
||||
Some(ImageModel::Gemini25Flash) => {
|
||||
return handle_gemini_25_flash(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await;
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(ImageModel::GptImage1) => {
|
||||
return handle_gpt_image_1(
|
||||
@@ -346,5 +319,5 @@ pub async fn dispatch_request(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalImageError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
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::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::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_gemini_25_flash(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
// TODO: Check if providers are available before proceeding.
|
||||
|
||||
info!("Providers by priority: {:?}", priority);
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Fal => {
|
||||
// Fallthrough (for now)
|
||||
}
|
||||
Provider::Sora => {
|
||||
// Fallthrough
|
||||
}
|
||||
Provider::Artcraft => {
|
||||
info!("Dispatching gemini 2.5 flash via Artcraft...");
|
||||
return handle_gemini_25_flash_artcraft(
|
||||
request,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager
|
||||
).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
}
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
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::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_api_defs::generate::image::edit::gemini_25_flash_edit_image::{Gemini25FlashEditImageNumImages, Gemini25FlashEditImageRequest};
|
||||
use artcraft_api_defs::generate::image::generate_gpt_image_1_text_to_image::{GenerateGptImage1TextToImageImageQuality, GenerateGptImage1TextToImageImageSize, GenerateGptImage1TextToImageNumImages, GenerateGptImage1TextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use storyteller_client::generate::image::edit::gemini_25_flash_edit_image::gemini_25_flash_edit_image;
|
||||
use storyteller_client::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
use storyteller_client::generate::image::generate_gpt_image_1_text_to_image::generate_gpt_image_1_text_to_image;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_gemini_25_flash_artcraft(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials));
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft gemini 2.5 flash ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let num_images = match request.number_images{
|
||||
None => None,
|
||||
Some(1) => Some(Gemini25FlashEditImageNumImages::One),
|
||||
Some(2) => Some(Gemini25FlashEditImageNumImages::Two),
|
||||
Some(3) => Some(Gemini25FlashEditImageNumImages::Three),
|
||||
Some(4) => Some(Gemini25FlashEditImageNumImages::Four),
|
||||
_ => Some(Gemini25FlashEditImageNumImages::One), // Default to one image if invalid number
|
||||
};
|
||||
|
||||
let request = Gemini25FlashEditImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt.clone(),
|
||||
image_media_tokens: request.image_media_tokens.clone(),
|
||||
num_images,
|
||||
image_quality: None,
|
||||
};
|
||||
|
||||
let result = gemini_25_flash_edit_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft gemini 2.5 flash. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gemini 2.5 flash: {:?}", err);
|
||||
return Err(GenerateError::ProviderFailure(ProviderFailureReason::StorytellerError(err)));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::GptImage1),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
})
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub (crate) mod handle_gemini_25_flash_artcraft;
|
||||
pub (super) mod handle_gemini_25_flash;
|
||||
+10
-11
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
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(GenerateError::needs_storyteller_credentials());
|
||||
return Err(InternalImageError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -54,17 +54,16 @@ pub async fn handle_image_artcraft(
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified));
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
ImageModel::Gemini25Flash |
|
||||
ImageModel::GptImage1 |
|
||||
ImageModel::Midjourney
|
||||
) => {
|
||||
return Err(GenerateError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
|
||||
}
|
||||
Some(ImageModel::Recraft3) => {
|
||||
return Err(GenerateError::NotYetImplemented(format!("not yet implemented in Artcraft")));
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented in Artcraft")));
|
||||
}
|
||||
Some(ImageModel::Flux1Dev) => {
|
||||
info!("enqueue Flux 1 Dev");
|
||||
@@ -101,7 +100,7 @@ pub async fn handle_image_artcraft(
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux 1 Dev text to image generation: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +139,7 @@ pub async fn handle_image_artcraft(
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux 1 Schnell text to image generation: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +178,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(GenerateError::from(err));
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,7 +217,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(GenerateError::from(err));
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-11
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
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(GenerateError::needs_fal_api_key());
|
||||
return Err(InternalImageError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
let api_key = fal_creds_manager.get_key_required()
|
||||
.map_err(|err| {
|
||||
error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
GenerateError::needs_fal_api_key()
|
||||
InternalImageError::NeedsFalApiKey
|
||||
})?;
|
||||
|
||||
let prompt = request.prompt.as_deref().unwrap_or("");
|
||||
@@ -48,21 +48,20 @@ pub async fn handle_image_fal(
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::NoModelSpecified));
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
ImageModel::GptImage1 |
|
||||
ImageModel::Midjourney
|
||||
) => {
|
||||
return Err(GenerateError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: another branch should handle this: {:?}", request.model)));
|
||||
}
|
||||
Some(
|
||||
ImageModel::Gemini25Flash |
|
||||
ImageModel::Flux1Dev |
|
||||
ImageModel::Flux1Dev |
|
||||
ImageModel::Flux1Schnell |
|
||||
ImageModel::FluxPro11
|
||||
) => {
|
||||
return Err(GenerateError::NotYetImplemented(format!("not yet implemented: {:?}", request.model)));
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented: {:?}", request.model)));
|
||||
}
|
||||
Some(ImageModel::FluxPro11Ultra) => {
|
||||
model = GenerationModel::FluxPro11Ultra;
|
||||
@@ -98,7 +97,7 @@ pub async fn handle_image_fal(
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(GenerateError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
|
||||
enqueued
|
||||
@@ -117,7 +116,7 @@ pub async fn handle_image_fal(
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalImageError::FalError(err));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+4
-3
@@ -1,10 +1,11 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
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;
|
||||
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;
|
||||
@@ -33,7 +34,7 @@ pub async fn handle_gpt_image_1(
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
@@ -69,5 +70,5 @@ pub async fn handle_gpt_image_1(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
Err(InternalImageError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,7 +1,8 @@
|
||||
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;
|
||||
@@ -29,12 +30,12 @@ pub async fn handle_gpt_image_1_artcraft(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(GenerateError::needs_storyteller_credentials());
|
||||
return Err(InternalImageError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -100,7 +101,7 @@ pub async fn handle_gpt_image_1_artcraft(
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+5
-5
@@ -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, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let mut creds = match sora_creds_manager.get_credentials() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
error!("Sora credentials not found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
return Err(InternalImageError::NeedsSoraCredentials);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Sora credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
return Err(InternalImageError::NeedsSoraCredentials);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,7 +96,7 @@ pub async fn handle_gpt_image_1_sora(
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalImageError::SoraError(err));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-10
@@ -1,7 +1,8 @@
|
||||
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;
|
||||
@@ -40,19 +41,19 @@ pub async fn handle_midjourney(
|
||||
request: &EnqueueTextToImageRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let creds = match mj_creds_manager.maybe_copy_cookie_store() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
error!("Midjourney credentials not found.");
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app);
|
||||
return Err(GenerateError::needs_midjourney_credentials());
|
||||
return Err(InternalImageError::NeedsMidjourneyCredentials);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Midjourney credentials: {:?}", err);
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Midjourney, &app);
|
||||
return Err(GenerateError::needs_midjourney_credentials());
|
||||
return Err(InternalImageError::NeedsMidjourneyCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -61,11 +62,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(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo));
|
||||
return Err(InternalImageError::NeedsMidjourneyUserInfo);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error reading Midjourney user info: {:?}", err);
|
||||
return Err(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserInfo));
|
||||
return Err(InternalImageError::NeedsMidjourneyUserInfo);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -73,7 +74,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(GenerateError::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyUserId));
|
||||
return Err(InternalImageError::NeedsMidjourneyUserId);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,7 +97,7 @@ pub async fn handle_midjourney(
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
error!("Failed to use MidJourney: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
return Err(InternalImageError::MidjourneyError(err));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -121,7 +122,7 @@ pub async fn handle_midjourney(
|
||||
fn handle_midjourney_errors(
|
||||
app: &AppHandle,
|
||||
maybe_errors: Option<Vec<TextToImageError>>
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
if let Some(errors) = maybe_errors {
|
||||
if !errors.is_empty() {
|
||||
let messages: Vec<String> = errors.iter()
|
||||
@@ -140,5 +141,5 @@ fn handle_midjourney_errors(
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::ProviderFailure(ProviderFailureReason::MidjourneyJobEnqueueFailed))
|
||||
Err(InternalImageError::MidjourneyJobEnqueueFailed)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod enqueue_text_to_image_command;
|
||||
pub mod generic;
|
||||
mod gemini_25_flash;
|
||||
pub mod internal_image_error;
|
||||
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::{error, info};
|
||||
use log::info;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
@@ -49,11 +49,4 @@ 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,6 +1,5 @@
|
||||
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;
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
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;
|
||||
}
|
||||
+5
-1
@@ -1,6 +1,8 @@
|
||||
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;
|
||||
|
||||
@@ -14,7 +16,9 @@ pub struct ShowProviderLoginModalEvent {
|
||||
impl ShowProviderLoginModalEvent {
|
||||
pub fn send_for_provider(provider: GenerationProvider, app: &AppHandle) {
|
||||
let event = Self { provider };
|
||||
event.send_infallible(&app);
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to send ShowProviderLoginModalEvent: {:?}", err); // Fail open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,14 +24,10 @@ pub enum GenerationModel {
|
||||
FluxPro11,
|
||||
#[serde(rename = "flux_pro_1.1_ultra")]
|
||||
FluxPro11Ultra,
|
||||
|
||||
#[serde(rename = "flux_pro_kontext_max")]
|
||||
FluxProKontextMax,
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
#[serde(rename = "gemini_25_flash")]
|
||||
Gemini25Flash,
|
||||
|
||||
#[serde(rename = "hunyuan_3d_2_0")]
|
||||
Hunyuan3d2_0,
|
||||
#[serde(rename = "hunyuan_3d_2_1")]
|
||||
|
||||
@@ -32,6 +32,3 @@ pub const TALL_MOCHI_WITH_GLASSES_IMAGE_URL : &str = "https://cdn-2.fakeyou.com/
|
||||
|
||||
// media token: m_7x18h6qnyay4a42kyyyw2xctasx5k9
|
||||
pub const TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL : &str = "https://cdn-2.fakeyou.com/media/r/e/j/b/p/rejbp61m56hqx4a498xh7j47vkjd7jnh/upload_rejbp61m56hqx4a498xh7j47vkjd7jnh.png";
|
||||
|
||||
// media token: m_77cpdyaby60z0qv9g6tv67gw2a1337
|
||||
pub const TREX_SKELETON_IMAGE_URL : &str = "https://cdn-2.fakeyou.com/media/f/4/j/5/3/f4j53296sc35wqs3csgw4j4vvjf5yyxe/image_f4j53296sc35wqs3csgw4j4vvjf5yyxe.png";
|
||||
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const GEMINI_25_FLASH_EDIT_IMAGE_PATH: &str = "/v1/generate/image/edit/gemini_25_flash";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Gemini25FlashEditImageRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
// TODO: OneOf for image_urls or image_media_tokens
|
||||
/// Image media tokens to include in the editing context.
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<Gemini25FlashEditImageNumImages>,
|
||||
|
||||
/// Quality of the image to generate. Default is High.
|
||||
pub image_quality: Option<Gemini25FlashEditImageImageQuality>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Gemini25FlashEditImageNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Gemini25FlashEditImageImageQuality {
|
||||
Auto,
|
||||
Low,
|
||||
Medium,
|
||||
High, // Default
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Gemini25FlashEditImageResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
pub mod flux_pro_kontext_max_edit_image;
|
||||
pub mod gpt_image_1_edit_image;
|
||||
pub mod gemini_25_flash_edit_image;
|
||||
pub mod qwen_edit_image;
|
||||
pub mod seededit_3_edit_image;
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const QWEN_EDIT_IMAGE_PATH: &str = "/v1/generate/image/edit/qwen";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct QwenEditImageRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// The image we're editing.
|
||||
pub image_media_token: MediaFileToken,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub negative_prompt: Option<String>,
|
||||
|
||||
/// Acceleration level for image generation.
|
||||
/// Options: 'none', 'regular'. Higher acceleration increases speed.
|
||||
/// 'regular' balances speed and quality. Default value: "none"
|
||||
pub acceleration: Option<QwenEditImageAcceleration>,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<QwenEditImageNumImages>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub enum QwenEditImageAcceleration {
|
||||
None,
|
||||
Regular,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QwenEditImageNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct QwenEditImageResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const SEEDEDIT_3_EDIT_IMAGE_PATH: &str = "/v1/generate/image/edit/seededit_3";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct SeedEdit3EditImageRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// The image we're editing.
|
||||
pub image_media_token: MediaFileToken,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct SeedEdit3EditImageResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const GENERATE_SEEDANCE_1_0_PRO_IMAGE_TO_VIDEO_URL_PATH: &str = "/v1/generate/video/seedance_1.0_pro_image_to_video";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateSeedance10ProImageToVideoRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Source of the image file to convert to video.
|
||||
/// It must be an image.
|
||||
pub media_file_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional: aspect ratio of the generated video.
|
||||
pub resolution: Option<GenerateSeedance10ProResolution>,
|
||||
|
||||
/// Optional: aspect ratio of the generated video.
|
||||
pub duration: Option<GenerateSeedance10ProDuration>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateSeedance10ProResolution {
|
||||
FourEightyP,
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateSeedance10ProDuration {
|
||||
ThreeSeconds,
|
||||
FourSeconds,
|
||||
FiveSeconds,
|
||||
SixSeconds,
|
||||
SevenSeconds,
|
||||
EightSeconds,
|
||||
NineSeconds,
|
||||
TenSeconds,
|
||||
ElevenSeconds,
|
||||
TwelveSeconds,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateSeedance10ProImageToVideoResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const GENERATE_VEO_3_FAST_IMAGE_TO_VIDEO_URL_PATH: &str = "/v1/generate/video/veo_3_fast_image_to_video";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateVeo3FastImageToVideoRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Source of the image file to convert to video.
|
||||
/// It must be an image.
|
||||
pub media_file_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional: resolution of the generated video.
|
||||
pub resolution: Option<GenerateVeo3FastResolution>,
|
||||
|
||||
/// Optional: aspect ratio of the generated video.
|
||||
pub duration: Option<GenerateVeo3FastDuration>,
|
||||
|
||||
/// Optional: Generate an audio track along with the video
|
||||
pub generate_audio: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateVeo3FastResolution {
|
||||
SevenTwentyP, // 720p
|
||||
TenEightyP, // 1080p
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateVeo3FastDuration {
|
||||
EightSeconds,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateVeo3FastImageToVideoResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const GENERATE_VEO_3_IMAGE_TO_VIDEO_URL_PATH: &str = "/v1/generate/video/veo_3_image_to_video";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateVeo3ImageToVideoRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Source of the image file to convert to video.
|
||||
/// It must be an image.
|
||||
pub media_file_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional: aspect ratio of the generated video.
|
||||
pub aspect_ratio: Option<GenerateVeo3AspectRatio>,
|
||||
|
||||
/// Optional: resolution of the generated video.
|
||||
pub resolution: Option<GenerateVeo3Resolution>,
|
||||
|
||||
/// Optional: aspect ratio of the generated video.
|
||||
pub duration: Option<GenerateVeo3Duration>,
|
||||
|
||||
/// Optional: Generate an audio track along with the video
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
// /// Optional.
|
||||
// pub enhance_prompt: Option<bool>,
|
||||
// /// Optional. Whether to automatically attempt to fix prompts that fail
|
||||
// /// content policy or other validation checks by rewriting them
|
||||
// /// Default value: true
|
||||
// pub auto_fix: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateVeo3AspectRatio {
|
||||
WideSixteenNine, // 16:9
|
||||
TallNineSixteen, // 9:16
|
||||
Square, // 1:1
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateVeo3Resolution {
|
||||
SevenTwentyP, // 720p
|
||||
TenEightyP, // 1080p
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateVeo3Duration {
|
||||
EightSeconds,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GenerateVeo3ImageToVideoResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
@@ -2,7 +2,4 @@ pub mod generate_kling_1_6_pro_image_to_video;
|
||||
pub mod generate_kling_2_1_master_image_to_video;
|
||||
pub mod generate_kling_2_1_pro_image_to_video;
|
||||
pub mod generate_seedance_1_0_lite_image_to_video;
|
||||
pub mod generate_seedance_1_0_pro_image_to_video;
|
||||
pub mod generate_veo_2_image_to_video;
|
||||
pub mod generate_veo_3_fast_image_to_video;
|
||||
pub mod generate_veo_3_image_to_video;
|
||||
pub mod generate_veo_2_image_to_video;
|
||||
@@ -31,12 +31,6 @@ pub enum ModelType {
|
||||
GptImage1,
|
||||
#[serde(rename = "recraft_3")]
|
||||
Recraft3,
|
||||
#[serde(rename = "seededit_3")]
|
||||
SeedEdit3,
|
||||
#[serde(rename = "qwen")]
|
||||
Qwen,
|
||||
#[serde(rename = "gemini_25_flash")]
|
||||
Gemini25Flash,
|
||||
|
||||
/// Midjourney without distinguishing a model type or version
|
||||
#[serde(rename = "midjourney")]
|
||||
@@ -69,14 +63,8 @@ pub enum ModelType {
|
||||
Kling21Master,
|
||||
#[serde(rename = "seedance_1p0_lite")]
|
||||
Seedance10Lite,
|
||||
#[serde(rename = "seedance_1p0_pro")]
|
||||
Seedance10Pro,
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
#[serde(rename = "veo_3")]
|
||||
Veo3,
|
||||
#[serde(rename = "veo_3_fast")]
|
||||
Veo3Fast,
|
||||
|
||||
// 3D Object generation models
|
||||
#[serde(rename = "hunyuan_3d_2p0")]
|
||||
@@ -104,9 +92,6 @@ impl ModelType {
|
||||
Self::FluxProKontextMax => "flux_pro_kontext_max",
|
||||
Self::GptImage1 => "gpt_image_1",
|
||||
Self::Recraft3 => "recraft_3",
|
||||
Self::SeedEdit3 => "seededit_3",
|
||||
Self::Qwen => "qwen",
|
||||
Self::Gemini25Flash => "gemini_25_flash",
|
||||
Self::Midjourney => "midjourney",
|
||||
Self::MidjourneyV6 => "midjourney_v6",
|
||||
Self::MidjourneyV6p1 => "midjourney_v6p1",
|
||||
@@ -121,10 +106,7 @@ impl ModelType {
|
||||
Self::Kling21Pro => "kling_2p1_pro",
|
||||
Self::Kling21Master => "kling_2p1_master",
|
||||
Self::Seedance10Lite => "seedance_1p0_lite",
|
||||
Self::Seedance10Pro => "seedance_1p0_pro",
|
||||
Self::Veo2 => "veo_2",
|
||||
Self::Veo3 => "veo_3",
|
||||
Self::Veo3Fast => "veo_3_fast",
|
||||
|
||||
// 3D Object generation models
|
||||
Self::Hunyuan3d2_0 => "hunyuan_3d_2p0",
|
||||
@@ -144,9 +126,6 @@ impl ModelType {
|
||||
"flux_pro_kontext_max" => Ok(Self::FluxProKontextMax),
|
||||
"gpt_image_1" => Ok(Self::GptImage1),
|
||||
"recraft_3" => Ok(Self::Recraft3),
|
||||
"seededit_3" => Ok(Self::SeedEdit3),
|
||||
"qwen" => Ok(Self::Qwen),
|
||||
"gemini_25_flash" => Ok(Self::Gemini25Flash),
|
||||
"midjourney" => Ok(Self::Midjourney),
|
||||
"midjourney_v6" => Ok(Self::MidjourneyV6),
|
||||
"midjourney_v6p1" => Ok(Self::MidjourneyV6p1),
|
||||
@@ -161,10 +140,7 @@ impl ModelType {
|
||||
"kling_2p1_pro" => Ok(Self::Kling21Pro),
|
||||
"kling_2p1_master" => Ok(Self::Kling21Master),
|
||||
"seedance_1p0_lite" => Ok(Self::Seedance10Lite),
|
||||
"seedance_1p0_pro" => Ok(Self::Seedance10Pro),
|
||||
"veo_2" => Ok(Self::Veo2),
|
||||
"veo_3" => Ok(Self::Veo3),
|
||||
"veo_3_fast" => Ok(Self::Veo3Fast),
|
||||
|
||||
// 3D Object generation models
|
||||
"hunyuan_3d_2p0" => Ok(Self::Hunyuan3d2_0),
|
||||
@@ -188,9 +164,6 @@ impl ModelType {
|
||||
Self::FluxProKontextMax,
|
||||
Self::GptImage1,
|
||||
Self::Recraft3,
|
||||
Self::SeedEdit3,
|
||||
Self::Qwen,
|
||||
Self::Gemini25Flash,
|
||||
Self::Midjourney,
|
||||
Self::MidjourneyV6,
|
||||
Self::MidjourneyV6p1,
|
||||
@@ -205,10 +178,7 @@ impl ModelType {
|
||||
Self::Kling21Pro,
|
||||
Self::Kling21Master,
|
||||
Self::Seedance10Lite,
|
||||
Self::Seedance10Pro,
|
||||
Self::Veo2,
|
||||
Self::Veo3,
|
||||
Self::Veo3Fast,
|
||||
|
||||
// 3D Object generation models
|
||||
Self::Hunyuan3d2_0,
|
||||
@@ -237,9 +207,6 @@ mod tests {
|
||||
assert_serialization(ModelType::FluxProKontextMax, "flux_pro_kontext_max");
|
||||
assert_serialization(ModelType::GptImage1, "gpt_image_1");
|
||||
assert_serialization(ModelType::Recraft3, "recraft_3");
|
||||
assert_serialization(ModelType::SeedEdit3, "seededit_3");
|
||||
assert_serialization(ModelType::Qwen, "qwen");
|
||||
assert_serialization(ModelType::Gemini25Flash, "gemini_25_flash");
|
||||
assert_serialization(ModelType::Midjourney, "midjourney");
|
||||
assert_serialization(ModelType::MidjourneyV6, "midjourney_v6");
|
||||
assert_serialization(ModelType::MidjourneyV6p1, "midjourney_v6p1");
|
||||
@@ -253,10 +220,7 @@ mod tests {
|
||||
assert_serialization(ModelType::Kling21Pro, "kling_2p1_pro");
|
||||
assert_serialization(ModelType::Kling21Master, "kling_2p1_master");
|
||||
assert_serialization(ModelType::Seedance10Lite, "seedance_1p0_lite");
|
||||
assert_serialization(ModelType::Seedance10Pro, "seedance_1p0_pro");
|
||||
assert_serialization(ModelType::Veo2, "veo_2");
|
||||
assert_serialization(ModelType::Veo3, "veo_3");
|
||||
assert_serialization(ModelType::Veo3Fast, "veo_3_fast");
|
||||
// 3D Object generation models
|
||||
assert_serialization(ModelType::Hunyuan3d2_0, "hunyuan_3d_2p0");
|
||||
assert_serialization(ModelType::Hunyuan3d2_1, "hunyuan_3d_2p1");
|
||||
@@ -274,9 +238,6 @@ mod tests {
|
||||
assert_eq!(ModelType::FluxProKontextMax.to_str(), "flux_pro_kontext_max");
|
||||
assert_eq!(ModelType::GptImage1.to_str(), "gpt_image_1");
|
||||
assert_eq!(ModelType::Recraft3.to_str(), "recraft_3");
|
||||
assert_eq!(ModelType::SeedEdit3.to_str(), "seededit_3");
|
||||
assert_eq!(ModelType::Qwen.to_str(), "qwen");
|
||||
assert_eq!(ModelType::Gemini25Flash.to_str(), "gemini_25_flash");
|
||||
assert_eq!(ModelType::Midjourney.to_str(), "midjourney");
|
||||
assert_eq!(ModelType::MidjourneyV6.to_str(), "midjourney_v6");
|
||||
assert_eq!(ModelType::MidjourneyV6p1.to_str(), "midjourney_v6p1");
|
||||
@@ -291,10 +252,7 @@ mod tests {
|
||||
assert_eq!(ModelType::Kling21Pro.to_str(), "kling_2p1_pro");
|
||||
assert_eq!(ModelType::Kling21Master.to_str(), "kling_2p1_master");
|
||||
assert_eq!(ModelType::Seedance10Lite.to_str(), "seedance_1p0_lite");
|
||||
assert_eq!(ModelType::Seedance10Pro.to_str(), "seedance_1p0_pro");
|
||||
assert_eq!(ModelType::Veo2.to_str(), "veo_2");
|
||||
assert_eq!(ModelType::Veo3.to_str(), "veo_3");
|
||||
assert_eq!(ModelType::Veo3Fast.to_str(), "veo_3_fast");
|
||||
|
||||
// 3D Object generation models
|
||||
assert_eq!(ModelType::Hunyuan3d2_0.to_str(), "hunyuan_3d_2p0");
|
||||
@@ -313,9 +271,6 @@ mod tests {
|
||||
assert_eq!(ModelType::from_str("flux_pro_kontext_max").unwrap(), ModelType::FluxProKontextMax);
|
||||
assert_eq!(ModelType::from_str("gpt_image_1").unwrap(), ModelType::GptImage1);
|
||||
assert_eq!(ModelType::from_str("recraft_3").unwrap(), ModelType::Recraft3);
|
||||
assert_eq!(ModelType::from_str("seededit_3").unwrap(), ModelType::SeedEdit3);
|
||||
assert_eq!(ModelType::from_str("qwen").unwrap(), ModelType::Qwen);
|
||||
assert_eq!(ModelType::from_str("gemini_25_flash").unwrap(), ModelType::Gemini25Flash);
|
||||
assert_eq!(ModelType::from_str("midjourney").unwrap(), ModelType::Midjourney);
|
||||
assert_eq!(ModelType::from_str("midjourney_v6").unwrap(), ModelType::MidjourneyV6);
|
||||
assert_eq!(ModelType::from_str("midjourney_v6p1").unwrap(), ModelType::MidjourneyV6p1);
|
||||
@@ -329,10 +284,7 @@ mod tests {
|
||||
assert_eq!(ModelType::from_str("kling_2p1_pro").unwrap(), ModelType::Kling21Pro);
|
||||
assert_eq!(ModelType::from_str("kling_2p1_master").unwrap(), ModelType::Kling21Master);
|
||||
assert_eq!(ModelType::from_str("seedance_1p0_lite").unwrap(), ModelType::Seedance10Lite);
|
||||
assert_eq!(ModelType::from_str("seedance_1p0_pro").unwrap(), ModelType::Seedance10Pro);
|
||||
assert_eq!(ModelType::from_str("veo_2").unwrap(), ModelType::Veo2);
|
||||
assert_eq!(ModelType::from_str("veo_3").unwrap(), ModelType::Veo3);
|
||||
assert_eq!(ModelType::from_str("veo_3_fast").unwrap(), ModelType::Veo3Fast);
|
||||
// 3D Object generation models
|
||||
assert_eq!(ModelType::from_str("hunyuan_3d_2p0").unwrap(), ModelType::Hunyuan3d2_0);
|
||||
assert_eq!(ModelType::from_str("hunyuan_3d_2p1").unwrap(), ModelType::Hunyuan3d2_1);
|
||||
@@ -341,7 +293,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = ModelType::all_variants();
|
||||
assert_eq!(variants.len(), 30);
|
||||
assert_eq!(variants.len(), 24);
|
||||
// Image models
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Dev));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Flux1Schnell));
|
||||
@@ -352,9 +304,6 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::FluxProKontextMax));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::GptImage1));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Recraft3));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::SeedEdit3));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Qwen));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Gemini25Flash));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Midjourney));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::MidjourneyV6));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::MidjourneyV6p1));
|
||||
@@ -368,10 +317,7 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Kling21Pro));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Kling21Master));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Seedance10Lite));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Seedance10Pro));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Veo2));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Veo3));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Veo3Fast));
|
||||
// 3D Object generation models
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Hunyuan3d2_0));
|
||||
assert_eq!(variants.pop_first(), Some(ModelType::Hunyuan3d2_1));
|
||||
|
||||
@@ -26,8 +26,6 @@ pub enum TaskModelType {
|
||||
FluxPro11Ultra,
|
||||
#[serde(rename = "flux_pro_kontext_max")]
|
||||
FluxProKontextMax,
|
||||
#[serde(rename = "gemini_25_flash")]
|
||||
Gemini25Flash,
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
#[serde(rename = "recraft_3")]
|
||||
@@ -73,7 +71,6 @@ impl TaskModelType {
|
||||
Self::FluxPro11 => "flux_pro_1.1",
|
||||
Self::FluxPro11Ultra => "flux_pro_1.1_ultra",
|
||||
Self::FluxProKontextMax => "flux_pro_kontext_max",
|
||||
Self::Gemini25Flash => "gemini_25_flash",
|
||||
Self::GptImage1 => "gpt_image_1",
|
||||
Self::Recraft3 => "recraft_3",
|
||||
Self::Midjourney => "midjourney",
|
||||
@@ -99,7 +96,6 @@ impl TaskModelType {
|
||||
"flux_pro_1.1" => Ok(Self::FluxPro11),
|
||||
"flux_pro_1.1_ultra" => Ok(Self::FluxPro11Ultra),
|
||||
"flux_pro_kontext_max" => Ok(Self::FluxProKontextMax),
|
||||
"gemini_25_flash" => Ok(Self::Gemini25Flash),
|
||||
"gpt_image_1" => Ok(Self::GptImage1),
|
||||
"recraft_3" => Ok(Self::Recraft3),
|
||||
"midjourney" => Ok(Self::Midjourney),
|
||||
@@ -128,7 +124,6 @@ impl TaskModelType {
|
||||
Self::FluxPro11,
|
||||
Self::FluxPro11Ultra,
|
||||
Self::FluxProKontextMax,
|
||||
Self::Gemini25Flash,
|
||||
Self::GptImage1,
|
||||
Self::Recraft3,
|
||||
Self::Midjourney,
|
||||
@@ -163,7 +158,6 @@ mod tests {
|
||||
assert_serialization(TaskModelType::FluxPro11, "flux_pro_1.1");
|
||||
assert_serialization(TaskModelType::FluxPro11Ultra, "flux_pro_1.1_ultra");
|
||||
assert_serialization(TaskModelType::FluxProKontextMax, "flux_pro_kontext_max");
|
||||
assert_serialization(TaskModelType::Gemini25Flash, "gemini_25_flash");
|
||||
assert_serialization(TaskModelType::GptImage1, "gpt_image_1");
|
||||
assert_serialization(TaskModelType::Recraft3, "recraft_3");
|
||||
assert_serialization(TaskModelType::Midjourney, "midjourney");
|
||||
@@ -188,7 +182,6 @@ mod tests {
|
||||
assert_eq!(TaskModelType::FluxPro11.to_str(), "flux_pro_1.1");
|
||||
assert_eq!(TaskModelType::FluxPro11Ultra.to_str(), "flux_pro_1.1_ultra");
|
||||
assert_eq!(TaskModelType::FluxProKontextMax.to_str(), "flux_pro_kontext_max");
|
||||
assert_eq!(TaskModelType::Gemini25Flash.to_str(), "gemini_25_flash");
|
||||
assert_eq!(TaskModelType::GptImage1.to_str(), "gpt_image_1");
|
||||
assert_eq!(TaskModelType::Recraft3.to_str(), "recraft_3");
|
||||
assert_eq!(TaskModelType::Midjourney.to_str(), "midjourney");
|
||||
@@ -213,7 +206,6 @@ mod tests {
|
||||
assert_eq!(TaskModelType::from_str("flux_pro_1.1").unwrap(), TaskModelType::FluxPro11);
|
||||
assert_eq!(TaskModelType::from_str("flux_pro_1.1_ultra").unwrap(), TaskModelType::FluxPro11Ultra);
|
||||
assert_eq!(TaskModelType::from_str("flux_pro_kontext_max").unwrap(), TaskModelType::FluxProKontextMax);
|
||||
assert_eq!(TaskModelType::from_str("gemini_25_flash").unwrap(), TaskModelType::Gemini25Flash);
|
||||
assert_eq!(TaskModelType::from_str("gpt_image_1").unwrap(), TaskModelType::GptImage1);
|
||||
assert_eq!(TaskModelType::from_str("recraft_3").unwrap(), TaskModelType::Recraft3);
|
||||
assert_eq!(TaskModelType::from_str("midjourney").unwrap(), TaskModelType::Midjourney);
|
||||
@@ -231,7 +223,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = TaskModelType::all_variants();
|
||||
assert_eq!(variants.len(), 18);
|
||||
assert_eq!(variants.len(), 17);
|
||||
// Image models
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Flux1Dev));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Flux1Schnell));
|
||||
@@ -240,7 +232,6 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::FluxPro11));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::FluxPro11Ultra));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::FluxProKontextMax));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Gemini25Flash));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::GptImage1));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Recraft3));
|
||||
assert_eq!(variants.pop_first(), Some(TaskModelType::Midjourney));
|
||||
|
||||
@@ -58,11 +58,6 @@ 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")]
|
||||
@@ -91,7 +86,6 @@ 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",
|
||||
}
|
||||
@@ -107,7 +101,6 @@ 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)),
|
||||
@@ -126,7 +119,6 @@ impl TauriEventName {
|
||||
Self::TextToImageGenerationCompleteEvent,
|
||||
Self::ImageEditCompleteEvent,
|
||||
Self::RefreshAccountStateEvent,
|
||||
Self::ShowProviderBillingModalEvent,
|
||||
Self::ShowProviderLoginModalEvent,
|
||||
Self::FlashUserInputErrorEvent,
|
||||
])
|
||||
@@ -151,7 +143,6 @@ 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");
|
||||
}
|
||||
@@ -166,7 +157,6 @@ 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");
|
||||
}
|
||||
@@ -181,7 +171,6 @@ 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);
|
||||
}
|
||||
@@ -189,7 +178,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = TauriEventName::all_variants();
|
||||
assert_eq!(variants.len(), 11);
|
||||
assert_eq!(variants.len(), 10);
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueSuccessEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueFailureEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationCompleteEvent));
|
||||
@@ -198,7 +187,6 @@ 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);
|
||||
|
||||
-3
@@ -15,8 +15,6 @@ pub enum CommonWebError {
|
||||
NotAuthorized,
|
||||
/// Server error with no reason.
|
||||
ServerError,
|
||||
/// Payment required with no extra info
|
||||
PaymentRequired,
|
||||
}
|
||||
|
||||
impl Error for CommonWebError {}
|
||||
@@ -50,7 +48,6 @@ impl ResponseError for CommonWebError {
|
||||
CommonWebError::NotFound => StatusCode::NOT_FOUND,
|
||||
CommonWebError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
CommonWebError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
CommonWebError::PaymentRequired => StatusCode::PAYMENT_REQUIRED,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub mod payments_error_test;
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
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(())
|
||||
}
|
||||
-4
@@ -2,7 +2,6 @@ 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,9 +45,6 @@ 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?;
|
||||
|
||||
-262
@@ -1,262 +0,0 @@
|
||||
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 actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::image::edit::gemini_25_flash_edit_image::{Gemini25FlashEditImageNumImages, Gemini25FlashEditImageRequest, Gemini25FlashEditImageResponse};
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::model_type::ModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_gemini_25_flash_edit_webhook::{enqueue_gemini_25_flash_edit_webhook, Gemini25FlashEditArgs, Gemini25FlashEditNumImages};
|
||||
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::enqueue_gpt_image_1_edit_image_webhook;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::GptEditImageNumImages;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::{GptEditImageByokArgs, GptEditImageQuality, GptEditImageSize};
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{error, info, warn};
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::insert_generic_inference_job_for_fal_queue;
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::InsertGenericInferenceForFalArgs;
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
|
||||
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
|
||||
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
|
||||
use sqlx::Acquire;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Gemini 2.5 Flash Image Editing
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Generate Images (Edit)",
|
||||
path = "/v1/generate/image/edit/gemini_25_flash",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = Gemini25FlashEditImageResponse),
|
||||
),
|
||||
params(
|
||||
("request" = Gemini25FlashEditImageRequest, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn gemini_25_flash_edit_image_handler(
|
||||
http_request: HttpRequest,
|
||||
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?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
// TODO: Limit usage for new accounts. Billing, free credits metering, etc.
|
||||
|
||||
//let user_session = match maybe_user_session {
|
||||
// Some(session) => session,
|
||||
// None => {
|
||||
// warn!("not logged in");
|
||||
// return Err(CommonWebError::NotAuthorized);
|
||||
// }
|
||||
//};
|
||||
|
||||
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
|
||||
}
|
||||
|
||||
const CAN_SEE_DELETED: bool = false;
|
||||
|
||||
let tokens = match request.image_media_tokens.as_ref() {
|
||||
Some(tokens) => tokens,
|
||||
None => {
|
||||
warn!("No image media tokens provided");
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage("No image media tokens provided".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let result = batch_get_media_files_by_tokens_with_connection(
|
||||
&mut mysql_connection,
|
||||
tokens,
|
||||
CAN_SEE_DELETED,
|
||||
).await;
|
||||
|
||||
let media_files = match result {
|
||||
Ok(files) => files,
|
||||
Err(err) => {
|
||||
error!("Error getting media files by tokens: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
if media_files.len() != tokens.len() {
|
||||
warn!("Wrong number of media files returned for tokens: {} found for {} tokens", media_files.len(), tokens.len());
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
format!("Not all media files could be found. Media files found: {}, tokens provided: {}",
|
||||
media_files.len(), tokens.len())));
|
||||
}
|
||||
|
||||
let media_domain = get_media_domain(&http_request);
|
||||
|
||||
let image_urls = media_files.iter()
|
||||
.map(|file| {
|
||||
let public_bucket_path = MediaFileBucketPath::from_object_hash(
|
||||
&file.public_bucket_directory_hash,
|
||||
file.maybe_public_bucket_prefix.as_deref(),
|
||||
file.maybe_public_bucket_extension.as_deref());
|
||||
|
||||
let media_links = MediaLinksBuilder::from_media_path_and_env(
|
||||
media_domain,
|
||||
server_state.server_environment,
|
||||
&public_bucket_path);
|
||||
|
||||
media_links.cdn_url.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error inserting idempotency token: {:?}", err);
|
||||
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
|
||||
})?;
|
||||
|
||||
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
|
||||
|
||||
let num_images = match request.num_images {
|
||||
Some(Gemini25FlashEditImageNumImages::One) => Gemini25FlashEditNumImages::One,
|
||||
Some(Gemini25FlashEditImageNumImages::Two) => Gemini25FlashEditNumImages::Two,
|
||||
Some(Gemini25FlashEditImageNumImages::Three) => Gemini25FlashEditNumImages::Three,
|
||||
Some(Gemini25FlashEditImageNumImages::Four) => Gemini25FlashEditNumImages::Four,
|
||||
None => Gemini25FlashEditNumImages::One, // Default to One
|
||||
};
|
||||
|
||||
let args = Gemini25FlashEditArgs {
|
||||
image_urls,
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
num_images,
|
||||
};
|
||||
|
||||
let fal_result = enqueue_gemini_25_flash_edit_webhook(args)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error calling enqueue_gpt_image_1_edit_image_webhook: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let external_job_id = fal_result.request_id
|
||||
.ok_or_else(|| {
|
||||
warn!("Fal request_id is None");
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
info!("Fal request_id: {}", external_job_id);
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let mut transaction = mysql_connection
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
// NB: Don't fail the job if the query fails.
|
||||
let prompt_result = insert_prompt(InsertPromptArgs {
|
||||
maybe_apriori_prompt_token: None,
|
||||
prompt_type: PromptType::ArtcraftApp,
|
||||
maybe_creator_user_token: maybe_user_session
|
||||
.as_ref()
|
||||
.map(|s| &s.user_token),
|
||||
maybe_model_type: Some(ModelType::Gemini25Flash),
|
||||
maybe_generation_provider: Some(GenerationProvider::Artcraft),
|
||||
maybe_positive_prompt: request.prompt.as_deref(),
|
||||
maybe_negative_prompt: None,
|
||||
maybe_other_args: None,
|
||||
creator_ip_address: &ip_address,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let prompt_token = match prompt_result {
|
||||
Ok(token) => Some(token),
|
||||
Err(err) => {
|
||||
warn!("Error inserting prompt: {:?}", err);
|
||||
None // Don't fail the job if the prompt insertion fails.
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(token) = prompt_token.as_ref() {
|
||||
let result = insert_batch_prompt_context_items(InsertBatchArgs {
|
||||
prompt_token: token.clone(),
|
||||
items: media_files.iter().map(|media_file| {
|
||||
PromptContextItem {
|
||||
media_token: media_file.token.clone(),
|
||||
context_semantic_type: PromptContextSemanticType::Imgref,
|
||||
}
|
||||
}).collect(),
|
||||
transaction: &mut transaction,
|
||||
}).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
// NB: Fail open.
|
||||
warn!("Error inserting batch prompt context items: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
maybe_external_third_party_id: &external_job_id,
|
||||
fal_category: FalCategory::ImageGeneration,
|
||||
maybe_inference_args: None,
|
||||
maybe_prompt_token: prompt_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let job_token = match db_result {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
warn!("Error inserting generic inference job for FAL queue: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error committing MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
Ok(Json(Gemini25FlashEditImageResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
}))
|
||||
}
|
||||
+5
-9
@@ -2,7 +2,6 @@ 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;
|
||||
@@ -10,15 +9,14 @@ use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest, GptImage1EditImageResponse};
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::model_type::ModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::enqueue_gpt_image_1_edit_image_webhook;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::GptEditImageNumImages;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::{GptEditImageByokArgs, GptEditImageQuality, GptEditImageSize};
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::enqueue_gpt_image_1_edit_image_webhook;
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageNumImages;
|
||||
use fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::{GptEditImageByokArgs, GptEditImageQuality, GptEditImageSize};
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{error, info, warn};
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::insert_generic_inference_job_for_fal_queue;
|
||||
@@ -26,10 +24,11 @@ use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::InsertGenericInferenceForFalArgs;
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
|
||||
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
|
||||
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
|
||||
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{InsertBatchArgs, PromptContextItem, insert_batch_prompt_context_items};
|
||||
use sqlx::Acquire;
|
||||
use utoipa::ToSchema;
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
|
||||
/// Gpt Image 1 Image Editing
|
||||
#[utoipa::path(
|
||||
@@ -48,9 +47,6 @@ 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?;
|
||||
|
||||
+1
-4
@@ -1,5 +1,2 @@
|
||||
pub mod flux_pro_kontext_max_edit_image_handler;
|
||||
pub mod gemini_25_flash_edit_image_handler;
|
||||
pub mod gpt_image_1_edit_image_handler;
|
||||
pub mod qwen_edit_image_handler;
|
||||
pub mod seededit_3_edit_image_handler;
|
||||
pub mod gpt_image_1_edit_image_handler;
|
||||
-271
@@ -1,271 +0,0 @@
|
||||
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 actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::image::edit::qwen_edit_image::{QwenEditImageAcceleration, QwenEditImageNumImages, QwenEditImageRequest, QwenEditImageResponse};
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::model_type::ModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_qwen_image_edit_webhook::{enqueue_qwen_image_edit_webhook, QwenImageEditArgs, QwenImageEditNumImages};
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{error, info, warn};
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::insert_generic_inference_job_for_fal_queue;
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::InsertGenericInferenceForFalArgs;
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
|
||||
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
|
||||
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
|
||||
use sqlx::Acquire;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Qwen Image Editing
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Generate Images (Edit)",
|
||||
path = "/v1/generate/image/edit/qwen",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = QwenEditImageResponse),
|
||||
),
|
||||
params(
|
||||
("request" = QwenEditImageRequest, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn qwen_edit_image_handler(
|
||||
http_request: HttpRequest,
|
||||
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?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
// TODO: Limit usage for new accounts. Billing, free credits metering, etc.
|
||||
|
||||
//let user_session = match maybe_user_session {
|
||||
// Some(session) => session,
|
||||
// None => {
|
||||
// warn!("not logged in");
|
||||
// return Err(CommonWebError::NotAuthorized);
|
||||
// }
|
||||
//};
|
||||
|
||||
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
|
||||
}
|
||||
|
||||
const CAN_SEE_DELETED: bool = false;
|
||||
|
||||
// TODO: Maybe don't use the batch endpoint.
|
||||
let media_tokens = vec![
|
||||
request.image_media_token.clone(),
|
||||
];
|
||||
|
||||
let result = batch_get_media_files_by_tokens_with_connection(
|
||||
&mut mysql_connection,
|
||||
&media_tokens,
|
||||
CAN_SEE_DELETED,
|
||||
).await;
|
||||
|
||||
let result = batch_get_media_files_by_tokens_with_connection(
|
||||
&mut mysql_connection,
|
||||
&media_tokens,
|
||||
CAN_SEE_DELETED,
|
||||
).await;
|
||||
|
||||
let media_files = match result {
|
||||
Ok(files) => files,
|
||||
Err(err) => {
|
||||
error!("Error getting media files by tokens: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let image_media_file = match media_files.first() {
|
||||
Some(file) => file,
|
||||
None => {
|
||||
warn!("No media files found for tokens: {:?}", media_tokens);
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
"No media files found for provided tokens".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let media_domain = get_media_domain(&http_request);
|
||||
|
||||
let image_url = {
|
||||
let public_bucket_path = MediaFileBucketPath::from_object_hash(
|
||||
&image_media_file.public_bucket_directory_hash,
|
||||
image_media_file.maybe_public_bucket_prefix.as_deref(),
|
||||
image_media_file.maybe_public_bucket_extension.as_deref());
|
||||
|
||||
let media_links = MediaLinksBuilder::from_media_path_and_env(
|
||||
media_domain,
|
||||
server_state.server_environment,
|
||||
&public_bucket_path);
|
||||
|
||||
media_links.cdn_url.to_string()
|
||||
};
|
||||
|
||||
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error inserting idempotency token: {:?}", err);
|
||||
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
|
||||
})?;
|
||||
|
||||
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
|
||||
|
||||
let num_images = match request.num_images {
|
||||
Some(QwenEditImageNumImages::One) => QwenImageEditNumImages::One,
|
||||
Some(QwenEditImageNumImages::Two) => QwenImageEditNumImages::Two,
|
||||
Some(QwenEditImageNumImages::Three) => QwenImageEditNumImages::Three,
|
||||
Some(QwenEditImageNumImages::Four) => QwenImageEditNumImages::Four,
|
||||
None => QwenImageEditNumImages::One, // Default to One
|
||||
};
|
||||
|
||||
let acceleration = request.acceleration.as_ref().map(|accel| match accel {
|
||||
QwenEditImageAcceleration::None => "none",
|
||||
QwenEditImageAcceleration::Regular => "regular",
|
||||
QwenEditImageAcceleration::High => "high",
|
||||
});
|
||||
|
||||
let args = QwenImageEditArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
image_url: image_url,
|
||||
num_images: Some(num_images),
|
||||
image_size: None,
|
||||
negative_prompt: request.negative_prompt.clone(),
|
||||
acceleration: acceleration.map(|s| s.to_string()),
|
||||
};
|
||||
|
||||
let fal_result = enqueue_qwen_image_edit_webhook(args)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error calling enqueue_qwen_image_edit_webhook: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let external_job_id = fal_result.request_id
|
||||
.ok_or_else(|| {
|
||||
warn!("Fal request_id is None");
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
info!("Fal request_id: {}", external_job_id);
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let mut transaction = mysql_connection
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
// NB: Don't fail the job if the query fails.
|
||||
let prompt_result = insert_prompt(InsertPromptArgs {
|
||||
maybe_apriori_prompt_token: None,
|
||||
prompt_type: PromptType::ArtcraftApp,
|
||||
maybe_creator_user_token: maybe_user_session
|
||||
.as_ref()
|
||||
.map(|s| &s.user_token),
|
||||
maybe_model_type: Some(ModelType::Qwen),
|
||||
maybe_generation_provider: Some(GenerationProvider::Artcraft),
|
||||
maybe_positive_prompt: request.prompt.as_deref(),
|
||||
maybe_negative_prompt: None,
|
||||
maybe_other_args: None,
|
||||
creator_ip_address: &ip_address,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let prompt_token = match prompt_result {
|
||||
Ok(token) => Some(token),
|
||||
Err(err) => {
|
||||
warn!("Error inserting prompt: {:?}", err);
|
||||
None // Don't fail the job if the prompt insertion fails.
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(token) = prompt_token.as_ref() {
|
||||
let result = insert_batch_prompt_context_items(InsertBatchArgs {
|
||||
prompt_token: token.clone(),
|
||||
items: vec![
|
||||
PromptContextItem {
|
||||
media_token: image_media_file.token.clone(),
|
||||
context_semantic_type: PromptContextSemanticType::Imgsrc,
|
||||
}
|
||||
],
|
||||
transaction: &mut transaction,
|
||||
}).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
// NB: Fail open.
|
||||
warn!("Error inserting batch prompt context items: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
maybe_external_third_party_id: &external_job_id,
|
||||
fal_category: FalCategory::ImageGeneration,
|
||||
maybe_inference_args: None,
|
||||
maybe_prompt_token: prompt_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let job_token = match db_result {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
warn!("Error inserting generic inference job for FAL queue: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error committing MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
Ok(Json(QwenEditImageResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
}))
|
||||
}
|
||||
-253
@@ -1,253 +0,0 @@
|
||||
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 actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::generate::image::edit::seededit_3_edit_image::{SeedEdit3EditImageRequest, SeedEdit3EditImageResponse};
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
|
||||
use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::model_type::ModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
|
||||
use fal_client::requests::webhook::image::edit::enqueue_seededit_v3_edit_webhook::{enqueue_seededit_v3_edit_webhook, SeedEditV3EditArgs};
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{error, info, warn};
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::insert_generic_inference_job_for_fal_queue;
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
|
||||
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::InsertGenericInferenceForFalArgs;
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
|
||||
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
|
||||
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
|
||||
use sqlx::Acquire;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// SeedEdit v3 Image Editing
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Generate Images (Edit)",
|
||||
path = "/v1/generate/image/edit/SeedEdit3",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = SeedEdit3EditImageResponse),
|
||||
),
|
||||
params(
|
||||
("request" = SeedEdit3EditImageRequest, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn seededit_3_edit_image_handler(
|
||||
http_request: HttpRequest,
|
||||
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?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
// TODO: Limit usage for new accounts. Billing, free credits metering, etc.
|
||||
|
||||
//let user_session = match maybe_user_session {
|
||||
// Some(session) => session,
|
||||
// None => {
|
||||
// warn!("not logged in");
|
||||
// return Err(CommonWebError::NotAuthorized);
|
||||
// }
|
||||
//};
|
||||
|
||||
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
|
||||
}
|
||||
|
||||
const CAN_SEE_DELETED: bool = false;
|
||||
|
||||
// TODO: Maybe don't use the batch endpoint.
|
||||
let media_tokens = vec![
|
||||
request.image_media_token.clone(),
|
||||
];
|
||||
|
||||
let result = batch_get_media_files_by_tokens_with_connection(
|
||||
&mut mysql_connection,
|
||||
&media_tokens,
|
||||
CAN_SEE_DELETED,
|
||||
).await;
|
||||
|
||||
let result = batch_get_media_files_by_tokens_with_connection(
|
||||
&mut mysql_connection,
|
||||
&media_tokens,
|
||||
CAN_SEE_DELETED,
|
||||
).await;
|
||||
|
||||
let media_files = match result {
|
||||
Ok(files) => files,
|
||||
Err(err) => {
|
||||
error!("Error getting media files by tokens: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let image_media_file = match media_files.first() {
|
||||
Some(file) => file,
|
||||
None => {
|
||||
warn!("No media files found for tokens: {:?}", media_tokens);
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
"No media files found for provided tokens".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let media_domain = get_media_domain(&http_request);
|
||||
|
||||
let image_url = {
|
||||
let public_bucket_path = MediaFileBucketPath::from_object_hash(
|
||||
&image_media_file.public_bucket_directory_hash,
|
||||
image_media_file.maybe_public_bucket_prefix.as_deref(),
|
||||
image_media_file.maybe_public_bucket_extension.as_deref());
|
||||
|
||||
let media_links = MediaLinksBuilder::from_media_path_and_env(
|
||||
media_domain,
|
||||
server_state.server_environment,
|
||||
&public_bucket_path);
|
||||
|
||||
media_links.cdn_url.to_string()
|
||||
};
|
||||
|
||||
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error inserting idempotency token: {:?}", err);
|
||||
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
|
||||
})?;
|
||||
|
||||
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
|
||||
|
||||
let args = SeedEditV3EditArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
webhook_url: &server_state.fal.webhook_url,
|
||||
api_key: &server_state.fal.api_key,
|
||||
image_url: image_url,
|
||||
};
|
||||
|
||||
let fal_result = enqueue_seededit_v3_edit_webhook(args)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error calling enqueue_seededit_v3_image_edit_webhook: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let external_job_id = fal_result.request_id
|
||||
.ok_or_else(|| {
|
||||
warn!("Fal request_id is None");
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
info!("Fal request_id: {}", external_job_id);
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let mut transaction = mysql_connection
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
// NB: Don't fail the job if the query fails.
|
||||
let prompt_result = insert_prompt(InsertPromptArgs {
|
||||
maybe_apriori_prompt_token: None,
|
||||
prompt_type: PromptType::ArtcraftApp,
|
||||
maybe_creator_user_token: maybe_user_session
|
||||
.as_ref()
|
||||
.map(|s| &s.user_token),
|
||||
maybe_model_type: Some(ModelType::SeedEdit3),
|
||||
maybe_generation_provider: Some(GenerationProvider::Artcraft),
|
||||
maybe_positive_prompt: request.prompt.as_deref(),
|
||||
maybe_negative_prompt: None,
|
||||
maybe_other_args: None,
|
||||
creator_ip_address: &ip_address,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let prompt_token = match prompt_result {
|
||||
Ok(token) => Some(token),
|
||||
Err(err) => {
|
||||
warn!("Error inserting prompt: {:?}", err);
|
||||
None // Don't fail the job if the prompt insertion fails.
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(token) = prompt_token.as_ref() {
|
||||
let result = insert_batch_prompt_context_items(InsertBatchArgs {
|
||||
prompt_token: token.clone(),
|
||||
items: vec![
|
||||
PromptContextItem {
|
||||
media_token: image_media_file.token.clone(),
|
||||
context_semantic_type: PromptContextSemanticType::Imgsrc,
|
||||
}
|
||||
],
|
||||
transaction: &mut transaction,
|
||||
}).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
// NB: Fail open.
|
||||
warn!("Error inserting batch prompt context items: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
maybe_external_third_party_id: &external_job_id,
|
||||
fal_category: FalCategory::ImageGeneration,
|
||||
maybe_inference_args: None,
|
||||
maybe_prompt_token: prompt_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
}).await;
|
||||
|
||||
let job_token = match db_result {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
warn!("Error inserting generic inference job for FAL queue: {:?}", err);
|
||||
return Err(CommonWebError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
let _r = transaction
|
||||
.commit()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error committing MySQL transaction: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
Ok(Json(SeedEdit3EditImageResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
}))
|
||||
}
|
||||
-4
@@ -1,7 +1,6 @@
|
||||
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,9 +39,6 @@ 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?;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user