mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-09 19:41:32 +00:00
artcraft_router artcraft
This commit is contained in:
1 parent
78899550cd
commit
6e3074d4e6
15 files changed
+565
No files matched your search
+6
@@ -16,6 +16,9 @@ use crate::generate::generate_image::providers::artcraft::flux_pro_1p1_ultra::bu
|
||||
use crate::generate::generate_image::providers::artcraft::gpt_image_1::build::build_artcraft_gpt_image_1;
|
||||
use crate::generate::generate_image::providers::artcraft::gpt_image_1p5::build::build_artcraft_gpt_image_1p5;
|
||||
use crate::generate::generate_image::providers::artcraft::gpt_image_2::build::build_artcraft_gpt_image_2;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7::build::build_artcraft_midjourney_7;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7_niji::build::build_artcraft_midjourney_7_niji;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_8::build::build_artcraft_midjourney_8;
|
||||
use crate::generate::generate_image::providers::artcraft::nano_banana::build::build_artcraft_nano_banana;
|
||||
use crate::generate::generate_image::providers::artcraft::nano_banana_2::build::build_artcraft_nano_banana_2;
|
||||
use crate::generate::generate_image::providers::artcraft::nano_banana_pro::build::build_artcraft_nano_banana_pro;
|
||||
@@ -118,6 +121,9 @@ impl GenerateImageRequestBuilder {
|
||||
(RouterProvider::Artcraft, RouterImageModel::Seedream5Lite) => build_artcraft_seedream_5_lite(self),
|
||||
(RouterProvider::Artcraft, RouterImageModel::QwenEdit2511Angles) => build_artcraft_qwen_edit_2511_angles(self),
|
||||
(RouterProvider::Artcraft, RouterImageModel::Flux2LoraAngles) => build_artcraft_flux_2_lora_angles(self),
|
||||
(RouterProvider::Artcraft, RouterImageModel::Midjourney7) => build_artcraft_midjourney_7(self),
|
||||
(RouterProvider::Artcraft, RouterImageModel::Midjourney7Niji) => build_artcraft_midjourney_7_niji(self),
|
||||
(RouterProvider::Artcraft, RouterImageModel::Midjourney8) => build_artcraft_midjourney_8(self),
|
||||
|
||||
(RouterProvider::Fal, RouterImageModel::Flux1Dev) => build_fal_flux_1_dev(self),
|
||||
(RouterProvider::Fal, RouterImageModel::Flux1Schnell) => build_fal_flux_1_schnell(self),
|
||||
|
||||
+33
@@ -19,6 +19,12 @@ use crate::generate::generate_image::providers::artcraft::gpt_image_1p5::cost::A
|
||||
use crate::generate::generate_image::providers::artcraft::gpt_image_1p5::request::ArtcraftGptImage1p5RequestState;
|
||||
use crate::generate::generate_image::providers::artcraft::gpt_image_2::cost::ArtcraftGptImage2CostState;
|
||||
use crate::generate::generate_image::providers::artcraft::gpt_image_2::request::ArtcraftGptImage2RequestState;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7::cost::ArtcraftMidjourney7CostState;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7::request::ArtcraftMidjourney7RequestState;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7_niji::cost::ArtcraftMidjourney7NijiCostState;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7_niji::request::ArtcraftMidjourney7NijiRequestState;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_8::cost::ArtcraftMidjourney8CostState;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_8::request::ArtcraftMidjourney8RequestState;
|
||||
use crate::generate::generate_image::providers::artcraft::nano_banana::cost::ArtcraftNanoBananaCostState;
|
||||
use crate::generate::generate_image::providers::artcraft::nano_banana::request::ArtcraftNanoBananaRequestState;
|
||||
use crate::generate::generate_image::providers::artcraft::nano_banana_2::cost::ArtcraftNanoBanana2CostState;
|
||||
@@ -88,6 +94,9 @@ pub enum ImageGenerationRequest {
|
||||
ArtcraftSeedream5Lite(ArtcraftSeedream5LiteRequestState),
|
||||
ArtcraftQwenEdit2511Angles(ArtcraftQwenEdit2511AnglesRequestState),
|
||||
ArtcraftFlux2LoraAngles(ArtcraftFlux2LoraAnglesRequestState),
|
||||
ArtcraftMidjourney7(ArtcraftMidjourney7RequestState),
|
||||
ArtcraftMidjourney7Niji(ArtcraftMidjourney7NijiRequestState),
|
||||
ArtcraftMidjourney8(ArtcraftMidjourney8RequestState),
|
||||
|
||||
// ── Fal provider ──
|
||||
FalFlux1Dev(FalFlux1DevRequestState),
|
||||
@@ -130,6 +139,9 @@ impl ImageGenerationRequest {
|
||||
Self::ArtcraftSeedream5Lite(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftQwenEdit2511Angles(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftFlux2LoraAngles(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftMidjourney7(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftMidjourney7Niji(_) => RouterProvider::Artcraft,
|
||||
Self::ArtcraftMidjourney8(_) => RouterProvider::Artcraft,
|
||||
|
||||
Self::FalFlux1Dev(_) => RouterProvider::Fal,
|
||||
Self::FalFlux1Schnell(_) => RouterProvider::Fal,
|
||||
@@ -201,6 +213,15 @@ impl ImageGenerationRequest {
|
||||
Self::ArtcraftFlux2LoraAngles(request) => {
|
||||
Ok(ArtcraftFlux2LoraAnglesCostState::from_request(request).estimate_cost())
|
||||
}
|
||||
Self::ArtcraftMidjourney7(request) => {
|
||||
Ok(ArtcraftMidjourney7CostState::from_request(request).estimate_cost())
|
||||
}
|
||||
Self::ArtcraftMidjourney7Niji(request) => {
|
||||
Ok(ArtcraftMidjourney7NijiCostState::from_request(request).estimate_cost())
|
||||
}
|
||||
Self::ArtcraftMidjourney8(request) => {
|
||||
Ok(ArtcraftMidjourney8CostState::from_request(request).estimate_cost())
|
||||
}
|
||||
|
||||
// ── Fal ──
|
||||
Self::FalFlux1Dev(request) => Ok(FalFlux1DevCostState::from_request(request).estimate_cost()),
|
||||
@@ -288,6 +309,18 @@ impl ImageGenerationRequest {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
request.send(artcraft_client).await
|
||||
}
|
||||
Self::ArtcraftMidjourney7(request) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
request.send(artcraft_client).await
|
||||
}
|
||||
Self::ArtcraftMidjourney7Niji(request) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
request.send(artcraft_client).await
|
||||
}
|
||||
Self::ArtcraftMidjourney8(request) => {
|
||||
let artcraft_client = client.get_artcraft_client_ref()?;
|
||||
request.send(artcraft_client).await
|
||||
}
|
||||
|
||||
// ── Fal ──
|
||||
Self::FalFlux1Dev(request) => {
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
use enums::common::generation::common_image_model::CommonImageModel as CommonImageModelEnum;
|
||||
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder;
|
||||
use crate::generate::generate_image::image_generation_draft_or_request::ImageGenerationDraftOrRequest;
|
||||
use crate::generate::generate_image::image_generation_request::ImageGenerationRequest;
|
||||
use crate::generate::generate_image::providers::artcraft::build_common::build_artcraft_omni_image_request;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7::request::ArtcraftMidjourney7RequestState;
|
||||
|
||||
pub fn build_artcraft_midjourney_7(
|
||||
builder: GenerateImageRequestBuilder,
|
||||
) -> Result<ImageGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
let request = build_artcraft_omni_image_request(builder, CommonImageModelEnum::Midjourney7)?;
|
||||
Ok(ImageGenerationDraftOrRequest::Request(
|
||||
ImageGenerationRequest::ArtcraftMidjourney7(ArtcraftMidjourney7RequestState { request }),
|
||||
))
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7::request::ArtcraftMidjourney7RequestState;
|
||||
|
||||
/// Cost state for Artcraft Midjourney 7. Pricing mirrors the Kinovi
|
||||
/// midjourney_7 provider 1:1 in USD cents — ArtCraft credits map cent-for-cent.
|
||||
///
|
||||
/// | batch | credits | USD cents |
|
||||
/// |-------|---------|-----------|
|
||||
/// | 1 | 6 | 6¢ |
|
||||
/// | 2 | 12 | 12¢ |
|
||||
/// | 3 | 19 | 19¢ | (interpolated; Kinovi doesn't expose 3)
|
||||
/// | 4 | 25 | 25¢ |
|
||||
///
|
||||
/// Kinovi-side math: 12 Kinovi credits per Midjourney task; 193 Kinovi
|
||||
/// credits per USD. Kinovi rounds to nearest cent, so batches 1/2/4 land
|
||||
/// on 6/12/25. Batch 3 is `ceil((12 × 3 / 193) × 100) = 19`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftMidjourney7CostState {
|
||||
pub num_images: u16,
|
||||
}
|
||||
|
||||
impl ArtcraftMidjourney7CostState {
|
||||
pub fn from_request(request: &ArtcraftMidjourney7RequestState) -> Self {
|
||||
Self {
|
||||
num_images: request.request.image_batch_count.unwrap_or(1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> ImageGenerationCostEstimate {
|
||||
let cost_in_usd_cents = midjourney_cost_for_batch(self.num_images);
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
cost_in_usd_cents: Some(cost_in_usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the customer-facing midjourney cost in USD cents for a given
|
||||
/// batch count. Shared between v7, v7-niji, and v8 (all three Midjourney
|
||||
/// models have identical pricing on Kinovi).
|
||||
pub(crate) fn midjourney_cost_for_batch(batch_count: u16) -> u64 {
|
||||
match batch_count {
|
||||
0 => 0, // Defensive; the builder rejects zero before reaching cost.
|
||||
1 => 6,
|
||||
2 => 12,
|
||||
3 => 19,
|
||||
4 => 25,
|
||||
// Should never happen — `plan_batch_count` clamps to 1..=4.
|
||||
_ => 25,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::router_image_model::RouterImageModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder;
|
||||
|
||||
fn cost_cents(image_batch_count: u16) -> u64 {
|
||||
let builder = GenerateImageRequestBuilder {
|
||||
model: RouterImageModel::Midjourney7,
|
||||
provider: RouterProvider::Artcraft,
|
||||
prompt: Some("a corgi astronaut".to_string()),
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
zoom: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
builder.build2().unwrap().estimate_cost().unwrap().cost_in_usd_cents.unwrap()
|
||||
}
|
||||
|
||||
fn cost_credits(image_batch_count: u16) -> u64 {
|
||||
let builder = GenerateImageRequestBuilder {
|
||||
model: RouterImageModel::Midjourney7,
|
||||
provider: RouterProvider::Artcraft,
|
||||
prompt: Some("a corgi astronaut".to_string()),
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
zoom: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
builder.build2().unwrap().estimate_cost().unwrap().cost_in_credits.unwrap()
|
||||
}
|
||||
|
||||
// ── Spot checks: each supported batch size ──
|
||||
|
||||
#[test]
|
||||
fn one_image_is_six_cents() { assert_eq!(cost_cents(1), 6); }
|
||||
|
||||
#[test]
|
||||
fn two_images_is_twelve_cents() { assert_eq!(cost_cents(2), 12); }
|
||||
|
||||
#[test]
|
||||
fn three_images_is_nineteen_cents() { assert_eq!(cost_cents(3), 19); }
|
||||
|
||||
#[test]
|
||||
fn four_images_is_twentyfive_cents() { assert_eq!(cost_cents(4), 25); }
|
||||
|
||||
// ── Credits track USD cents exactly (1 credit = 1 cent) ──
|
||||
|
||||
#[test]
|
||||
fn credits_equal_usd_cents() {
|
||||
for batch in 1..=4u16 {
|
||||
assert_eq!(cost_credits(batch), cost_cents(batch),
|
||||
"credits should equal usd cents at batch {}", batch);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 1:1 cost parity with Kinovi for batches 1/2/4 ──
|
||||
//
|
||||
// Kinovi midjourney_7 returns 6/12/25¢ for batches 1/2/4. The artcraft
|
||||
// provider must match those values byte-for-byte so users pay the same.
|
||||
|
||||
mod kinovi_parity {
|
||||
use super::*;
|
||||
use crate::generate::generate_image::providers::kinovi::midjourney_7::cost::KinoviMidjourney7CostState;
|
||||
use seedance2pro_client::generate::image::generate_midjourney_v7::KinoviMidjourneyBatchCount;
|
||||
|
||||
#[test]
|
||||
fn batch_1_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7CostState { batch_count: KinoviMidjourneyBatchCount::One }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(1), kinovi.cost_in_usd_cents.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7CostState { batch_count: KinoviMidjourneyBatchCount::Two }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(2), kinovi.cost_in_usd_cents.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7CostState { batch_count: KinoviMidjourneyBatchCount::Four }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(4), kinovi.cost_in_usd_cents.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// ── Monotonicity ──
|
||||
|
||||
#[test]
|
||||
fn cost_is_monotonically_increasing() {
|
||||
let c1 = cost_cents(1);
|
||||
let c2 = cost_cents(2);
|
||||
let c3 = cost_cents(3);
|
||||
let c4 = cost_cents(4);
|
||||
assert!(c1 < c2, "{} < {}", c1, c2);
|
||||
assert!(c2 < c3, "{} < {}", c2, c3);
|
||||
assert!(c3 < c4, "{} < {}", c3, c4);
|
||||
}
|
||||
|
||||
// ── Flags ──
|
||||
|
||||
#[test]
|
||||
fn cost_flags_are_correct() {
|
||||
let cost = ArtcraftMidjourney7CostState { num_images: 1 }.estimate_cost();
|
||||
assert!(!cost.is_free);
|
||||
assert!(!cost.is_unlimited);
|
||||
assert!(!cost.is_rate_limited);
|
||||
assert!(!cost.has_watermark);
|
||||
assert_eq!(cost.failures_are_refunded, None);
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_image_cost_and_generate_request::OmniGenImageCostAndGenerateRequest;
|
||||
|
||||
use crate::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_image::generate_image_response::GenerateImageResponse;
|
||||
use crate::generate::generate_image::providers::artcraft::request_common::send_artcraft_omni_image_request;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftMidjourney7RequestState {
|
||||
pub request: OmniGenImageCostAndGenerateRequest,
|
||||
}
|
||||
|
||||
impl ArtcraftMidjourney7RequestState {
|
||||
pub async fn send(&self, client: &RouterArtcraftClient) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
send_artcraft_omni_image_request(&self.request, client).await
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
use enums::common::generation::common_image_model::CommonImageModel as CommonImageModelEnum;
|
||||
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder;
|
||||
use crate::generate::generate_image::image_generation_draft_or_request::ImageGenerationDraftOrRequest;
|
||||
use crate::generate::generate_image::image_generation_request::ImageGenerationRequest;
|
||||
use crate::generate::generate_image::providers::artcraft::build_common::build_artcraft_omni_image_request;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7_niji::request::ArtcraftMidjourney7NijiRequestState;
|
||||
|
||||
pub fn build_artcraft_midjourney_7_niji(
|
||||
builder: GenerateImageRequestBuilder,
|
||||
) -> Result<ImageGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
let request = build_artcraft_omni_image_request(builder, CommonImageModelEnum::Midjourney7Niji)?;
|
||||
Ok(ImageGenerationDraftOrRequest::Request(
|
||||
ImageGenerationRequest::ArtcraftMidjourney7Niji(ArtcraftMidjourney7NijiRequestState { request }),
|
||||
))
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7::cost::midjourney_cost_for_batch;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7_niji::request::ArtcraftMidjourney7NijiRequestState;
|
||||
|
||||
/// Cost state for Artcraft Midjourney 7 Niji. Pricing is identical to v7
|
||||
/// and v8 on Kinovi, so we share the per-batch table via
|
||||
/// `midjourney_cost_for_batch`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftMidjourney7NijiCostState {
|
||||
pub num_images: u16,
|
||||
}
|
||||
|
||||
impl ArtcraftMidjourney7NijiCostState {
|
||||
pub fn from_request(request: &ArtcraftMidjourney7NijiRequestState) -> Self {
|
||||
Self {
|
||||
num_images: request.request.image_batch_count.unwrap_or(1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> ImageGenerationCostEstimate {
|
||||
let cost_in_usd_cents = midjourney_cost_for_batch(self.num_images);
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
cost_in_usd_cents: Some(cost_in_usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::router_image_model::RouterImageModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder;
|
||||
|
||||
fn cost_cents(image_batch_count: u16) -> u64 {
|
||||
let builder = GenerateImageRequestBuilder {
|
||||
model: RouterImageModel::Midjourney7Niji,
|
||||
provider: RouterProvider::Artcraft,
|
||||
prompt: Some("anime forest".to_string()),
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
zoom: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
builder.build2().unwrap().estimate_cost().unwrap().cost_in_usd_cents.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_1_is_6() { assert_eq!(cost_cents(1), 6); }
|
||||
|
||||
#[test]
|
||||
fn batch_2_is_12() { assert_eq!(cost_cents(2), 12); }
|
||||
|
||||
#[test]
|
||||
fn batch_3_is_19() { assert_eq!(cost_cents(3), 19); }
|
||||
|
||||
#[test]
|
||||
fn batch_4_is_25() { assert_eq!(cost_cents(4), 25); }
|
||||
|
||||
#[test]
|
||||
fn niji_pricing_matches_v7() {
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7::cost::ArtcraftMidjourney7CostState;
|
||||
for batch in 1..=4u16 {
|
||||
let v7 = ArtcraftMidjourney7CostState { num_images: batch }.estimate_cost();
|
||||
let niji = ArtcraftMidjourney7NijiCostState { num_images: batch }.estimate_cost();
|
||||
assert_eq!(v7.cost_in_usd_cents, niji.cost_in_usd_cents, "batch {}", batch);
|
||||
assert_eq!(v7.cost_in_credits, niji.cost_in_credits, "batch {}", batch);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 1:1 cost parity with Kinovi 7-niji ──
|
||||
|
||||
mod kinovi_parity {
|
||||
use super::*;
|
||||
use crate::generate::generate_image::providers::kinovi::midjourney_7_niji::cost::KinoviMidjourney7NijiCostState;
|
||||
use seedance2pro_client::generate::image::generate_midjourney_v7_niji::KinoviMidjourneyBatchCount;
|
||||
|
||||
#[test]
|
||||
fn batch_1_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7NijiCostState { batch_count: KinoviMidjourneyBatchCount::One }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(1), kinovi.cost_in_usd_cents.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7NijiCostState { batch_count: KinoviMidjourneyBatchCount::Two }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(2), kinovi.cost_in_usd_cents.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7NijiCostState { batch_count: KinoviMidjourneyBatchCount::Four }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(4), kinovi.cost_in_usd_cents.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_image_cost_and_generate_request::OmniGenImageCostAndGenerateRequest;
|
||||
|
||||
use crate::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_image::generate_image_response::GenerateImageResponse;
|
||||
use crate::generate::generate_image::providers::artcraft::request_common::send_artcraft_omni_image_request;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftMidjourney7NijiRequestState {
|
||||
pub request: OmniGenImageCostAndGenerateRequest,
|
||||
}
|
||||
|
||||
impl ArtcraftMidjourney7NijiRequestState {
|
||||
pub async fn send(&self, client: &RouterArtcraftClient) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
send_artcraft_omni_image_request(&self.request, client).await
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
use enums::common::generation::common_image_model::CommonImageModel as CommonImageModelEnum;
|
||||
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder;
|
||||
use crate::generate::generate_image::image_generation_draft_or_request::ImageGenerationDraftOrRequest;
|
||||
use crate::generate::generate_image::image_generation_request::ImageGenerationRequest;
|
||||
use crate::generate::generate_image::providers::artcraft::build_common::build_artcraft_omni_image_request;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_8::request::ArtcraftMidjourney8RequestState;
|
||||
|
||||
pub fn build_artcraft_midjourney_8(
|
||||
builder: GenerateImageRequestBuilder,
|
||||
) -> Result<ImageGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
let request = build_artcraft_omni_image_request(builder, CommonImageModelEnum::Midjourney8)?;
|
||||
Ok(ImageGenerationDraftOrRequest::Request(
|
||||
ImageGenerationRequest::ArtcraftMidjourney8(ArtcraftMidjourney8RequestState { request }),
|
||||
))
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
use crate::generate::generate_image::image_generation_cost_estimate::ImageGenerationCostEstimate;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7::cost::midjourney_cost_for_batch;
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_8::request::ArtcraftMidjourney8RequestState;
|
||||
|
||||
/// Cost state for Artcraft Midjourney 8. Pricing is identical to v7 and
|
||||
/// v7-niji on Kinovi; shares the per-batch table via
|
||||
/// `midjourney_cost_for_batch`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftMidjourney8CostState {
|
||||
pub num_images: u16,
|
||||
}
|
||||
|
||||
impl ArtcraftMidjourney8CostState {
|
||||
pub fn from_request(request: &ArtcraftMidjourney8RequestState) -> Self {
|
||||
Self {
|
||||
num_images: request.request.image_batch_count.unwrap_or(1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> ImageGenerationCostEstimate {
|
||||
let cost_in_usd_cents = midjourney_cost_for_batch(self.num_images);
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
cost_in_usd_cents: Some(cost_in_usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::api::router_image_model::RouterImageModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::generate::generate_image::generate_image_request_builder::GenerateImageRequestBuilder;
|
||||
|
||||
fn cost_cents(image_batch_count: u16) -> u64 {
|
||||
let builder = GenerateImageRequestBuilder {
|
||||
model: RouterImageModel::Midjourney8,
|
||||
provider: RouterProvider::Artcraft,
|
||||
prompt: Some("a haunted mansion".to_string()),
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: Some(image_batch_count),
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
zoom: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
};
|
||||
builder.build2().unwrap().estimate_cost().unwrap().cost_in_usd_cents.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_1_is_6() { assert_eq!(cost_cents(1), 6); }
|
||||
|
||||
#[test]
|
||||
fn batch_2_is_12() { assert_eq!(cost_cents(2), 12); }
|
||||
|
||||
#[test]
|
||||
fn batch_3_is_19() { assert_eq!(cost_cents(3), 19); }
|
||||
|
||||
#[test]
|
||||
fn batch_4_is_25() { assert_eq!(cost_cents(4), 25); }
|
||||
|
||||
#[test]
|
||||
fn v8_pricing_matches_v7() {
|
||||
use crate::generate::generate_image::providers::artcraft::midjourney_7::cost::ArtcraftMidjourney7CostState;
|
||||
for batch in 1..=4u16 {
|
||||
let v7 = ArtcraftMidjourney7CostState { num_images: batch }.estimate_cost();
|
||||
let v8 = ArtcraftMidjourney8CostState { num_images: batch }.estimate_cost();
|
||||
assert_eq!(v7.cost_in_usd_cents, v8.cost_in_usd_cents, "batch {}", batch);
|
||||
assert_eq!(v7.cost_in_credits, v8.cost_in_credits, "batch {}", batch);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 1:1 cost parity with Kinovi v8 ──
|
||||
|
||||
mod kinovi_parity {
|
||||
use super::*;
|
||||
use crate::generate::generate_image::providers::kinovi::midjourney_8::cost::KinoviMidjourney8CostState;
|
||||
use seedance2pro_client::generate::image::generate_midjourney_v8::KinoviMidjourneyBatchCount;
|
||||
|
||||
#[test]
|
||||
fn batch_1_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney8CostState { batch_count: KinoviMidjourneyBatchCount::One }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(1), kinovi.cost_in_usd_cents.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney8CostState { batch_count: KinoviMidjourneyBatchCount::Two }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(2), kinovi.cost_in_usd_cents.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney8CostState { batch_count: KinoviMidjourneyBatchCount::Four }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(4), kinovi.cost_in_usd_cents.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_image_cost_and_generate_request::OmniGenImageCostAndGenerateRequest;
|
||||
|
||||
use crate::client::router_artcraft_client::RouterArtcraftClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::generate::generate_image::generate_image_response::GenerateImageResponse;
|
||||
use crate::generate::generate_image::providers::artcraft::request_common::send_artcraft_omni_image_request;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftMidjourney8RequestState {
|
||||
pub request: OmniGenImageCostAndGenerateRequest,
|
||||
}
|
||||
|
||||
impl ArtcraftMidjourney8RequestState {
|
||||
pub async fn send(&self, client: &RouterArtcraftClient) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
send_artcraft_omni_image_request(&self.request, client).await
|
||||
}
|
||||
}
|
||||
+3
@@ -8,6 +8,9 @@ pub mod flux_pro_1p1_ultra;
|
||||
pub mod gpt_image_1;
|
||||
pub mod gpt_image_1p5;
|
||||
pub mod gpt_image_2;
|
||||
pub mod midjourney_7;
|
||||
pub mod midjourney_7_niji;
|
||||
pub mod midjourney_8;
|
||||
pub mod nano_banana;
|
||||
pub mod nano_banana_2;
|
||||
pub mod nano_banana_pro;
|
||||
|
||||
Reference in new issue
Block a user