Compare commits

...
Author SHA1 Message Date
Brandon Thomas cb0d302750 this doesn't seem right either 2026-03-02 06:47:33 -05:00
Brandon Thomas daa689c042 will netlify even allow this? 2026-03-02 06:41:43 -05:00
Brandon Thomas b3dd2c98d9 fix rust build for api docs 2026-03-02 06:24:05 -05:00
Brandon Thomas 621b962a7f move, refactor scripts 2026-03-02 06:14:39 -05:00
Brandon Thomas a1e2f82473 old script from 2025? 2026-03-02 06:10:49 -05:00
Brandon Thomas 748fd316c1 show NBP cost estimates in artcraft (#1076) 2026-03-02 06:05:14 -05:00
Brandon Thomas aab8cbdfe1 NBP via artcraft_router in Tauri; image cost estimates in tauri (#1075) 2026-03-02 05:13:23 -05:00
Brandon Thomas bc91785628 fix compile error 2026-03-02 02:52:16 -05:00
Brandon Thomas d9f5e0e78b model router: fal nbp (#1074)
we'll soon be able to support fal API keys and cost estimates for Nano
Banana Pro

- NBP cost estimates
- NBP at ArtCraft
- NBP at FAL
2026-03-01 23:48:28 -05:00
bflatastic 428f7c5c7a update tp pricing 2026-03-02 00:11:27 +07:00
bflatastic 3b6a30bb52 pricing page update 2026-03-01 22:26:25 +07:00
Brandon Thomas 66d58660fb image model router with nano banana pro (#1073) 2026-03-01 04:58:50 -05:00
Brandon Thomas f828cd2980 refactor how router plans are built 2026-03-01 02:38:34 -05:00
Brandon Thomas 052949e012 move package 2026-03-01 02:26:06 -05:00
Brandon Thomas 6d8c0fe381 rename storyteller_client -> artcraft_client 2026-03-01 02:13:44 -05:00
Brandon Thomas a18042d8e0 bump artcraft version -> 0.10.0 2026-03-01 01:36:36 -05:00
bflatastic 2992902c8e credits icon update, buy credits modal restyled 2026-03-01 12:37:44 +07:00
BFlat 08ceb5fb08 logo removal, tooltip/menu fixes, added center icon (#1071)
### UI Refinements: Topbar, Menu, and Tooltips
- **Top bar organization:** Removed the extra logo from the top bar and
added the Artcraft icon to the center text for a cleaner layout.
- **Menu interaction:** Improved the behavior of the menu icon selector
so that confusing tooltips no longer appear when the menu is temporarily
disabled (e.g., during product switching).
- **Tooltip positioning:** Fixed an issue where tooltips were
occasionally extending beyond the edges of the screen, ensuring they now
stay fully visible.
2026-03-01 12:21:37 +07:00
Brandon Thomas 28beaab2c5 label as 0.9.x 2026-02-28 08:30:33 -05:00
99592a1bff credits usage UI (#1053)
- shows credits to be deducted on generate buttons
- credits usage modal to show costs in detail (toggleable from bottom
right)

Do not merge. Awaiting backend cost calculation implementation.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-02-28 08:27:44 -05:00
Brandon Thomas e600980b20 tauri controller for cost estimation (#1067)
We can add caching to this later.
2026-02-28 06:58:58 -05:00
Brandon Thomas 159e8796ea pricing updates 2026-02-28 06:02:39 -05:00
Brandon Thomas 6d9cd01464 cost estimator endpoint (#1065) 2026-02-28 00:29:09 -05:00
BFlat 90b8e89c61 task queue to show estimated time, seedance 2 progress time adjustmen… (#1064)
…t, model name mapping
2026-02-28 02:14:33 +07:00
Brandon Thomas c00d6c62ca Refund failed Seedance 2 Pro generations, Fix Seedance 2 client (#1061) 2026-02-27 02:02:54 -05:00
BFlat e8d88ca318 lightbox navigation and performance updates (#1059)
**Lightbox & Gallery Navigation Improvements**
- Added prev/next navigation buttons to lightbox (hover-visible arrows
on left/right of image)
- Added arrow key (←/→) keyboard navigation between gallery items in
lightbox
- Gallery builds a flat ordered item list to power sequential lightbox
navigation
- Debounced prompt/batch API calls (180ms) to prevent spam when rapidly
navigating
- Stale in-flight responses are cancelled so wrong data never flashes
- Added shimmer loading skeletons for prompt, reference images, and
generation details
- Removed focus outlines on nav buttons and modal when using arrow keys
- Fixed toggleBulkSelect used-before-declaration error in gallery modal
- Added display name for flux_dev_juggernaut
2026-02-25 23:09:44 -08:00
bflatastic cbf83ac2dd bulk select thumbnails fixes 2026-02-26 13:03:02 +07:00
bflatastic ce91aa2485 pricing text update and social icons bug fix 2026-02-26 00:26:20 +07:00
Brandon Thomas 46c73b08bd fix seedance api (#1057) 2026-02-25 06:58:26 +07:00
Brandon Thomas 73b17b2005 fix seedance api break (#1056) 2026-02-25 06:24:31 +07:00
393 changed files with 7377 additions and 1838 deletions
+3
View File
@@ -68,6 +68,9 @@ __pycache__/
# We use diesel for DB migrations only. We don't care about its codgen.
_ignored_schema.rs
# Diesel lock file
.diesel_lock
# AI Chat Sidecar has its own schema managed by diesel, but we don't care for diesel codegen.
crates/programs/aichatbot-sidecar/_ignored_schema.rs
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nUPDATE wallets\nSET\n banked_credits = ?,\n version = version + 1\nWHERE token = ?\nLIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "01f6369e0ab7b0fa5b95b4595827dcf15897f3b50cce5b1dc421101b157b7008"
}
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = NULL,\n maybe_model_token = NULL,\n\n maybe_input_source_token = NULL,\n maybe_input_source_token_type = NULL,\n\n maybe_download_url = NULL,\n maybe_cover_image_media_file_token = NULL,\n\n maybe_prompt_token = ?,\n\n maybe_raw_inference_text = NULL,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n priority_level = 0,\n is_keepalive_required = FALSE,\n max_duration_seconds = 0,\n\n is_debug_request = FALSE,\n maybe_routing_tag = NULL,\n\n status = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 14
},
"nullable": []
},
"hash": "33aa8a1cf85bf1b8b68f4e2ab46541f25d7e8bb5eeb503c5ec14cb5e115f69ce"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nUPDATE wallet_ledger_entries\nSET\n is_refunded = TRUE,\n maybe_linked_refund_ledger_token = ?\nWHERE token = ?\nLIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "6701a7adad65bc351e4600dad3ebf0f5dbdefa7dc9a0f890790cfaf368bf572d"
}
@@ -0,0 +1,102 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n wallet_token as `wallet_token: tokens::tokens::wallets::WalletToken`,\n entry_type,\n is_refunded as `is_refunded: bool`,\n credits_delta,\n banked_credits_before,\n banked_credits_after,\n monthly_credits_before,\n monthly_credits_after\nFROM wallet_ledger_entries\nWHERE token = ?\nLIMIT 1\nFOR UPDATE\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "wallet_token: tokens::tokens::wallets::WalletToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "entry_type",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "is_refunded: bool",
"type_info": {
"type": "Tiny",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 3,
"name": "credits_delta",
"type_info": {
"type": "Long",
"flags": "NOT_NULL",
"char_set": 63,
"max_size": 11
}
},
{
"ordinal": 4,
"name": "banked_credits_before",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 5,
"name": "banked_credits_after",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 6,
"name": "monthly_credits_before",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 7,
"name": "monthly_credits_after",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "a7365998006f4d40ad26efb42e6958a7aed689562a4c5b8c91cbe0033d0270e7"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n jobs.maybe_external_third_party_id as `order_id`,\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n jobs.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\n AND jobs.status NOT IN ('complete_success', 'complete_failure')\n AND jobs.maybe_external_third_party_id IS NOT NULL\n\nLIMIT 25000\n ",
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n jobs.maybe_external_third_party_id as `order_id`,\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n jobs.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n jobs.maybe_wallet_ledger_entry_token as `maybe_wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\n AND jobs.status NOT IN ('complete_success', 'complete_failure')\n AND jobs.maybe_external_third_party_id IS NOT NULL\n\nLIMIT 25000\n ",
"describe": {
"columns": [
{
@@ -72,6 +72,16 @@
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 7,
"name": "maybe_wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
}
],
"parameters": {
@@ -84,8 +94,9 @@
true,
false,
false,
true,
true
]
},
"hash": "ca96942d8214edb84f163cf40131ceed873209e86718054e1502219fc81d2dc6"
"hash": "cb33fd483f0fef7ec2a1f5950218f312a19e38c7971b88db949ccaa05bf7b7c4"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = NULL,\n maybe_model_token = NULL,\n\n maybe_input_source_token = NULL,\n maybe_input_source_token_type = NULL,\n\n maybe_download_url = NULL,\n maybe_cover_image_media_file_token = NULL,\n\n maybe_prompt_token = ?,\n\n maybe_wallet_ledger_entry_token = ?,\n\n maybe_raw_inference_text = NULL,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n priority_level = 0,\n is_keepalive_required = FALSE,\n max_duration_seconds = 0,\n\n is_debug_request = FALSE,\n maybe_routing_tag = NULL,\n\n status = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 15
},
"nullable": []
},
"hash": "ee228ab4a079fde6b3683b6cde2a2384d3def27a5d09baa5caf00a521d5d2bcd"
}
Generated
+35 -31
View File
@@ -677,10 +677,11 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "artcraft"
version = "0.8.0"
version = "0.0.1"
dependencies = [
"anyhow",
"artcraft_api_defs",
"artcraft_client",
"artcraft_router",
"base64 0.22.1",
"build_metadata",
@@ -714,7 +715,6 @@ dependencies = [
"serde_json",
"serial_test",
"sqlite_tasks",
"storyteller_client",
"tauri",
"tauri-build",
"tauri-plugin-dialog",
@@ -751,16 +751,47 @@ dependencies = [
"workspace-build-acceleration",
]
[[package]]
name = "artcraft_client"
version = "0.0.1"
dependencies = [
"anyhow",
"artcraft_api_defs",
"backoff",
"chrono",
"cloudflare_errors",
"cookie 0.18.1",
"enums",
"enums_public",
"errors",
"idempotency",
"log",
"mimetypes",
"reqwest 0.12.23",
"serde",
"serde_derive",
"serde_json",
"tokens",
"tokio",
"url",
"url-builder",
"uuid 1.18.1",
"workspace-build-acceleration",
]
[[package]]
name = "artcraft_router"
version = "0.0.1"
dependencies = [
"anyhow",
"artcraft_api_defs",
"artcraft_client",
"fal_client",
"log",
"seedance2pro",
"serde",
"serde_derive",
"storyteller_client",
"test_data",
"tokens",
"tokio",
"uuid 1.18.1",
@@ -11477,6 +11508,7 @@ dependencies = [
"actix_helpers",
"anyhow",
"artcraft_api_defs",
"artcraft_router",
"async-stripe 0.26.0",
"async-trait",
"base64 0.22.1",
@@ -11573,34 +11605,6 @@ dependencies = [
"zip 0.6.6",
]
[[package]]
name = "storyteller_client"
version = "0.0.1"
dependencies = [
"anyhow",
"artcraft_api_defs",
"backoff",
"chrono",
"cloudflare_errors",
"cookie 0.18.1",
"enums",
"enums_public",
"errors",
"idempotency",
"log",
"mimetypes",
"reqwest 0.12.23",
"serde",
"serde_derive",
"serde_json",
"tokens",
"tokio",
"url",
"url-builder",
"uuid 1.18.1",
"workspace-build-acceleration",
]
[[package]]
name = "storyteller_root"
version = "0.0.1"
+4 -4
View File
@@ -7,7 +7,7 @@ members = [
# API clients for external services
# These might be custom implementations or wrappers over other client libraries.
"crates/api_clients/artcraft/artcraft_api_defs", # TODO: api_clients/{artcraft}/{api_defs,client}
"crates/api_clients/artcraft/storyteller_client",
"crates/api_clients/artcraft/artcraft_client",
"crates/api_clients/cloud_storage",
"crates/api_clients/fal_client",
"crates/api_clients/google_drive_common",
@@ -72,7 +72,7 @@ members = [
"crates/lib/files/videos",
"crates/lib/http_headers",
"crates/lib/idempotency",
"crates/lib/artcraft_router",
"crates/api_clients/artcraft/artcraft_router",
"crates/lib/image_algorithms",
"crates/lib/jobs_common",
"crates/lib/jwt_light",
@@ -208,7 +208,7 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
# Setting paths here ensures we don't need to do so in every crate's Cargo.toml
# It makes refactoring much easier.
actix_artcraft = { path = "crates/lib/actix_artcraft" }
artcraft_router = { path = "crates/lib/artcraft_router" }
artcraft_router = { path = "crates/api_clients/artcraft/artcraft_router" }
actix_helpers = { path = "crates/lib/actix_helpers" }
artcraft_api_defs = { path = "crates/api_clients/artcraft/artcraft_api_defs" }
build_metadata = { path = "crates/lib/build_metadata" }
@@ -240,7 +240,7 @@ seedance2pro = { path = "crates/api_clients/seedance2pro" }
primitives = { path = "crates/lib/data/primitives" }
shared_service_components = { path = "crates/lib/shared_service_components" }
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
storyteller_client = { path = "crates/api_clients/artcraft/storyteller_client" }
artcraft_client = { path = "crates/api_clients/artcraft/artcraft_client" }
subprocess_common = { path = "crates/lib/subprocess_common" }
test_data = { path = "crates/lib/test_data" }
testing = { path = "crates/lib/testing" }
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE generic_inference_jobs
DROP COLUMN
maybe_wallet_ledger_entry_token;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE generic_inference_jobs
ADD COLUMN
maybe_wallet_ledger_entry_token VARCHAR(32) DEFAULT NULL;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE wallet_ledger_entries
DROP COLUMN
is_refunded;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE wallet_ledger_entries
ADD COLUMN
is_refunded BOOLEAN NOT NULL DEFAULT FALSE;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE wallet_ledger_entries
DROP COLUMN
maybe_linked_refund_ledger_token;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE wallet_ledger_entries
ADD COLUMN
maybe_linked_refund_ledger_token VARCHAR(32) DEFAULT NULL;
@@ -182,6 +182,9 @@ CREATE TABLE generic_inference_jobs (
is_from_api_user BOOLEAN NOT NULL DEFAULT FALSE,
is_for_twitch BOOLEAN NOT NULL DEFAULT FALSE,
-- If credits were spent for this job, this references the corresponding wallet ledger entry.
maybe_wallet_ledger_entry_token VARCHAR(32) DEFAULT NULL,
-- ========== DEVELOPMENT AND DEBUGGING METADATA ==========
-- If true, the request gets a "debug" flag, which may do different
@@ -40,6 +40,12 @@ CREATE TABLE wallet_ledger_entries (
-- Balance of monthly credits after the event.
monthly_credits_after INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- Whether this ledger entry has been refunded.
is_refunded BOOLEAN NOT NULL DEFAULT FALSE,
-- If this entry was refunded, this points to the refund ledger entry.
maybe_linked_refund_ledger_token VARCHAR(32) DEFAULT NULL,
-- ========== RECORD TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
@@ -0,0 +1,116 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_image_model::CommonImageModel;
use enums::common::generation::common_video_resolution::CommonVideoResolution;
use enums::common::generation_provider::GenerationProvider;
pub const ESTIMATE_IMAGE_COST_PATH: &str = "/v1/generate/cost_estimate/image";
/// Request body for the image cost estimate endpoint.
#[derive(Serialize, Deserialize, ToSchema)]
pub struct EstimateImageCostRequest {
/// The image model to estimate costs for.
pub model: CommonImageModel,
/// The provider to route the generation through.
pub provider: GenerationProvider,
/// The type of generation (determines whether input images are involved).
/// This is a tagged enum, so it looks like:
/// "generation_mode": {"type": "text_to_image"}
/// "generation_mode": {"type": "image_edit", "count": 2}
pub generation_mode: GenerationMode,
/// Optional aspect ratio.
pub aspect_ratio: Option<CommonAspectRatio>,
/// Optional resolution.
pub resolution: Option<CommonVideoResolution>,
/// Number of images to generate in parallel.
pub image_batch_count: Option<u16>,
}
/// Describes the type of image generation being requested.
#[derive(Serialize, Deserialize, ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum GenerationMode {
/// No input images — generate from prompt only.
TextToImage,
/// One or more input images are provided for editing/compositing.
ImageEdit { count: u32 },
}
/// Response body for the image cost estimate endpoint.
#[derive(Serialize, Deserialize, ToSchema)]
pub struct EstimateImageCostResponse {
pub success: bool,
/// Estimated cost in credits.
pub cost_in_credits: Option<u64>,
/// Estimated cost in USD cents.
pub cost_in_usd_cents: Option<u64>,
/// Whether the generation is free for this user/plan.
pub is_free: bool,
/// Whether the user has unlimited generations.
pub is_unlimited: bool,
/// Whether the user is rate limited.
pub is_rate_limited: bool,
/// Whether the output will have a watermark.
pub has_watermark: bool,
}
/// Error response for the image cost estimate endpoint.
#[derive(Serialize, Deserialize, ToSchema)]
pub struct EstimateImageCostError {
pub success: bool,
pub error_type: EstimateImageCostErrorType,
pub error_message: String,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum EstimateImageCostErrorType {
InvalidProviderForModel,
InvalidInput,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialization_text_to_image() {
let request = EstimateImageCostRequest {
model: CommonImageModel::NanaBananaPro,
provider: GenerationProvider::Artcraft,
generation_mode: GenerationMode::TextToImage,
aspect_ratio: None,
resolution: None,
image_batch_count: None,
};
let serialized = serde_json::to_string(&request).unwrap();
assert!(serialized.contains("\"generation_mode\":{\"type\":\"text_to_image\"}"));
}
#[test]
fn test_serialization_image_edit() {
let request = EstimateImageCostRequest {
model: CommonImageModel::NanaBananaPro,
provider: GenerationProvider::Artcraft,
generation_mode: GenerationMode::ImageEdit { count: 2 },
aspect_ratio: None,
resolution: None,
image_batch_count: None,
};
let serialized = serde_json::to_string(&request).unwrap();
assert!(serialized.contains("\"generation_mode\":{\"type\":\"image_edit\",\"count\":2}"));
}
}
@@ -0,0 +1,122 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
use enums::common::generation::common_video_model::CommonVideoModel;
use enums::common::generation::common_video_resolution::CommonVideoResolution;
use enums::common::generation_provider::GenerationProvider;
pub const ESTIMATE_VIDEO_COST_PATH: &str = "/v1/generate/cost_estimate/video";
/// Request body for the video cost estimate endpoint.
#[derive(Serialize, Deserialize, ToSchema)]
pub struct EstimateVideoCostRequest {
/// The video model to estimate costs for.
pub model: CommonVideoModel,
/// The provider to route the generation through.
pub provider: GenerationProvider,
/// The type of generation (determines how many input images are involved).
/// This is a tagged enum, so it looks like:
/// "generation_mode": {"type": "text_to_video"}
/// "generation_mode": {"type": "reference_image_to_video", "count": 1}
pub generation_mode: GenerationMode,
/// Optional aspect ratio.
pub aspect_ratio: Option<CommonAspectRatio>,
/// Optional resolution.
pub resolution: Option<CommonVideoResolution>,
/// Duration in seconds.
pub duration_seconds: Option<u16>,
/// Number of videos to generate in parallel.
pub video_batch_count: Option<u16>,
}
/// Describes the type of video generation being requested.
#[derive(Serialize, Deserialize, ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum GenerationMode {
TextToVideo,
StartFrameToVideo,
StartAndEndFrameToVideo,
ReferenceImageToVideo { count: u32 },
}
/// Response body for the video cost estimate endpoint.
#[derive(Serialize, Deserialize, ToSchema)]
pub struct EstimateVideoCostResponse {
pub success: bool,
/// Estimated cost in credits.
pub cost_in_credits: Option<u64>,
/// Estimated cost in USD cents.
pub cost_in_usd_cents: Option<u64>,
/// Whether the generation is free for this user/plan.
pub is_free: bool,
/// Whether the user has unlimited generations.
pub is_unlimited: bool,
/// Whether the user is rate limited.
pub is_rate_limited: bool,
/// Whether the output will have a watermark.
pub has_watermark: bool,
}
/// Error response for the video cost estimate endpoint.
#[derive(Serialize, Deserialize, ToSchema)]
pub struct EstimateVideoCostError {
pub success: bool,
pub error_type: EstimateVideoCostErrorType,
pub error_message: String,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum EstimateVideoCostErrorType {
InvalidProviderForModel,
InvalidInput,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialization_tagging1() {
let request = EstimateVideoCostRequest {
model: CommonVideoModel::GrokVideo,
provider: GenerationProvider::Artcraft,
generation_mode: GenerationMode::TextToVideo,
aspect_ratio: None,
resolution: None,
duration_seconds: None,
video_batch_count: None,
};
let serialized = serde_json::to_string(&request).unwrap();
assert!(serialized.contains("\"generation_mode\":{\"type\":\"text_to_video\"}"));
//assert_eq!(serialized, "{}");
}
#[test]
fn test_serialization_tagging2() {
let request = EstimateVideoCostRequest {
model: CommonVideoModel::GrokVideo,
provider: GenerationProvider::Artcraft,
generation_mode: GenerationMode::ReferenceImageToVideo { count: 1 },
aspect_ratio: None,
resolution: None,
duration_seconds: None,
video_batch_count: None,
};
let serialized = serde_json::to_string(&request).unwrap();
assert!(serialized.contains("\"generation_mode\":{\"type\":\"reference_image_to_video\",\"count\":1}"));
}
}
@@ -0,0 +1,2 @@
pub mod estimate_image_cost;
pub mod estimate_video_cost;
@@ -1,3 +1,4 @@
pub mod cost_estimate;
pub mod image;
pub mod object;
pub mod video;
@@ -1,5 +1,5 @@
[package]
name = "storyteller_client"
name = "artcraft_client"
edition = "2021"
version = "0.0.1"
authors = [
@@ -15,7 +15,7 @@ normal = ["workspace-build-acceleration"]
# unused_imports = "forbid"
[lib]
name = "storyteller_client"
name = "artcraft_client"
path = "src/lib.rs"
[dependencies]
@@ -0,0 +1,20 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::cost_estimate::estimate_image_cost::{
EstimateImageCostRequest, EstimateImageCostResponse, ESTIMATE_IMAGE_COST_PATH,
};
pub async fn estimate_image_cost(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: EstimateImageCostRequest,
) -> Result<EstimateImageCostResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
ESTIMATE_IMAGE_COST_PATH,
maybe_creds,
request,
).await?)
}
@@ -0,0 +1 @@
pub mod estimate_image_cost;
@@ -0,0 +1,2 @@
pub mod image;
pub mod video;
@@ -0,0 +1,20 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::cost_estimate::estimate_video_cost::{
EstimateVideoCostRequest, EstimateVideoCostResponse, ESTIMATE_VIDEO_COST_PATH,
};
pub async fn estimate_video_cost(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: EstimateVideoCostRequest,
) -> Result<EstimateVideoCostResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
ESTIMATE_VIDEO_COST_PATH,
maybe_creds,
request,
).await?)
}
@@ -0,0 +1 @@
pub mod estimate_video_cost;
@@ -1,3 +1,4 @@
pub mod cost_estimate;
pub mod image;
pub mod object;
pub mod video;

Some files were not shown because too many files have changed in this diff Show More