Compare commits

...
Author SHA1 Message Date
Brandon Thomas 49249d568f qwen 2026-02-03 00:44:43 -05:00
Brandon Thomas 6839f18cd2 flux 2026-02-03 00:28:31 -05:00
Brandon Thomas f20d62a4d2 flux ultra 2026-02-03 00:18:04 -05:00
Brandon Thomas dc5d13fe4f more endpoints 2026-02-02 23:53:25 -05:00
Brandon Thomas 1b6cf5590e image model pricing 2026-02-02 21:48:39 -05:00
26 changed files with 752 additions and 313 deletions
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::bytedance::seedream::bytedance_seedream_v4_edit_image::{bytedance_seedream_v4_edit_image, BytedanceSeedreamV4EditImageInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -53,6 +54,23 @@ pub enum EnqueueBytedanceSeedreamV4EditImageSize {
Auto4k,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV4EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.03 per image.
let unit_cost = 3;
let cost = match self.num_images {
None => unit_cost,
Some(EnqueueBytedanceSeedreamV4EditImageNumImages::One) => unit_cost,
Some(EnqueueBytedanceSeedreamV4EditImageNumImages::Two) => unit_cost * 2,
Some(EnqueueBytedanceSeedreamV4EditImageNumImages::Three) => unit_cost * 3,
Some(EnqueueBytedanceSeedreamV4EditImageNumImages::Four) => unit_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_bytedance_seedream_v4_edit_image_webhook<R: IntoUrl>(
args: EnqueueBytedanceSeedreamV4EditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::bytedance::seedream::bytedance_seedream_v4p5_edit_image::{bytedance_seedream_v4p5_edit_image, BytedanceSeedreamV4p5EditImageInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -52,6 +53,23 @@ pub enum EnqueueBytedanceSeedreamV4p5EditImageSize {
Auto4k,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV4p5EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.04 per image.
let unit_cost = 4;
let cost = match self.num_images {
None => unit_cost,
Some(EnqueueBytedanceSeedreamV4p5EditImageNumImages::One) => unit_cost,
Some(EnqueueBytedanceSeedreamV4p5EditImageNumImages::Two) => unit_cost * 2,
Some(EnqueueBytedanceSeedreamV4p5EditImageNumImages::Three) => unit_cost * 3,
Some(EnqueueBytedanceSeedreamV4p5EditImageNumImages::Four) => unit_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_bytedance_seedream_v4p5_edit_image_webhook<R: IntoUrl>(
args: EnqueueBytedanceSeedreamV4p5EditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
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;
@@ -49,6 +50,20 @@ pub enum Gemini25FlashEditAspectRatio {
NineBySixteen, // NB: No NineByTwentyOne ?
}
impl <U: IntoUrl> FalRequestCostCalculator for Gemini25FlashEditArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.039 per image. For $1.00, you can run this model 25 times.
match self.num_images {
Gemini25FlashEditNumImages::One => 4,
Gemini25FlashEditNumImages::Two => 8,
Gemini25FlashEditNumImages::Three => 12,
Gemini25FlashEditNumImages::Four => 16,
}
}
}
pub async fn enqueue_gemini_25_flash_edit_webhook<R: IntoUrl>(
args: Gemini25FlashEditArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -2,6 +2,7 @@ use crate::creds::fal_api_key::FalApiKey;
use crate::creds::open_ai_api_key::OpenAiApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::gpt_image_1::edit_image::byok::{gpt_edit_image, GptEditImageRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -44,6 +45,31 @@ pub enum GptEditImageNumImages{
Four,
}
// NB: These are BYOK, so they're not Fal's prices
impl <U: IntoUrl> FalRequestCostCalculator for GptEditImageByokArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Can't find details, so using this: https://www.reddit.com/r/OpenAI/comments/1krfwa1/pricing_gpt_image_1_model/
// Prompts are billed similarly to other GPT models. Image outputs cost approximately $0.01 (low), $0.04 (medium),
// and $0.17 (high) for square images.
// We're likely losing money on this, but that's okay. Will adjust in the future to be fair to users and us.
let base_cost = match self.quality {
GptEditImageQuality::Auto => 17,
GptEditImageQuality::Low => 1,
GptEditImageQuality::Medium => 4,
GptEditImageQuality::High => 17,
};
let cost = match self.num_images {
GptEditImageNumImages::One => base_cost,
GptEditImageNumImages::Two => base_cost * 2,
GptEditImageNumImages::Three => base_cost * 3,
GptEditImageNumImages::Four => base_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_gpt_image_1_edit_image_webhook<V: IntoUrl>(
args: GptEditImageByokArgs<'_, V>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::gpt_image::gpt_image_1p5_image_edit::{gpt_image_1p5_image_edit, GptImage1p5ImageEditInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -69,6 +70,34 @@ pub enum EnqueueGptImage1p5EditImageOutputFormat {
Webp,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueGptImage1p5EditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost different amounts based on the number of images, quality, and size.
// For low quality, you will be charged $0.009 for 1024x1024 or $0.013 for any other size per image.
// For medium quality, you will be charged $0.034 for 1024x1024, $0.051 for 1024x1536 and $0.050 for 1536x1024 per image.
// For high quality, you will be charged $0.133 for 1024x1024, $0.200 for 1024x1536 or $0.199 for 1536x1024 per image.
let use_quality = self.quality.unwrap_or(EnqueueGptImage1p5EditImageQuality::Medium);
let use_size = self.image_size.unwrap_or(EnqueueGptImage1p5EditImageSize::Square);
let base_cost = match (use_quality, use_size) {
(EnqueueGptImage1p5EditImageQuality::Low, EnqueueGptImage1p5EditImageSize::Square) => 1,
(EnqueueGptImage1p5EditImageQuality::Low, _) => 1,
(EnqueueGptImage1p5EditImageQuality::Medium, EnqueueGptImage1p5EditImageSize::Square) => 3,
(EnqueueGptImage1p5EditImageQuality::Medium, _) => 5,
(EnqueueGptImage1p5EditImageQuality::High, EnqueueGptImage1p5EditImageSize::Square) => 13,
(EnqueueGptImage1p5EditImageQuality::High, _) => 20,
};
let cost = match self.num_images {
EnqueueGptImage1p5EditImageNumImages::One => base_cost,
EnqueueGptImage1p5EditImageNumImages::Two => base_cost * 2,
EnqueueGptImage1p5EditImageNumImages::Three => base_cost * 3,
EnqueueGptImage1p5EditImageNumImages::Four => base_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_gpt_image_1p5_image_edit_webhook<R: IntoUrl>(
args: EnqueueGptImage1p5EditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::nano_banana_pro::nano_banana_pro_image_edit::{nano_banana_pro_image_edit, NanoBananaProImageEditInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -56,6 +57,30 @@ pub enum EnqueueNanoBananaProEditImageAspectRatio {
NineBySixteen, // NB: No NineByTwentyOne ?
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueNanoBananaProEditImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.15 per image.
// For $1.00, you can run this model 7 times.
// 4K outputs will be charged at double the standard rate.
// If web search is used, an additional $0.015 will be charged.
// Note: Pricing may change in the future.
let cost = match self.resolution {
None => 15,
Some(EnqueueNanoBananaProEditImageResolution::OneK) => 15,
Some(EnqueueNanoBananaProEditImageResolution::TwoK) => 15,
Some(EnqueueNanoBananaProEditImageResolution::FourK) => 30,
};
let cost = match self.num_images {
EnqueueNanoBananaProEditImageNumImages::One => cost,
EnqueueNanoBananaProEditImageNumImages::Two => cost * 2,
EnqueueNanoBananaProEditImageNumImages::Three => cost * 3,
EnqueueNanoBananaProEditImageNumImages::Four => cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_nano_banana_pro_image_edit_webhook<R: IntoUrl>(
args: EnqueueNanoBananaProEditImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::qwen_image_edit::{qwen_image_edit, QwenImageEditInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -45,6 +46,23 @@ pub enum QwenImageEditSize {
//Custom { width: u32, height: u32 }, // TODO
}
impl <U: IntoUrl, R: IntoUrl> FalRequestCostCalculator for QwenImageEditArgs<'_, U, R> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.03 per megapixel. -- Ugh, not calculating that
let base_cost = 3;
let cost = match self.num_images {
None => base_cost,
Some(QwenImageEditNumImages::One) => base_cost,
Some(QwenImageEditNumImages::Two) => base_cost * 2,
Some(QwenImageEditNumImages::Three) => base_cost * 3,
Some(QwenImageEditNumImages::Four) => base_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_qwen_image_edit_webhook<U: IntoUrl, R: IntoUrl>(
args: QwenImageEditArgs<'_, U, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::flux_pro::v1_1::{v1_1, FluxProPlusTextToImageInput, ImageSizeProperty};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -33,6 +34,22 @@ pub enum FluxPro11NumImages {
Four,
}
impl <U: IntoUrl> FalRequestCostCalculator for FluxPro11Args<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.04 per megapixel. -- NB: What? Not going to calculate that.
let base_cost = 6;
let cost = match self.num_images {
FluxPro11NumImages::One => base_cost,
FluxPro11NumImages::Two => base_cost * 2,
FluxPro11NumImages::Three => base_cost * 3,
FluxPro11NumImages::Four => base_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_flux_pro_11_text_to_image_webhook<U: IntoUrl>(
args: FluxPro11Args<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::{v1_1_ultra, AspectRatioProperty, FluxProUltraTextToImageInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -35,6 +36,22 @@ pub enum FluxPro11UltraNumImages {
Four,
}
impl <U: IntoUrl> FalRequestCostCalculator for FluxPro11UltraArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.06 per image.
let base_cost = 6;
let cost = match self.num_images {
FluxPro11UltraNumImages::One => base_cost,
FluxPro11UltraNumImages::Two => base_cost * 2,
FluxPro11UltraNumImages::Three => base_cost * 3,
FluxPro11UltraNumImages::Four => base_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_flux_pro_11_ultra_text_to_image_webhook<U: IntoUrl>(
args: FluxPro11UltraArgs<'_, U>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -2,6 +2,7 @@ use crate::creds::fal_api_key::FalApiKey;
use crate::creds::open_ai_api_key::OpenAiApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::prelude::fal_ai::gpt_image_1::edit_image::byok::{gpt_text_to_image, GptTextToImageRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -43,6 +44,31 @@ pub enum GptTextToImageNumImages{
Four,
}
// NB: These are BYOK, so they're not Fal's prices
impl <U: IntoUrl> FalRequestCostCalculator for GptTextToImageByokArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Can't find details, so using this: https://www.reddit.com/r/OpenAI/comments/1krfwa1/pricing_gpt_image_1_model/
// Prompts are billed similarly to other GPT models. Image outputs cost approximately $0.01 (low), $0.04 (medium),
// and $0.17 (high) for square images.
// We're likely losing money on this, but that's okay. Will adjust in the future to be fair to users and us.
let base_cost = match self.quality {
GptTextToImageQuality::Auto => 17,
GptTextToImageQuality::Low => 1,
GptTextToImageQuality::Medium => 4,
GptTextToImageQuality::High => 17,
};
let cost = match self.num_images {
GptTextToImageNumImages::One => base_cost,
GptTextToImageNumImages::Two => base_cost * 2,
GptTextToImageNumImages::Three => base_cost * 3,
GptTextToImageNumImages::Four => base_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_gpt_image_1_text_to_image_webhook<V: IntoUrl>(
args: GptTextToImageByokArgs<'_, V>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::bytedance::seedream::bytedance_seedream_v4_text_to_image::BytedanceSeedreamV4TextToImageInput;
use fal::prelude::fal_ai::bytedance::seedream::bytedance_seedream_v4_text_to_image::bytedance_seedream_v4_text_to_image;
use fal::webhook::WebhookResponse;
@@ -53,6 +54,24 @@ pub enum EnqueueBytedanceSeedreamV4TextToImageSize {
Auto4k,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV4TextToImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Copied from edit image version
// Your request will cost $0.03 per image.
let unit_cost = 3;
let cost = match self.num_images {
None => unit_cost,
Some(EnqueueBytedanceSeedreamV4TextToImageNumImages::One) => unit_cost,
Some(EnqueueBytedanceSeedreamV4TextToImageNumImages::Two) => unit_cost * 2,
Some(EnqueueBytedanceSeedreamV4TextToImageNumImages::Three) => unit_cost * 3,
Some(EnqueueBytedanceSeedreamV4TextToImageNumImages::Four) => unit_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_bytedance_seedream_v4_text_to_image_webhook<R: IntoUrl>(
args: EnqueueBytedanceSeedreamV4TextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -4,6 +4,8 @@ use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::bytedance::seedream::bytedance_seedream_v4p5_text_to_image::{bytedance_seedream_v4p5_text_to_image, BytedanceSeedreamV4p5TextToImageInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use crate::requests::webhook::image::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{EnqueueBytedanceSeedreamV4p5EditImageArgs, EnqueueBytedanceSeedreamV4p5EditImageNumImages};
pub struct EnqueueBytedanceSeedreamV4p5TextToImageArgs<'a, R: IntoUrl> {
// Request required
@@ -51,6 +53,24 @@ pub enum EnqueueBytedanceSeedreamV4p5TextToImageSize {
Auto4k,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueBytedanceSeedreamV4p5TextToImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// NB: Copied from edit image costs
// Your request will cost $0.04 per image.
let unit_cost = 4;
let cost = match self.num_images {
None => unit_cost,
Some(EnqueueBytedanceSeedreamV4p5TextToImageNumImages::One) => unit_cost,
Some(EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Two) => unit_cost * 2,
Some(EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Three) => unit_cost * 3,
Some(EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Four) => unit_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_bytedance_seedream_v4p5_text_to_image_webhook<R: IntoUrl>(
args: EnqueueBytedanceSeedreamV4p5TextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -5,6 +5,8 @@ use fal::prelude::fal_ai::gemini_25_flash_image::text_to_image::gemini_25_flash_
use fal::prelude::fal_ai::gemini_25_flash_image::text_to_image::Gemini25FlashTextToImageInput;
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use crate::requests::webhook::image::edit::enqueue_gemini_25_flash_edit_webhook::{Gemini25FlashEditArgs, Gemini25FlashEditNumImages};
pub struct Gemini25FlashTextToImageArgs<'a, R: IntoUrl> {
// Request required
@@ -46,6 +48,21 @@ pub enum Gemini25FlashTextToImageAspectRatio {
NineBySixteen, // NB: No NineByTwentyOne ?
}
impl <U: IntoUrl> FalRequestCostCalculator for Gemini25FlashTextToImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// NB: This was copied from the image-to-image case.
// Your request will cost $0.039 per image. For $1.00, you can run this model 25 times.
match self.num_images {
Gemini25FlashTextToImageNumImages::One => 4,
Gemini25FlashTextToImageNumImages::Two => 8,
Gemini25FlashTextToImageNumImages::Three => 12,
Gemini25FlashTextToImageNumImages::Four => 16,
}
}
}
pub async fn enqueue_gemini_25_flash_text_to_image_webhook<R: IntoUrl>(
args: Gemini25FlashTextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::gpt_image::gpt_image_1p5_text_to_image::{gpt_image_1p5_text_to_image, GptImage1p5TextToImageInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -61,6 +62,34 @@ pub enum EnqueueGptImage1p5TextToImageOutputFormat {
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueGptImage1p5TextToImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// NB: copied from edit image case - should be correct, but not verified.
// Your request will cost different amounts based on the number of images, quality, and size.
// For low quality, you will be charged $0.009 for 1024x1024 or $0.013 for any other size per image.
// For medium quality, you will be charged $0.034 for 1024x1024, $0.051 for 1024x1536 and $0.050 for 1536x1024 per image.
// For high quality, you will be charged $0.133 for 1024x1024, $0.200 for 1024x1536 or $0.199 for 1536x1024 per image.
let use_quality = self.quality.unwrap_or(EnqueueGptImage1p5TextToImageQuality::Medium);
let use_size = self.image_size.unwrap_or(EnqueueGptImage1p5TextToImageSize::Square);
let base_cost = match (use_quality, use_size) {
(EnqueueGptImage1p5TextToImageQuality::Low, EnqueueGptImage1p5TextToImageSize::Square) => 1,
(EnqueueGptImage1p5TextToImageQuality::Low, _) => 1,
(EnqueueGptImage1p5TextToImageQuality::Medium, EnqueueGptImage1p5TextToImageSize::Square) => 3,
(EnqueueGptImage1p5TextToImageQuality::Medium, _) => 5,
(EnqueueGptImage1p5TextToImageQuality::High, EnqueueGptImage1p5TextToImageSize::Square) => 13,
(EnqueueGptImage1p5TextToImageQuality::High, _) => 20,
};
let cost = match self.num_images {
EnqueueGptImage1p5TextToImageNumImages::One => base_cost,
EnqueueGptImage1p5TextToImageNumImages::Two => base_cost * 2,
EnqueueGptImage1p5TextToImageNumImages::Three => base_cost * 3,
EnqueueGptImage1p5TextToImageNumImages::Four => base_cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_gpt_image_1p5_text_to_image_webhook<R: IntoUrl>(
args: EnqueueGptImage1p5TextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -4,6 +4,8 @@ use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::nano_banana_pro::nano_banana_pro_text_to_image::{nano_banana_pro_text_to_image, NanoBananaProTextToImageInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use crate::requests::webhook::image::edit::enqueue_nano_banana_pro_edit_image_webhook::{EnqueueNanoBananaProEditImageArgs, EnqueueNanoBananaProEditImageNumImages, EnqueueNanoBananaProEditImageResolution};
pub struct EnqueueNanoBananaProTextToImageArgs<'a, R: IntoUrl> {
// Request required
@@ -53,6 +55,31 @@ pub enum EnqueueNanoBananaProTextToImageAspectRatio {
NineBySixteen, // NB: No NineByTwentyOne ?
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueNanoBananaProTextToImageArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// Your request will cost $0.15 per image.
// For $1.00, you can run this model 7 times.
// 4K outputs will be charged at double the standard rate.
// If web search is used, an additional $0.015 will be charged.
// Note: Pricing may change in the future.
let cost = match self.resolution {
None => 15,
Some(EnqueueNanoBananaProTextToImageResolution::OneK) => 15,
Some(EnqueueNanoBananaProTextToImageResolution::TwoK) => 15,
Some(EnqueueNanoBananaProTextToImageResolution::FourK) => 30,
};
let cost = match self.num_images {
EnqueueNanoBananaProTextToImageNumImages::One => cost,
EnqueueNanoBananaProTextToImageNumImages::Two => cost * 2,
EnqueueNanoBananaProTextToImageNumImages::Three => cost * 3,
EnqueueNanoBananaProTextToImageNumImages::Four => cost * 4,
};
cost as UsdCents
}
}
pub async fn enqueue_nano_banana_pro_text_to_image_webhook<R: IntoUrl>(
args: EnqueueNanoBananaProTextToImageArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -34,6 +34,10 @@ use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPrompt
use mysql_queries::queries::users::user_subscriptions::find_subscription_for_owner_user::find_subscription_for_owner_user_using_connection;
use sqlx::Acquire;
use utoipa::ToSchema;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
/// Gemini 2.5 Flash Image Editing
#[utoipa::path(
@@ -73,43 +77,34 @@ pub async fn gemini_25_flash_edit_image_handler(
CommonWebError::ServerError
})?;
let maybe_user_token = maybe_user_session
.as_ref()
.map(|s| &s.user_token);
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);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let mut downgrade_for_free_user = true;
if let Some(user_token) = maybe_user_token.as_ref() {
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
}
@@ -194,6 +189,17 @@ pub async fn gemini_25_flash_edit_image_handler(
aspect_ratio: None, // TODO
};
let apriori_job_token = InferenceJobToken::generate();
//let cost = args.calculate_cost_in_cents();
//info!("Charging wallet: {}", cost);
//attempt_wallet_deduction_else_common_web_error(
// user_token,
// Some(apriori_job_token.as_str()),
// cost,
// &mut mysql_connection,
//).await?;
let fal_result = enqueue_gemini_25_flash_edit_webhook(args)
.await
.map_err(|err| {
@@ -223,7 +229,7 @@ pub async fn gemini_25_flash_edit_image_handler(
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_token,
maybe_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::Gemini25Flash),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -260,13 +266,14 @@ pub async fn gemini_25_flash_edit_image_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_token,
maybe_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -18,6 +19,7 @@ use enums::common::payments_namespace::PaymentsNamespace;
use enums::common::stripe_subscription_status::StripeSubscriptionStatus;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
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};
@@ -32,7 +34,9 @@ use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_it
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use mysql_queries::queries::users::user_subscriptions::find_subscription_for_owner_user::find_subscription_for_owner_user_using_connection;
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
/// Gpt Image 1 Image Editing
#[utoipa::path(
@@ -75,39 +79,31 @@ pub async fn gpt_image_1_edit_image_handler(
.avt_cookie_manager
.get_avt_token_from_request(&http_request);
let maybe_user_token = maybe_user_session
.as_ref()
.map(|s| &s.user_token);
// 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);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let mut downgrade_for_free_user = true;
if let Some(user_token) = maybe_user_token.as_ref() {
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
let result = find_subscription_for_owner_user_using_connection(
&user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
&user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
}
@@ -213,6 +209,19 @@ pub async fn gpt_image_1_edit_image_handler(
openai_api_key: &openai_api_key,
};
let apriori_job_token = InferenceJobToken::generate();
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_gpt_image_1_edit_image_webhook(args)
.await
.map_err(|err| {
@@ -242,7 +251,7 @@ pub async fn gpt_image_1_edit_image_handler(
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_token,
maybe_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::GptImage1),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -279,13 +288,14 @@ pub async fn gpt_image_1_edit_image_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_token,
maybe_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -16,17 +17,20 @@ 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::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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 tokens::tokens::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
/// Qwen Image Editing
@@ -66,15 +70,12 @@ pub async fn qwen_edit_image_handler(
.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);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
@@ -135,6 +136,8 @@ pub async fn qwen_edit_image_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let num_images = match request.num_images {
Some(QwenEditImageNumImages::One) => QwenImageEditNumImages::One,
Some(QwenEditImageNumImages::Two) => QwenImageEditNumImages::Two,
@@ -160,6 +163,17 @@ pub async fn qwen_edit_image_handler(
acceleration: acceleration.map(|s| s.to_string()),
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_qwen_image_edit_webhook(args)
.await
.map_err(|err| {
@@ -189,9 +203,7 @@ pub async fn qwen_edit_image_handler(
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_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::Qwen),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -228,13 +240,14 @@ pub async fn qwen_edit_image_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::billing::wallets::temporary_test_wallet_deduction::temporary_test_wallet_deduction;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
@@ -14,6 +15,7 @@ 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::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::image::enqueue_flux_pro_11_text_to_image_webhook::FluxPro11Args;
use fal_client::requests::webhook::image::enqueue_flux_pro_11_text_to_image_webhook::{enqueue_flux_pro_11_text_to_image_webhook, FluxPro11AspectRatio, FluxPro11NumImages};
use http_server_common::request::get_request_ip::get_request_ip;
@@ -21,9 +23,11 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
/// Flux Pro 1.1
@@ -70,16 +74,6 @@ pub async fn generate_flux_pro_11_text_to_image_handler(
}
};
// 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(GenerateFluxProUltraTextToImageError::NotAuthorized);
// }
//};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
}
@@ -91,33 +85,12 @@ pub async fn generate_flux_pro_11_text_to_image_handler(
CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string())
})?;
//// TODO: This is test code
//let credits = match request.num_images {
// Some(GenerateFluxPro11TextToImageNumImages::One) => 25,
// Some(GenerateFluxPro11TextToImageNumImages::Two) => 50,
// Some(GenerateFluxPro11TextToImageNumImages::Three) => 75,
// Some(GenerateFluxPro11TextToImageNumImages::Four) => 100,
// None => 100,
//};
//// TODO: This is test code
//let result = temporary_test_wallet_deduction(
// user_token,
// Some("todo-reference-token"),
// credits,
// &mut mysql_connection,
//).await;
//// TODO: This is test code
//if let Err(err) = result {
// warn!("Temporary wallet deduction failed: {:?}", err); // Infallible for now.
//}
const IS_MOD : bool = false;
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let aspect_ratio = match request.aspect_ratio {
Some(GenerateFluxPro11TextToImageAspectRatio::Square) => FluxPro11AspectRatio::Square,
Some(GenerateFluxPro11TextToImageAspectRatio::LandscapeFourByThree) => FluxPro11AspectRatio::LandscapeFourByThree,
@@ -143,6 +116,17 @@ pub async fn generate_flux_pro_11_text_to_image_handler(
num_images,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_flux_pro_11_text_to_image_webhook(args)
.await
.map_err(|err| {
@@ -172,9 +156,7 @@ pub async fn generate_flux_pro_11_text_to_image_handler(
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_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::FluxPro11),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -193,13 +175,14 @@ pub async fn generate_flux_pro_11_text_to_image_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::billing::wallets::temporary_test_wallet_deduction::temporary_test_wallet_deduction;
use crate::http_server::common_responses::common_web_error::CommonWebError;
use crate::http_server::endpoints::generate::common::payments_error_test::payments_error_test;
@@ -14,6 +15,7 @@ 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::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::image::enqueue_flux_pro_11_ultra_text_to_image_webhook::FluxPro11UltraArgs;
use fal_client::requests::webhook::image::enqueue_flux_pro_11_ultra_text_to_image_webhook::{enqueue_flux_pro_11_ultra_text_to_image_webhook, FluxPro11UltraAspectRatio, FluxPro11UltraNumImages};
use http_server_common::request::get_request_ip::get_request_ip;
@@ -21,9 +23,11 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
/// Flux Pro 1.1 Ultra
@@ -70,16 +74,6 @@ pub async fn generate_flux_pro_11_ultra_text_to_image_handler(
}
};
// 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(GenerateFluxProUltraTextToImageError::NotAuthorized);
// }
//};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
}
@@ -91,32 +85,12 @@ pub async fn generate_flux_pro_11_ultra_text_to_image_handler(
CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string())
})?;
// // TODO: This is test code
// let credits = match request.num_images {
// Some(GenerateFluxPro11UltraTextToImageNumImages::One) => 25,
// Some(GenerateFluxPro11UltraTextToImageNumImages::Two) => 50,
// Some(GenerateFluxPro11UltraTextToImageNumImages::Three) => 75,
// Some(GenerateFluxPro11UltraTextToImageNumImages::Four) => 100,
// None => 100,
// };
// // TODO: This is test code
// let result = temporary_test_wallet_deduction(
// user_token,
// Some("todo-reference-token"),
// credits,
// &mut mysql_connection,
// ).await;
// // TODO: This is test code
// if let Err(err) = result {
// warn!("Temporary wallet deduction failed: {:?}", err); // Infallible for now.
// }
const IS_MOD : bool = false;
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let aspect_ratio = match request.aspect_ratio {
Some(GenerateFluxPro11UltraTextToImageAspectRatio::Square) => FluxPro11UltraAspectRatio::Square,
Some(GenerateFluxPro11UltraTextToImageAspectRatio::LandscapeFourByThree) => FluxPro11UltraAspectRatio::LandscapeFourByThree,
@@ -146,6 +120,17 @@ pub async fn generate_flux_pro_11_ultra_text_to_image_handler(
num_images,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_flux_pro_11_ultra_text_to_image_webhook(args)
.await
.map_err(|err| {
@@ -175,9 +160,7 @@ pub async fn generate_flux_pro_11_ultra_text_to_image_handler(
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_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::FluxPro11Ultra),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -196,13 +179,14 @@ pub async fn generate_flux_pro_11_ultra_text_to_image_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -18,18 +19,21 @@ use enums::common::payments_namespace::PaymentsNamespace;
use enums::common::stripe_subscription_status::StripeSubscriptionStatus;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::image::enqueue_gpt_image_1_text_to_image_webhook::{enqueue_gpt_image_1_text_to_image_webhook, GptTextToImageByokArgs, GptTextToImageNumImages, GptTextToImageQuality, GptTextToImageSize};
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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::users::user_subscriptions::find_subscription_for_owner_user::find_subscription_for_owner_user_using_connection;
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
/// Gpt Image 1
@@ -73,39 +77,31 @@ pub async fn generate_gpt_image_1_text_to_image_handler(
.avt_cookie_manager
.get_avt_token_from_request(&http_request);
let maybe_user_token = maybe_user_session
.as_ref()
.map(|s| &s.user_token);
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
// 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);
// }
//};
let mut downgrade_for_free_user = true;
if let Some(user_token) = maybe_user_token.as_ref() {
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
let result = find_subscription_for_owner_user_using_connection(
&user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
&user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
}
@@ -152,7 +148,7 @@ pub async fn generate_gpt_image_1_text_to_image_handler(
if downgrade_for_free_user {
num_images = GptTextToImageNumImages::One;
}
let openai_api_key = OpenAiApiKey::from_str(&server_state.openai.api_key);
let args = GptTextToImageByokArgs {
@@ -165,6 +161,19 @@ pub async fn generate_gpt_image_1_text_to_image_handler(
openai_api_key: &openai_api_key,
};
let apriori_job_token = InferenceJobToken::generate();
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_gpt_image_1_text_to_image_webhook(args)
.await
.map_err(|err| {
@@ -194,7 +203,7 @@ pub async fn generate_gpt_image_1_text_to_image_handler(
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_token,
maybe_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::GptImage1),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -213,13 +222,14 @@ pub async fn generate_gpt_image_1_text_to_image_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_token,
maybe_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -2,6 +2,7 @@ use std::collections::HashSet;
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -19,6 +20,7 @@ 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::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4_edit_image_webhook::{enqueue_bytedance_seedream_v4_edit_image_webhook, EnqueueBytedanceSeedreamV4EditImageArgs, EnqueueBytedanceSeedreamV4EditImageNumImages, EnqueueBytedanceSeedreamV4EditImageSize};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4_text_to_image_webhook::{enqueue_bytedance_seedream_v4_text_to_image_webhook, EnqueueBytedanceSeedreamV4TextToImageArgs, EnqueueBytedanceSeedreamV4TextToImageNumImages, EnqueueBytedanceSeedreamV4TextToImageSize};
use http_server_common::request::get_request_ip::get_request_ip;
@@ -26,6 +28,7 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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};
@@ -33,6 +36,7 @@ use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPrompt
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -73,15 +77,12 @@ pub async fn bytedance_seedream_v4_multi_function_image_gen_handler(
.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);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
@@ -109,6 +110,8 @@ pub async fn bytedance_seedream_v4_multi_function_image_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(input_image_urls) = image_urls.as_deref() {
@@ -149,6 +152,17 @@ pub async fn bytedance_seedream_v4_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_bytedance_seedream_v4_edit_image_webhook(args)
.await
.map_err(|err| {
@@ -193,6 +207,17 @@ pub async fn bytedance_seedream_v4_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_bytedance_seedream_v4_text_to_image_webhook(args)
.await
.map_err(|err| {
@@ -223,9 +248,7 @@ pub async fn bytedance_seedream_v4_multi_function_image_gen_handler(
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_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::Seedream4),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -264,13 +287,14 @@ pub async fn bytedance_seedream_v4_multi_function_image_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -2,6 +2,7 @@ use std::collections::HashSet;
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -19,6 +20,7 @@ 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::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::image::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{enqueue_bytedance_seedream_v4p5_edit_image_webhook, EnqueueBytedanceSeedreamV4p5EditImageArgs, EnqueueBytedanceSeedreamV4p5EditImageNumImages, EnqueueBytedanceSeedreamV4p5EditImageSize};
use fal_client::requests::webhook::image::text::enqueue_bytedance_seedream_v4p5_text_to_image_webhook::{enqueue_bytedance_seedream_v4p5_text_to_image_webhook, EnqueueBytedanceSeedreamV4p5TextToImageArgs, EnqueueBytedanceSeedreamV4p5TextToImageNumImages, EnqueueBytedanceSeedreamV4p5TextToImageSize};
use http_server_common::request::get_request_ip::get_request_ip;
@@ -26,6 +28,7 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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};
@@ -33,6 +36,7 @@ use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPrompt
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -73,15 +77,12 @@ pub async fn bytedance_seedream_v4p5_multi_function_image_gen_handler(
.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);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
@@ -109,6 +110,8 @@ pub async fn bytedance_seedream_v4p5_multi_function_image_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(input_image_urls) = image_urls.as_deref() {
@@ -148,6 +151,17 @@ pub async fn bytedance_seedream_v4p5_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_bytedance_seedream_v4p5_edit_image_webhook(args)
.await
.map_err(|err| {
@@ -191,6 +205,17 @@ pub async fn bytedance_seedream_v4p5_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_bytedance_seedream_v4p5_text_to_image_webhook(args)
.await
.map_err(|err| {
@@ -221,9 +246,7 @@ pub async fn bytedance_seedream_v4p5_multi_function_image_gen_handler(
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_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::Seedream4p5),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -262,13 +285,14 @@ pub async fn bytedance_seedream_v4p5_multi_function_image_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -21,6 +22,7 @@ use enums::common::payments_namespace::PaymentsNamespace;
use enums::common::stripe_subscription_status::StripeSubscriptionStatus;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{enqueue_gpt_image_1p5_image_edit_webhook, EnqueueGptImage1p5EditImageArgs, EnqueueGptImage1p5EditImageBackground, EnqueueGptImage1p5EditImageInputFidelity, EnqueueGptImage1p5EditImageNumImages, EnqueueGptImage1p5EditImageQuality, EnqueueGptImage1p5EditImageSize};
use fal_client::requests::webhook::image::text::enqueue_gpt_image_1p5_text_to_image_webhook::{enqueue_gpt_image_1p5_text_to_image_webhook, EnqueueGptImage1p5TextToImageArgs, EnqueueGptImage1p5TextToImageBackground, EnqueueGptImage1p5TextToImageNumImages, EnqueueGptImage1p5TextToImageQuality, EnqueueGptImage1p5TextToImageSize};
use http_server_common::request::get_request_ip::get_request_ip;
@@ -28,6 +30,7 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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};
@@ -36,6 +39,7 @@ use mysql_queries::queries::users::user_subscriptions::find_subscription_for_own
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -80,39 +84,31 @@ pub async fn gpt_image_1p5_multi_function_image_gen_handler(
.avt_cookie_manager
.get_avt_token_from_request(&http_request);
let maybe_user_token = maybe_user_session
.as_ref()
.map(|s| &s.user_token);
// 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);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let mut downgrade_for_free_user = true;
if let Some(user_token) = maybe_user_token.as_ref() {
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
}
@@ -174,6 +170,8 @@ pub async fn gpt_image_1p5_multi_function_image_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(input_image_urls) = maybe_image_urls.as_deref() {
@@ -232,6 +230,17 @@ pub async fn gpt_image_1p5_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_gpt_image_1p5_image_edit_webhook(args)
.await
.map_err(|err| {
@@ -286,6 +295,17 @@ pub async fn gpt_image_1p5_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_gpt_image_1p5_text_to_image_webhook(args)
.await
.map_err(|err| {
@@ -316,7 +336,7 @@ pub async fn gpt_image_1p5_multi_function_image_gen_handler(
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_token,
maybe_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::GptImage1p5),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -355,13 +375,14 @@ pub async fn gpt_image_1p5_multi_function_image_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_token,
maybe_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -2,6 +2,7 @@ use std::collections::HashSet;
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -22,6 +23,7 @@ use enums::common::payments_namespace::PaymentsNamespace;
use enums::common::stripe_subscription_status::StripeSubscriptionStatus;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::image::edit::enqueue_gemini_25_flash_edit_webhook::{enqueue_gemini_25_flash_edit_webhook, Gemini25FlashEditArgs, Gemini25FlashEditAspectRatio, Gemini25FlashEditNumImages};
use fal_client::requests::webhook::image::edit::enqueue_nano_banana_pro_edit_image_webhook::{enqueue_nano_banana_pro_image_edit_webhook, EnqueueNanoBananaProEditImageArgs, EnqueueNanoBananaProEditImageAspectRatio, EnqueueNanoBananaProEditImageNumImages, EnqueueNanoBananaProEditImageResolution};
use fal_client::requests::webhook::image::text::enqueue_gemini_25_flash_text_to_image_webhook::{enqueue_gemini_25_flash_text_to_image_webhook, Gemini25FlashTextToImageArgs, Gemini25FlashTextToImageAspectRatio, Gemini25FlashTextToImageNumImages};
@@ -31,6 +33,7 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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};
@@ -39,6 +42,7 @@ use mysql_queries::queries::users::user_subscriptions::find_subscription_for_own
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -83,39 +87,31 @@ pub async fn nano_banana_multi_function_image_gen_handler(
.avt_cookie_manager
.get_avt_token_from_request(&http_request);
let maybe_user_token = maybe_user_session
.as_ref()
.map(|s| &s.user_token);
// 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);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let mut downgrade_for_free_user = true;
if let Some(user_token) = maybe_user_token.as_ref() {
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
}
@@ -143,6 +139,8 @@ pub async fn nano_banana_multi_function_image_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(input_image_urls) = image_urls.as_deref() {
@@ -184,6 +182,17 @@ pub async fn nano_banana_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_gemini_25_flash_edit_webhook(args)
.await
.map_err(|err| {
@@ -229,6 +238,17 @@ pub async fn nano_banana_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_gemini_25_flash_text_to_image_webhook(args)
.await
.map_err(|err| {
@@ -259,7 +279,7 @@ pub async fn nano_banana_multi_function_image_gen_handler(
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_token,
maybe_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::NanoBanana),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -298,13 +318,14 @@ pub async fn nano_banana_multi_function_image_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_token,
maybe_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,
@@ -2,6 +2,7 @@ use std::collections::HashSet;
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -21,6 +22,7 @@ use enums::common::payments_namespace::PaymentsNamespace;
use enums::common::stripe_subscription_status::StripeSubscriptionStatus;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::image::edit::enqueue_nano_banana_pro_edit_image_webhook::{enqueue_nano_banana_pro_image_edit_webhook, EnqueueNanoBananaProEditImageArgs, EnqueueNanoBananaProEditImageAspectRatio, EnqueueNanoBananaProEditImageNumImages, EnqueueNanoBananaProEditImageResolution};
use fal_client::requests::webhook::image::text::enqueue_nano_banana_pro_text_to_image_webhook::{enqueue_nano_banana_pro_text_to_image_webhook, EnqueueNanoBananaProTextToImageArgs, EnqueueNanoBananaProTextToImageAspectRatio, EnqueueNanoBananaProTextToImageNumImages, EnqueueNanoBananaProTextToImageResolution};
use http_server_common::request::get_request_ip::get_request_ip;
@@ -28,6 +30,7 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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};
@@ -36,6 +39,7 @@ use mysql_queries::queries::users::user_subscriptions::find_subscription_for_own
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -80,42 +84,34 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
.avt_cookie_manager
.get_avt_token_from_request(&http_request);
let maybe_user_token = maybe_user_session
.as_ref()
.map(|s| &s.user_token);
// 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);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let mut downgrade_for_free_user = true;
if let Some(user_token) = maybe_user_token.as_ref() {
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
let result = find_subscription_for_owner_user_using_connection(
user_token,
PaymentsNamespace::Artcraft,
&mut mysql_connection,
).await;
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
if let Ok(Some(subscription)) = result {
info!("User {:?} has subscription: {:?} (stripe customer: {:?}, status: {:?})",
user_token,
subscription.token,
subscription.stripe_customer_id,
subscription.stripe_subscription_status);
// NB: Failing open means subscribers might get fewer results, but they're free right now.
if subscription.stripe_subscription_status == StripeSubscriptionStatus::Active {
downgrade_for_free_user = false;
}
}
info!("downgrade_for_free_user: {}", downgrade_for_free_user);
let image_urls = match request.image_media_tokens.as_ref() {
@@ -140,6 +136,8 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(input_image_urls) = image_urls.as_deref() {
@@ -190,6 +188,17 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_nano_banana_pro_image_edit_webhook(args)
.await
.map_err(|err| {
@@ -244,6 +253,17 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_nano_banana_pro_text_to_image_webhook(args)
.await
.map_err(|err| {
@@ -274,7 +294,7 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
let prompt_result = insert_prompt(InsertPromptArgs {
maybe_apriori_prompt_token: None,
prompt_type: PromptType::ArtcraftApp,
maybe_creator_user_token: maybe_user_token,
maybe_creator_user_token: Some(&user_token),
maybe_model_type: Some(ModelType::NanoBananaPro),
maybe_generation_provider: Some(GenerationProvider::Artcraft),
maybe_positive_prompt: request.prompt.as_deref(),
@@ -313,13 +333,14 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
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_token,
maybe_creator_user_token: Some(&user_token),
maybe_avt_token: maybe_avt_token.as_ref(),
creator_ip_address: &ip_address,
creator_set_visibility: Visibility::Public,