From fef83ad2718badaada274b5fca3b5fea4fa9ac6f Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 25 Jun 2026 15:33:51 -0400 Subject: [PATCH] mini models in api gateway (#1697) --- .../src/api/router_video_model.rs | 29 + .../generate_video_request_builder.rs | 10 + .../generate_video/providers/artcraft/mod.rs | 3 + .../artcraft/seedance_2p0_bp_mini/build.rs | 225 ++++++++ .../artcraft/seedance_2p0_bp_mini/cost.rs | 176 ++++++ .../artcraft/seedance_2p0_bp_mini/mod.rs | 3 + .../artcraft/seedance_2p0_bp_mini/request.rs | 86 +++ .../artcraft/seedance_2p0_bpu_mini/build.rs | 225 ++++++++ .../artcraft/seedance_2p0_bpu_mini/cost.rs | 176 ++++++ .../artcraft/seedance_2p0_bpu_mini/mod.rs | 3 + .../artcraft/seedance_2p0_bpu_mini/request.rs | 86 +++ .../artcraft/seedance_2p0_mini/build.rs | 225 ++++++++ .../artcraft/seedance_2p0_mini/cost.rs | 176 ++++++ .../artcraft/seedance_2p0_mini/mod.rs | 3 + .../artcraft/seedance_2p0_mini/request.rs | 86 +++ .../providers/artcraft/seedance_common.rs | 253 +++++++++ .../generate_video/providers/kinovi/mod.rs | 1 + .../kinovi/seedance_2p0_mini/build.rs | 510 ++++++++++++++++++ .../kinovi/seedance_2p0_mini/cost.rs | 378 +++++++++++++ .../kinovi/seedance_2p0_mini/draft.rs | 99 ++++ .../providers/kinovi/seedance_2p0_mini/mod.rs | 4 + .../kinovi/seedance_2p0_mini/request.rs | 37 ++ .../generate_video/video_generation_draft.rs | 9 + .../video_generation_request.rs | 36 ++ ...router_video_request_to_artcraft_prompt.rs | 3 + .../inference_model_type.rs | 27 + .../common/generation/common_model_type.rs | 35 +- .../common/generation/common_video_model.rs | 24 + .../by_type/seedance_2p0_video_models.rs | 141 ++++- .../video/estimate_video_cost_handler.rs | 3 + .../video/omni_api_video_generate_handler.rs | 2 + .../video/omni_gen_video_generate_handler.rs | 2 + .../video/pipeline_v2/run_pipeline_v2.rs | 3 + frontend/apps/artcraft-webapp/src/main.tsx | 2 +- .../libs/components/moodboard/tsconfig.json | 9 +- .../components/moodboard/tsconfig.lib.json | 9 +- frontend/package-lock.json | 405 ++++---------- 37 files changed, 3192 insertions(+), 312 deletions(-) create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/build.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/cost.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/mod.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/request.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/build.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/cost.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/mod.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/request.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/build.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/cost.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/mod.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/request.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/build.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/cost.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/draft.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/mod.rs create mode 100644 crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/request.rs diff --git a/crates/api_clients/artcraft/artcraft_router/src/api/router_video_model.rs b/crates/api_clients/artcraft/artcraft_router/src/api/router_video_model.rs index 3926a3b739..6ce13d8f40 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/api/router_video_model.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/api/router_video_model.rs @@ -65,6 +65,15 @@ pub enum RouterVideoModel { #[serde(rename = "seedance_2p0_bpu_fast")] Seedance2p0BytePlusUltraFast, + #[serde(rename = "seedance_2p0_mini")] + Seedance2p0Mini, + + #[serde(rename = "seedance_2p0_bp_mini")] + Seedance2p0BytePlusMini, + + #[serde(rename = "seedance_2p0_bpu_mini")] + Seedance2p0BytePlusUltraMini, + #[serde(rename = "happy_horse_1p0")] HappyHorse1p0, @@ -95,3 +104,23 @@ pub enum RouterVideoModel { #[serde(rename = "preview_model_fast")] PreviewModelFast, } + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_serde_round_trip(model: RouterVideoModel, expected: &str) { + let json = serde_json::to_string(&model).unwrap(); + assert_eq!(json, format!("\"{expected}\"")); + let parsed: RouterVideoModel = serde_json::from_str(&json).unwrap(); + // RouterVideoModel isn't PartialEq, so round-trip back to the wire form. + assert_eq!(serde_json::to_string(&parsed).unwrap(), json); + } + + #[test] + fn seedance_2p0_mini_variants_serialize() { + assert_serde_round_trip(RouterVideoModel::Seedance2p0Mini, "seedance_2p0_mini"); + assert_serde_round_trip(RouterVideoModel::Seedance2p0BytePlusMini, "seedance_2p0_bp_mini"); + assert_serde_round_trip(RouterVideoModel::Seedance2p0BytePlusUltraMini, "seedance_2p0_bpu_mini"); + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs index 70e21a6258..9c6b726022 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs @@ -32,6 +32,9 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp::build use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_fast::build::build_artcraft_seedance_2p0_bp_fast; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu::build::build_artcraft_seedance_2p0_bpu; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_fast::build::build_artcraft_seedance_2p0_bpu_fast; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::build::build_artcraft_seedance_2p0_mini; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::build::build_artcraft_seedance_2p0_bp_mini; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::build::build_artcraft_seedance_2p0_bpu_mini; use crate::generate::generate_video::providers::artcraft::sora_2::build::build_artcraft_sora_2; use crate::generate::generate_video::providers::artcraft::sora_2_pro::build::build_artcraft_sora_2_pro; use crate::generate::generate_video::providers::artcraft::veo_2::build::build_artcraft_veo_2; @@ -62,6 +65,7 @@ use crate::generate::generate_video::providers::fal::veo_3_fast::build::build_fa use crate::generate::generate_video::providers::fal::veo_3p1::build::build_fal_veo_3p1; use crate::generate::generate_video::providers::fal::veo_3p1_fast::build::build_fal_veo_3p1_fast; use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::build::build_kinovi_seedance_2p0_fast; +use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::build::build_kinovi_seedance_2p0_mini; use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; /// RouterProvider-agnostic video generation request. Distilled by `build2()` into a @@ -176,6 +180,9 @@ impl GenerateVideoRequestBuilder { (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusFast) => build_artcraft_seedance_2p0_bp_fast(self), (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusUltra) => build_artcraft_seedance_2p0_bpu(self), (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusUltraFast) => build_artcraft_seedance_2p0_bpu_fast(self), + (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0Mini) => build_artcraft_seedance_2p0_mini(self), + (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusMini) => build_artcraft_seedance_2p0_bp_mini(self), + (RouterProvider::Artcraft, RouterVideoModel::Seedance2p0BytePlusUltraMini) => build_artcraft_seedance_2p0_bpu_mini(self), (RouterProvider::Artcraft, RouterVideoModel::PreviewModel) => build_artcraft_preview_model(self), (RouterProvider::Artcraft, RouterVideoModel::PreviewModelFast) => build_artcraft_preview_model_fast(self), (RouterProvider::Artcraft, RouterVideoModel::Seedance10Lite) => build_artcraft_seedance_1p0_lite(self), @@ -214,6 +221,9 @@ impl GenerateVideoRequestBuilder { (RouterProvider::Seedance2Pro, RouterVideoModel::HappyHorse1p0) => build_kinovi_happy_horse_1p0(self), (RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0) => build_kinovi_seedance_2p0(self), (RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0Fast) => build_kinovi_seedance_2p0_fast(self), + (RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0Mini) => build_kinovi_seedance_2p0_mini(self), + (RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0BytePlusMini) => build_kinovi_seedance_2p0_mini(self), + (RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0BytePlusUltraMini) => build_kinovi_seedance_2p0_mini(self), _ => self.unsupported_provider_and_model(), } } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs index 13aaf8b336..b6db1c4411 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs @@ -26,6 +26,9 @@ pub mod seedance_2p0_bp; pub mod seedance_2p0_bp_fast; pub mod seedance_2p0_bpu; pub mod seedance_2p0_bpu_fast; +pub mod seedance_2p0_mini; +pub mod seedance_2p0_bp_mini; +pub mod seedance_2p0_bpu_mini; pub mod sora_2; pub mod sora_2_pro; pub mod veo_2; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/build.rs new file mode 100644 index 0000000000..59ac7fb3d3 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/build.rs @@ -0,0 +1,225 @@ +use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; + +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; +use crate::generate::generate_video::providers::artcraft::build_common::{ + build_artcraft_omni_video_request, SupportedResolutions, UltraWideSupport, +}; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::request::ArtcraftSeedance2p0BytePlusMiniRequestState; +use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; +use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; + +pub fn build_artcraft_seedance_2p0_bp_mini(builder: GenerateVideoRequestBuilder) -> Result { + // Seedance 2.0 Mini supports 480p and 720p only (higher resolutions downgrade + // to 720p), and all six aspect ratios including 21:9. + let request = build_artcraft_omni_video_request( + builder, + CommonVideoModelEnum::Seedance2p0BytePlusMini, + SupportedResolutions::Fast, + UltraWideSupport::Supported, + )?; + let state = ArtcraftSeedance2p0BytePlusMiniRequestState { request }; + Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0BytePlusMini(state))) +} + +#[cfg(test)] +mod tests { + use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum; + use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum; + use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; + use enums::common::generation::common_bitrate::CommonBitrate as CommonBitrateEnum; + use tokens::tokens::characters::CharacterToken; + use tokens::tokens::media_files::MediaFileToken; + + use crate::api::character_list_ref::CharacterListRef; + use crate::api::router_aspect_ratio::RouterAspectRatio; + use crate::api::router_bitrate::RouterBitrate; + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::image_list_ref::ImageListRef; + use crate::api::image_ref::ImageRef; + use crate::api::router_provider::RouterProvider; + use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; + use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; + + use super::*; + + mod field_conversions { + use super::*; + + #[test] + fn prompt_is_passed_through() { + let req = unwrap_request(builder_with(|b| { b.prompt = Some("test".to_string()); })); + assert_eq!(req.request.prompt, Some("test".to_string())); + } + + #[test] + fn model_is_correct() { + let req = unwrap_request(builder_with(|_| {})); + assert!(matches!(req.request.model, Some(CommonVideoModelEnum::Seedance2p0BytePlusMini))); + } + + #[test] + fn duration_passed_through() { + let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(10); })); + assert_eq!(req.request.duration_seconds, Some(10)); + } + + #[test] + fn duration_clamped_to_max() { + let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(99); })); + assert_eq!(req.request.duration_seconds, Some(15)); + } + + #[test] + fn batch_count_passed_through() { + let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(4); })); + assert_eq!(req.request.video_batch_count, Some(4)); + } + + #[test] + fn batch_count_clamped_to_platform_max() { + // The ArtCraft platform caps batch at 4 and downgrades higher requests. + let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(8); })); + assert_eq!(req.request.video_batch_count, Some(4)); + } + + #[test] + fn bitrate_passed_through() { + let req = unwrap_request(builder_with(|b| { b.bitrate = Some(RouterBitrate::High); })); + assert_eq!(req.request.bitrate, Some(CommonBitrateEnum::High)); + } + } + + mod aspect_ratio_tests { + use super::*; + + #[test] + fn wide() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideSixteenByNine); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideSixteenByNine)); + } + + #[test] + fn ultra_wide() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideTwentyOneByNine); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideTwentyOneByNine)); + } + + #[test] + fn tall() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::TallNineBySixteen); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::TallNineBySixteen)); + } + + #[test] + fn square() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::Square); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::Square)); + } + } + + mod resolution_tests { + use super::*; + + #[test] + fn res_480p() { + let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::FourEightyP); })); + assert_eq!(req.request.resolution, Some(CommonResolutionEnum::FourEightyP)); + } + + #[test] + fn res_720p() { + let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::SevenTwentyP); })); + assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP)); + } + + #[test] + fn res_1080p_downgrades_to_720p() { + let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::TenEightyP); })); + assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP)); + } + + #[test] + fn res_1080p_error_out() { + let result = build_artcraft_seedance_2p0_bp_mini(GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusMini, + provider: RouterProvider::Artcraft, + resolution: Some(RouterResolution::TenEightyP), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + ..Default::default() + }); + assert!(result.is_err()); + } + + #[test] + fn none_stays_none() { + let req = unwrap_request(builder_with(|_| {})); + assert!(req.request.resolution.is_none()); + } + } + + mod media_token_tests { + use super::*; + + #[test] + fn start_frame_token_passed_through() { + let token = MediaFileToken::new("mf_start".to_string()); + let req = unwrap_request(builder_with(|b| { b.start_frame = Some(ImageRef::MediaFileToken(token.clone())); })); + assert_eq!(req.request.start_frame_image_media_token, Some(token)); + } + + #[test] + fn url_start_frame_rejected() { + let result = build_artcraft_seedance_2p0_bp_mini(GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusMini, + provider: RouterProvider::Artcraft, + start_frame: Some(ImageRef::Url("https://example.com".to_string())), + ..Default::default() + }); + assert!(result.is_err()); + } + + #[test] + fn reference_image_tokens_passed_through() { + let tokens = vec![ + MediaFileToken::new("mf_a".to_string()), + MediaFileToken::new("mf_b".to_string()), + ]; + let req = unwrap_request(builder_with(|b| { b.reference_images = Some(ImageListRef::MediaFileTokens(tokens.clone())); })); + assert_eq!(req.request.reference_image_media_tokens, Some(tokens)); + } + + #[test] + fn character_tokens_passed_through() { + let tokens = vec![ + CharacterToken::new("char_a".to_string()), + CharacterToken::new("char_b".to_string()), + ]; + let req = unwrap_request(builder_with(|b| { b.reference_character_tokens = Some(CharacterListRef::CharacterTokens(tokens.clone())); })); + assert_eq!(req.request.reference_character_tokens, Some(tokens)); + } + } + + fn builder_with(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder { + let mut builder = GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusMini, + provider: RouterProvider::Artcraft, + duration_seconds: Some(5), + video_batch_count: Some(1), + ..Default::default() + }; + f(&mut builder); + builder + } + + fn unwrap_request(builder: GenerateVideoRequestBuilder) -> ArtcraftSeedance2p0BytePlusMiniRequestState { + let result = build_artcraft_seedance_2p0_bp_mini(builder).expect("build should succeed"); + match result { + VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0BytePlusMini(state)) => state, + _ => panic!("expected ArtcraftSeedance2p0BytePlusMini request"), + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/cost.rs new file mode 100644 index 0000000000..ffad17da03 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/cost.rs @@ -0,0 +1,176 @@ +use enums::common::generation::common_resolution::CommonResolution; + +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; +use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_byteplus_mini_usd_cents; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::request::ArtcraftSeedance2p0BytePlusMiniRequestState; + +pub struct ArtcraftSeedance2p0BytePlusMiniCostState { + pub resolution: CommonResolution, + pub duration_seconds: u16, + pub batch_count: u16, + pub has_video_reference: bool, +} + +impl ArtcraftSeedance2p0BytePlusMiniCostState { + pub fn from_request(request: &ArtcraftSeedance2p0BytePlusMiniRequestState) -> Self { + let resolution = request.request.resolution + .unwrap_or(CommonResolution::SevenTwentyP); + let duration_seconds = request.request.duration_seconds.unwrap_or(5); + let batch_count = request.request.video_batch_count.unwrap_or(1); + let has_video_reference = request.request.reference_video_media_tokens + .as_ref() + .is_some_and(|tokens| !tokens.is_empty()); + + Self { resolution, duration_seconds, batch_count, has_video_reference } + } + + pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { + // BytePlus / BytePlus Ultra Mini share a higher-margin rate set; see + // seedance_common::seedance_2p0_byteplus_mini_usd_cents. + let usd_cents = seedance_2p0_byteplus_mini_usd_cents( + self.resolution, + self.duration_seconds, + self.batch_count, + self.has_video_reference, + ); + + // ArtCraft credits: 100 credits = $1.00, so credits = cents. + VideoGenerationCostEstimate { + cost_in_credits: Some(usd_cents), + cost_in_usd_cents: Some(usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + failures_are_refunded: None, + } + } +} + +#[cfg(test)] +mod tests { + use tokens::tokens::media_files::MediaFileToken; + + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::router_provider::RouterProvider; + use crate::api::video_list_ref::VideoListRef; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + + // Every combination, driven through the full build pipeline. These prices are + // the supplier's per-combo cost plus a 5% margin, rounded up to whole cents. + + mod pricing_480p { + use super::*; + + #[test] + fn without_video_reference() { + assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14); + assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17); + assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33); + assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 50); + } + + #[test] + fn with_video_reference() { + assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17); + assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21); + assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42); + assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 63); + } + } + + mod pricing_720p { + use super::*; + + #[test] + fn without_video_reference() { + assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35); + assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44); + assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 88); + assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 131); + } + + #[test] + fn with_video_reference() { + assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42); + assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 53); + assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 105); + assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 158); + } + + #[test] + fn none_defaults_to_720p() { + assert_eq!(cents_no_ref(None, 5, 1), cents(RouterResolution::SevenTwentyP, 5, 1, false)); + } + } + + mod credits_tests { + use super::*; + + #[test] + fn credits_equal_usd_cents_all_combos() { + let resolutions = [ + Some(RouterResolution::FourEightyP), + Some(RouterResolution::SevenTwentyP), + None, + ]; + for res in resolutions { + for dur in [4u16, 5, 10, 15] { + for batch in [1u16, 2, 4] { + for has_ref in [false, true] { + let cost = build_cost(res, dur, batch, has_ref); + assert_eq!(cost.cost_in_credits, cost.cost_in_usd_cents); + } + } + } + } + } + } + + fn build_cost( + resolution: Option, + duration_seconds: u16, + video_batch_count: u16, + with_video_reference: bool, + ) -> VideoGenerationCostEstimate { + let reference_videos = with_video_reference.then(|| { + VideoListRef::MediaFileTokens(vec![MediaFileToken::new("mf_ref".to_string())]) + }); + let builder = GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusMini, + provider: RouterProvider::Artcraft, + resolution, + duration_seconds: Some(duration_seconds), + video_batch_count: Some(video_batch_count), + reference_videos, + ..Default::default() + }; + builder.build2() + .expect("build2 should succeed") + .estimate_cost() + .expect("estimate_cost should succeed") + } + + fn cents( + resolution: RouterResolution, + duration_seconds: u16, + video_batch_count: u16, + with_video_reference: bool, + ) -> u64 { + build_cost(Some(resolution), duration_seconds, video_batch_count, with_video_reference) + .cost_in_usd_cents + .unwrap() + } + + fn cents_no_ref( + resolution: Option, + duration_seconds: u16, + video_batch_count: u16, + ) -> u64 { + build_cost(resolution, duration_seconds, video_batch_count, false) + .cost_in_usd_cents + .unwrap() + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/mod.rs new file mode 100644 index 0000000000..98150bd014 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/mod.rs @@ -0,0 +1,3 @@ +pub mod build; +pub mod cost; +pub mod request; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/request.rs new file mode 100644 index 0000000000..74bc9b59d1 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bp_mini/request.rs @@ -0,0 +1,86 @@ +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; + +use crate::client::router_artcraft_client::RouterArtcraftClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; +use crate::generate::generate_video::providers::artcraft::request_common::send_artcraft_omni_video_request; + +#[derive(Clone, Debug)] +pub struct ArtcraftSeedance2p0BytePlusMiniRequestState { + pub request: OmniGenVideoCostAndGenerateRequest, +} + +impl ArtcraftSeedance2p0BytePlusMiniRequestState { + pub async fn send(&self, client: &RouterArtcraftClient) -> Result { + send_artcraft_omni_video_request(&self.request, client).await + } +} + +#[cfg(test)] +mod tests { + use crate::api::router_aspect_ratio::RouterAspectRatio; + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::router_provider::RouterProvider; + use crate::client::router_artcraft_client::RouterArtcraftClient; + use crate::client::router_client::RouterClient; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; + use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; + use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; + use artcraft_client::utils::api_host::ApiHost; + + #[tokio::test] + #[ignore] // manually run — fires a real API request and incurs cost + async fn text_to_video() { + let response = run_pipeline(GenerateVideoRequestBuilder { + prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()), + aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), + ..mini_builder() + }).await; + assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); + assert_eq!(1, 2, "Inspect output above"); + } + + #[tokio::test] + #[ignore] // manually run — fires a real API request and incurs cost + async fn res_480p() { + let response = run_pipeline(GenerateVideoRequestBuilder { + prompt: Some("A shiba inu playing in autumn leaves.".to_string()), + resolution: Some(RouterResolution::FourEightyP), + ..mini_builder() + }).await; + assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); + assert_eq!(1, 2, "Inspect output above"); + } + + fn mini_builder() -> GenerateVideoRequestBuilder { + GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusMini, + provider: RouterProvider::Artcraft, + duration_seconds: Some(4), + video_batch_count: Some(1), + ..Default::default() + } + } + + fn get_artcraft_client() -> RouterClient { + let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/artcraft_cookies.txt") + .expect("Failed to read artcraft cookies"); + let cookies = cookies.trim().to_string(); + let credentials = StorytellerCredentialSet::parse_multi_cookie_header(&cookies) + .expect("Failed to parse cookies") + .expect("No credentials found"); + RouterClient::Artcraft(RouterArtcraftClient::new(ApiHost::Storyteller, credentials)) + } + + async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse { + let client = get_artcraft_client(); + let draft_or_request = builder.build2().expect("build2 should succeed"); + let request = match draft_or_request { + VideoGenerationDraftOrRequest::Request(r) => r, + _ => panic!("expected Request variant (Artcraft skips draft)"), + }; + request.send_request(&client).await.expect("send_request should succeed") + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/build.rs new file mode 100644 index 0000000000..aa4bac1a45 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/build.rs @@ -0,0 +1,225 @@ +use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; + +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; +use crate::generate::generate_video::providers::artcraft::build_common::{ + build_artcraft_omni_video_request, SupportedResolutions, UltraWideSupport, +}; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::request::ArtcraftSeedance2p0BytePlusUltraMiniRequestState; +use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; +use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; + +pub fn build_artcraft_seedance_2p0_bpu_mini(builder: GenerateVideoRequestBuilder) -> Result { + // Seedance 2.0 Mini supports 480p and 720p only (higher resolutions downgrade + // to 720p), and all six aspect ratios including 21:9. + let request = build_artcraft_omni_video_request( + builder, + CommonVideoModelEnum::Seedance2p0BytePlusUltraMini, + SupportedResolutions::Fast, + UltraWideSupport::Supported, + )?; + let state = ArtcraftSeedance2p0BytePlusUltraMiniRequestState { request }; + Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraMini(state))) +} + +#[cfg(test)] +mod tests { + use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum; + use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum; + use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; + use enums::common::generation::common_bitrate::CommonBitrate as CommonBitrateEnum; + use tokens::tokens::characters::CharacterToken; + use tokens::tokens::media_files::MediaFileToken; + + use crate::api::character_list_ref::CharacterListRef; + use crate::api::router_aspect_ratio::RouterAspectRatio; + use crate::api::router_bitrate::RouterBitrate; + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::image_list_ref::ImageListRef; + use crate::api::image_ref::ImageRef; + use crate::api::router_provider::RouterProvider; + use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; + use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; + + use super::*; + + mod field_conversions { + use super::*; + + #[test] + fn prompt_is_passed_through() { + let req = unwrap_request(builder_with(|b| { b.prompt = Some("test".to_string()); })); + assert_eq!(req.request.prompt, Some("test".to_string())); + } + + #[test] + fn model_is_correct() { + let req = unwrap_request(builder_with(|_| {})); + assert!(matches!(req.request.model, Some(CommonVideoModelEnum::Seedance2p0BytePlusUltraMini))); + } + + #[test] + fn duration_passed_through() { + let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(10); })); + assert_eq!(req.request.duration_seconds, Some(10)); + } + + #[test] + fn duration_clamped_to_max() { + let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(99); })); + assert_eq!(req.request.duration_seconds, Some(15)); + } + + #[test] + fn batch_count_passed_through() { + let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(4); })); + assert_eq!(req.request.video_batch_count, Some(4)); + } + + #[test] + fn batch_count_clamped_to_platform_max() { + // The ArtCraft platform caps batch at 4 and downgrades higher requests. + let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(8); })); + assert_eq!(req.request.video_batch_count, Some(4)); + } + + #[test] + fn bitrate_passed_through() { + let req = unwrap_request(builder_with(|b| { b.bitrate = Some(RouterBitrate::High); })); + assert_eq!(req.request.bitrate, Some(CommonBitrateEnum::High)); + } + } + + mod aspect_ratio_tests { + use super::*; + + #[test] + fn wide() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideSixteenByNine); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideSixteenByNine)); + } + + #[test] + fn ultra_wide() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideTwentyOneByNine); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideTwentyOneByNine)); + } + + #[test] + fn tall() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::TallNineBySixteen); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::TallNineBySixteen)); + } + + #[test] + fn square() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::Square); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::Square)); + } + } + + mod resolution_tests { + use super::*; + + #[test] + fn res_480p() { + let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::FourEightyP); })); + assert_eq!(req.request.resolution, Some(CommonResolutionEnum::FourEightyP)); + } + + #[test] + fn res_720p() { + let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::SevenTwentyP); })); + assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP)); + } + + #[test] + fn res_1080p_downgrades_to_720p() { + let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::TenEightyP); })); + assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP)); + } + + #[test] + fn res_1080p_error_out() { + let result = build_artcraft_seedance_2p0_bpu_mini(GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusUltraMini, + provider: RouterProvider::Artcraft, + resolution: Some(RouterResolution::TenEightyP), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + ..Default::default() + }); + assert!(result.is_err()); + } + + #[test] + fn none_stays_none() { + let req = unwrap_request(builder_with(|_| {})); + assert!(req.request.resolution.is_none()); + } + } + + mod media_token_tests { + use super::*; + + #[test] + fn start_frame_token_passed_through() { + let token = MediaFileToken::new("mf_start".to_string()); + let req = unwrap_request(builder_with(|b| { b.start_frame = Some(ImageRef::MediaFileToken(token.clone())); })); + assert_eq!(req.request.start_frame_image_media_token, Some(token)); + } + + #[test] + fn url_start_frame_rejected() { + let result = build_artcraft_seedance_2p0_bpu_mini(GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusUltraMini, + provider: RouterProvider::Artcraft, + start_frame: Some(ImageRef::Url("https://example.com".to_string())), + ..Default::default() + }); + assert!(result.is_err()); + } + + #[test] + fn reference_image_tokens_passed_through() { + let tokens = vec![ + MediaFileToken::new("mf_a".to_string()), + MediaFileToken::new("mf_b".to_string()), + ]; + let req = unwrap_request(builder_with(|b| { b.reference_images = Some(ImageListRef::MediaFileTokens(tokens.clone())); })); + assert_eq!(req.request.reference_image_media_tokens, Some(tokens)); + } + + #[test] + fn character_tokens_passed_through() { + let tokens = vec![ + CharacterToken::new("char_a".to_string()), + CharacterToken::new("char_b".to_string()), + ]; + let req = unwrap_request(builder_with(|b| { b.reference_character_tokens = Some(CharacterListRef::CharacterTokens(tokens.clone())); })); + assert_eq!(req.request.reference_character_tokens, Some(tokens)); + } + } + + fn builder_with(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder { + let mut builder = GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusUltraMini, + provider: RouterProvider::Artcraft, + duration_seconds: Some(5), + video_batch_count: Some(1), + ..Default::default() + }; + f(&mut builder); + builder + } + + fn unwrap_request(builder: GenerateVideoRequestBuilder) -> ArtcraftSeedance2p0BytePlusUltraMiniRequestState { + let result = build_artcraft_seedance_2p0_bpu_mini(builder).expect("build should succeed"); + match result { + VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraMini(state)) => state, + _ => panic!("expected ArtcraftSeedance2p0BytePlusUltraMini request"), + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/cost.rs new file mode 100644 index 0000000000..88b0d8cbb3 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/cost.rs @@ -0,0 +1,176 @@ +use enums::common::generation::common_resolution::CommonResolution; + +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; +use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_byteplus_mini_usd_cents; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::request::ArtcraftSeedance2p0BytePlusUltraMiniRequestState; + +pub struct ArtcraftSeedance2p0BytePlusUltraMiniCostState { + pub resolution: CommonResolution, + pub duration_seconds: u16, + pub batch_count: u16, + pub has_video_reference: bool, +} + +impl ArtcraftSeedance2p0BytePlusUltraMiniCostState { + pub fn from_request(request: &ArtcraftSeedance2p0BytePlusUltraMiniRequestState) -> Self { + let resolution = request.request.resolution + .unwrap_or(CommonResolution::SevenTwentyP); + let duration_seconds = request.request.duration_seconds.unwrap_or(5); + let batch_count = request.request.video_batch_count.unwrap_or(1); + let has_video_reference = request.request.reference_video_media_tokens + .as_ref() + .is_some_and(|tokens| !tokens.is_empty()); + + Self { resolution, duration_seconds, batch_count, has_video_reference } + } + + pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { + // BytePlus / BytePlus Ultra Mini share a higher-margin rate set; see + // seedance_common::seedance_2p0_byteplus_mini_usd_cents. + let usd_cents = seedance_2p0_byteplus_mini_usd_cents( + self.resolution, + self.duration_seconds, + self.batch_count, + self.has_video_reference, + ); + + // ArtCraft credits: 100 credits = $1.00, so credits = cents. + VideoGenerationCostEstimate { + cost_in_credits: Some(usd_cents), + cost_in_usd_cents: Some(usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + failures_are_refunded: None, + } + } +} + +#[cfg(test)] +mod tests { + use tokens::tokens::media_files::MediaFileToken; + + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::router_provider::RouterProvider; + use crate::api::video_list_ref::VideoListRef; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + + // Every combination, driven through the full build pipeline. These prices are + // the supplier's per-combo cost plus a 5% margin, rounded up to whole cents. + + mod pricing_480p { + use super::*; + + #[test] + fn without_video_reference() { + assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 14); + assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17); + assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33); + assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 50); + } + + #[test] + fn with_video_reference() { + assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17); + assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21); + assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42); + assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 63); + } + } + + mod pricing_720p { + use super::*; + + #[test] + fn without_video_reference() { + assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35); + assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44); + assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 88); + assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 131); + } + + #[test] + fn with_video_reference() { + assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42); + assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 53); + assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 105); + assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 158); + } + + #[test] + fn none_defaults_to_720p() { + assert_eq!(cents_no_ref(None, 5, 1), cents(RouterResolution::SevenTwentyP, 5, 1, false)); + } + } + + mod credits_tests { + use super::*; + + #[test] + fn credits_equal_usd_cents_all_combos() { + let resolutions = [ + Some(RouterResolution::FourEightyP), + Some(RouterResolution::SevenTwentyP), + None, + ]; + for res in resolutions { + for dur in [4u16, 5, 10, 15] { + for batch in [1u16, 2, 4] { + for has_ref in [false, true] { + let cost = build_cost(res, dur, batch, has_ref); + assert_eq!(cost.cost_in_credits, cost.cost_in_usd_cents); + } + } + } + } + } + } + + fn build_cost( + resolution: Option, + duration_seconds: u16, + video_batch_count: u16, + with_video_reference: bool, + ) -> VideoGenerationCostEstimate { + let reference_videos = with_video_reference.then(|| { + VideoListRef::MediaFileTokens(vec![MediaFileToken::new("mf_ref".to_string())]) + }); + let builder = GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusUltraMini, + provider: RouterProvider::Artcraft, + resolution, + duration_seconds: Some(duration_seconds), + video_batch_count: Some(video_batch_count), + reference_videos, + ..Default::default() + }; + builder.build2() + .expect("build2 should succeed") + .estimate_cost() + .expect("estimate_cost should succeed") + } + + fn cents( + resolution: RouterResolution, + duration_seconds: u16, + video_batch_count: u16, + with_video_reference: bool, + ) -> u64 { + build_cost(Some(resolution), duration_seconds, video_batch_count, with_video_reference) + .cost_in_usd_cents + .unwrap() + } + + fn cents_no_ref( + resolution: Option, + duration_seconds: u16, + video_batch_count: u16, + ) -> u64 { + build_cost(resolution, duration_seconds, video_batch_count, false) + .cost_in_usd_cents + .unwrap() + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/mod.rs new file mode 100644 index 0000000000..98150bd014 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/mod.rs @@ -0,0 +1,3 @@ +pub mod build; +pub mod cost; +pub mod request; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/request.rs new file mode 100644 index 0000000000..c794885c52 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_bpu_mini/request.rs @@ -0,0 +1,86 @@ +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; + +use crate::client::router_artcraft_client::RouterArtcraftClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; +use crate::generate::generate_video::providers::artcraft::request_common::send_artcraft_omni_video_request; + +#[derive(Clone, Debug)] +pub struct ArtcraftSeedance2p0BytePlusUltraMiniRequestState { + pub request: OmniGenVideoCostAndGenerateRequest, +} + +impl ArtcraftSeedance2p0BytePlusUltraMiniRequestState { + pub async fn send(&self, client: &RouterArtcraftClient) -> Result { + send_artcraft_omni_video_request(&self.request, client).await + } +} + +#[cfg(test)] +mod tests { + use crate::api::router_aspect_ratio::RouterAspectRatio; + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::router_provider::RouterProvider; + use crate::client::router_artcraft_client::RouterArtcraftClient; + use crate::client::router_client::RouterClient; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; + use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; + use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; + use artcraft_client::utils::api_host::ApiHost; + + #[tokio::test] + #[ignore] // manually run — fires a real API request and incurs cost + async fn text_to_video() { + let response = run_pipeline(GenerateVideoRequestBuilder { + prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()), + aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), + ..mini_builder() + }).await; + assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); + assert_eq!(1, 2, "Inspect output above"); + } + + #[tokio::test] + #[ignore] // manually run — fires a real API request and incurs cost + async fn res_480p() { + let response = run_pipeline(GenerateVideoRequestBuilder { + prompt: Some("A shiba inu playing in autumn leaves.".to_string()), + resolution: Some(RouterResolution::FourEightyP), + ..mini_builder() + }).await; + assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); + assert_eq!(1, 2, "Inspect output above"); + } + + fn mini_builder() -> GenerateVideoRequestBuilder { + GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0BytePlusUltraMini, + provider: RouterProvider::Artcraft, + duration_seconds: Some(4), + video_batch_count: Some(1), + ..Default::default() + } + } + + fn get_artcraft_client() -> RouterClient { + let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/artcraft_cookies.txt") + .expect("Failed to read artcraft cookies"); + let cookies = cookies.trim().to_string(); + let credentials = StorytellerCredentialSet::parse_multi_cookie_header(&cookies) + .expect("Failed to parse cookies") + .expect("No credentials found"); + RouterClient::Artcraft(RouterArtcraftClient::new(ApiHost::Storyteller, credentials)) + } + + async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse { + let client = get_artcraft_client(); + let draft_or_request = builder.build2().expect("build2 should succeed"); + let request = match draft_or_request { + VideoGenerationDraftOrRequest::Request(r) => r, + _ => panic!("expected Request variant (Artcraft skips draft)"), + }; + request.send_request(&client).await.expect("send_request should succeed") + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/build.rs new file mode 100644 index 0000000000..b484487935 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/build.rs @@ -0,0 +1,225 @@ +use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; + +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; +use crate::generate::generate_video::providers::artcraft::build_common::{ + build_artcraft_omni_video_request, SupportedResolutions, UltraWideSupport, +}; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::request::ArtcraftSeedance2p0MiniRequestState; +use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; +use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; + +pub fn build_artcraft_seedance_2p0_mini(builder: GenerateVideoRequestBuilder) -> Result { + // Seedance 2.0 Mini supports 480p and 720p only (higher resolutions downgrade + // to 720p), and all six aspect ratios including 21:9. + let request = build_artcraft_omni_video_request( + builder, + CommonVideoModelEnum::Seedance2p0Mini, + SupportedResolutions::Fast, + UltraWideSupport::Supported, + )?; + let state = ArtcraftSeedance2p0MiniRequestState { request }; + Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0Mini(state))) +} + +#[cfg(test)] +mod tests { + use enums::common::generation::common_aspect_ratio::CommonAspectRatio as CommonAspectRatioEnum; + use enums::common::generation::common_resolution::CommonResolution as CommonResolutionEnum; + use enums::common::generation::common_video_model::CommonVideoModel as CommonVideoModelEnum; + use enums::common::generation::common_bitrate::CommonBitrate as CommonBitrateEnum; + use tokens::tokens::characters::CharacterToken; + use tokens::tokens::media_files::MediaFileToken; + + use crate::api::character_list_ref::CharacterListRef; + use crate::api::router_aspect_ratio::RouterAspectRatio; + use crate::api::router_bitrate::RouterBitrate; + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::image_list_ref::ImageListRef; + use crate::api::image_ref::ImageRef; + use crate::api::router_provider::RouterProvider; + use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; + use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; + + use super::*; + + mod field_conversions { + use super::*; + + #[test] + fn prompt_is_passed_through() { + let req = unwrap_request(builder_with(|b| { b.prompt = Some("test".to_string()); })); + assert_eq!(req.request.prompt, Some("test".to_string())); + } + + #[test] + fn model_is_correct() { + let req = unwrap_request(builder_with(|_| {})); + assert!(matches!(req.request.model, Some(CommonVideoModelEnum::Seedance2p0Mini))); + } + + #[test] + fn duration_passed_through() { + let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(10); })); + assert_eq!(req.request.duration_seconds, Some(10)); + } + + #[test] + fn duration_clamped_to_max() { + let req = unwrap_request(builder_with(|b| { b.duration_seconds = Some(99); })); + assert_eq!(req.request.duration_seconds, Some(15)); + } + + #[test] + fn batch_count_passed_through() { + let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(4); })); + assert_eq!(req.request.video_batch_count, Some(4)); + } + + #[test] + fn batch_count_clamped_to_platform_max() { + // The ArtCraft platform caps batch at 4 and downgrades higher requests. + let req = unwrap_request(builder_with(|b| { b.video_batch_count = Some(8); })); + assert_eq!(req.request.video_batch_count, Some(4)); + } + + #[test] + fn bitrate_passed_through() { + let req = unwrap_request(builder_with(|b| { b.bitrate = Some(RouterBitrate::High); })); + assert_eq!(req.request.bitrate, Some(CommonBitrateEnum::High)); + } + } + + mod aspect_ratio_tests { + use super::*; + + #[test] + fn wide() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideSixteenByNine); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideSixteenByNine)); + } + + #[test] + fn ultra_wide() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::WideTwentyOneByNine); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::WideTwentyOneByNine)); + } + + #[test] + fn tall() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::TallNineBySixteen); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::TallNineBySixteen)); + } + + #[test] + fn square() { + let req = unwrap_request(builder_with(|b| { b.aspect_ratio = Some(RouterAspectRatio::Square); })); + assert_eq!(req.request.aspect_ratio, Some(CommonAspectRatioEnum::Square)); + } + } + + mod resolution_tests { + use super::*; + + #[test] + fn res_480p() { + let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::FourEightyP); })); + assert_eq!(req.request.resolution, Some(CommonResolutionEnum::FourEightyP)); + } + + #[test] + fn res_720p() { + let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::SevenTwentyP); })); + assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP)); + } + + #[test] + fn res_1080p_downgrades_to_720p() { + let req = unwrap_request(builder_with(|b| { b.resolution = Some(RouterResolution::TenEightyP); })); + assert_eq!(req.request.resolution, Some(CommonResolutionEnum::SevenTwentyP)); + } + + #[test] + fn res_1080p_error_out() { + let result = build_artcraft_seedance_2p0_mini(GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0Mini, + provider: RouterProvider::Artcraft, + resolution: Some(RouterResolution::TenEightyP), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + ..Default::default() + }); + assert!(result.is_err()); + } + + #[test] + fn none_stays_none() { + let req = unwrap_request(builder_with(|_| {})); + assert!(req.request.resolution.is_none()); + } + } + + mod media_token_tests { + use super::*; + + #[test] + fn start_frame_token_passed_through() { + let token = MediaFileToken::new("mf_start".to_string()); + let req = unwrap_request(builder_with(|b| { b.start_frame = Some(ImageRef::MediaFileToken(token.clone())); })); + assert_eq!(req.request.start_frame_image_media_token, Some(token)); + } + + #[test] + fn url_start_frame_rejected() { + let result = build_artcraft_seedance_2p0_mini(GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0Mini, + provider: RouterProvider::Artcraft, + start_frame: Some(ImageRef::Url("https://example.com".to_string())), + ..Default::default() + }); + assert!(result.is_err()); + } + + #[test] + fn reference_image_tokens_passed_through() { + let tokens = vec![ + MediaFileToken::new("mf_a".to_string()), + MediaFileToken::new("mf_b".to_string()), + ]; + let req = unwrap_request(builder_with(|b| { b.reference_images = Some(ImageListRef::MediaFileTokens(tokens.clone())); })); + assert_eq!(req.request.reference_image_media_tokens, Some(tokens)); + } + + #[test] + fn character_tokens_passed_through() { + let tokens = vec![ + CharacterToken::new("char_a".to_string()), + CharacterToken::new("char_b".to_string()), + ]; + let req = unwrap_request(builder_with(|b| { b.reference_character_tokens = Some(CharacterListRef::CharacterTokens(tokens.clone())); })); + assert_eq!(req.request.reference_character_tokens, Some(tokens)); + } + } + + fn builder_with(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder { + let mut builder = GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0Mini, + provider: RouterProvider::Artcraft, + duration_seconds: Some(5), + video_batch_count: Some(1), + ..Default::default() + }; + f(&mut builder); + builder + } + + fn unwrap_request(builder: GenerateVideoRequestBuilder) -> ArtcraftSeedance2p0MiniRequestState { + let result = build_artcraft_seedance_2p0_mini(builder).expect("build should succeed"); + match result { + VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::ArtcraftSeedance2p0Mini(state)) => state, + _ => panic!("expected ArtcraftSeedance2p0Mini request"), + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/cost.rs new file mode 100644 index 0000000000..9c36520ba2 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/cost.rs @@ -0,0 +1,176 @@ +use enums::common::generation::common_resolution::CommonResolution; + +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; +use crate::generate::generate_video::providers::artcraft::seedance_common::seedance_2p0_mini_usd_cents; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::request::ArtcraftSeedance2p0MiniRequestState; + +pub struct ArtcraftSeedance2p0MiniCostState { + pub resolution: CommonResolution, + pub duration_seconds: u16, + pub batch_count: u16, + pub has_video_reference: bool, +} + +impl ArtcraftSeedance2p0MiniCostState { + pub fn from_request(request: &ArtcraftSeedance2p0MiniRequestState) -> Self { + let resolution = request.request.resolution + .unwrap_or(CommonResolution::SevenTwentyP); + let duration_seconds = request.request.duration_seconds.unwrap_or(5); + let batch_count = request.request.video_batch_count.unwrap_or(1); + let has_video_reference = request.request.reference_video_media_tokens + .as_ref() + .is_some_and(|tokens| !tokens.is_empty()); + + Self { resolution, duration_seconds, batch_count, has_video_reference } + } + + pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { + // Seedance 2.0 Mini is priced uniformly across its variants; see + // seedance_common::seedance_2p0_mini_usd_cents. + let usd_cents = seedance_2p0_mini_usd_cents( + self.resolution, + self.duration_seconds, + self.batch_count, + self.has_video_reference, + ); + + // ArtCraft credits: 100 credits = $1.00, so credits = cents. + VideoGenerationCostEstimate { + cost_in_credits: Some(usd_cents), + cost_in_usd_cents: Some(usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + failures_are_refunded: None, + } + } +} + +#[cfg(test)] +mod tests { + use tokens::tokens::media_files::MediaFileToken; + + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::router_provider::RouterProvider; + use crate::api::video_list_ref::VideoListRef; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; + + // Every combination, driven through the full build pipeline. These prices are + // the supplier's per-combo cost plus a 5% margin, rounded up to whole cents. + + mod pricing_480p { + use super::*; + + #[test] + fn without_video_reference() { + assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, false), 13); + assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, false), 17); + assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, false), 33); + assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, false), 49); + } + + #[test] + fn with_video_reference() { + assert_eq!(cents(RouterResolution::FourEightyP, 4, 1, true), 17); + assert_eq!(cents(RouterResolution::FourEightyP, 5, 1, true), 21); + assert_eq!(cents(RouterResolution::FourEightyP, 10, 1, true), 42); + assert_eq!(cents(RouterResolution::FourEightyP, 15, 1, true), 62); + } + } + + mod pricing_720p { + use super::*; + + #[test] + fn without_video_reference() { + assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, false), 35); + assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, false), 44); + assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, false), 87); + assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, false), 130); + } + + #[test] + fn with_video_reference() { + assert_eq!(cents(RouterResolution::SevenTwentyP, 4, 1, true), 42); + assert_eq!(cents(RouterResolution::SevenTwentyP, 5, 1, true), 52); + assert_eq!(cents(RouterResolution::SevenTwentyP, 10, 1, true), 104); + assert_eq!(cents(RouterResolution::SevenTwentyP, 15, 1, true), 156); + } + + #[test] + fn none_defaults_to_720p() { + assert_eq!(cents_no_ref(None, 5, 1), cents(RouterResolution::SevenTwentyP, 5, 1, false)); + } + } + + mod credits_tests { + use super::*; + + #[test] + fn credits_equal_usd_cents_all_combos() { + let resolutions = [ + Some(RouterResolution::FourEightyP), + Some(RouterResolution::SevenTwentyP), + None, + ]; + for res in resolutions { + for dur in [4u16, 5, 10, 15] { + for batch in [1u16, 2, 4] { + for has_ref in [false, true] { + let cost = build_cost(res, dur, batch, has_ref); + assert_eq!(cost.cost_in_credits, cost.cost_in_usd_cents); + } + } + } + } + } + } + + fn build_cost( + resolution: Option, + duration_seconds: u16, + video_batch_count: u16, + with_video_reference: bool, + ) -> VideoGenerationCostEstimate { + let reference_videos = with_video_reference.then(|| { + VideoListRef::MediaFileTokens(vec![MediaFileToken::new("mf_ref".to_string())]) + }); + let builder = GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0Mini, + provider: RouterProvider::Artcraft, + resolution, + duration_seconds: Some(duration_seconds), + video_batch_count: Some(video_batch_count), + reference_videos, + ..Default::default() + }; + builder.build2() + .expect("build2 should succeed") + .estimate_cost() + .expect("estimate_cost should succeed") + } + + fn cents( + resolution: RouterResolution, + duration_seconds: u16, + video_batch_count: u16, + with_video_reference: bool, + ) -> u64 { + build_cost(Some(resolution), duration_seconds, video_batch_count, with_video_reference) + .cost_in_usd_cents + .unwrap() + } + + fn cents_no_ref( + resolution: Option, + duration_seconds: u16, + video_batch_count: u16, + ) -> u64 { + build_cost(resolution, duration_seconds, video_batch_count, false) + .cost_in_usd_cents + .unwrap() + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/mod.rs new file mode 100644 index 0000000000..98150bd014 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/mod.rs @@ -0,0 +1,3 @@ +pub mod build; +pub mod cost; +pub mod request; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/request.rs new file mode 100644 index 0000000000..1efad69ea3 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_2p0_mini/request.rs @@ -0,0 +1,86 @@ +use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest; + +use crate::client::router_artcraft_client::RouterArtcraftClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; +use crate::generate::generate_video::providers::artcraft::request_common::send_artcraft_omni_video_request; + +#[derive(Clone, Debug)] +pub struct ArtcraftSeedance2p0MiniRequestState { + pub request: OmniGenVideoCostAndGenerateRequest, +} + +impl ArtcraftSeedance2p0MiniRequestState { + pub async fn send(&self, client: &RouterArtcraftClient) -> Result { + send_artcraft_omni_video_request(&self.request, client).await + } +} + +#[cfg(test)] +mod tests { + use crate::api::router_aspect_ratio::RouterAspectRatio; + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::router_provider::RouterProvider; + use crate::client::router_artcraft_client::RouterArtcraftClient; + use crate::client::router_client::RouterClient; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::generate_video_response::GenerateVideoResponse; + use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; + use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet; + use artcraft_client::utils::api_host::ApiHost; + + #[tokio::test] + #[ignore] // manually run — fires a real API request and incurs cost + async fn text_to_video() { + let response = run_pipeline(GenerateVideoRequestBuilder { + prompt: Some("A corgi running through a field of wildflowers at sunset.".to_string()), + aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), + ..mini_builder() + }).await; + assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); + assert_eq!(1, 2, "Inspect output above"); + } + + #[tokio::test] + #[ignore] // manually run — fires a real API request and incurs cost + async fn res_480p() { + let response = run_pipeline(GenerateVideoRequestBuilder { + prompt: Some("A shiba inu playing in autumn leaves.".to_string()), + resolution: Some(RouterResolution::FourEightyP), + ..mini_builder() + }).await; + assert!(matches!(response, GenerateVideoResponse::Artcraft(_))); + assert_eq!(1, 2, "Inspect output above"); + } + + fn mini_builder() -> GenerateVideoRequestBuilder { + GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0Mini, + provider: RouterProvider::Artcraft, + duration_seconds: Some(4), + video_batch_count: Some(1), + ..Default::default() + } + } + + fn get_artcraft_client() -> RouterClient { + let cookies = std::fs::read_to_string("/Users/bt/Artcraft/credentials/artcraft_cookies.txt") + .expect("Failed to read artcraft cookies"); + let cookies = cookies.trim().to_string(); + let credentials = StorytellerCredentialSet::parse_multi_cookie_header(&cookies) + .expect("Failed to parse cookies") + .expect("No credentials found"); + RouterClient::Artcraft(RouterArtcraftClient::new(ApiHost::Storyteller, credentials)) + } + + async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse { + let client = get_artcraft_client(); + let draft_or_request = builder.build2().expect("build2 should succeed"); + let request = match draft_or_request { + VideoGenerationDraftOrRequest::Request(r) => r, + _ => panic!("expected Request variant (Artcraft skips draft)"), + }; + request.send_request(&client).await.expect("send_request should succeed") + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_common.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_common.rs index 396ae2ded0..9559745e74 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_common.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/artcraft/seedance_common.rs @@ -2,6 +2,8 @@ //! //! ArtCraft credits equal USD cents (100 credits = $1.00). +use enums::common::generation::common_resolution::CommonResolution; + // 4K is priced uniformly across the non-Fast Seedance 2.0 models. Rates are held // in hundredths of a USD cent per second so the math is exact integer arithmetic // (no floating point), then rounded up to whole cents. @@ -35,6 +37,107 @@ pub fn seedance_2p0_four_k_usd_cents( total_centi_cents.div_ceil(100) } +// ── Seedance 2.0 Mini pricing ── +// +// Mini offers only 480p and 720p. The rates below are the per-second USER price +// in USD cents (margin already included); a reference video adds a per-second +// surcharge. Rates are fractional, so the total is rounded UP to a whole cent +// once at the end (after multiplying by duration × batch). +// +// The regular Mini and the BytePlus / BytePlus Ultra Minis carry DIFFERENT +// margins, so they have separate rate sets and helpers below. + +/// Regular Mini — 480p price, USD cents per second. +const MINI_CENTS_PER_SECOND_480P: f64 = 3.24074074; +/// Regular Mini — 480p reference-video surcharge, USD cents per second. +const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.86419753; +/// Regular Mini — 720p price, USD cents per second. +const MINI_CENTS_PER_SECOND_720P: f64 = 8.64197531; +/// Regular Mini — 720p reference-video surcharge, USD cents per second. +const MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.72839506; + +/// BytePlus / BytePlus Ultra Mini — 480p price, USD cents per second. +const BYTEPLUS_MINI_CENTS_PER_SECOND_480P: f64 = 3.27160494; +/// BytePlus / BytePlus Ultra Mini — 480p reference-video surcharge, USD cents per second. +const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P: f64 = 0.87242798; +/// BytePlus / BytePlus Ultra Mini — 720p price, USD cents per second. +const BYTEPLUS_MINI_CENTS_PER_SECOND_720P: f64 = 8.72427984; +/// BytePlus / BytePlus Ultra Mini — 720p reference-video surcharge, USD cents per second. +const BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P: f64 = 1.74485597; + +/// ArtCraft's user-facing price (USD cents) for the regular Seedance 2.0 Mini. +/// +/// Only 480p and 720p are offered; any other resolution prices at 720p. A +/// reference video adds the per-second surcharge. The fractional total is +/// rounded UP to a whole cent. +pub fn seedance_2p0_mini_usd_cents( + resolution: CommonResolution, + duration_seconds: u16, + batch_count: u16, + has_video_reference: bool, +) -> u64 { + mini_usd_cents( + resolution, + duration_seconds, + batch_count, + has_video_reference, + MINI_CENTS_PER_SECOND_480P, + MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P, + MINI_CENTS_PER_SECOND_720P, + MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P, + ) +} + +/// ArtCraft's user-facing price (USD cents) for the Seedance 2.0 BytePlus Mini +/// and BytePlus Ultra Mini (which share the same rates — a higher margin than +/// the regular Mini). +/// +/// Only 480p and 720p are offered; any other resolution prices at 720p. A +/// reference video adds the per-second surcharge. The fractional total is +/// rounded UP to a whole cent. +pub fn seedance_2p0_byteplus_mini_usd_cents( + resolution: CommonResolution, + duration_seconds: u16, + batch_count: u16, + has_video_reference: bool, +) -> u64 { + mini_usd_cents( + resolution, + duration_seconds, + batch_count, + has_video_reference, + BYTEPLUS_MINI_CENTS_PER_SECOND_480P, + BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_480P, + BYTEPLUS_MINI_CENTS_PER_SECOND_720P, + BYTEPLUS_MINI_VIDEO_REFERENCE_SURCHARGE_CENTS_PER_SECOND_720P, + ) +} + +#[allow(clippy::too_many_arguments)] +fn mini_usd_cents( + resolution: CommonResolution, + duration_seconds: u16, + batch_count: u16, + has_video_reference: bool, + cents_per_second_480p: f64, + video_reference_surcharge_per_second_480p: f64, + cents_per_second_720p: f64, + video_reference_surcharge_per_second_720p: f64, +) -> u64 { + let (base_per_second, video_reference_surcharge_per_second) = match resolution { + CommonResolution::FourEightyP => (cents_per_second_480p, video_reference_surcharge_per_second_480p), + // Everything else (including 720p and unsupported resolutions) prices at 720p. + _ => (cents_per_second_720p, video_reference_surcharge_per_second_720p), + }; + + let mut cents_per_second = base_per_second; + if has_video_reference { + cents_per_second += video_reference_surcharge_per_second; + } + + (cents_per_second * duration_seconds as f64 * batch_count as f64).ceil() as u64 +} + #[cfg(test)] mod tests { use super::*; @@ -72,4 +175,154 @@ mod tests { assert_eq!(seedance_2p0_four_k_usd_cents(5, 4, false), 1732); assert_eq!(seedance_2p0_four_k_usd_cents(5, 2, true), 1038); } + + // ── Seedance 2.0 Mini ── + // + // Every combination (480p/720p × with/without video reference × 4/5/10/15s) + // at batch 1. These are the supplier's per-combo cost plus a 5% margin, + // rounded up to whole cents. + + mod mini { + use super::*; + + fn cents(res: CommonResolution, dur: u16, has_ref: bool) -> u64 { + seedance_2p0_mini_usd_cents(res, dur, 1, has_ref) + } + + #[test] + fn four_eighty_p_without_video_reference() { + assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 13); + assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 17); + assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 33); + assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 49); + } + + #[test] + fn four_eighty_p_with_video_reference() { + assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 17); + assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 21); + assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 42); + assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 62); + } + + #[test] + fn seven_twenty_p_without_video_reference() { + assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 35); + assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 44); + assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 87); + assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 130); + } + + #[test] + fn seven_twenty_p_with_video_reference() { + assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 42); + assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 52); + assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 104); + assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 156); + } + + #[test] + fn video_reference_always_costs_more() { + for res in [CommonResolution::FourEightyP, CommonResolution::SevenTwentyP] { + for dur in [4u16, 5, 10, 15] { + assert!( + cents(res, dur, true) > cents(res, dur, false), + "video ref should cost more at {res:?} {dur}s", + ); + } + } + } + + #[test] + fn unsupported_resolution_prices_at_720p() { + assert_eq!( + seedance_2p0_mini_usd_cents(CommonResolution::TenEightyP, 5, 1, false), + seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 1, false), + ); + } + + #[test] + fn batch_count_scales_the_total() { + // Batch is baked in before the single round-up, so batched totals can be + // a cent under N× the single price. + assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 87); + assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 173); + assert_eq!(seedance_2p0_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 33); + } + } + + // ── Seedance 2.0 BytePlus / BytePlus Ultra Mini ── + // + // Same combinations as the regular Mini, but at a higher margin (6% vs 5%), + // so the prices are equal-or-higher than the regular Mini. + + mod byteplus_mini { + use super::*; + + fn cents(res: CommonResolution, dur: u16, has_ref: bool) -> u64 { + seedance_2p0_byteplus_mini_usd_cents(res, dur, 1, has_ref) + } + + #[test] + fn four_eighty_p_without_video_reference() { + assert_eq!(cents(CommonResolution::FourEightyP, 4, false), 14); + assert_eq!(cents(CommonResolution::FourEightyP, 5, false), 17); + assert_eq!(cents(CommonResolution::FourEightyP, 10, false), 33); + assert_eq!(cents(CommonResolution::FourEightyP, 15, false), 50); + } + + #[test] + fn four_eighty_p_with_video_reference() { + assert_eq!(cents(CommonResolution::FourEightyP, 4, true), 17); + assert_eq!(cents(CommonResolution::FourEightyP, 5, true), 21); + assert_eq!(cents(CommonResolution::FourEightyP, 10, true), 42); + assert_eq!(cents(CommonResolution::FourEightyP, 15, true), 63); + } + + #[test] + fn seven_twenty_p_without_video_reference() { + assert_eq!(cents(CommonResolution::SevenTwentyP, 4, false), 35); + assert_eq!(cents(CommonResolution::SevenTwentyP, 5, false), 44); + assert_eq!(cents(CommonResolution::SevenTwentyP, 10, false), 88); + assert_eq!(cents(CommonResolution::SevenTwentyP, 15, false), 131); + } + + #[test] + fn seven_twenty_p_with_video_reference() { + assert_eq!(cents(CommonResolution::SevenTwentyP, 4, true), 42); + assert_eq!(cents(CommonResolution::SevenTwentyP, 5, true), 53); + assert_eq!(cents(CommonResolution::SevenTwentyP, 10, true), 105); + assert_eq!(cents(CommonResolution::SevenTwentyP, 15, true), 158); + } + + #[test] + fn never_cheaper_than_regular_mini() { + for res in [CommonResolution::FourEightyP, CommonResolution::SevenTwentyP] { + for dur in [4u16, 5, 10, 15] { + for has_ref in [false, true] { + assert!( + seedance_2p0_byteplus_mini_usd_cents(res, dur, 1, has_ref) + >= seedance_2p0_mini_usd_cents(res, dur, 1, has_ref), + "byteplus mini (6%) should be >= regular mini (5%) at {res:?} {dur}s ref={has_ref}", + ); + } + } + } + } + + #[test] + fn unsupported_resolution_prices_at_720p() { + assert_eq!( + seedance_2p0_byteplus_mini_usd_cents(CommonResolution::TenEightyP, 5, 1, false), + seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 1, false), + ); + } + + #[test] + fn batch_count_scales_the_total() { + assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 2, false), 88); + assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::SevenTwentyP, 5, 4, false), 175); + assert_eq!(seedance_2p0_byteplus_mini_usd_cents(CommonResolution::FourEightyP, 5, 2, false), 33); + } + } } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/mod.rs index 9b8fe26517..6537d5a9a3 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/mod.rs @@ -1,5 +1,6 @@ pub mod happy_horse_1p0; pub mod seedance_2p0; pub mod seedance_2p0_fast; +pub mod seedance_2p0_mini; pub(crate) mod resolve; pub(crate) mod upload; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/build.rs new file mode 100644 index 0000000000..7580094a67 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/build.rs @@ -0,0 +1,510 @@ +use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{ + KinoviSeedance2p0MiniAspectRatio as KinoviAspectRatio, + KinoviSeedance2p0MiniBitrate as KinoviBitrate, + KinoviSeedance2p0MiniBatchCount as KinoviBatchCount, + KinoviSeedance2p0MiniOutputResolution as KinoviOutputResolution, +}; + +use crate::api::router_aspect_ratio::RouterAspectRatio; +use crate::api::router_bitrate::RouterBitrate; +use crate::api::router_resolution::RouterResolution; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::client_error::ClientError; +use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; +use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::draft::{KinoviSeedance2p0MiniDraftState, KinoviSeedance2p0MiniRemainingItems}; +use crate::generate::generate_video::video_generation_draft::VideoGenerationDraftRequest; +use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; + +pub fn build_kinovi_seedance_2p0_mini(builder: GenerateVideoRequestBuilder) -> Result { + let draft = do_build_kinovi_seedance_2p0_mini(builder)?; + Ok(VideoGenerationDraftOrRequest::Draft(VideoGenerationDraftRequest::KinoviSeedance2p0Mini(draft))) +} + +fn do_build_kinovi_seedance_2p0_mini(mut builder: GenerateVideoRequestBuilder) -> Result { + let strategy = builder.request_mismatch_mitigation_strategy; + + let aspect_ratio = plan_aspect_ratio(builder.aspect_ratio.take(), strategy)?; + let resolution = plan_output_resolution(builder.resolution.take(), strategy)?; + let batch_count = plan_batch_count(builder.video_batch_count.take(), strategy)?; + let duration_seconds = plan_duration(builder.duration_seconds.take(), strategy)?; + let bitrate = plan_bitrate(builder.bitrate.take()); + let prompt = builder.prompt.take().unwrap_or_default(); + + let unhandled_request_state = KinoviSeedance2p0MiniRemainingItems { + start_frame: builder.start_frame.take(), + end_frame: builder.end_frame.take(), + reference_images: builder.reference_images.take(), + reference_videos: builder.reference_videos.take(), + reference_audio: builder.reference_audio.take(), + reference_character_tokens: builder.reference_character_tokens.take(), + }; + + Ok(KinoviSeedance2p0MiniDraftState { + aspect_ratio, + resolution, + batch_count, + duration_seconds, + bitrate, + prompt, + unhandled_request_state: Some(unhandled_request_state), + }) +} + +// ── Plan helpers ── + +// Seedance 2.0 Mini supports all six aspect ratios: +// 16:9, 21:9, 9:16, 1:1, 4:3, 3:4. All supported ratios cost the same, so +// both upgrade and downgrade pick the nearest match. +fn plan_aspect_ratio( + aspect_ratio: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + match aspect_ratio { + // No preference or auto — default to landscape + None + | Some(RouterAspectRatio::Auto) + | Some(RouterAspectRatio::Auto2k) + | Some(RouterAspectRatio::Auto4k) => Ok(KinoviAspectRatio::Landscape16x9), + + // Direct mappings + Some(RouterAspectRatio::WideSixteenByNine) | Some(RouterAspectRatio::Wide) => { + Ok(KinoviAspectRatio::Landscape16x9) + } + Some(RouterAspectRatio::TallNineBySixteen) | Some(RouterAspectRatio::Tall) => { + Ok(KinoviAspectRatio::Portrait9x16) + } + Some(RouterAspectRatio::Square) | Some(RouterAspectRatio::SquareHd) => { + Ok(KinoviAspectRatio::Square1x1) + } + Some(RouterAspectRatio::WideFourByThree) => Ok(KinoviAspectRatio::Standard4x3), + Some(RouterAspectRatio::TallThreeByFour) => Ok(KinoviAspectRatio::Portrait3x4), + Some(RouterAspectRatio::WideTwentyOneByNine) => Ok(KinoviAspectRatio::UltraWide21x9), + + // Mismatches — apply strategy + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "aspect_ratio", + value: format!("{:?}", unsupported), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade + | RequestMismatchMitigationStrategy::PayLessDowngrade => { + Ok(nearest_aspect_ratio(unsupported)) + } + }, + } +} + +/// Pick the nearest supported aspect ratio by AR value (width / height). +fn nearest_aspect_ratio(aspect_ratio: RouterAspectRatio) -> KinoviAspectRatio { + match aspect_ratio { + RouterAspectRatio::WideFiveByFour => KinoviAspectRatio::Standard4x3, // 1.25, nearest 1.33 + RouterAspectRatio::WideThreeByTwo => KinoviAspectRatio::Standard4x3, // 1.50, nearest 1.33 + RouterAspectRatio::TallFourByFive => KinoviAspectRatio::Portrait3x4, // 0.80, nearest 0.75 + RouterAspectRatio::TallTwoByThree => KinoviAspectRatio::Portrait3x4, // 0.67, nearest 0.75 + RouterAspectRatio::TallNineByTwentyOne => KinoviAspectRatio::Portrait9x16, // 0.43, nearest 0.56 + _ => KinoviAspectRatio::Square1x1, + } +} + +// Seedance 2.0 Mini supports output resolutions: 480p and 720p only. +// 1080p (and higher) is NOT supported — downgrade to 720p or error based on strategy. +fn plan_output_resolution( + resolution: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + match resolution { + None => Ok(None), + + // Direct mappings + Some(RouterResolution::FourEightyP) => Ok(Some(KinoviOutputResolution::FourEightyP)), + Some(RouterResolution::SevenTwentyP) => Ok(Some(KinoviOutputResolution::SevenTwentyP)), + + // 1080p is not supported for Mini — handle via strategy + Some(RouterResolution::TenEightyP) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "resolution", + value: format!("{:?}", RouterResolution::TenEightyP), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade + | RequestMismatchMitigationStrategy::PayLessDowngrade => { + // 1080p not available — downgrade to 720p (highest supported) + Ok(Some(KinoviOutputResolution::SevenTwentyP)) + } + }, + + // Other unsupported resolutions + Some(unsupported) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "resolution", + value: format!("{:?}", unsupported), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade + | RequestMismatchMitigationStrategy::PayLessDowngrade => { + Ok(Some(match unsupported { + RouterResolution::HalfK => KinoviOutputResolution::FourEightyP, + _ => KinoviOutputResolution::SevenTwentyP, + })) + } + }, + } +} + +// Seedance 2.0 Mini supports batch counts of 1–8. +fn plan_batch_count( + video_batch_count: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + let count = video_batch_count.unwrap_or(1); + match count { + 0 => Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations)), + 1 => Ok(KinoviBatchCount::One), + 2 => Ok(KinoviBatchCount::Two), + 3 => Ok(KinoviBatchCount::Three), + 4 => Ok(KinoviBatchCount::Four), + 5 => Ok(KinoviBatchCount::Five), + 6 => Ok(KinoviBatchCount::Six), + 7 => Ok(KinoviBatchCount::Seven), + 8 => Ok(KinoviBatchCount::Eight), + // Over the maximum of 8. + _ => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "video_batch_count", + value: format!("{}", count), + })) + } + RequestMismatchMitigationStrategy::PayMoreUpgrade + | RequestMismatchMitigationStrategy::PayLessDowngrade => Ok(KinoviBatchCount::Eight), + }, + } +} + +// Seedance 2.0 Mini supports duration of 4–15 seconds. +fn plan_duration( + duration_seconds: Option, + strategy: RequestMismatchMitigationStrategy, +) -> Result { + const MIN: u16 = 4; + const MAX: u16 = 15; + const DEFAULT: u8 = 5; + match duration_seconds { + None => Ok(DEFAULT), + Some(d) if d >= MIN && d <= MAX => Ok(d as u8), + Some(d) => match strategy { + RequestMismatchMitigationStrategy::ErrorOut => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "duration_seconds", + value: format!("{}", d), + })) + } + _ => Ok(d.clamp(MIN, MAX) as u8), + }, + } +} + +// Seedance 2.0 Mini bitrate: "standard" (default, field omitted) or "high". +// Bitrate does not affect cost. `Normal` and unset both map to standard (None); +// only `High` requests the higher bitrate. +fn plan_bitrate(bitrate: Option) -> Option { + match bitrate { + Some(RouterBitrate::High) => Some(KinoviBitrate::High), + Some(RouterBitrate::Normal) | None => None, + } +} + +#[cfg(test)] +mod tests { + use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{ + KinoviSeedance2p0MiniAspectRatio as KinoviAspectRatio, + KinoviSeedance2p0MiniBatchCount as KinoviBatchCount, + KinoviSeedance2p0MiniOutputResolution as KinoviOutputResolution, + }; + use tokens::tokens::characters::CharacterToken; + use tokens::tokens::media_files::MediaFileToken; + + use crate::api::audio_list_ref::AudioListRef; + use crate::api::character_list_ref::CharacterListRef; + use crate::api::router_aspect_ratio::RouterAspectRatio; + use crate::api::router_resolution::RouterResolution; + use crate::api::router_video_model::RouterVideoModel; + use crate::api::image_list_ref::ImageListRef; + use crate::api::image_ref::ImageRef; + use crate::api::router_provider::RouterProvider; + use crate::api::video_list_ref::VideoListRef; + use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::draft::KinoviSeedance2p0MiniDraftState; + use crate::generate::generate_video::video_generation_draft::VideoGenerationDraftRequest; + use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; + + use super::*; + + mod materialized_field_conversions { + use super::*; + + #[test] + fn prompt_is_passed_through() { + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(mini_builder())); + assert_eq!(draft.prompt, "a cat dancing"); + } + + #[test] + fn prompt_defaults_to_empty() { + let builder = GenerateVideoRequestBuilder { prompt: None, ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert_eq!(draft.prompt, ""); + } + + #[test] + fn duration_seconds_converted() { + let builder = GenerateVideoRequestBuilder { duration_seconds: Some(10), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert_eq!(draft.duration_seconds, 10); + } + + #[test] + fn duration_defaults_to_5() { + let builder = GenerateVideoRequestBuilder { duration_seconds: None, ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert_eq!(draft.duration_seconds, 5); + } + + #[test] + fn duration_clamped_to_max() { + let builder = GenerateVideoRequestBuilder { duration_seconds: Some(99), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert_eq!(draft.duration_seconds, 15); + } + } + + mod batch_count_conversions { + use super::*; + + #[test] + fn batch_counts_one_through_eight() { + let expected = [ + (1u16, KinoviBatchCount::One), + (2, KinoviBatchCount::Two), + (3, KinoviBatchCount::Three), + (4, KinoviBatchCount::Four), + (5, KinoviBatchCount::Five), + (6, KinoviBatchCount::Six), + (7, KinoviBatchCount::Seven), + (8, KinoviBatchCount::Eight), + ]; + for (count, variant) in expected { + let builder = GenerateVideoRequestBuilder { video_batch_count: Some(count), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!( + std::mem::discriminant(&draft.batch_count) == std::mem::discriminant(&variant), + "batch {count} mapped wrong", + ); + } + } + + #[test] + fn batch_over_max_downgrades_to_eight() { + let builder = GenerateVideoRequestBuilder { + video_batch_count: Some(12), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade, + ..mini_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.batch_count, KinoviBatchCount::Eight)); + } + + #[test] + fn batch_over_max_error_out() { + let builder = GenerateVideoRequestBuilder { + video_batch_count: Some(12), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + ..mini_builder() + }; + assert!(build_kinovi_seedance_2p0_mini(builder).is_err()); + } + + #[test] + fn batch_zero_errors() { + let builder = GenerateVideoRequestBuilder { video_batch_count: Some(0), ..mini_builder() }; + assert!(build_kinovi_seedance_2p0_mini(builder).is_err()); + } + } + + mod aspect_ratio_conversions { + use super::*; + + #[test] + fn aspect_ratio_wide() { + let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::WideSixteenByNine), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); + } + + #[test] + fn aspect_ratio_ultra_wide() { + let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::WideTwentyOneByNine), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::UltraWide21x9)); + } + + #[test] + fn aspect_ratio_tall() { + let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::TallNineBySixteen), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Portrait9x16)); + } + + #[test] + fn aspect_ratio_square() { + let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::Square), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Square1x1)); + } + + #[test] + fn aspect_ratio_four_by_three() { + let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::WideFourByThree), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Standard4x3)); + } + + #[test] + fn aspect_ratio_three_by_four() { + let builder = GenerateVideoRequestBuilder { aspect_ratio: Some(RouterAspectRatio::TallThreeByFour), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Portrait3x4)); + } + + #[test] + fn aspect_ratio_defaults_to_landscape() { + let builder = GenerateVideoRequestBuilder { aspect_ratio: None, ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); + } + } + + mod resolution_conversions { + use super::*; + + #[test] + fn resolution_480p() { + let builder = GenerateVideoRequestBuilder { resolution: Some(RouterResolution::FourEightyP), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::FourEightyP))); + } + + #[test] + fn resolution_720p() { + let builder = GenerateVideoRequestBuilder { resolution: Some(RouterResolution::SevenTwentyP), ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); + } + + #[test] + fn resolution_1080p_downgrades_to_720p() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(RouterResolution::TenEightyP), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade, + ..mini_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); + } + + #[test] + fn resolution_1080p_error_out() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(RouterResolution::TenEightyP), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + ..mini_builder() + }; + assert!(build_kinovi_seedance_2p0_mini(builder).is_err()); + } + + #[test] + fn resolution_none() { + let builder = GenerateVideoRequestBuilder { resolution: None, ..mini_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + assert!(draft.resolution.is_none()); + } + } + + mod unhandled_request_state { + use super::*; + + #[test] + fn media_refs_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + start_frame: Some(ImageRef::Url("https://example.com/start.jpg".to_string())), + end_frame: Some(ImageRef::Url("https://example.com/end.jpg".to_string())), + reference_images: Some(ImageListRef::Urls(vec!["https://example.com/ref.jpg".to_string()])), + reference_videos: Some(VideoListRef::Urls(vec!["https://example.com/vid.mp4".to_string()])), + reference_audio: Some(AudioListRef::Urls(vec!["https://example.com/audio.mp3".to_string()])), + reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![ + CharacterToken::new("char_abc".to_string()), + ])), + ..mini_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(remaining.start_frame.is_some()); + assert!(remaining.end_frame.is_some()); + assert!(remaining.reference_images.is_some()); + assert!(remaining.reference_videos.is_some()); + assert!(remaining.reference_audio.is_some()); + assert!(remaining.reference_character_tokens.is_some()); + } + + #[test] + fn start_frame_media_token_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new("mf_test123".to_string()))), + ..mini_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.start_frame, Some(ImageRef::MediaFileToken(t)) if t.as_str() == "mf_test123")); + } + + #[test] + fn empty_refs_are_none_in_unhandled() { + let draft = unwrap_draft(build_kinovi_seedance_2p0_mini(mini_builder())); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(remaining.start_frame.is_none()); + assert!(remaining.end_frame.is_none()); + assert!(remaining.reference_images.is_none()); + assert!(remaining.reference_videos.is_none()); + assert!(remaining.reference_audio.is_none()); + assert!(remaining.reference_character_tokens.is_none()); + } + } + + // ── Helpers ── + + fn mini_builder() -> GenerateVideoRequestBuilder { + GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0Mini, + provider: RouterProvider::Seedance2Pro, + prompt: Some("a cat dancing".to_string()), + duration_seconds: Some(5), + video_batch_count: Some(1), + ..Default::default() + } + } + + fn unwrap_draft(result: Result) -> KinoviSeedance2p0MiniDraftState { + match result.expect("build should succeed") { + VideoGenerationDraftOrRequest::Draft( + VideoGenerationDraftRequest::KinoviSeedance2p0Mini(draft) + ) => draft, + _ => panic!("expected KinoviSeedance2p0Mini draft"), + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/cost.rs new file mode 100644 index 0000000000..082b8230b0 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/cost.rs @@ -0,0 +1,378 @@ +use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{ + GenerateSeedance2p0MiniRequest, KinoviSeedance2p0MiniBatchCount, + KinoviSeedance2p0MiniOutputResolution, +}; + +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; +use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::draft::KinoviSeedance2p0MiniDraftState; +use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::request::KinoviSeedance2p0MiniRequestState; + +pub struct KinoviSeedance2p0MiniCostState { + pub resolution: Option, + pub duration_seconds: u8, + pub batch_count: Option, + pub has_video_reference: bool, +} + +impl KinoviSeedance2p0MiniCostState { + pub fn from_request(request: &KinoviSeedance2p0MiniRequestState) -> Self { + Self { + resolution: request.request.output_resolution, + duration_seconds: request.request.duration_seconds, + batch_count: request.request.batch_count, + has_video_reference: request.request.reference_video_urls + .as_ref() + .is_some_and(|urls| !urls.is_empty()), + } + } + + pub fn from_draft(draft: &KinoviSeedance2p0MiniDraftState) -> Self { + let has_video_reference = draft.unhandled_request_state + .as_ref() + .and_then(|rem| rem.reference_videos.as_ref()) + .is_some(); + + Self { + resolution: draft.resolution, + duration_seconds: draft.duration_seconds, + batch_count: Some(draft.batch_count), + has_video_reference, + } + } + + pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { + let pricing_request = GenerateSeedance2p0MiniRequest { + output_resolution: self.resolution, + duration_seconds: self.duration_seconds, + batch_count: self.batch_count, + + // PRESENCE of reference videos changes the price (per-second + // surcharge); the URL contents don't. + reference_video_urls: if self.has_video_reference { + Some(vec!["pricing-placeholder".to_string()]) + } else { + None + }, + + // No impact on price + prompt: String::new(), + aspect_ratio: None, + start_frame_url: None, + end_frame_url: None, + reference_image_urls: None, + reference_audio_urls: None, + character_ids: None, + use_face_blur_hack: None, + bitrate: None, + }; + + let costs = pricing_request.calculate_costs(); + // Mini credits can be fractional (e.g. 480p odd durations land on 37.5). + // The router's credit field is an integer, so round to the nearest credit; + // the USD cents (the authoritative charge) are already rounded up. + let cost_in_credits = costs.total_cost.kinovi_credits.round() as u64; + let cost_in_usd_cents = costs.total_cost.usd_cents_rounded_up; + + VideoGenerationCostEstimate { + cost_in_credits: Some(cost_in_credits), + 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_resolution::RouterResolution; + use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{ + KinoviSeedance2p0MiniOutputResolution as KinoviOutputResolution, + KinoviSeedance2p0MiniBatchCount as KinoviBatchCount, + }; + + use crate::api::router_video_model::RouterVideoModel; + use crate::api::router_provider::RouterProvider; + use crate::api::video_list_ref::VideoListRef; + use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; + use crate::generate::generate_video::video_generation_draft::VideoGenerationDraftRequest; + use crate::generate::generate_video::video_generation_draft_or_request::VideoGenerationDraftOrRequest; + + // ── USD cents: every combination (480p/720p × with/without video ref) ── + + mod pricing_480p { + use super::*; + + #[test] + fn without_video_reference() { + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One, false), 13); + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One, false), 16); + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One, false), 31); + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One, false), 47); + } + + #[test] + fn with_video_reference() { + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One, true), 16); + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One, true), 20); + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One, true), 40); + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One, true), 59); + } + } + + mod pricing_720p { + use super::*; + + #[test] + fn without_video_reference() { + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One, false), 33); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, false), 42); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One, false), 83); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One, false), 124); + } + + #[test] + fn with_video_reference() { + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One, true), 40); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, true), 50); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One, true), 99); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One, true), 149); + } + } + + // ── Credits (mini bills fractional credits, rounded for the integer field) ── + + mod credits_tests { + use super::*; + + #[test] + fn credits_480p() { + // 7.5 credits/s; odd durations are fractional (37.5 → 38, 112.5 → 113). + assert_eq!(credits(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One, false), 30); + assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One, false), 38); + assert_eq!(credits(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One, false), 75); + assert_eq!(credits(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One, false), 113); + } + + #[test] + fn credits_720p() { + assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, false), 100); + assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One, false), 300); + } + } + + // ── Relative pricing ── + + mod relative_pricing_tests { + use super::*; + + #[test] + fn cost_480p_cheaper_than_720p() { + let c480 = usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One, false); + let c720 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, false); + assert!(c480 < c720, "480p ({c480}) should be cheaper than 720p ({c720})"); + } + + #[test] + fn video_reference_costs_more() { + for res in [KinoviOutputResolution::FourEightyP, KinoviOutputResolution::SevenTwentyP] { + for dur in [4u8, 5, 10, 15] { + let without = usd_cents(res, dur, KinoviBatchCount::One, false); + let with = usd_cents(res, dur, KinoviBatchCount::One, true); + assert!(with > without, "video ref should cost more at {res:?} {dur}s"); + } + } + } + + #[test] + fn cost_scales_with_duration_and_batch() { + let c4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One, false); + let c15 = usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One, false); + assert!(c4 < c15); + let b1 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One, false); + let b4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four, false); + assert!(b1 < b4); + } + } + + // ── from_request() ── + + mod from_request_tests { + use super::*; + + #[test] + fn from_request_720p() { + let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false); + let cost = KinoviSeedance2p0MiniCostState::from_request(&req); + assert!(matches!(cost.resolution, Some(KinoviOutputResolution::SevenTwentyP))); + assert_eq!(cost.duration_seconds, 5); + assert!(!cost.has_video_reference); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(42)); + } + + #[test] + fn from_request_480p_with_video_reference() { + let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 5, KinoviBatchCount::One, true); + let cost = KinoviSeedance2p0MiniCostState::from_request(&req); + assert!(cost.has_video_reference); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(20)); + } + + #[test] + fn from_request_none_defaults_to_720p() { + let req = make_request_state(None, 5, KinoviBatchCount::One, false); + let cost = KinoviSeedance2p0MiniCostState::from_request(&req); + assert!(cost.resolution.is_none()); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(42)); + } + } + + // ── from_draft() ── + + mod from_draft_tests { + use super::*; + + #[test] + fn from_draft_480p() { + let draft = make_draft(5, 1, Some(RouterResolution::FourEightyP), false); + let cost = KinoviSeedance2p0MiniCostState::from_draft(&draft); + assert!(matches!(cost.resolution, Some(KinoviOutputResolution::FourEightyP))); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(16)); + } + + #[test] + fn from_draft_with_video_reference() { + let draft = make_draft(5, 1, Some(RouterResolution::SevenTwentyP), true); + let cost = KinoviSeedance2p0MiniCostState::from_draft(&draft); + assert!(cost.has_video_reference); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(50)); + } + + #[test] + fn from_draft_batch_8() { + // Mini supports up to batch 8: 720p 5s × 8 = 800 credits → 80000/243 → 330¢. + let draft = make_draft(5, 8, Some(RouterResolution::SevenTwentyP), false); + let cost = KinoviSeedance2p0MiniCostState::from_draft(&draft); + assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Eight))); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(330)); + } + } + + // ── Cross-check: from_draft matches from_request ── + + mod cross_check_tests { + use super::*; + + #[test] + fn draft_and_request_produce_same_cost() { + let draft = make_draft(10, 2, Some(RouterResolution::FourEightyP), true); + let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 10, KinoviBatchCount::Two, true); + let draft_cost = KinoviSeedance2p0MiniCostState::from_draft(&draft).estimate_cost(); + let req_cost = KinoviSeedance2p0MiniCostState::from_request(&req).estimate_cost(); + assert_eq!(draft_cost.cost_in_usd_cents, req_cost.cost_in_usd_cents); + assert_eq!(draft_cost.cost_in_credits, req_cost.cost_in_credits); + } + } + + // ── Helpers ── + + fn usd_cents( + resolution: KinoviOutputResolution, + duration_seconds: u8, + batch_count: KinoviBatchCount, + has_video_reference: bool, + ) -> u64 { + KinoviSeedance2p0MiniCostState { + resolution: Some(resolution), + duration_seconds, + batch_count: Some(batch_count), + has_video_reference, + } + .estimate_cost() + .cost_in_usd_cents + .unwrap() + } + + fn credits( + resolution: KinoviOutputResolution, + duration_seconds: u8, + batch_count: KinoviBatchCount, + has_video_reference: bool, + ) -> u64 { + KinoviSeedance2p0MiniCostState { + resolution: Some(resolution), + duration_seconds, + batch_count: Some(batch_count), + has_video_reference, + } + .estimate_cost() + .cost_in_credits + .unwrap() + } + + /// Build a draft via the builder to test from_draft(). + fn make_draft( + duration_seconds: u16, + video_batch_count: u16, + resolution: Option, + with_video_ref: bool, + ) -> KinoviSeedance2p0MiniDraftState { + let reference_videos = if with_video_ref { + Some(VideoListRef::Urls(vec!["https://example.com/video.mp4".to_string()])) + } else { + None + }; + + let builder = GenerateVideoRequestBuilder { + model: RouterVideoModel::Seedance2p0Mini, + provider: RouterProvider::Seedance2Pro, + resolution, + reference_videos, + duration_seconds: Some(duration_seconds), + video_batch_count: Some(video_batch_count), + ..Default::default() + }; + + match builder.build2().expect("build2 should succeed") { + VideoGenerationDraftOrRequest::Draft( + VideoGenerationDraftRequest::KinoviSeedance2p0Mini(draft) + ) => draft, + _ => panic!("expected KinoviSeedance2p0Mini draft"), + } + } + + /// Build a request state for from_request() tests. + fn make_request_state( + resolution: Option, + duration_seconds: u8, + batch_count: KinoviBatchCount, + with_video_ref: bool, + ) -> KinoviSeedance2p0MiniRequestState { + let reference_video_urls = if with_video_ref { + Some(vec!["https://cdn.seedance2-pro.com/video.mp4".to_string()]) + } else { + None + }; + + KinoviSeedance2p0MiniRequestState { + request: GenerateSeedance2p0MiniRequest { + prompt: "test".to_string(), + aspect_ratio: None, + output_resolution: resolution, + duration_seconds, + batch_count: Some(batch_count), + start_frame_url: None, + end_frame_url: None, + reference_image_urls: None, + reference_video_urls, + reference_audio_urls: None, + character_ids: None, + use_face_blur_hack: None, + bitrate: None, + }, + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/draft.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/draft.rs new file mode 100644 index 0000000000..c5706dcfb9 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/draft.rs @@ -0,0 +1,99 @@ +use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{ + GenerateSeedance2p0MiniRequest, KinoviSeedance2p0MiniAspectRatio, KinoviSeedance2p0MiniBitrate, + KinoviSeedance2p0MiniBatchCount, KinoviSeedance2p0MiniOutputResolution, +}; + +use crate::api::audio_list_ref::AudioListRef; +use crate::api::character_list_ref::CharacterListRef; +use crate::api::image_list_ref::ImageListRef; +use crate::api::image_ref::ImageRef; +use crate::api::video_list_ref::VideoListRef; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::providers::kinovi::resolve::{ + audio_list_ref_into_urls_or_tokens, image_list_ref_into_urls_or_tokens, + resolve_and_upload_list, resolve_and_upload_single, resolve_character_tokens, + video_list_ref_into_urls_or_tokens, +}; +use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::request::KinoviSeedance2p0MiniRequestState; +use crate::generate::generate_video::video_generation_draft_context::VideoGenerationDraftContext; + +#[derive(Debug, Clone)] +pub struct KinoviSeedance2p0MiniDraftState { + pub prompt: String, + pub aspect_ratio: KinoviSeedance2p0MiniAspectRatio, + pub resolution: Option, + pub duration_seconds: u8, + pub batch_count: KinoviSeedance2p0MiniBatchCount, + pub bitrate: Option, + + pub unhandled_request_state: Option, +} + +#[derive(Debug, Clone)] +pub struct KinoviSeedance2p0MiniRemainingItems { + pub start_frame: Option, + pub end_frame: Option, + pub reference_images: Option, + pub reference_videos: Option, + pub reference_audio: Option, + pub reference_character_tokens: Option, +} + +impl KinoviSeedance2p0MiniDraftState { + pub async fn to_request( + &mut self, + draft_context: &VideoGenerationDraftContext<'_>, + ) -> Result { + let client = draft_context.get_seedance2pro_client_ref()?; + let session = &client.session; + + let mut start_frame_url = None; + let mut end_frame_url = None; + let mut reference_image_urls = None; + let mut reference_video_urls = None; + let mut reference_audio_urls = None; + let mut character_ids = None; + + if let Some(remaining) = self.unhandled_request_state.take() { + let map = draft_context.media_file_to_artcraft_url_map; + + start_frame_url = resolve_and_upload_single(session, remaining.start_frame, map).await?; + end_frame_url = resolve_and_upload_single(session, remaining.end_frame, map).await?; + + reference_image_urls = resolve_and_upload_list( + session, remaining.reference_images.map(image_list_ref_into_urls_or_tokens), map, + ).await?; + + reference_video_urls = resolve_and_upload_list( + session, remaining.reference_videos.map(video_list_ref_into_urls_or_tokens), map, + ).await?; + + reference_audio_urls = resolve_and_upload_list( + session, remaining.reference_audio.map(audio_list_ref_into_urls_or_tokens), map, + ).await?; + + character_ids = resolve_character_tokens( + remaining.reference_character_tokens.as_ref(), + draft_context, + )?; + } + + let request = GenerateSeedance2p0MiniRequest { + prompt: self.prompt.clone(), + aspect_ratio: Some(self.aspect_ratio), + output_resolution: self.resolution, + duration_seconds: self.duration_seconds, + batch_count: Some(self.batch_count), + start_frame_url, + end_frame_url, + reference_image_urls, + reference_video_urls, + reference_audio_urls, + character_ids, + use_face_blur_hack: None, + bitrate: self.bitrate, + }; + + Ok(KinoviSeedance2p0MiniRequestState { request }) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/mod.rs new file mode 100644 index 0000000000..fcfc9ddce8 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/mod.rs @@ -0,0 +1,4 @@ +pub mod build; +pub mod draft; +pub mod request; +pub mod cost; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/request.rs new file mode 100644 index 0000000000..c861f19171 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/providers/kinovi/seedance_2p0_mini/request.rs @@ -0,0 +1,37 @@ +use seedance2pro_client::generate::video::generate_seedance_2p0_mini::{ + generate_seedance_2p0_mini, GenerateSeedance2p0MiniArgs, GenerateSeedance2p0MiniRequest, +}; + +use crate::client::router_seedance2pro_client::RouterSeedance2ProClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{GenerateVideoResponse, Seedance2proVideoResponsePayload}; + +#[derive(Debug, Clone)] +pub struct KinoviSeedance2p0MiniRequestState { + /// Final materialized request; ready to fire. + pub request: GenerateSeedance2p0MiniRequest, +} + +impl KinoviSeedance2p0MiniRequestState { + pub async fn send(&self, client: &RouterSeedance2ProClient) -> Result { + let session = &client.session; + + let args = GenerateSeedance2p0MiniArgs { + session, + host_override: None, + request: self.request.clone(), + }; + + let response = generate_seedance_2p0_mini(args) + .await + .map_err(|err| ArtcraftRouterError::Provider(ProviderError::Seedance2Pro(err)))?; + + Ok(GenerateVideoResponse::Seedance2Pro(Seedance2proVideoResponsePayload { + order_id: response.order_id, + task_id: response.task_id, + maybe_order_ids: response.order_ids, + maybe_task_ids: response.task_ids, + })) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_draft.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_draft.rs index a557dfd4d2..5d3c0f7950 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_draft.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_draft.rs @@ -7,6 +7,8 @@ use crate::generate::generate_video::providers::kinovi::seedance_2p0::cost::Kino use crate::generate::generate_video::providers::kinovi::seedance_2p0::draft::KinoviSeedance2p0DraftState; use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::cost::KinoviSeedance2p0FastCostState; use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::draft::KinoviSeedance2p0FastDraftState; +use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::cost::KinoviSeedance2p0MiniCostState; +use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::draft::KinoviSeedance2p0MiniDraftState; use crate::generate::generate_video::video_generation_draft_context::VideoGenerationDraftContext; use crate::generate::generate_video::video_generation_request::VideoGenerationRequest; @@ -18,6 +20,7 @@ pub enum VideoGenerationDraftRequest { KinoviHappyHorse1p0(KinoviHappyHorse1p0DraftState), KinoviSeedance2p0(KinoviSeedance2p0DraftState), KinoviSeedance2p0Fast(KinoviSeedance2p0FastDraftState), + KinoviSeedance2p0Mini(KinoviSeedance2p0MiniDraftState), } impl VideoGenerationDraftRequest { @@ -27,6 +30,7 @@ impl VideoGenerationDraftRequest { Self::KinoviHappyHorse1p0(_) => RouterProvider::Seedance2Pro, Self::KinoviSeedance2p0(_) => RouterProvider::Seedance2Pro, Self::KinoviSeedance2p0Fast(_) => RouterProvider::Seedance2Pro, + Self::KinoviSeedance2p0Mini(_) => RouterProvider::Seedance2Pro, } } @@ -36,6 +40,7 @@ impl VideoGenerationDraftRequest { VideoGenerationDraftRequest::KinoviHappyHorse1p0(draft) => Ok(KinoviHappyHorse1p0CostState::from_draft(draft).estimate_cost()), VideoGenerationDraftRequest::KinoviSeedance2p0(draft) => Ok(KinoviSeedance2p0CostState::from_draft(draft).estimate_cost()), VideoGenerationDraftRequest::KinoviSeedance2p0Fast(draft) => Ok(KinoviSeedance2p0FastCostState::from_draft(draft).estimate_cost()), + VideoGenerationDraftRequest::KinoviSeedance2p0Mini(draft) => Ok(KinoviSeedance2p0MiniCostState::from_draft(draft).estimate_cost()), } } @@ -55,6 +60,10 @@ impl VideoGenerationDraftRequest { let result = draft.to_request(&draft_context).await?; Ok(VideoGenerationRequest::KinoviSeedance2p0Fast(result)) }, + VideoGenerationDraftRequest::KinoviSeedance2p0Mini(mut draft) => { + let result = draft.to_request(&draft_context).await?; + Ok(VideoGenerationRequest::KinoviSeedance2p0Mini(result)) + }, } } } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_request.rs index fe0f18df67..2c5680ddf7 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_request.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/video_generation_request.rs @@ -47,6 +47,12 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu::cost use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu::request::ArtcraftSeedance2p0BytePlusUltraRequestState; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_fast::cost::ArtcraftSeedance2p0BytePlusUltraFastCostState; use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_fast::request::ArtcraftSeedance2p0BytePlusUltraFastRequestState; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::cost::ArtcraftSeedance2p0MiniCostState; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_mini::request::ArtcraftSeedance2p0MiniRequestState; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::cost::ArtcraftSeedance2p0BytePlusMiniCostState; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bp_mini::request::ArtcraftSeedance2p0BytePlusMiniRequestState; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::cost::ArtcraftSeedance2p0BytePlusUltraMiniCostState; +use crate::generate::generate_video::providers::artcraft::seedance_2p0_bpu_mini::request::ArtcraftSeedance2p0BytePlusUltraMiniRequestState; use crate::generate::generate_video::providers::artcraft::sora_2::cost::ArtcraftSora2CostState; use crate::generate::generate_video::providers::artcraft::sora_2::request::ArtcraftSora2RequestState; use crate::generate::generate_video::providers::artcraft::sora_2_pro::cost::ArtcraftSora2ProCostState; @@ -107,6 +113,8 @@ use crate::generate::generate_video::providers::kinovi::seedance_2p0::cost::Kino use crate::generate::generate_video::providers::kinovi::seedance_2p0::request::KinoviSeedance2p0RequestState; use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::cost::KinoviSeedance2p0FastCostState; use crate::generate::generate_video::providers::kinovi::seedance_2p0_fast::request::KinoviSeedance2p0FastRequestState; +use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::cost::KinoviSeedance2p0MiniCostState; +use crate::generate::generate_video::providers::kinovi::seedance_2p0_mini::request::KinoviSeedance2p0MiniRequestState; #[derive(Clone, Debug)] pub enum VideoGenerationRequest { @@ -132,6 +140,9 @@ pub enum VideoGenerationRequest { ArtcraftSeedance2p0BytePlusFast(ArtcraftSeedance2p0BytePlusFastRequestState), ArtcraftSeedance2p0BytePlusUltra(ArtcraftSeedance2p0BytePlusUltraRequestState), ArtcraftSeedance2p0BytePlusUltraFast(ArtcraftSeedance2p0BytePlusUltraFastRequestState), + ArtcraftSeedance2p0Mini(ArtcraftSeedance2p0MiniRequestState), + ArtcraftSeedance2p0BytePlusMini(ArtcraftSeedance2p0BytePlusMiniRequestState), + ArtcraftSeedance2p0BytePlusUltraMini(ArtcraftSeedance2p0BytePlusUltraMiniRequestState), ArtcraftSora2(ArtcraftSora2RequestState), ArtcraftSora2Pro(ArtcraftSora2ProRequestState), ArtcraftVeo2(ArtcraftVeo2RequestState), @@ -162,6 +173,7 @@ pub enum VideoGenerationRequest { KinoviHappyHorse1p0(KinoviHappyHorse1p0RequestState), KinoviSeedance2p0(KinoviSeedance2p0RequestState), KinoviSeedance2p0Fast(KinoviSeedance2p0FastRequestState), + KinoviSeedance2p0Mini(KinoviSeedance2p0MiniRequestState), } impl VideoGenerationRequest { @@ -190,6 +202,9 @@ impl VideoGenerationRequest { Self::ArtcraftSeedance2p0BytePlusFast(_) => RouterProvider::Artcraft, Self::ArtcraftSeedance2p0BytePlusUltra(_) => RouterProvider::Artcraft, Self::ArtcraftSeedance2p0BytePlusUltraFast(_) => RouterProvider::Artcraft, + Self::ArtcraftSeedance2p0Mini(_) => RouterProvider::Artcraft, + Self::ArtcraftSeedance2p0BytePlusMini(_) => RouterProvider::Artcraft, + Self::ArtcraftSeedance2p0BytePlusUltraMini(_) => RouterProvider::Artcraft, Self::ArtcraftSora2(_) => RouterProvider::Artcraft, Self::ArtcraftSora2Pro(_) => RouterProvider::Artcraft, Self::ArtcraftVeo2(_) => RouterProvider::Artcraft, @@ -220,6 +235,7 @@ impl VideoGenerationRequest { Self::KinoviHappyHorse1p0(_) => RouterProvider::Seedance2Pro, Self::KinoviSeedance2p0(_) => RouterProvider::Seedance2Pro, Self::KinoviSeedance2p0Fast(_) => RouterProvider::Seedance2Pro, + Self::KinoviSeedance2p0Mini(_) => RouterProvider::Seedance2Pro, } } @@ -248,6 +264,9 @@ impl VideoGenerationRequest { VideoGenerationRequest::ArtcraftSeedance2p0BytePlusFast(request) => Ok(ArtcraftSeedance2p0BytePlusFastCostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltra(request) => Ok(ArtcraftSeedance2p0BytePlusUltraCostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraFast(request) => Ok(ArtcraftSeedance2p0BytePlusUltraFastCostState::from_request(request).estimate_cost()), + VideoGenerationRequest::ArtcraftSeedance2p0Mini(request) => Ok(ArtcraftSeedance2p0MiniCostState::from_request(request).estimate_cost()), + VideoGenerationRequest::ArtcraftSeedance2p0BytePlusMini(request) => Ok(ArtcraftSeedance2p0BytePlusMiniCostState::from_request(request).estimate_cost()), + VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraMini(request) => Ok(ArtcraftSeedance2p0BytePlusUltraMiniCostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSora2(request) => Ok(ArtcraftSora2CostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSora2Pro(request) => Ok(ArtcraftSora2ProCostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftVeo2(request) => Ok(ArtcraftVeo2CostState::from_request(request).estimate_cost()), @@ -278,6 +297,7 @@ impl VideoGenerationRequest { VideoGenerationRequest::KinoviHappyHorse1p0(request) => Ok(KinoviHappyHorse1p0CostState::from_request(request).estimate_cost()), VideoGenerationRequest::KinoviSeedance2p0(request) => Ok(KinoviSeedance2p0CostState::from_request(request).estimate_cost()), VideoGenerationRequest::KinoviSeedance2p0Fast(request) => Ok(KinoviSeedance2p0FastCostState::from_request(request).estimate_cost()), + VideoGenerationRequest::KinoviSeedance2p0Mini(request) => Ok(KinoviSeedance2p0MiniCostState::from_request(request).estimate_cost()), } } @@ -373,6 +393,18 @@ impl VideoGenerationRequest { let client_ref = client.get_artcraft_client_ref()?; request.send(client_ref).await }, + VideoGenerationRequest::ArtcraftSeedance2p0Mini(request) => { + let client_ref = client.get_artcraft_client_ref()?; + request.send(client_ref).await + }, + VideoGenerationRequest::ArtcraftSeedance2p0BytePlusMini(request) => { + let client_ref = client.get_artcraft_client_ref()?; + request.send(client_ref).await + }, + VideoGenerationRequest::ArtcraftSeedance2p0BytePlusUltraMini(request) => { + let client_ref = client.get_artcraft_client_ref()?; + request.send(client_ref).await + }, VideoGenerationRequest::ArtcraftSora2(request) => { let client_ref = client.get_artcraft_client_ref()?; request.send(client_ref).await @@ -493,6 +525,10 @@ impl VideoGenerationRequest { let client_ref = client.get_seedance2pro_client_ref()?; request.send(client_ref).await }, + VideoGenerationRequest::KinoviSeedance2p0Mini(request) => { + let client_ref = client.get_seedance2pro_client_ref()?; + request.send(client_ref).await + }, } } } diff --git a/crates/desktop/artcraft/src/core/commands/generate/common/router_video_request_to_artcraft_prompt.rs b/crates/desktop/artcraft/src/core/commands/generate/common/router_video_request_to_artcraft_prompt.rs index bddb27002f..7a04afa90f 100644 --- a/crates/desktop/artcraft/src/core/commands/generate/common/router_video_request_to_artcraft_prompt.rs +++ b/crates/desktop/artcraft/src/core/commands/generate/common/router_video_request_to_artcraft_prompt.rs @@ -74,6 +74,9 @@ fn video_model_to_common_model_type(model: RouterVideoModel) -> Option Some(CommonModelType::Seedance2p0UltraFast), RouterVideoModel::Seedance2p0BytePlusUltra => Some(CommonModelType::Seedance2p0BytePlusUltra), RouterVideoModel::Seedance2p0BytePlusUltraFast => Some(CommonModelType::Seedance2p0BytePlusUltraFast), + RouterVideoModel::Seedance2p0Mini => Some(CommonModelType::Seedance2p0Mini), + RouterVideoModel::Seedance2p0BytePlusMini => Some(CommonModelType::Seedance2p0BytePlusMini), + RouterVideoModel::Seedance2p0BytePlusUltraMini => Some(CommonModelType::Seedance2p0BytePlusUltraMini), RouterVideoModel::PreviewModel => Some(CommonModelType::PreviewModel), RouterVideoModel::PreviewModelFast => Some(CommonModelType::PreviewModelFast), RouterVideoModel::GrokImagineVideo => Some(CommonModelType::GrokImagineVideo), diff --git a/crates/schema/public/enums/src/by_table/generic_inference_jobs/inference_model_type.rs b/crates/schema/public/enums/src/by_table/generic_inference_jobs/inference_model_type.rs index 545a16fd79..f564e4a56b 100644 --- a/crates/schema/public/enums/src/by_table/generic_inference_jobs/inference_model_type.rs +++ b/crates/schema/public/enums/src/by_table/generic_inference_jobs/inference_model_type.rs @@ -198,6 +198,12 @@ pub enum InferenceModelType { Seedance2p0BytePlusUltra, #[serde(rename = "seedance_2p0_bpu_fast")] Seedance2p0BytePlusUltraFast, + #[serde(rename = "seedance_2p0_mini")] + Seedance2p0Mini, + #[serde(rename = "seedance_2p0_bp_mini")] + Seedance2p0BytePlusMini, + #[serde(rename = "seedance_2p0_bpu_mini")] + Seedance2p0BytePlusUltraMini, #[serde(rename = "sora_2")] Sora2, #[serde(rename = "sora_2_pro")] @@ -320,6 +326,9 @@ impl InferenceModelType { Self::Seedance2p0UltraFast => "seedance_2p0_u_fast", Self::Seedance2p0BytePlusUltra => "seedance_2p0_bpu", Self::Seedance2p0BytePlusUltraFast => "seedance_2p0_bpu_fast", + Self::Seedance2p0Mini => "seedance_2p0_mini", + Self::Seedance2p0BytePlusMini => "seedance_2p0_bp_mini", + Self::Seedance2p0BytePlusUltraMini => "seedance_2p0_bpu_mini", Self::Sora2 => "sora_2", Self::Sora2Pro => "sora_2_pro", Self::Veo2 => "veo_2", @@ -422,6 +431,9 @@ impl InferenceModelType { "seedance_2p0_u_fast" => Ok(Self::Seedance2p0UltraFast), "seedance_2p0_bpu" => Ok(Self::Seedance2p0BytePlusUltra), "seedance_2p0_bpu_fast" => Ok(Self::Seedance2p0BytePlusUltraFast), + "seedance_2p0_mini" => Ok(Self::Seedance2p0Mini), + "seedance_2p0_bp_mini" => Ok(Self::Seedance2p0BytePlusMini), + "seedance_2p0_bpu_mini" => Ok(Self::Seedance2p0BytePlusUltraMini), "sora_2" => Ok(Self::Sora2), "sora_2_pro" => Ok(Self::Sora2Pro), "veo_2" => Ok(Self::Veo2), @@ -528,6 +540,9 @@ impl InferenceModelType { Self::Seedance2p0UltraFast, Self::Seedance2p0BytePlusUltra, Self::Seedance2p0BytePlusUltraFast, + Self::Seedance2p0Mini, + Self::Seedance2p0BytePlusMini, + Self::Seedance2p0BytePlusUltraMini, Self::Sora2, Self::Sora2Pro, Self::Veo2, @@ -617,6 +632,9 @@ impl InferenceModelType { CommonModelType::Seedance2p0UltraFast => Self::Seedance2p0UltraFast, CommonModelType::Seedance2p0BytePlusUltra => Self::Seedance2p0BytePlusUltra, CommonModelType::Seedance2p0BytePlusUltraFast => Self::Seedance2p0BytePlusUltraFast, + CommonModelType::Seedance2p0Mini => Self::Seedance2p0Mini, + CommonModelType::Seedance2p0BytePlusMini => Self::Seedance2p0BytePlusMini, + CommonModelType::Seedance2p0BytePlusUltraMini => Self::Seedance2p0BytePlusUltraMini, CommonModelType::Sora2 => Self::Sora2, CommonModelType::Sora2Pro => Self::Sora2Pro, CommonModelType::Veo2 => Self::Veo2, @@ -729,6 +747,9 @@ mod tests { assert_serialization(InferenceModelType::Seedance2p0UltraFast, "seedance_2p0_u_fast"); assert_serialization(InferenceModelType::Seedance2p0BytePlusUltra, "seedance_2p0_bpu"); assert_serialization(InferenceModelType::Seedance2p0BytePlusUltraFast, "seedance_2p0_bpu_fast"); + assert_serialization(InferenceModelType::Seedance2p0Mini, "seedance_2p0_mini"); + assert_serialization(InferenceModelType::Seedance2p0BytePlusMini, "seedance_2p0_bp_mini"); + assert_serialization(InferenceModelType::Seedance2p0BytePlusUltraMini, "seedance_2p0_bpu_mini"); assert_serialization(InferenceModelType::Sora2, "sora_2"); assert_serialization(InferenceModelType::Sora2Pro, "sora_2_pro"); assert_serialization(InferenceModelType::Veo2, "veo_2"); @@ -830,6 +851,9 @@ mod tests { assert_eq!(InferenceModelType::Seedance2p0UltraFast.to_str(), "seedance_2p0_u_fast"); assert_eq!(InferenceModelType::Seedance2p0BytePlusUltra.to_str(), "seedance_2p0_bpu"); assert_eq!(InferenceModelType::Seedance2p0BytePlusUltraFast.to_str(), "seedance_2p0_bpu_fast"); + assert_eq!(InferenceModelType::Seedance2p0Mini.to_str(), "seedance_2p0_mini"); + assert_eq!(InferenceModelType::Seedance2p0BytePlusMini.to_str(), "seedance_2p0_bp_mini"); + assert_eq!(InferenceModelType::Seedance2p0BytePlusUltraMini.to_str(), "seedance_2p0_bpu_mini"); assert_eq!(InferenceModelType::Sora2.to_str(), "sora_2"); assert_eq!(InferenceModelType::Sora2Pro.to_str(), "sora_2_pro"); assert_eq!(InferenceModelType::Veo2.to_str(), "veo_2"); @@ -931,6 +955,9 @@ mod tests { assert_eq!(InferenceModelType::from_str("seedance_2p0_u_fast").unwrap(), InferenceModelType::Seedance2p0UltraFast); assert_eq!(InferenceModelType::from_str("seedance_2p0_bpu").unwrap(), InferenceModelType::Seedance2p0BytePlusUltra); assert_eq!(InferenceModelType::from_str("seedance_2p0_bpu_fast").unwrap(), InferenceModelType::Seedance2p0BytePlusUltraFast); + assert_eq!(InferenceModelType::from_str("seedance_2p0_mini").unwrap(), InferenceModelType::Seedance2p0Mini); + assert_eq!(InferenceModelType::from_str("seedance_2p0_bp_mini").unwrap(), InferenceModelType::Seedance2p0BytePlusMini); + assert_eq!(InferenceModelType::from_str("seedance_2p0_bpu_mini").unwrap(), InferenceModelType::Seedance2p0BytePlusUltraMini); assert_eq!(InferenceModelType::from_str("sora_2").unwrap(), InferenceModelType::Sora2); assert_eq!(InferenceModelType::from_str("sora_2_pro").unwrap(), InferenceModelType::Sora2Pro); assert_eq!(InferenceModelType::from_str("veo_2").unwrap(), InferenceModelType::Veo2); diff --git a/crates/schema/public/enums/src/common/generation/common_model_type.rs b/crates/schema/public/enums/src/common/generation/common_model_type.rs index 897d48d53c..30038a6014 100644 --- a/crates/schema/public/enums/src/common/generation/common_model_type.rs +++ b/crates/schema/public/enums/src/common/generation/common_model_type.rs @@ -167,6 +167,12 @@ pub enum CommonModelType { Seedance2p0BytePlusUltra, #[serde(rename = "seedance_2p0_bpu_fast")] Seedance2p0BytePlusUltraFast, + #[serde(rename = "seedance_2p0_mini")] + Seedance2p0Mini, + #[serde(rename = "seedance_2p0_bp_mini")] + Seedance2p0BytePlusMini, + #[serde(rename = "seedance_2p0_bpu_mini")] + Seedance2p0BytePlusUltraMini, #[serde(rename = "sora_2")] Sora2, #[serde(rename = "sora_2_pro")] @@ -273,6 +279,9 @@ impl CommonModelType { Self::Seedance2p0UltraFast => "seedance_2p0_u_fast", Self::Seedance2p0BytePlusUltra => "seedance_2p0_bpu", Self::Seedance2p0BytePlusUltraFast => "seedance_2p0_bpu_fast", + Self::Seedance2p0Mini => "seedance_2p0_mini", + Self::Seedance2p0BytePlusMini => "seedance_2p0_bp_mini", + Self::Seedance2p0BytePlusUltraMini => "seedance_2p0_bpu_mini", Self::Sora2 => "sora_2", Self::Sora2Pro => "sora_2_pro", Self::Veo2 => "veo_2", @@ -358,6 +367,9 @@ impl CommonModelType { "seedance_2p0_u_fast" => Ok(Self::Seedance2p0UltraFast), "seedance_2p0_bpu" => Ok(Self::Seedance2p0BytePlusUltra), "seedance_2p0_bpu_fast" => Ok(Self::Seedance2p0BytePlusUltraFast), + "seedance_2p0_mini" => Ok(Self::Seedance2p0Mini), + "seedance_2p0_bp_mini" => Ok(Self::Seedance2p0BytePlusMini), + "seedance_2p0_bpu_mini" => Ok(Self::Seedance2p0BytePlusUltraMini), "sora_2" => Ok(Self::Sora2), "sora_2_pro" => Ok(Self::Sora2Pro), "veo_2" => Ok(Self::Veo2), @@ -447,6 +459,9 @@ impl CommonModelType { Self::Seedance2p0UltraFast, Self::Seedance2p0BytePlusUltra, Self::Seedance2p0BytePlusUltraFast, + Self::Seedance2p0Mini, + Self::Seedance2p0BytePlusMini, + Self::Seedance2p0BytePlusUltraMini, Self::Sora2, Self::Sora2Pro, Self::Veo2, @@ -533,6 +548,9 @@ impl CommonModelType { Self::Seedance2p0UltraFast => CommonModelClass::Video, Self::Seedance2p0BytePlusUltra => CommonModelClass::Video, Self::Seedance2p0BytePlusUltraFast => CommonModelClass::Video, + Self::Seedance2p0Mini => CommonModelClass::Video, + Self::Seedance2p0BytePlusMini => CommonModelClass::Video, + Self::Seedance2p0BytePlusUltraMini => CommonModelClass::Video, Self::Sora2 => CommonModelClass::Video, Self::Sora2Pro => CommonModelClass::Video, Self::Veo2 => CommonModelClass::Video, @@ -628,6 +646,9 @@ mod tests { assert_serialization(CommonModelType::Seedance2p0UltraFast, "seedance_2p0_u_fast"); assert_serialization(CommonModelType::Seedance2p0BytePlusUltra, "seedance_2p0_bpu"); assert_serialization(CommonModelType::Seedance2p0BytePlusUltraFast, "seedance_2p0_bpu_fast"); + assert_serialization(CommonModelType::Seedance2p0Mini, "seedance_2p0_mini"); + assert_serialization(CommonModelType::Seedance2p0BytePlusMini, "seedance_2p0_bp_mini"); + assert_serialization(CommonModelType::Seedance2p0BytePlusUltraMini, "seedance_2p0_bpu_mini"); assert_serialization(CommonModelType::Sora2, "sora_2"); assert_serialization(CommonModelType::Sora2Pro, "sora_2_pro"); assert_serialization(CommonModelType::Veo2, "veo_2"); @@ -710,6 +731,9 @@ mod tests { assert_eq!(CommonModelType::Seedance2p0UltraFast.to_str(), "seedance_2p0_u_fast"); assert_eq!(CommonModelType::Seedance2p0BytePlusUltra.to_str(), "seedance_2p0_bpu"); assert_eq!(CommonModelType::Seedance2p0BytePlusUltraFast.to_str(), "seedance_2p0_bpu_fast"); + assert_eq!(CommonModelType::Seedance2p0Mini.to_str(), "seedance_2p0_mini"); + assert_eq!(CommonModelType::Seedance2p0BytePlusMini.to_str(), "seedance_2p0_bp_mini"); + assert_eq!(CommonModelType::Seedance2p0BytePlusUltraMini.to_str(), "seedance_2p0_bpu_mini"); assert_eq!(CommonModelType::Sora2.to_str(), "sora_2"); assert_eq!(CommonModelType::Sora2Pro.to_str(), "sora_2_pro"); assert_eq!(CommonModelType::Veo2.to_str(), "veo_2"); @@ -792,6 +816,9 @@ mod tests { assert_eq!(CommonModelType::from_str("seedance_2p0_u_fast").unwrap(), CommonModelType::Seedance2p0UltraFast); assert_eq!(CommonModelType::from_str("seedance_2p0_bpu").unwrap(), CommonModelType::Seedance2p0BytePlusUltra); assert_eq!(CommonModelType::from_str("seedance_2p0_bpu_fast").unwrap(), CommonModelType::Seedance2p0BytePlusUltraFast); + assert_eq!(CommonModelType::from_str("seedance_2p0_mini").unwrap(), CommonModelType::Seedance2p0Mini); + assert_eq!(CommonModelType::from_str("seedance_2p0_bp_mini").unwrap(), CommonModelType::Seedance2p0BytePlusMini); + assert_eq!(CommonModelType::from_str("seedance_2p0_bpu_mini").unwrap(), CommonModelType::Seedance2p0BytePlusUltraMini); assert_eq!(CommonModelType::from_str("sora_2").unwrap(), CommonModelType::Sora2); assert_eq!(CommonModelType::from_str("sora_2_pro").unwrap(), CommonModelType::Sora2Pro); assert_eq!(CommonModelType::from_str("veo_2").unwrap(), CommonModelType::Veo2); @@ -815,7 +842,7 @@ mod tests { #[test] fn all_variants() { let mut variants = CommonModelType::all_variants(); - assert_eq!(variants.len(), 73); + assert_eq!(variants.len(), 76); // Image models assert_eq!(variants.pop_first(), Some(CommonModelType::Flux1Dev)); assert_eq!(variants.pop_first(), Some(CommonModelType::Flux1Schnell)); @@ -876,6 +903,9 @@ mod tests { assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0UltraFast)); assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0BytePlusUltra)); assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0BytePlusUltraFast)); + assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0Mini)); + assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0BytePlusMini)); + assert_eq!(variants.pop_first(), Some(CommonModelType::Seedance2p0BytePlusUltraMini)); assert_eq!(variants.pop_first(), Some(CommonModelType::Sora2)); assert_eq!(variants.pop_first(), Some(CommonModelType::Sora2Pro)); assert_eq!(variants.pop_first(), Some(CommonModelType::Veo2)); @@ -974,6 +1004,9 @@ mod tests { assert_eq!(CommonModelType::Veo3.get_model_class(), CommonModelClass::Video); assert_eq!(CommonModelType::Sora2.get_model_class(), CommonModelClass::Video); assert_eq!(CommonModelType::Kling3p0Pro.get_model_class(), CommonModelClass::Video); + assert_eq!(CommonModelType::Seedance2p0Mini.get_model_class(), CommonModelClass::Video); + assert_eq!(CommonModelType::Seedance2p0BytePlusMini.get_model_class(), CommonModelClass::Video); + assert_eq!(CommonModelType::Seedance2p0BytePlusUltraMini.get_model_class(), CommonModelClass::Video); } #[test] diff --git a/crates/schema/public/enums/src/common/generation/common_video_model.rs b/crates/schema/public/enums/src/common/generation/common_video_model.rs index f3c86f7947..f585390bd0 100644 --- a/crates/schema/public/enums/src/common/generation/common_video_model.rs +++ b/crates/schema/public/enums/src/common/generation/common_video_model.rs @@ -71,6 +71,15 @@ pub enum CommonVideoModel { #[serde(rename = "seedance_2p0_bpu_fast")] Seedance2p0BytePlusUltraFast, + #[serde(rename = "seedance_2p0_mini")] + Seedance2p0Mini, + + #[serde(rename = "seedance_2p0_bp_mini")] + Seedance2p0BytePlusMini, + + #[serde(rename = "seedance_2p0_bpu_mini")] + Seedance2p0BytePlusUltraMini, + #[serde(rename = "sora_2")] Sora2, @@ -128,6 +137,9 @@ impl CommonVideoModel { Self::Seedance2p0UltraFast => CommonModelType::Seedance2p0UltraFast, Self::Seedance2p0BytePlusUltra => CommonModelType::Seedance2p0BytePlusUltra, Self::Seedance2p0BytePlusUltraFast => CommonModelType::Seedance2p0BytePlusUltraFast, + Self::Seedance2p0Mini => CommonModelType::Seedance2p0Mini, + Self::Seedance2p0BytePlusMini => CommonModelType::Seedance2p0BytePlusMini, + Self::Seedance2p0BytePlusUltraMini => CommonModelType::Seedance2p0BytePlusUltraMini, Self::Sora2 => CommonModelType::Sora2, Self::Sora2Pro => CommonModelType::Sora2Pro, Self::Veo2 => CommonModelType::Veo2, @@ -170,6 +182,9 @@ mod tests { assert_serialization(CommonVideoModel::Seedance2p0UltraFast, "seedance_2p0_u_fast"); assert_serialization(CommonVideoModel::Seedance2p0BytePlusUltra, "seedance_2p0_bpu"); assert_serialization(CommonVideoModel::Seedance2p0BytePlusUltraFast, "seedance_2p0_bpu_fast"); + assert_serialization(CommonVideoModel::Seedance2p0Mini, "seedance_2p0_mini"); + assert_serialization(CommonVideoModel::Seedance2p0BytePlusMini, "seedance_2p0_bp_mini"); + assert_serialization(CommonVideoModel::Seedance2p0BytePlusUltraMini, "seedance_2p0_bpu_mini"); assert_serialization(CommonVideoModel::Sora2, "sora_2"); assert_serialization(CommonVideoModel::Sora2Pro, "sora_2_pro"); assert_serialization(CommonVideoModel::Veo2, "veo_2"); @@ -205,6 +220,9 @@ mod tests { ("seedance_2p0_u_fast", CommonVideoModel::Seedance2p0UltraFast), ("seedance_2p0_bpu", CommonVideoModel::Seedance2p0BytePlusUltra), ("seedance_2p0_bpu_fast", CommonVideoModel::Seedance2p0BytePlusUltraFast), + ("seedance_2p0_mini", CommonVideoModel::Seedance2p0Mini), + ("seedance_2p0_bp_mini", CommonVideoModel::Seedance2p0BytePlusMini), + ("seedance_2p0_bpu_mini", CommonVideoModel::Seedance2p0BytePlusUltraMini), ("sora_2", CommonVideoModel::Sora2), ("sora_2_pro", CommonVideoModel::Sora2Pro), ("veo_2", CommonVideoModel::Veo2), @@ -247,6 +265,9 @@ mod tests { CommonVideoModel::Seedance2p0UltraFast, CommonVideoModel::Seedance2p0BytePlusUltra, CommonVideoModel::Seedance2p0BytePlusUltraFast, + CommonVideoModel::Seedance2p0Mini, + CommonVideoModel::Seedance2p0BytePlusMini, + CommonVideoModel::Seedance2p0BytePlusUltraMini, CommonVideoModel::Sora2, CommonVideoModel::Sora2Pro, CommonVideoModel::Veo2, @@ -288,6 +309,9 @@ mod tests { (CommonVideoModel::Seedance2p0UltraFast, CommonModelType::Seedance2p0UltraFast), (CommonVideoModel::Seedance2p0BytePlusUltra, CommonModelType::Seedance2p0BytePlusUltra), (CommonVideoModel::Seedance2p0BytePlusUltraFast, CommonModelType::Seedance2p0BytePlusUltraFast), + (CommonVideoModel::Seedance2p0Mini, CommonModelType::Seedance2p0Mini), + (CommonVideoModel::Seedance2p0BytePlusMini, CommonModelType::Seedance2p0BytePlusMini), + (CommonVideoModel::Seedance2p0BytePlusUltraMini, CommonModelType::Seedance2p0BytePlusUltraMini), (CommonVideoModel::Sora2, CommonModelType::Sora2), (CommonVideoModel::Sora2Pro, CommonModelType::Sora2Pro), (CommonVideoModel::Veo2, CommonModelType::Veo2), diff --git a/crates/service/web/storyteller_web/src/configs/omni_gen/video_models/by_type/seedance_2p0_video_models.rs b/crates/service/web/storyteller_web/src/configs/omni_gen/video_models/by_type/seedance_2p0_video_models.rs index d7713e374f..8ca806a161 100644 --- a/crates/service/web/storyteller_web/src/configs/omni_gen/video_models/by_type/seedance_2p0_video_models.rs +++ b/crates/service/web/storyteller_web/src/configs/omni_gen/video_models/by_type/seedance_2p0_video_models.rs @@ -7,7 +7,8 @@ use enums::common::generation::model_creator::ModelCreator; /// The Seedance 2.0 family of video models, in display order: /// Seedance 2.0, Seedance 2.0 Fast, Seedance 2.0 BytePlus, Seedance 2.0 BytePlus -/// Fast, Seedance 2.0 BytePlus Ultra, Seedance 2.0 BytePlus Ultra Fast. +/// Fast, Seedance 2.0 BytePlus Ultra, Seedance 2.0 BytePlus Ultra Fast, +/// Seedance 2.0 Mini, Seedance 2.0 BytePlus Mini, Seedance 2.0 BytePlus Ultra Mini. pub fn seedance_2p0_video_models() -> Vec { let mut models = Vec::new(); @@ -292,5 +293,143 @@ pub fn seedance_2p0_video_models() -> Vec { ..Default::default() }); + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Seedance2p0Mini, + model_creator: Some(ModelCreator::Bytedance), + full_name: Some("Seedance 2.0 Mini".to_string()), + extra_info: Some("The Chinese Volcengine (ByteDance China API platform) version of Seedance 2.0 Mini. This may be better at some characters than the other Seedance models.".to_string()), + extra_info_short: Some("Chinese Volcengine Version".to_string()), + text_prompt_supported: Some(true), + starting_keyframe_supported: Some(true), + ending_keyframe_supported: Some(true), + image_references_supported: Some(true), + image_references_max: Some(9), + audio_references_supported: Some(true), + audio_references_max: Some(3), + audio_references_max_total_duration_seconds: Some(15), + video_references_supported: Some(true), + video_references_max: Some(3), + video_references_max_total_duration_seconds: Some(15), + character_references_supported: Some(true), + character_references_max: Some(9), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::WideTwentyOneByNine, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::WideFourByThree, + CommonAspectRatio::Square, + CommonAspectRatio::TallThreeByFour, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + resolution_options: Some(vec![ + CommonResolution::FourEightyP, + CommonResolution::SevenTwentyP, + ]), + resolution_default: Some(CommonResolution::SevenTwentyP), + bitrate_options: Some(vec![ + CommonBitrate::Normal, + CommonBitrate::High, + ]), + bitrate_default: Some(CommonBitrate::Normal), + batch_size_options: Some(vec![1, 2, 4]), + batch_size_default: Some(1), + duration_seconds_min: Some(4), + duration_seconds_max: Some(15), + duration_seconds_default: Some(5), + ..Default::default() + }); + + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Seedance2p0BytePlusMini, + model_creator: Some(ModelCreator::Bytedance), + full_name: Some("Seedance 2.0 Plus Mini".to_string()), + extra_info: Some("The Chinese BytePlus (ByteDance's Western API platform) version of Seedance 2.0 Mini. This has fewer restrictions on faces and IP.".to_string()), + extra_info_short: Some("BytePlus Version".to_string()), + text_prompt_supported: Some(true), + starting_keyframe_supported: Some(true), + ending_keyframe_supported: Some(true), + image_references_supported: Some(true), + image_references_max: Some(9), + audio_references_supported: Some(true), + audio_references_max: Some(3), + audio_references_max_total_duration_seconds: Some(15), + video_references_supported: Some(true), + video_references_max: Some(3), + video_references_max_total_duration_seconds: Some(15), + character_references_supported: Some(false), // TODO + character_references_max: Some(9), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::WideTwentyOneByNine, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::WideFourByThree, + CommonAspectRatio::Square, + CommonAspectRatio::TallThreeByFour, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + resolution_options: Some(vec![ + CommonResolution::FourEightyP, + CommonResolution::SevenTwentyP, + ]), + resolution_default: Some(CommonResolution::SevenTwentyP), + bitrate_options: Some(vec![ + CommonBitrate::Normal, + CommonBitrate::High, + ]), + bitrate_default: Some(CommonBitrate::Normal), + batch_size_options: Some(vec![1, 2, 4]), + batch_size_default: Some(1), + duration_seconds_min: Some(4), + duration_seconds_max: Some(15), + duration_seconds_default: Some(5), + ..Default::default() + }); + + models.push(OmniGenVideoModelDetails { + model: CommonVideoModel::Seedance2p0BytePlusUltraMini, + model_creator: Some(ModelCreator::Bytedance), + full_name: Some("Seedance 2.0 Plus Ultra Mini".to_string()), + extra_info: Some("This is the same BytePlus version of Seedance 2.0 Mini, but with even fewer restrictions around content. Horror movies, action movie violence, and more is possible.".to_string()), + extra_info_short: Some("BytePlus Version".to_string()), + text_prompt_supported: Some(true), + starting_keyframe_supported: Some(true), + ending_keyframe_supported: Some(true), + image_references_supported: Some(true), + image_references_max: Some(9), + audio_references_supported: Some(true), + audio_references_max: Some(3), + audio_references_max_total_duration_seconds: Some(15), + video_references_supported: Some(true), + video_references_max: Some(3), + video_references_max_total_duration_seconds: Some(15), + character_references_supported: Some(false), // TODO + character_references_max: Some(9), + aspect_ratio_options: Some(vec![ + CommonAspectRatio::WideTwentyOneByNine, + CommonAspectRatio::WideSixteenByNine, + CommonAspectRatio::WideFourByThree, + CommonAspectRatio::Square, + CommonAspectRatio::TallThreeByFour, + CommonAspectRatio::TallNineBySixteen, + ]), + aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine), + resolution_options: Some(vec![ + CommonResolution::FourEightyP, + CommonResolution::SevenTwentyP, + ]), + resolution_default: Some(CommonResolution::SevenTwentyP), + bitrate_options: Some(vec![ + CommonBitrate::Normal, + CommonBitrate::High, + ]), + bitrate_default: Some(CommonBitrate::Normal), + batch_size_options: Some(vec![1, 2, 4]), + batch_size_default: Some(1), + duration_seconds_min: Some(4), + duration_seconds_max: Some(15), + duration_seconds_default: Some(5), + ..Default::default() + }); + models } diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/video/estimate_video_cost_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/video/estimate_video_cost_handler.rs index 0e56a68bb3..5594664c16 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/video/estimate_video_cost_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/cost_estimate/video/estimate_video_cost_handler.rs @@ -150,6 +150,9 @@ fn map_video_model(model: CommonVideoModel) -> Result RouterVideoModel::Seedance2p0BytePlusFast, CommonVideoModel::Seedance2p0BytePlusUltra => RouterVideoModel::Seedance2p0BytePlusUltra, CommonVideoModel::Seedance2p0BytePlusUltraFast => RouterVideoModel::Seedance2p0BytePlusUltraFast, + CommonVideoModel::Seedance2p0Mini => RouterVideoModel::Seedance2p0Mini, + CommonVideoModel::Seedance2p0BytePlusMini => RouterVideoModel::Seedance2p0BytePlusMini, + CommonVideoModel::Seedance2p0BytePlusUltraMini => RouterVideoModel::Seedance2p0BytePlusUltraMini, CommonVideoModel::HappyHorse1p0 => RouterVideoModel::HappyHorse1p0, CommonVideoModel::Sora2 => RouterVideoModel::Sora2, CommonVideoModel::Sora2Pro => RouterVideoModel::Sora2Pro, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs index 29365d5218..76a9792c9e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_api/generate/video/omni_api_video_generate_handler.rs @@ -188,9 +188,11 @@ pub async fn omni_api_video_generate_handler( // BytePlus Ultra Some(CommonVideoModel::Seedance2p0BytePlusUltra) => KinoviAccount::BytePlusUltra, Some(CommonVideoModel::Seedance2p0BytePlusUltraFast) => KinoviAccount::BytePlusUltra, + Some(CommonVideoModel::Seedance2p0BytePlusUltraMini) => KinoviAccount::BytePlusUltra, // BytePlus Some(CommonVideoModel::Seedance2p0BytePlus) => KinoviAccount::BytePlus, Some(CommonVideoModel::Seedance2p0BytePlusFast) => KinoviAccount::BytePlus, + Some(CommonVideoModel::Seedance2p0BytePlusMini) => KinoviAccount::BytePlus, Some(CommonVideoModel::PreviewModel) => KinoviAccount::BytePlus, Some(CommonVideoModel::PreviewModelFast) => KinoviAccount::BytePlus, // Everything else goes through Volcengine diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs index 3b0e752fc4..d1895e7c38 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/omni_gen_video_generate_handler.rs @@ -178,9 +178,11 @@ pub async fn omni_gen_video_generate_handler( // BytePlus Ultra Some(CommonVideoModel::Seedance2p0BytePlusUltra) => KinoviAccount::BytePlusUltra, Some(CommonVideoModel::Seedance2p0BytePlusUltraFast) => KinoviAccount::BytePlusUltra, + Some(CommonVideoModel::Seedance2p0BytePlusUltraMini) => KinoviAccount::BytePlusUltra, // BytePlus Some(CommonVideoModel::Seedance2p0BytePlus) => KinoviAccount::BytePlus, Some(CommonVideoModel::Seedance2p0BytePlusFast) => KinoviAccount::BytePlus, + Some(CommonVideoModel::Seedance2p0BytePlusMini) => KinoviAccount::BytePlus, Some(CommonVideoModel::PreviewModel) => KinoviAccount::BytePlus, Some(CommonVideoModel::PreviewModelFast) => KinoviAccount::BytePlus, // Everything else goes through Volcengine diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/pipeline_v2/run_pipeline_v2.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/pipeline_v2/run_pipeline_v2.rs index 0bcd0ee432..99a8250c78 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/pipeline_v2/run_pipeline_v2.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/omni_gen/generate/video/pipeline_v2/run_pipeline_v2.rs @@ -67,6 +67,9 @@ pub async fn run_pipeline_v2(args: RunPipelineV2Args<'_>) -> Result RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0 => RouterProvider::Seedance2Pro, RouterVideoModel::Seedance2p0Fast => RouterProvider::Seedance2Pro, + RouterVideoModel::Seedance2p0Mini => RouterProvider::Seedance2Pro, + RouterVideoModel::Seedance2p0BytePlusMini => RouterProvider::Seedance2Pro, + RouterVideoModel::Seedance2p0BytePlusUltraMini => RouterProvider::Seedance2Pro, //RouterVideoModel::Seedance2p0Ultra => RouterProvider::GmiCloud, //RouterVideoModel::Seedance2p0UltraFast => RouterProvider::GmiCloud, RouterVideoModel::GrokImagineVideo => RouterProvider::GrokApi, diff --git a/frontend/apps/artcraft-webapp/src/main.tsx b/frontend/apps/artcraft-webapp/src/main.tsx index c605a64d68..e22bd5eeee 100644 --- a/frontend/apps/artcraft-webapp/src/main.tsx +++ b/frontend/apps/artcraft-webapp/src/main.tsx @@ -22,7 +22,7 @@ if (import.meta.env.DEV) { // (disabled — it overrides the origin above and points at localhost:12345, // which breaks dev unless a local backend is running. Re-enable only when // testing against a local storyteller-web.) - // StorytellerApiHostStore.getInstance().setDevelopment(); + StorytellerApiHostStore.getInstance().setDevelopment(); } catch (e) { console.warn("Failed to set dev API host override", e); } diff --git a/frontend/libs/components/moodboard/tsconfig.json b/frontend/libs/components/moodboard/tsconfig.json index 57a0f82236..ec9b027b9e 100644 --- a/frontend/libs/components/moodboard/tsconfig.json +++ b/frontend/libs/components/moodboard/tsconfig.json @@ -3,16 +3,19 @@ "include": [], "references": [ { - "path": "../popover" + "path": "../modal" }, { - "path": "../button-icon-select" + "path": "../button" }, { "path": "../tooltip" }, { - "path": "../button" + "path": "../popover" + }, + { + "path": "../button-icon-select" }, { "path": "./tsconfig.lib.json" diff --git a/frontend/libs/components/moodboard/tsconfig.lib.json b/frontend/libs/components/moodboard/tsconfig.lib.json index 900bddafc1..b2416adc27 100644 --- a/frontend/libs/components/moodboard/tsconfig.lib.json +++ b/frontend/libs/components/moodboard/tsconfig.lib.json @@ -26,16 +26,19 @@ "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"], "references": [ { - "path": "../popover/tsconfig.lib.json" + "path": "../modal/tsconfig.lib.json" }, { - "path": "../button-icon-select/tsconfig.lib.json" + "path": "../button/tsconfig.lib.json" }, { "path": "../tooltip/tsconfig.lib.json" }, { - "path": "../button/tsconfig.lib.json" + "path": "../popover/tsconfig.lib.json" + }, + { + "path": "../button-icon-select/tsconfig.lib.json" } ] } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 98fb0146c6..1fd80784a2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,7 +10,9 @@ "license": "MIT", "workspaces": [ "apps/**/*", - "libs/**/*" + "libs/**/*", + "!apps/artcraft-dashboard", + "!apps/artcraft-dashboard/**" ], "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -400,6 +402,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -415,6 +418,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -531,6 +535,7 @@ "license": "MIT" }, "apps/video-info-website": { + "name": "@frontend/video-info-website", "version": "0.0.1" }, "libs/analytics/google-analytics": { @@ -886,6 +891,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -1398,7 +1404,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1411,7 +1416,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1424,7 +1428,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -1437,7 +1440,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1500,7 +1502,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1513,7 +1514,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1541,7 +1541,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1554,7 +1553,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1567,7 +1565,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -1580,7 +1577,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1593,7 +1589,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1606,7 +1601,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -1619,7 +1613,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -1635,7 +1628,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -2870,6 +2862,7 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -2946,6 +2939,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -3087,6 +3081,7 @@ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", "license": "MIT", + "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" @@ -3097,6 +3092,7 @@ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -3122,6 +3118,7 @@ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", "license": "MIT", + "peer": true, "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -3145,7 +3142,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=12" } @@ -3163,7 +3159,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } @@ -3181,7 +3176,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } @@ -3199,7 +3193,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } @@ -3217,7 +3210,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=12" } @@ -3235,7 +3227,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=12" } @@ -3253,7 +3244,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -3271,7 +3261,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -3289,7 +3278,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3307,7 +3295,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3325,7 +3312,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3343,7 +3329,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3361,7 +3346,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3379,7 +3363,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3397,7 +3380,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3415,7 +3397,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3433,7 +3414,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } @@ -3467,7 +3447,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -3501,7 +3480,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=12" } @@ -3535,7 +3513,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=12" } @@ -3553,7 +3530,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } @@ -3571,7 +3547,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } @@ -3589,7 +3564,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } @@ -3819,6 +3793,7 @@ "resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -4088,7 +4063,6 @@ "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -4098,7 +4072,6 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "license": "ISC", - "peer": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -4115,7 +4088,6 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "license": "MIT", - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -4125,7 +4097,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -4135,7 +4106,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -4149,7 +4119,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "license": "MIT", - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -4163,7 +4132,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -4176,7 +4144,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -4192,7 +4159,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -4205,7 +4171,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -4225,7 +4190,6 @@ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -4244,7 +4208,6 @@ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -4293,7 +4256,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -4307,7 +4269,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -4322,15 +4283,13 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jest/create-cache-key-function": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3" }, @@ -4343,7 +4302,6 @@ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -4360,7 +4318,6 @@ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -4387,7 +4344,6 @@ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -4406,7 +4362,6 @@ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -4423,7 +4378,6 @@ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -4467,8 +4421,7 @@ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jest/reporters/node_modules/brace-expansion": { "version": "1.1.15", @@ -4476,7 +4429,6 @@ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4489,7 +4441,6 @@ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4511,7 +4462,6 @@ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -4527,7 +4477,6 @@ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4541,7 +4490,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4564,7 +4512,6 @@ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -4580,7 +4527,6 @@ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -4597,7 +4543,6 @@ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -4640,6 +4585,7 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -6115,6 +6061,7 @@ "integrity": "sha512-pYUNvaQQBEwP66TLrjmmfkDIrTmPnX0kK86HgClkWLQKkX/oCgnqDxEgNbjeCc75dwUvZP6fW2d0pZ5++XILTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@module-federation/runtime": "2.5.1", "@module-federation/webpack-bundler-runtime": "2.5.1" @@ -6292,6 +6239,7 @@ "integrity": "sha512-kzFn3ObUeBp5vaEtN1WMxhTYBuYEErxugu1RzFUERD21X3BZ+b4cWwdFJuBDlsmVjctIg/QSOoZoPXRKAO0foA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@module-federation/runtime": "0.15.0", "@module-federation/webpack-bundler-runtime": "0.15.0" @@ -6391,6 +6339,7 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -8765,7 +8714,6 @@ "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.84.1.tgz", "integrity": "sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 20.19.4" } @@ -8775,7 +8723,6 @@ "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.84.1.tgz", "integrity": "sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", @@ -8797,7 +8744,6 @@ "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.84.1.tgz", "integrity": "sha512-f6a+mJEJ6Joxlt/050TqYUr7uRRbeKnz8lnpL7JajhpsgZLEbkJRjH8HY5QiLcRdUwWFtizml4V+vcO3P4RxoQ==", "license": "MIT", - "peer": true, "dependencies": { "@react-native/dev-middleware": "0.84.1", "debug": "^4.4.0", @@ -8828,7 +8774,6 @@ "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.84.1.tgz", "integrity": "sha512-rUU/Pyh3R5zT0WkVgB+yA6VwOp7HM5Hz4NYE97ajFS07OUIcv8JzBL3MXVdSSjLfldfqOuPEuKUaZcAOwPgabw==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">= 20.19.4" } @@ -8838,7 +8783,6 @@ "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.84.1.tgz", "integrity": "sha512-LIGhh4q4ette3yW5OzmukNMYwmINYrRGDZqKyTYc/VZyNpblZPw72coXVHXdfpPT6+YlxHqXzn3UjFZpNODGCQ==", "license": "MIT", - "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "debug": "^4.4.0", @@ -8853,7 +8797,6 @@ "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.84.1.tgz", "integrity": "sha512-Z83ra+Gk6ElAhH3XRrv3vwbwCPTb04sPPlNpotxcFZb5LtRQZwT91ZQEXw3GOJCVIFp9EQ/gj8AQbVvtHKOUlQ==", "license": "MIT", - "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.84.1", @@ -8877,7 +8820,6 @@ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "license": "MIT", - "peer": true, "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -8894,7 +8836,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -8916,7 +8857,6 @@ "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.84.1.tgz", "integrity": "sha512-7uVlPBE3uluRNRX4MW7PUJIO1LDBTpAqStKHU7LHH+GRrdZbHsWtOEAX8PiY4GFfBEvG8hEjiuTOqAxMjV+hDg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 20.19.4" } @@ -8926,7 +8866,6 @@ "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.84.1.tgz", "integrity": "sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 20.19.4" } @@ -8935,8 +8874,7 @@ "version": "0.84.1", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.84.1.tgz", "integrity": "sha512-/UPaQ4jl95soXnLDEJ6Cs6lnRXhwbxtT4KbZz+AFDees7prMV2NOLcHfCnzmTabf5Y3oxENMVBL666n4GMLcTA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@react-oauth/google": { "version": "0.13.5", @@ -9075,6 +9013,7 @@ "integrity": "sha512-yEDrKcIICHnaJdUOdBAXQTNXoehB0J3FBcZr4CnK6Alic6cVJGGBGMtJdIO7DUwNySwoYvkUTS3qnXuk2zGYZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.8", "@babel/generator": "^7.21.5", @@ -9867,6 +9806,7 @@ "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.5.tgz", "integrity": "sha512-SyJ5n2pQyo4ERGvjjLvL2PpRWBzlC2qzO5KkkOE2ygOiNcgOu9WQnnom9GI8KgsTRCO8JnIeLHFRcmxZnVBjfw==", "license": "MIT", + "peer": true, "dependencies": { "@remix-run/router": "1.23.3", "@types/cookie": "^0.6.0", @@ -10489,6 +10429,7 @@ "integrity": "sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@module-federation/runtime-tools": "0.22.0", "@rspack/binding": "1.7.11", @@ -10781,7 +10722,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "type-detect": "4.0.8" } @@ -10791,7 +10731,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -11226,6 +11165,7 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -11408,6 +11348,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.8" @@ -11656,6 +11597,7 @@ "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -12014,6 +11956,7 @@ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -12352,7 +12295,6 @@ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -12502,6 +12444,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.42.tgz", "integrity": "sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -12524,6 +12467,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.0.tgz", "integrity": "sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -12859,6 +12803,7 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -15130,6 +15075,7 @@ "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -15191,6 +15137,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -15272,6 +15219,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -15345,8 +15293,7 @@ "version": "1.4.10", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ansi-colors": { "version": "4.1.3", @@ -15363,7 +15310,6 @@ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -15648,8 +15594,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/assertion-error": { "version": "2.0.1", @@ -15879,7 +15824,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -15896,7 +15840,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -15908,7 +15851,6 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -15929,7 +15871,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -15946,7 +15887,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -15959,7 +15899,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -15969,7 +15908,6 @@ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "license": "ISC", - "peer": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -15984,7 +15922,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -16095,7 +16032,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", "license": "MIT", - "peer": true, "dependencies": { "hermes-parser": "0.32.0" } @@ -16115,7 +16051,6 @@ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -16142,7 +16077,6 @@ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "license": "MIT", - "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -16362,6 +16296,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -16394,7 +16329,6 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "node-int64": "^0.4.0" } @@ -16686,7 +16620,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -16808,7 +16741,6 @@ "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -16837,7 +16769,6 @@ "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -16867,8 +16798,7 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/class-variance-authority": { "version": "0.7.1", @@ -17106,8 +17036,7 @@ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", @@ -17257,7 +17186,6 @@ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", @@ -17273,7 +17201,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -17283,7 +17210,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -17293,7 +17219,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -17311,15 +17236,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/connect/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "license": "MIT", - "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -17332,7 +17255,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -17497,7 +17419,6 @@ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -17692,7 +17613,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/culori": { "version": "4.0.2", @@ -18079,7 +18001,6 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -18426,7 +18347,8 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -18456,7 +18378,6 @@ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18496,6 +18417,7 @@ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -18603,7 +18525,6 @@ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "license": "MIT", - "peer": true, "dependencies": { "stackframe": "^1.3.4" } @@ -18815,6 +18736,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -19239,6 +19161,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -19295,6 +19218,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -19420,6 +19344,7 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -20111,7 +20036,6 @@ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -20173,14 +20097,14 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/express": { "version": "4.22.2", "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -20354,7 +20278,6 @@ "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", "license": "(MIT OR Apache-2.0)", - "peer": true, "bin": { "dotslash": "bin/dotslash" }, @@ -20367,7 +20290,6 @@ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "bser": "2.1.1" } @@ -20482,6 +20404,7 @@ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -20675,8 +20598,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/follow-redirects": { "version": "1.16.0", @@ -21079,7 +21001,6 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -21543,22 +21464,19 @@ "version": "250829098.0.9", "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.9.tgz", "integrity": "sha512-hZ5O7PDz1vQ99TS7HD3FJ9zVynfU1y+VWId6U1Pldvd8hmAYrNec/XLPYJKD3dLOW6NXak6aAQAuMuSo3ji0tQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/hermes-estree": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/hermes-parser": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", "license": "MIT", - "peer": true, "dependencies": { "hermes-estree": "0.32.0" } @@ -21581,6 +21499,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.24.tgz", "integrity": "sha512-I36D1s+HgQc55KbhEr4iybfxv/9o1zdpw+XEM6dJa91LqQD0HCoSGdxpRJCZE+aavs87j4V3Ls2OJzq8C/U4iw==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -21897,7 +21816,6 @@ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", "license": "MIT", - "peer": true, "dependencies": { "queue": "6.0.2" }, @@ -21940,7 +21858,6 @@ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -22357,7 +22274,6 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -22826,7 +22742,6 @@ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -22987,7 +22902,6 @@ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -23003,7 +22917,6 @@ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -23036,7 +22949,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23050,7 +22962,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23065,8 +22976,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-cli": { "version": "29.7.0", @@ -23074,7 +22984,6 @@ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -23109,7 +23018,6 @@ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -23156,7 +23064,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23170,7 +23077,6 @@ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -23183,7 +23089,6 @@ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -23205,7 +23110,6 @@ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -23219,7 +23123,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23234,8 +23137,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-diff": { "version": "29.7.0", @@ -23290,7 +23192,6 @@ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -23304,7 +23205,6 @@ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -23322,7 +23222,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23336,7 +23235,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23351,15 +23249,13 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -23386,7 +23282,6 @@ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -23413,7 +23308,6 @@ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -23428,7 +23322,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23442,7 +23335,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23457,8 +23349,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-matcher-utils": { "version": "29.7.0", @@ -23568,7 +23459,6 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -23584,7 +23474,6 @@ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -23602,7 +23491,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "license": "MIT", - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -23613,7 +23501,6 @@ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -23635,7 +23522,6 @@ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -23650,7 +23536,6 @@ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -23684,7 +23569,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -23695,7 +23579,6 @@ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -23707,7 +23590,6 @@ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -23742,7 +23624,6 @@ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -23755,7 +23636,6 @@ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -23777,7 +23657,6 @@ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -23791,7 +23670,6 @@ "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -23824,7 +23702,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23838,7 +23715,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23853,14 +23729,14 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -23890,7 +23766,6 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -23908,7 +23783,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23921,7 +23795,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23935,8 +23808,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-watcher": { "version": "29.7.0", @@ -23944,7 +23816,6 @@ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -23964,7 +23835,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -23980,7 +23850,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -24049,8 +23918,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/jsdom": { "version": "22.1.0", @@ -24058,6 +23926,7 @@ "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "abab": "^2.0.6", "cssstyle": "^3.0.0", @@ -24368,7 +24237,8 @@ "url": "https://github.com/sponsors/lavrton" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/language-subtag-registry": { "version": "0.3.23", @@ -24426,7 +24296,6 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -24450,7 +24319,6 @@ "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" @@ -24461,7 +24329,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -24470,14 +24337,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", + "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -25156,8 +25023,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -25480,7 +25346,6 @@ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -25499,8 +25364,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -25766,8 +25630,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/merge-descriptors": { "version": "1.0.3", @@ -25814,7 +25677,6 @@ "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.7.tgz", "integrity": "sha512-SPaPEyvTsTmd0LpT7RaZciQyDw2i/JB7+iY9L5VfBo72+psescFxBqpI1TL9dnL+pmnfkU+l/J1mEEGLeF65EQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", @@ -25868,7 +25730,6 @@ "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.7.tgz", "integrity": "sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", @@ -25884,15 +25745,13 @@ "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", - "peer": true, "dependencies": { "hermes-estree": "0.35.0" } @@ -25902,7 +25761,6 @@ "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.7.tgz", "integrity": "sha512-E9SRePXQ1Zvlj79VcOk57q7VC7rMHMFQ+jhmPHBiq+dJ0bJB5BL87lWZF6oh5X76Cci5tpDuQNaDwwuSCToEeg==", "license": "MIT", - "peer": true, "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", @@ -25918,7 +25776,6 @@ "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.7.tgz", "integrity": "sha512-W1c2Nmx8MiJTJt+eWhMO08z9VKi3kZOaz99IYGdqeqDgY9j+yZjXl62rUav4Di0heZfh4/n2s722PqRL1OODeg==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, @@ -25931,7 +25788,6 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 14" } @@ -25941,7 +25797,6 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", - "peer": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -25955,7 +25810,6 @@ "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.7.tgz", "integrity": "sha512-83mjWFbFOt2GeJ6pFIum5mSnc1uTsZJAtD8o4ej0s4NVsYsA7fB+pHvTfHhFrpeMONaobu2riKavkPei05Er/Q==", "license": "MIT", - "peer": true, "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", @@ -25975,7 +25829,6 @@ "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.7.tgz", "integrity": "sha512-6yn3w1wnltT6RQl7p7YES2l95ArC+mWrOssEiH8p5/DDrJS65/szf9LsC9JrBv8c5DdvSY3V3f0GRYg0Ox7hCg==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", @@ -25990,7 +25843,6 @@ "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.7.tgz", "integrity": "sha512-+j0F1m+FQYVAQ6syf+mwhIPV5GoFQrkInX8bppuc50IzNsZbMrp8R5H/Sx/K2daQ3YEa9F/XwkeZT8gzJfgeCw==", "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", @@ -26011,7 +25863,6 @@ "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.7.tgz", "integrity": "sha512-MfJar2IS4tBRuLb9svwb0Gu5l9BsH+pcRm8eGcEi/wy8MzZinfinh5dFLt2nWkocnulIgtGB5NkFDdbXqMXKhQ==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" @@ -26025,7 +25876,6 @@ "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.7.tgz", "integrity": "sha512-WSJIENlMcoSsuz66IfBHOkgfp3KJt2UW2TnEHPf1b8pIG2eEXNOVmo2+03A0H17WY2XGXWgxL0CG7FAopqgB1A==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, @@ -26038,7 +25888,6 @@ "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.7.tgz", "integrity": "sha512-9GKkJURaB2iyYoEExKnedzAHzxmKtSi+k0tsZUvMoU27tBZJElchYt7JH/Ai/XzYAI9lCAaV7u5HZSI8J5Z+wQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" @@ -26052,7 +25901,6 @@ "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.7.tgz", "integrity": "sha512-JgA1h7oc1a1jydBe1GhVFsUoMYo3wLPk7oRA32rjlDsq+sP2JLt9x2p2lWbNSxTm/u8NV4VRid3hvEJgcX8tKw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", @@ -26073,7 +25921,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -26083,7 +25930,6 @@ "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.7.tgz", "integrity": "sha512-g4suyxw20WOHWI680c+Kq4wC/NF+Hx5pRH9afrMp+sMTxqLeKcPR1Xf4wMhsjlbvx7LbIREdke6q928jEjvJWw==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", @@ -26104,7 +25950,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -26114,7 +25959,6 @@ "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.7.tgz", "integrity": "sha512-Ss0FpBiZDjX2kwhukMDl5sNdYK8T/06IPqxNE4H6PTlRlfs9q11cef13c/xESY/Pm4VCkp1yJUZO3kXzvMxQFA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", @@ -26132,7 +25976,6 @@ "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.7.tgz", "integrity": "sha512-UegCo7ygB2fT64mRK2nbAjQVJ1zSwIIHy8d96jJv2nKZFDaViYBiughEdu5HM/Ceq0WN3LZrZk3zhl9aoiLYFw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", @@ -26157,7 +26000,6 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", - "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -26170,22 +26012,19 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro/node_modules/hermes-estree": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/metro/node_modules/hermes-parser": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", - "peer": true, "dependencies": { "hermes-estree": "0.35.0" } @@ -26195,7 +26034,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -26212,7 +26050,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -26222,7 +26059,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -26232,7 +26068,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -27447,6 +27282,7 @@ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -27491,8 +27327,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-machine-id": { "version": "1.1.12", @@ -27679,8 +27514,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/nwsapi": { "version": "2.2.24", @@ -27695,6 +27529,7 @@ "integrity": "sha512-2wL/2fSmIbRWn6zXaQ/g3kj5DfEaTw/aJkPr6ozJh8BUq5iYKE+tS9oh0PjsVVwN6Pybe80Lu+mn9RgWyeV3xw==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -27852,7 +27687,6 @@ "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.7.tgz", "integrity": "sha512-9M5kpuOLyTPogMtZiQUIxdAZxl7Dxs6tVBbJErSumsqGMuhVSoUbkfeZ3XNPpLpwBBtqY5QDUzGwggLHX3slQg==", "license": "MIT", - "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, @@ -28241,7 +28075,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -28563,7 +28396,6 @@ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -28577,7 +28409,6 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -28592,7 +28423,6 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -28606,7 +28436,6 @@ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -28623,7 +28452,6 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -28693,6 +28521,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -28998,6 +28827,7 @@ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -29085,6 +28915,7 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -29236,7 +29067,6 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", "license": "MIT", - "peer": true, "dependencies": { "asap": "~2.0.6" } @@ -29386,8 +29216,7 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/qs": { "version": "6.15.2", @@ -29439,7 +29268,6 @@ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", "license": "MIT", - "peer": true, "dependencies": { "inherits": "~2.0.3" } @@ -29589,6 +29417,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -29658,7 +29487,6 @@ "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "license": "MIT", - "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -29669,7 +29497,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -29691,6 +29518,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -30083,7 +29911,6 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz", "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -30132,7 +29959,6 @@ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", "license": "MIT", - "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.9" }, @@ -30155,7 +29981,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -30182,7 +30007,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -30193,7 +30017,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -30203,7 +30026,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -30217,8 +30039,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-spring/node_modules/react-konva": { "version": "19.2.4", @@ -30239,7 +30060,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@types/react-reconciler": "^0.33.0", "its-fine": "^2.0.0", @@ -30257,7 +30077,6 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.33.0.tgz", "integrity": "sha512-HZOXsKT0tGI9LlUw2LuedXsVeB88wFa536vVL0M6vE8zN63nI+sSr1ByxmPToP5K5bukaVscyeCJcF9guVNJ1g==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "*" } @@ -30267,7 +30086,6 @@ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", "license": "MIT", - "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.9" }, @@ -30280,7 +30098,6 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "*" } @@ -30290,7 +30107,6 @@ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -30365,7 +30181,6 @@ "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.84.1.tgz", "integrity": "sha512-sJoDunzhci8ZsqxlUiKoLut4xQeQcmbIgvDHGQKeBz6uEq9HgU+hCWOijMRr6sLP0slQVfBAza34Rq7IbXZZOA==", "license": "MIT", - "peer": true, "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -30388,15 +30203,13 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-spring/node_modules/ws": { "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.3.0" }, @@ -30457,7 +30270,6 @@ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" @@ -30651,8 +30463,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", @@ -30841,8 +30652,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.8", @@ -30868,7 +30678,6 @@ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "resolve-from": "^5.0.0" }, @@ -30882,7 +30691,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -31397,7 +31205,6 @@ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -32056,7 +31863,6 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -32397,15 +32203,13 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/stacktrace-parser": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.7.1" }, @@ -32418,7 +32222,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=8" } @@ -32559,7 +32362,6 @@ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -32826,7 +32628,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -32972,7 +32773,6 @@ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", "license": "MIT", - "peer": true, "peerDependencies": { "react": ">=17.0" } @@ -33274,6 +33074,7 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -33492,14 +33293,14 @@ "version": "0.171.0", "resolved": "https://registry.npmjs.org/three/-/three-0.171.0.tgz", "integrity": "sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/through2": { "version": "2.0.5", @@ -33643,8 +33444,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -33949,7 +33749,6 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -33960,7 +33759,6 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -34065,6 +33863,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -34675,7 +34474,6 @@ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -34780,6 +34578,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -35676,6 +35475,7 @@ "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.6", @@ -35754,8 +35554,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/vscode-uri": { "version": "3.1.0", @@ -35782,7 +35581,6 @@ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "makeerror": "1.0.12" } @@ -35870,6 +35668,7 @@ "integrity": "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", @@ -35970,8 +35769,7 @@ "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/whatwg-mimetype": { "version": "3.0.0", @@ -36255,7 +36053,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "license": "ISC", - "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -36270,6 +36067,7 @@ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -36492,6 +36290,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }