mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-09 11:31:27 +00:00
gpt-image-1 + quality
This commit is contained in:
1 parent
8c34b87cd9
commit
c90b2390f7
51 files changed
+987
-672
No files matched your search
@@ -0,0 +1,11 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
/// Common quality levels you can specify when enqueuing a generation.
|
||||
/// Not every model will use this; models that don't simply ignore it.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CommonQuality {
|
||||
High,
|
||||
Medium,
|
||||
Low,
|
||||
}
|
||||
@@ -2,6 +2,7 @@ pub mod audio_list_ref;
|
||||
pub mod character_list_ref;
|
||||
pub mod common_aspect_ratio;
|
||||
pub mod common_image_model;
|
||||
pub mod common_quality;
|
||||
pub mod common_resolution;
|
||||
pub mod common_splat_model;
|
||||
pub mod common_video_model;
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ mod tests {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+195
-72
@@ -6,25 +6,40 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf
|
||||
pub(crate) fn estimate_image_cost_artcraft_gpt_image_1(
|
||||
plan: &PlanArtcraftGptImage1<'_>,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// What we charge in the legacy storyteller-web handlers (BYOK pricing
|
||||
// approximation, kept for parity with `enqueue_gpt_image_1_byok_*` cost):
|
||||
// Auto: 17¢/image (treated as High)
|
||||
// Low: 1¢/image
|
||||
// Medium: 4¢/image
|
||||
// High: 17¢/image
|
||||
// Cost is independent of image size on the artcraft (BYOK) tier.
|
||||
let cost_per_image: u64 = match plan.quality {
|
||||
ArtcraftGptImage1Quality::Auto => 17,
|
||||
ArtcraftGptImage1Quality::Low => 1,
|
||||
ArtcraftGptImage1Quality::Medium => 4,
|
||||
ArtcraftGptImage1Quality::High => 17,
|
||||
// Pricing per the fal-ai GPT Image 1 docs, adapted for our artcraft billing:
|
||||
//
|
||||
// Output image cost (per output image):
|
||||
// Low – $0.011 (1024×1024) / $0.016 (other) → 2¢ / 2¢
|
||||
// Medium – $0.042 (1024×1024) / $0.063 (other) → 5¢ / 7¢
|
||||
// High – $0.167 (1024×1024) / $0.250 (other) → 17¢ / 25¢
|
||||
//
|
||||
// Input image cost (edit mode only, per input image, high-fidelity assumed):
|
||||
// 3,050 tokens × $0.005/1K tokens ≈ $0.01525 → 2¢ per input image
|
||||
//
|
||||
// Input text tokens: free (we don't charge for these).
|
||||
//
|
||||
let is_square = matches!(
|
||||
plan.image_size,
|
||||
None | Some(ArtcraftGptImage1Size::Square)
|
||||
);
|
||||
|
||||
let output_cost_per_image: u64 = match (plan.quality, is_square) {
|
||||
(ArtcraftGptImage1Quality::Auto, _) => 17, // Auto treated as High square
|
||||
(ArtcraftGptImage1Quality::Low, true) => 2,
|
||||
(ArtcraftGptImage1Quality::Low, false) => 2,
|
||||
(ArtcraftGptImage1Quality::Medium, true) => 5,
|
||||
(ArtcraftGptImage1Quality::Medium, false) => 7,
|
||||
(ArtcraftGptImage1Quality::High, true) => 17,
|
||||
(ArtcraftGptImage1Quality::High, false) => 25,
|
||||
};
|
||||
|
||||
// Reference `image_size` so the field is exercised in coverage; pricing
|
||||
// currently doesn't depend on it.
|
||||
let _ = plan.image_size.unwrap_or(ArtcraftGptImage1Size::Square);
|
||||
let num_output_images = plan.num_images.as_u64();
|
||||
let output_cost = output_cost_per_image * num_output_images;
|
||||
|
||||
let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64();
|
||||
// Input image token cost: 2¢ per input image (high-fidelity estimate).
|
||||
let input_image_cost: u64 = 2 * plan.num_input_images;
|
||||
|
||||
let cost_in_usd_cents = output_cost + input_image_cost;
|
||||
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -40,77 +55,185 @@ pub(crate) fn estimate_image_cost_artcraft_gpt_image_1(
|
||||
mod tests {
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_image_model::CommonImageModel;
|
||||
use crate::api::common_quality::CommonQuality;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
fn estimate_usd_cents(image_batch_count: u16, aspect_ratio: Option<CommonAspectRatio>) -> u64 {
|
||||
let request = GenerateImageRequest {
|
||||
fn base() -> GenerateImageRequest<'static> {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::GptImage1,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
zoom: None,
|
||||
};
|
||||
request.build()
|
||||
.expect("build should succeed")
|
||||
.estimate_costs()
|
||||
.cost_in_usd_cents
|
||||
.expect("cost_in_usd_cents should be present")
|
||||
}
|
||||
|
||||
// The artcraft (BYOK) tier defaults plan.quality to High = 17¢/image and is
|
||||
// independent of size.
|
||||
|
||||
#[test]
|
||||
fn default_quality_one_image_costs_17_cents() {
|
||||
assert_eq!(estimate_usd_cents(1, None), 17);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_quality_two_images_costs_34_cents() {
|
||||
assert_eq!(estimate_usd_cents(2, None), 34);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_quality_three_images_costs_51_cents() {
|
||||
assert_eq!(estimate_usd_cents(3, None), 51);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_quality_four_images_costs_68_cents() {
|
||||
assert_eq!(estimate_usd_cents(4, None), 68);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_is_independent_of_aspect_ratio() {
|
||||
// The BYOK tier doesn't price by image size — square, wide, tall, auto
|
||||
// and unset all bill the same.
|
||||
let ars = [
|
||||
None,
|
||||
Some(CommonAspectRatio::Square),
|
||||
Some(CommonAspectRatio::SquareHd),
|
||||
Some(CommonAspectRatio::WideSixteenByNine),
|
||||
Some(CommonAspectRatio::TallNineBySixteen),
|
||||
Some(CommonAspectRatio::Auto),
|
||||
Some(CommonAspectRatio::Auto2k),
|
||||
Some(CommonAspectRatio::Auto4k),
|
||||
];
|
||||
for ar in ars {
|
||||
assert_eq!(
|
||||
estimate_usd_cents(1, ar),
|
||||
17,
|
||||
"expected 17¢ regardless of aspect ratio (got {:?})",
|
||||
ar,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn estimate_text(
|
||||
quality: Option<CommonQuality>,
|
||||
aspect_ratio: Option<CommonAspectRatio>,
|
||||
batch: u16,
|
||||
) -> u64 {
|
||||
let request = GenerateImageRequest {
|
||||
quality,
|
||||
aspect_ratio,
|
||||
image_batch_count: Some(batch),
|
||||
..base()
|
||||
};
|
||||
request.build().unwrap().estimate_costs()
|
||||
.cost_in_usd_cents.unwrap()
|
||||
}
|
||||
|
||||
fn estimate_edit(
|
||||
quality: Option<CommonQuality>,
|
||||
aspect_ratio: Option<CommonAspectRatio>,
|
||||
batch: u16,
|
||||
num_input_images: usize,
|
||||
) -> u64 {
|
||||
let tokens: Vec<MediaFileToken> = (0..num_input_images)
|
||||
.map(|i| MediaFileToken::new_from_str(&format!("mf_test{:028}", i)))
|
||||
.collect();
|
||||
let request = GenerateImageRequest {
|
||||
quality,
|
||||
aspect_ratio,
|
||||
image_batch_count: Some(batch),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
..base()
|
||||
};
|
||||
request.build().unwrap().estimate_costs()
|
||||
.cost_in_usd_cents.unwrap()
|
||||
}
|
||||
|
||||
// ── Default quality (None → High) ───────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn default_quality_square_one_image_costs_17_cents() {
|
||||
assert_eq!(estimate_text(None, None, 1), 17);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_quality_square_four_images_costs_68_cents() {
|
||||
assert_eq!(estimate_text(None, None, 4), 68);
|
||||
}
|
||||
|
||||
// ── Low quality (2¢/image regardless of size) ───────────────────────────
|
||||
|
||||
#[test]
|
||||
fn low_square_one_image() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::Low), Some(CommonAspectRatio::Square), 1), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_wide_one_image() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::Low), Some(CommonAspectRatio::WideSixteenByNine), 1), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_tall_one_image() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::Low), Some(CommonAspectRatio::TallNineBySixteen), 1), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_four_images() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::Low), None, 4), 8);
|
||||
}
|
||||
|
||||
// ── Medium quality (5¢ square, 7¢ wide/tall) ───────────────────────────
|
||||
|
||||
#[test]
|
||||
fn medium_square_one_image() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::Medium), Some(CommonAspectRatio::Square), 1), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_wide_one_image() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::Medium), Some(CommonAspectRatio::WideSixteenByNine), 1), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_tall_one_image() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::Medium), Some(CommonAspectRatio::TallNineBySixteen), 1), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_square_four_images() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::Medium), Some(CommonAspectRatio::Square), 4), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_wide_four_images() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::Medium), Some(CommonAspectRatio::WideSixteenByNine), 4), 28);
|
||||
}
|
||||
|
||||
// ── High quality (17¢ square, 25¢ wide/tall) ───────────────────────────
|
||||
|
||||
#[test]
|
||||
fn high_square_one_image() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::High), Some(CommonAspectRatio::Square), 1), 17);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_wide_one_image() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::High), Some(CommonAspectRatio::WideSixteenByNine), 1), 25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_tall_one_image() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::High), Some(CommonAspectRatio::TallNineBySixteen), 1), 25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_square_four_images() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::High), Some(CommonAspectRatio::Square), 4), 68);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_wide_four_images() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::High), Some(CommonAspectRatio::WideSixteenByNine), 4), 100);
|
||||
}
|
||||
|
||||
// ── Edit mode: adds 2¢ per input image ─────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn edit_one_input_adds_2_cents() {
|
||||
// High square 1 output = 17¢, plus 1 input × 2¢ = 19¢
|
||||
assert_eq!(estimate_edit(Some(CommonQuality::High), Some(CommonAspectRatio::Square), 1, 1), 19);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_three_inputs_adds_6_cents() {
|
||||
// High square 1 output = 17¢, plus 3 inputs × 2¢ = 23¢
|
||||
assert_eq!(estimate_edit(Some(CommonQuality::High), Some(CommonAspectRatio::Square), 1, 3), 23);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_five_inputs_adds_10_cents() {
|
||||
// Medium wide 2 outputs = 7×2 = 14¢, plus 5 inputs × 2¢ = 24¢
|
||||
assert_eq!(estimate_edit(Some(CommonQuality::Medium), Some(CommonAspectRatio::WideSixteenByNine), 2, 5), 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_low_quality_with_inputs() {
|
||||
// Low square 1 output = 2¢, plus 2 inputs × 2¢ = 6¢
|
||||
assert_eq!(estimate_edit(Some(CommonQuality::Low), None, 1, 2), 6);
|
||||
}
|
||||
|
||||
// ── Text mode (no input images) has no input cost ──────────────────────
|
||||
|
||||
#[test]
|
||||
fn text_mode_no_input_cost() {
|
||||
assert_eq!(estimate_text(Some(CommonQuality::High), Some(CommonAspectRatio::Square), 1), 17);
|
||||
}
|
||||
}
|
||||
+1
@@ -82,6 +82,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -56,6 +56,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -50,6 +50,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ mod tests {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -43,6 +43,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+129
-54
@@ -7,12 +7,19 @@ pub(crate) fn estimate_image_cost_fal_gpt_image_1(
|
||||
plan: &PlanFalGptImage1<'_>,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Per fal docs (fal-ai/gpt-image-1/{text-to-image,edit-image}):
|
||||
// Low: $0.011 (1024x1024) / $0.016 (other) per image
|
||||
// Medium: $0.042 (1024x1024) / $0.063 (other) per image
|
||||
// High: $0.167 (1024x1024) / $0.250 (other) per image
|
||||
// Round up to whole cents.
|
||||
//
|
||||
// Output image cost (per output image):
|
||||
// Low: $0.011 (1024×1024) / $0.016 (other) → 2¢ / 2¢
|
||||
// Medium: $0.042 (1024×1024) / $0.063 (other) → 5¢ / 7¢
|
||||
// High: $0.167 (1024×1024) / $0.250 (other) → 17¢ / 25¢
|
||||
//
|
||||
// Input image tokens (edit mode, per input image, high-fidelity assumed):
|
||||
// 3,050 tokens × $0.005/1K tokens ≈ $0.01525 → 2¢ per input image
|
||||
//
|
||||
// Input text tokens: free (we don't charge for these).
|
||||
//
|
||||
let is_square = matches!(plan.image_size, None | Some(FalGptImage1ImageSize::Square));
|
||||
let cost_per_image: u64 = match (plan.quality, is_square) {
|
||||
let output_cost_per_image: u64 = match (plan.quality, is_square) {
|
||||
(FalGptImage1Quality::Low, true) => 2,
|
||||
(FalGptImage1Quality::Low, false) => 2,
|
||||
(FalGptImage1Quality::Medium, true) => 5,
|
||||
@@ -20,7 +27,18 @@ pub(crate) fn estimate_image_cost_fal_gpt_image_1(
|
||||
(FalGptImage1Quality::High, true) => 17,
|
||||
(FalGptImage1Quality::High, false) => 25,
|
||||
};
|
||||
let cost_in_usd_cents = cost_per_image * plan.num_images.as_u64();
|
||||
|
||||
let num_output_images = plan.num_images.as_u64();
|
||||
let output_cost = output_cost_per_image * num_output_images;
|
||||
|
||||
// Input image token cost: 2¢ per input image (high-fidelity estimate).
|
||||
let input_image_cost: u64 = if plan.image_urls.is_empty() {
|
||||
0
|
||||
} else {
|
||||
2 * plan.image_urls.len() as u64
|
||||
};
|
||||
|
||||
let cost_in_usd_cents = output_cost + input_image_cost;
|
||||
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -37,7 +55,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_image_1::FalGptImage1NumImages;
|
||||
|
||||
fn plan(
|
||||
fn text_plan(
|
||||
quality: FalGptImage1Quality,
|
||||
image_size: Option<FalGptImage1ImageSize>,
|
||||
num_images: FalGptImage1NumImages,
|
||||
@@ -51,113 +69,170 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn cents(
|
||||
fn edit_plan(
|
||||
quality: FalGptImage1Quality,
|
||||
image_size: Option<FalGptImage1ImageSize>,
|
||||
num_images: FalGptImage1NumImages,
|
||||
num_input_images: usize,
|
||||
) -> PlanFalGptImage1<'static> {
|
||||
let image_urls = (0..num_input_images)
|
||||
.map(|i| format!("https://fake.example.com/img{}.png", i))
|
||||
.collect();
|
||||
PlanFalGptImage1 {
|
||||
prompt: Some("p"),
|
||||
image_urls,
|
||||
image_size,
|
||||
quality,
|
||||
num_images,
|
||||
}
|
||||
}
|
||||
|
||||
fn cents_text(
|
||||
quality: FalGptImage1Quality,
|
||||
image_size: Option<FalGptImage1ImageSize>,
|
||||
num_images: FalGptImage1NumImages,
|
||||
) -> u64 {
|
||||
estimate_image_cost_fal_gpt_image_1(&plan(quality, image_size, num_images))
|
||||
estimate_image_cost_fal_gpt_image_1(&text_plan(quality, image_size, num_images))
|
||||
.cost_in_usd_cents
|
||||
.expect("cost_in_usd_cents should be present")
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// ── Low quality (2¢/image, both square and non-square) ────────────────────
|
||||
fn cents_edit(
|
||||
quality: FalGptImage1Quality,
|
||||
image_size: Option<FalGptImage1ImageSize>,
|
||||
num_images: FalGptImage1NumImages,
|
||||
num_input_images: usize,
|
||||
) -> u64 {
|
||||
estimate_image_cost_fal_gpt_image_1(&edit_plan(quality, image_size, num_images, num_input_images))
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// ── Low quality text-to-image (2¢/image, both sizes) ──────────────────
|
||||
|
||||
#[test]
|
||||
fn low_square_one_image_costs_2_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Low, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One), 2);
|
||||
fn low_square_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Low, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_horizontal_one_image_costs_2_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Low, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::One), 2);
|
||||
fn low_horizontal_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Low, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::One), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_vertical_one_image_costs_2_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Low, Some(FalGptImage1ImageSize::Vertical), FalGptImage1NumImages::One), 2);
|
||||
fn low_vertical_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Low, Some(FalGptImage1ImageSize::Vertical), FalGptImage1NumImages::One), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_unset_size_costs_2_cents() {
|
||||
// Unset image_size is treated as square in pricing.
|
||||
assert_eq!(cents(FalGptImage1Quality::Low, None, FalGptImage1NumImages::One), 2);
|
||||
fn low_unset_size_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Low, None, FalGptImage1NumImages::One), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn low_four_images_costs_8_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Low, None, FalGptImage1NumImages::Four), 8);
|
||||
fn low_four_images() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Low, None, FalGptImage1NumImages::Four), 8);
|
||||
}
|
||||
|
||||
// ── Medium quality (5¢ square, 7¢ wide/tall) ──────────────────────────────
|
||||
// ── Medium quality text-to-image (5¢ square, 7¢ wide/tall) ────────────
|
||||
|
||||
#[test]
|
||||
fn medium_square_one_image_costs_5_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One), 5);
|
||||
fn medium_square_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_horizontal_one_image_costs_7_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::One), 7);
|
||||
fn medium_horizontal_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::One), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_vertical_one_image_costs_7_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Vertical), FalGptImage1NumImages::One), 7);
|
||||
fn medium_vertical_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Vertical), FalGptImage1NumImages::One), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_unset_size_costs_5_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Medium, None, FalGptImage1NumImages::One), 5);
|
||||
fn medium_square_four_images() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::Four), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_square_four_images_costs_20_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::Four), 20);
|
||||
fn medium_horizontal_four_images() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::Four), 28);
|
||||
}
|
||||
|
||||
// ── High quality text-to-image (17¢ square, 25¢ wide/tall) ────────────
|
||||
|
||||
#[test]
|
||||
fn high_square_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One), 17);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn medium_horizontal_four_images_costs_28_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::Four), 28);
|
||||
}
|
||||
|
||||
// ── High quality (17¢ square, 25¢ wide/tall) ──────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn high_square_one_image_costs_17_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One), 17);
|
||||
fn high_horizontal_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::One), 25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_horizontal_one_image_costs_25_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::One), 25);
|
||||
fn high_vertical_one_image() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Vertical), FalGptImage1NumImages::One), 25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_vertical_one_image_costs_25_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Vertical), FalGptImage1NumImages::One), 25);
|
||||
fn high_square_two_images() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::Two), 34);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_unset_size_costs_17_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::High, None, FalGptImage1NumImages::One), 17);
|
||||
fn high_horizontal_four_images() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::Four), 100);
|
||||
}
|
||||
|
||||
// ── Edit mode adds 2¢ per input image ─────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn edit_high_square_one_output_one_input() {
|
||||
// 17¢ output + 2¢ input = 19¢
|
||||
assert_eq!(cents_edit(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One, 1), 19);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_square_two_images_costs_34_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::Two), 34);
|
||||
fn edit_high_square_one_output_three_inputs() {
|
||||
// 17¢ output + 3×2¢ input = 23¢
|
||||
assert_eq!(cents_edit(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One, 3), 23);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_horizontal_four_images_costs_100_cents() {
|
||||
assert_eq!(cents(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::Four), 100);
|
||||
fn edit_medium_wide_two_outputs_five_inputs() {
|
||||
// 2×7¢ output + 5×2¢ input = 24¢
|
||||
assert_eq!(cents_edit(FalGptImage1Quality::Medium, Some(FalGptImage1ImageSize::Horizontal), FalGptImage1NumImages::Two, 5), 24);
|
||||
}
|
||||
|
||||
// ── Metadata flags ────────────────────────────────────────────────────────
|
||||
#[test]
|
||||
fn edit_low_square_one_output_two_inputs() {
|
||||
// 2¢ output + 2×2¢ input = 6¢
|
||||
assert_eq!(cents_edit(FalGptImage1Quality::Low, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One, 2), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_high_vertical_four_outputs_one_input() {
|
||||
// 4×25¢ output + 1×2¢ input = 102¢
|
||||
assert_eq!(cents_edit(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Vertical), FalGptImage1NumImages::Four, 1), 102);
|
||||
}
|
||||
|
||||
// ── Text mode has no input image cost ─────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn text_mode_no_input_cost() {
|
||||
assert_eq!(cents_text(FalGptImage1Quality::High, Some(FalGptImage1ImageSize::Square), FalGptImage1NumImages::One), 17);
|
||||
}
|
||||
|
||||
// ── Metadata flags ────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn metadata_flags_are_default() {
|
||||
let estimate = estimate_image_cost_fal_gpt_image_1(&plan(
|
||||
let estimate = estimate_image_cost_fal_gpt_image_1(&text_plan(
|
||||
FalGptImage1Quality::Medium,
|
||||
None,
|
||||
FalGptImage1NumImages::One,
|
||||
|
||||
+1
@@ -48,6 +48,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+4
-1
@@ -47,6 +47,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_flux_1_dev_image_request()
|
||||
@@ -69,6 +70,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
..base_flux_1_dev_image_request()
|
||||
@@ -94,7 +96,8 @@ mod tests {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a bear walking through an abandoned desert city at night"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_1_dev_image_request()
|
||||
|
||||
+4
-1
@@ -47,6 +47,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a man and woman walking through a cyberpunk city at night"),
|
||||
..base_flux_1_schnell_image_request()
|
||||
@@ -69,6 +70,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
..base_flux_1_schnell_image_request()
|
||||
@@ -94,7 +96,8 @@ mod tests {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a man and woman walking through a futuristic city at evening"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_1_schnell_image_request()
|
||||
|
||||
+1
@@ -51,6 +51,7 @@ mod tests {
|
||||
let tokens = vec![MediaFileToken::new_from_str("m_r45apt2swza6wp8j2z10237t92kkr8")];
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::SquareHd),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
horizontal_angle: Some(30.0),
|
||||
|
||||
+4
-1
@@ -47,6 +47,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_flux_pro_1p1_image_request()
|
||||
@@ -69,6 +70,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
..base_flux_pro_1p1_image_request()
|
||||
@@ -94,7 +96,8 @@ mod tests {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a bear walking through an abandoned desert city at night"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
|
||||
+4
-1
@@ -47,6 +47,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
@@ -69,6 +70,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
@@ -94,7 +96,8 @@ mod tests {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a t-rex walking through an abandoned desert city at night"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
|
||||
+6
-2
@@ -50,6 +50,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_nano_banana_image_request()
|
||||
@@ -79,6 +80,7 @@ mod tests {
|
||||
prompt: Some("Change the background to a sunny beach"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
..base_nano_banana_image_request()
|
||||
};
|
||||
@@ -103,7 +105,8 @@ mod tests {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a shiba walking through a cyberpunk city at night"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_nano_banana_image_request()
|
||||
@@ -138,7 +141,8 @@ mod tests {
|
||||
prompt: Some("Change the background to a sunny beach"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_nano_banana_image_request()
|
||||
};
|
||||
|
||||
+1
@@ -49,6 +49,7 @@ mod tests {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_nano_banana_2_image_request()
|
||||
|
||||
+3
@@ -51,6 +51,7 @@ mod tests {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_image_request()
|
||||
@@ -74,6 +75,7 @@ mod tests {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
..base_image_request()
|
||||
@@ -106,6 +108,7 @@ mod tests {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto), // edit mode: preserve source dimensions
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
..base_image_request()
|
||||
};
|
||||
|
||||
+1
@@ -52,6 +52,7 @@ mod tests {
|
||||
let tokens = vec![MediaFileToken::new_from_str("m_r45apt2swza6wp8j2z10237t92kkr8")];
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::SquareHd),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: None,
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
|
||||
+6
-2
@@ -51,6 +51,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
..base_seedream_4_image_request()
|
||||
@@ -80,6 +81,7 @@ mod tests {
|
||||
prompt: Some("Change the background to a sunny beach"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
..base_seedream_4_image_request()
|
||||
};
|
||||
@@ -104,7 +106,8 @@ mod tests {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a shiba walking through a cyberpunk city at night"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_seedream_4_image_request()
|
||||
@@ -139,7 +142,8 @@ mod tests {
|
||||
prompt: Some("Change the background to a sunny beach"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_seedream_4_image_request()
|
||||
};
|
||||
|
||||
+6
-2
@@ -51,6 +51,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night"),
|
||||
..base_seedream_4p5_image_request()
|
||||
@@ -80,6 +81,7 @@ mod tests {
|
||||
prompt: Some("Change the background to a spooky haunted house"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto2k),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
..base_seedream_4p5_image_request()
|
||||
};
|
||||
@@ -104,7 +106,8 @@ mod tests {
|
||||
println!("--- text-to-image aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night"),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_seedream_4p5_image_request()
|
||||
@@ -139,7 +142,8 @@ mod tests {
|
||||
prompt: Some("Change the background to a spooky haunted house"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_seedream_4p5_image_request()
|
||||
};
|
||||
|
||||
+1
@@ -47,6 +47,7 @@ mod tests {
|
||||
let client = get_artcraft_client();
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night"),
|
||||
..base_seedream_5_lite_image_request()
|
||||
|
||||
+1
@@ -75,6 +75,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+3
@@ -105,6 +105,7 @@ mod tests {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
resolution: Some(CommonResolution::TwoK),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cyberpunk city skyline at dusk, neon lights reflecting on rain-soaked streets"),
|
||||
..base_fal_image_request()
|
||||
@@ -129,6 +130,7 @@ mod tests {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a golden retriever surfing a wave, cinematic, 4K"),
|
||||
..base_fal_image_request()
|
||||
@@ -161,6 +163,7 @@ mod tests {
|
||||
image_inputs: Some(ImageListRef::Urls(&image_urls)),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto), // edit mode: preserve source dimensions
|
||||
resolution: Some(CommonResolution::TwoK),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
..base_fal_image_request()
|
||||
};
|
||||
|
||||
+4
@@ -1,5 +1,6 @@
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_image_model::CommonImageModel;
|
||||
use crate::api::common_quality::CommonQuality;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::provider::Provider;
|
||||
@@ -57,6 +58,9 @@ pub struct GenerateImageRequest<'a> {
|
||||
/// The aspect ratio to use.
|
||||
pub aspect_ratio: Option<CommonAspectRatio>,
|
||||
|
||||
/// The quality level for generation. Not all models use this.
|
||||
pub quality: Option<CommonQuality>,
|
||||
|
||||
/// How many images to generate.
|
||||
pub image_batch_count: Option<u16>,
|
||||
|
||||
|
||||
+3
@@ -268,6 +268,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_dev_image_request()
|
||||
@@ -295,6 +296,7 @@ mod tests {
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_1_dev_image_request()
|
||||
@@ -312,6 +314,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_dev_image_request()
|
||||
|
||||
+3
@@ -264,6 +264,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_schnell_image_request()
|
||||
@@ -291,6 +292,7 @@ mod tests {
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_1_schnell_image_request()
|
||||
@@ -308,6 +310,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_1_schnell_image_request()
|
||||
|
||||
+1
@@ -224,6 +224,7 @@ mod tests {
|
||||
let cases = [(1, N::One), (2, N::Two), (3, N::Three), (4, N::Four)];
|
||||
for (count, expected) in cases {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(count),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
..base_flux_2_lora_angles_image_request()
|
||||
|
||||
+3
@@ -268,6 +268,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
@@ -295,6 +296,7 @@ mod tests {
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
@@ -312,6 +314,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
|
||||
+3
@@ -271,6 +271,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
@@ -298,6 +299,7 @@ mod tests {
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
@@ -315,6 +317,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
|
||||
+26
-2
@@ -1,4 +1,5 @@
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_quality::CommonQuality;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
@@ -46,6 +47,10 @@ pub struct PlanArtcraftGptImage1<'a> {
|
||||
pub prompt: Option<&'a str>,
|
||||
/// Input images for image editing. None means text-to-image mode.
|
||||
pub image_inputs: Option<&'a Vec<MediaFileToken>>,
|
||||
/// Number of input images (0 for text-to-image). This is counted from either
|
||||
/// media tokens or hydrated URLs, so the cost estimator can use it without
|
||||
/// needing to inspect image_inputs (which is None for URL-form inputs).
|
||||
pub num_input_images: u64,
|
||||
pub image_size: Option<ArtcraftGptImage1Size>,
|
||||
/// Quality defaults to High when the request leaves it unspecified, matching
|
||||
/// the legacy storyteller-web handler defaults.
|
||||
@@ -60,15 +65,17 @@ pub fn plan_generate_image_artcraft_gpt_image_1<'a>(
|
||||
let strategy = request.request_mismatch_mitigation_strategy;
|
||||
|
||||
let image_inputs = resolve_image_list_ref(request.image_inputs)?;
|
||||
let num_input_images = count_input_images(request.image_inputs);
|
||||
let image_size = plan_image_size(request.aspect_ratio);
|
||||
let quality = plan_quality(request.quality);
|
||||
let num_images = plan_num_images(request.image_batch_count, strategy)?;
|
||||
|
||||
Ok(ImageGenerationPlan::ArtcraftGptImage1(PlanArtcraftGptImage1 {
|
||||
prompt: request.prompt,
|
||||
image_inputs,
|
||||
num_input_images,
|
||||
image_size,
|
||||
// Match the legacy handler default of High for both modes.
|
||||
quality: ArtcraftGptImage1Quality::High,
|
||||
quality,
|
||||
num_images,
|
||||
idempotency_token: request.get_or_generate_idempotency_token(),
|
||||
}))
|
||||
@@ -88,6 +95,23 @@ fn resolve_image_list_ref<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn count_input_images(image_inputs: Option<ImageListRef<'_>>) -> u64 {
|
||||
match image_inputs {
|
||||
None => 0,
|
||||
Some(ImageListRef::MediaFileTokens(tokens)) => tokens.len() as u64,
|
||||
Some(ImageListRef::Urls(urls)) => urls.len() as u64,
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_quality(quality: Option<CommonQuality>) -> ArtcraftGptImage1Quality {
|
||||
match quality {
|
||||
Some(CommonQuality::Low) => ArtcraftGptImage1Quality::Low,
|
||||
Some(CommonQuality::Medium) => ArtcraftGptImage1Quality::Medium,
|
||||
Some(CommonQuality::High) => ArtcraftGptImage1Quality::High,
|
||||
None => ArtcraftGptImage1Quality::High, // Legacy handler default
|
||||
}
|
||||
}
|
||||
|
||||
// GPT Image 1 image sizes: Square (1024x1024), Horizontal (1536x1024),
|
||||
// Vertical (1024x1536). All 17 CommonAspectRatio variants map to one of these
|
||||
// or None (let the legacy handler default to Square).
|
||||
|
||||
+3
@@ -294,6 +294,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_nano_banana_image_request()
|
||||
@@ -327,6 +328,7 @@ mod tests {
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_nano_banana_image_request()
|
||||
@@ -345,6 +347,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_nano_banana_image_request()
|
||||
|
||||
+3
@@ -357,6 +357,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_image_request()
|
||||
@@ -390,6 +391,7 @@ mod tests {
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_image_request()
|
||||
@@ -408,6 +410,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_image_request()
|
||||
|
||||
+1
@@ -226,6 +226,7 @@ mod tests {
|
||||
let cases = [(1, N::One), (2, N::Two), (3, N::Three), (4, N::Four)];
|
||||
for (count, expected) in cases {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(count),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
..base_qwen_edit_2511_angles_image_request()
|
||||
|
||||
+3
@@ -295,6 +295,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_seedream_4_image_request()
|
||||
@@ -322,6 +323,7 @@ mod tests {
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_seedream_4_image_request()
|
||||
@@ -336,6 +338,7 @@ mod tests {
|
||||
fn num_images_out_of_range_clamps_to_four() {
|
||||
for strategy in [RequestMismatchMitigationStrategy::PayMoreUpgrade, RequestMismatchMitigationStrategy::PayLessDowngrade] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_seedream_4_image_request()
|
||||
|
||||
+3
@@ -296,6 +296,7 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_seedream_4p5_image_request()
|
||||
@@ -323,6 +324,7 @@ mod tests {
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_seedream_4p5_image_request()
|
||||
@@ -337,6 +339,7 @@ mod tests {
|
||||
fn num_images_out_of_range_clamps_to_four() {
|
||||
for strategy in [RequestMismatchMitigationStrategy::PayMoreUpgrade, RequestMismatchMitigationStrategy::PayLessDowngrade] {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_seedream_4p5_image_request()
|
||||
|
||||
+13
-1
@@ -1,4 +1,5 @@
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_quality::CommonQuality;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
@@ -56,13 +57,14 @@ pub fn plan_generate_image_fal_gpt_image_1<'a>(
|
||||
let strategy = request.request_mismatch_mitigation_strategy;
|
||||
let image_urls = resolve_image_list_ref(request.image_inputs)?;
|
||||
let image_size = plan_image_size(request.aspect_ratio);
|
||||
let quality = plan_quality(request.quality);
|
||||
let num_images = plan_num_images(request.image_batch_count, strategy)?;
|
||||
|
||||
Ok(ImageGenerationPlan::FalGptImage1(PlanFalGptImage1 {
|
||||
prompt: request.prompt,
|
||||
image_urls,
|
||||
image_size,
|
||||
quality: FalGptImage1Quality::Medium,
|
||||
quality,
|
||||
num_images,
|
||||
}))
|
||||
}
|
||||
@@ -79,6 +81,15 @@ fn resolve_image_list_ref(
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_quality(quality: Option<CommonQuality>) -> FalGptImage1Quality {
|
||||
match quality {
|
||||
Some(CommonQuality::Low) => FalGptImage1Quality::Low,
|
||||
Some(CommonQuality::Medium) => FalGptImage1Quality::Medium,
|
||||
Some(CommonQuality::High) => FalGptImage1Quality::High,
|
||||
None => FalGptImage1Quality::Medium, // Fal API default
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_image_size(aspect_ratio: Option<CommonAspectRatio>) -> Option<FalGptImage1ImageSize> {
|
||||
use FalGptImage1ImageSize as S;
|
||||
match aspect_ratio {
|
||||
@@ -215,6 +226,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+6
-2
@@ -256,6 +256,7 @@ mod tests {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -478,7 +479,8 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(0),
|
||||
quality: None,
|
||||
image_batch_count: Some(0),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_fal_request()
|
||||
};
|
||||
@@ -511,6 +513,7 @@ mod tests {
|
||||
#[test]
|
||||
fn num_images_out_of_range_error_out() {
|
||||
let request = GenerateImageRequest {
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
..base_fal_request()
|
||||
@@ -529,7 +532,8 @@ mod tests {
|
||||
RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
] {
|
||||
let request = GenerateImageRequest {
|
||||
image_batch_count: Some(5),
|
||||
quality: None,
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: strategy,
|
||||
..base_fal_request()
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ pub fn base_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -55,6 +56,7 @@ pub fn base_fal_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -73,6 +75,7 @@ pub fn base_seedream_4_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -91,6 +94,7 @@ pub fn base_seedream_4p5_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -109,6 +113,7 @@ pub fn base_seedream_5_lite_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -127,6 +132,7 @@ pub fn base_nano_banana_2_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -145,6 +151,7 @@ pub fn base_nano_banana_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -163,6 +170,7 @@ pub fn base_gpt_image_1_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -181,6 +189,7 @@ pub fn base_gpt_image_1p5_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -199,6 +208,7 @@ pub fn base_flux_1_dev_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -217,6 +227,7 @@ pub fn base_flux_1_schnell_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -235,6 +246,7 @@ pub fn base_flux_pro_1p1_image_request() -> GenerateImageRequest<'static> {
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -253,6 +265,7 @@ pub fn base_qwen_edit_2511_angles_image_request() -> GenerateImageRequest<'stati
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -271,6 +284,7 @@ pub fn base_flux_2_lora_angles_image_request() -> GenerateImageRequest<'static>
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
@@ -289,6 +303,7 @@ pub fn base_flux_pro_1p1_ultra_image_request() -> GenerateImageRequest<'static>
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
|
||||
+1
@@ -45,6 +45,7 @@ pub async fn estimate_image_cost_handler(
|
||||
image_inputs: None, // TODO: Only some models charge for this - we'll need to add later.
|
||||
resolution: router_resolution,
|
||||
aspect_ratio: router_aspect_ratio,
|
||||
quality: None,
|
||||
image_batch_count: request.image_batch_count,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade,
|
||||
generation_mode_mismatch_strategy: Some(GenerationModeMismatchStrategy::GenerateAnyway),
|
||||
|
||||
+18
@@ -1,6 +1,7 @@
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_image_cost_and_generate_request::OmniGenImageCostAndGenerateRequest;
|
||||
use artcraft_router::api::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioRouter;
|
||||
use artcraft_router::api::common_image_model::CommonImageModel as CommonImageModelRouter;
|
||||
use artcraft_router::api::common_quality::CommonQuality as CommonQualityRouter;
|
||||
use artcraft_router::api::common_resolution::CommonResolution as CommonResolutionRouter;
|
||||
use artcraft_router::api::image_list_ref::ImageListRef;
|
||||
use artcraft_router::api::provider::Provider;
|
||||
@@ -8,6 +9,7 @@ use artcraft_router::client::request_mismatch_mitigation_strategy::RequestMismat
|
||||
use artcraft_router::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum;
|
||||
use enums::common::generation::common_image_model::CommonImageModel as CommonImageModelEnum;
|
||||
use enums::common::generation::common_quality::CommonQuality as CommonQualityEnum;
|
||||
use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum;
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
@@ -45,6 +47,17 @@ fn convert_resolution(
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_quality(
|
||||
quality: &CommonQualityEnum,
|
||||
) -> Result<CommonQualityRouter, AdvancedCommonWebError> {
|
||||
let json = serde_json::to_string(quality)?;
|
||||
serde_json::from_str(&json).map_err(|e| {
|
||||
AdvancedCommonWebError::BadInputWithSimpleMessage(
|
||||
format!("Unsupported quality: {}", e),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hydrate_to_router_request(
|
||||
request: &OmniGenImageCostAndGenerateRequest,
|
||||
) -> Result<GenerateImageRequest<'_>, AdvancedCommonWebError> {
|
||||
@@ -63,6 +76,10 @@ pub fn hydrate_to_router_request(
|
||||
.map(convert_resolution)
|
||||
.transpose()?;
|
||||
|
||||
let quality = request.quality.as_ref()
|
||||
.map(convert_quality)
|
||||
.transpose()?;
|
||||
|
||||
Ok(GenerateImageRequest {
|
||||
model,
|
||||
provider: Provider::Artcraft,
|
||||
@@ -71,6 +88,7 @@ pub fn hydrate_to_router_request(
|
||||
.map(ImageListRef::MediaFileTokens),
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
quality,
|
||||
image_batch_count: request.image_batch_count,
|
||||
horizontal_angle: request.horizontal_angle,
|
||||
vertical_angle: request.vertical_angle,
|
||||
|
||||
+1
@@ -144,6 +144,7 @@ pub fn distill_image_request(
|
||||
image_inputs: image_inputs_static,
|
||||
resolution: initial.resolution,
|
||||
aspect_ratio: initial.aspect_ratio,
|
||||
quality: initial.quality,
|
||||
image_batch_count: initial.image_batch_count,
|
||||
horizontal_angle: initial.horizontal_angle,
|
||||
vertical_angle: initial.vertical_angle,
|
||||
|
||||
+482
-531
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user