Compare commits

...
Author SHA1 Message Date
Brandon Thomas 4b6e074878 pricing data 2026-03-09 09:55:25 -04:00
Brandon Thomas 3a800f5f7a cleanup 2026-03-09 09:41:04 -04:00
Brandon Thomas 1a5bc77ff3 return information on worlds in operation 2026-03-09 09:40:00 -04:00
Brandon Thomas fe2969a833 cleanup api 2026-03-09 09:27:45 -04:00
15 changed files with 388 additions and 49 deletions
Generated
+1
View File
@@ -15020,6 +15020,7 @@ dependencies = [
"log",
"serde",
"serde_json",
"strum 0.28.0",
"tokio",
"uuid 1.18.1",
"workspace-build-acceleration",
@@ -33,3 +33,4 @@ filesys.workspace = true
env_logger.workspace = true
env.workspace = true
chrono.workspace = true
strum.workspace = true
@@ -1,3 +1,4 @@
pub mod media_asset_id;
pub mod operation_id;
pub mod world_id;
pub mod world_labs_model;
@@ -1,17 +1,8 @@
use uuid::Uuid;
/// Type for World IDs.
/// These are used to refer to worlds
/// These appear to be bare UUIDs.
/// Strongly-typed World ID
#[derive(Clone, Debug)]
pub struct WorldObjectId(pub String);
impl WorldObjectId {
pub fn new() -> Self {
let uuid = Uuid::new_v4().to_string();
Self(uuid)
}
pub struct WorldId(pub String);
impl WorldId {
pub fn from_str(s: &str) -> Self {
Self(s.to_string())
}
@@ -19,8 +10,4 @@ impl WorldObjectId {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn to_string(&self) -> String {
self.0.clone()
}
}
@@ -0,0 +1,23 @@
/// The available World Labs generation models.
#[derive(Clone, Copy, Debug)]
pub enum WorldLabsModel {
/// `Marble 0.1-mini` is good for quick drafts
/// Generation time: 30-45 seconds
/// Cost: 150-330 credits
Marble0p1Mini,
/// `Marble 0.1-plus` is best for final renders
/// Generation time: ~5 minutes,
/// Cost: 1500-1600 credits
Marble0p1Plus,
}
impl WorldLabsModel {
/// Returns the official API name string used in HTTP requests.
pub fn get_model_api_name_str(&self) -> &'static str {
match self {
Self::Marble0p1Mini => "Marble 0.1-mini",
Self::Marble0p1Plus => "Marble 0.1-plus",
}
}
}
@@ -1,3 +1,4 @@
use crate::api::api_types::world_id::WorldId;
use crate::api::requests::delete_world::http_request::RawResponse;
use crate::credentials::world_labs_api_creds::WorldLabsApiCreds;
use crate::error::filter_world_labs_http_error::filter_world_labs_http_error;
@@ -11,12 +12,12 @@ const BASE_URL: &str = "https://api.worldlabs.ai/marble/v1/worlds";
pub struct DeleteWorldArgs<'a> {
pub creds: &'a WorldLabsApiCreds,
pub world_id: &'a str,
pub world_id: &'a WorldId,
pub request_timeout: Option<Duration>,
}
pub struct DeleteWorldResponse {
pub world_id: String,
pub world_id: WorldId,
pub deleted: bool,
}
@@ -26,7 +27,7 @@ pub struct DeleteWorldResponse {
pub async fn delete_world(args: DeleteWorldArgs<'_>) -> Result<DeleteWorldResponse, WorldLabsError> {
let client = Client::new();
let url = format!("{}/{}", BASE_URL, args.world_id);
let url = format!("{}/{}", BASE_URL, args.world_id.as_str());
debug!("Requesting URL: {}", url);
@@ -65,7 +66,7 @@ pub async fn delete_world(args: DeleteWorldArgs<'_>) -> Result<DeleteWorldRespon
.map_err(|err| WorldLabsGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?;
Ok(DeleteWorldResponse {
world_id: raw.world_id,
world_id: WorldId(raw.world_id),
deleted: raw.deleted,
})
}
@@ -85,15 +86,15 @@ mod tests {
let creds = get_test_api_key().unwrap();
// Use a known world_id to delete
let world_id = "REPLACE_WITH_REAL_ID";
let world_id = WorldId::from_str("REPLACE_WITH_REAL_ID");
let response = delete_world(DeleteWorldArgs {
creds: &creds,
world_id,
world_id: &world_id,
request_timeout: None,
}).await.unwrap();
println!("World ID: {}", response.world_id);
println!("World ID: {}", response.world_id.as_str());
println!("Deleted: {}", response.deleted);
assert_eq!(1, 2);
@@ -1,4 +1,5 @@
use crate::api::api_types::operation_id::OperationId;
use crate::api::api_types::world_labs_model::WorldLabsModel;
use crate::api::requests::generate_world::http_request::{RawRequest, RawResponse, WorldPrompt, Permission};
use crate::credentials::world_labs_api_creds::WorldLabsApiCreds;
use crate::error::filter_world_labs_http_error::filter_world_labs_http_error;
@@ -14,7 +15,7 @@ pub struct GenerateWorldArgs<'a> {
pub creds: &'a WorldLabsApiCreds,
pub world_prompt: WorldPrompt,
pub display_name: Option<String>,
pub model: Option<String>,
pub model: WorldLabsModel,
pub seed: Option<u32>,
pub tags: Option<Vec<String>>,
pub permission: Option<Permission>,
@@ -35,7 +36,7 @@ pub async fn generate_world(args: GenerateWorldArgs<'_>) -> Result<GenerateWorld
let payload = RawRequest {
world_prompt: args.world_prompt,
display_name: args.display_name,
model: args.model,
model: Some(args.model.get_model_api_name_str().to_string()),
seed: args.seed,
tags: args.tags,
permission: args.permission,
@@ -107,7 +108,7 @@ mod tests {
disable_recaption: None,
},
display_name: Some("Test World".to_string()),
model: Some("Marble 0.1-mini".to_string()),
model: WorldLabsModel::Marble0p1Mini,
seed: None,
tags: None,
permission: None,
@@ -138,7 +139,7 @@ mod tests {
disable_recaption: None,
},
display_name: None,
model: Some("Marble 0.1-mini".to_string()),
model: WorldLabsModel::Marble0p1Mini,
seed: None,
tags: None,
permission: None,
@@ -1,10 +1,11 @@
use crate::api::api_types::operation_id::OperationId;
use crate::api::requests::get_operation::http_request::RawResponse;
use crate::api::api_types::world_id::WorldId;
use crate::api::requests::get_operation::http_request::{RawResponse, RawWorld};
use crate::credentials::world_labs_api_creds::WorldLabsApiCreds;
use crate::error::filter_world_labs_http_error::filter_world_labs_http_error;
use crate::error::world_labs_error::WorldLabsError;
use crate::error::world_labs_generic_api_error::WorldLabsGenericApiError;
use log::{debug, error};
use log::{debug, error, warn};
use std::time::Duration;
use wreq::Client;
@@ -24,8 +25,8 @@ pub struct GetOperationResponse {
pub expires_at: Option<String>,
pub error: Option<OperationError>,
pub metadata: Option<serde_json::Value>,
/// When done=true and no error, contains the World object.
pub response: Option<serde_json::Value>,
/// Structured World object, present when done=true and no error.
pub world: Option<World>,
}
#[derive(Debug, Clone)]
@@ -34,10 +35,47 @@ pub struct OperationError {
pub message: Option<String>,
}
/// A completed World returned from an operation.
#[derive(Debug, Clone)]
pub struct World {
pub world_id: WorldId,
pub display_name: Option<String>,
pub world_marble_url: Option<String>,
pub created_at: Option<String>,
pub updated_at: Option<String>,
pub model: Option<String>,
pub status: Option<String>,
pub tags: Option<Vec<String>>,
pub assets: Option<WorldAssets>,
}
/// Assets associated with a completed World.
#[derive(Debug, Clone)]
pub struct WorldAssets {
pub caption: Option<String>,
pub thumbnail_url: Option<String>,
pub pano_url: Option<String>,
pub collider_mesh_url: Option<String>,
pub splats: Option<SplatAssets>,
}
/// Splat (3D Gaussian) assets for a World.
#[derive(Debug, Clone)]
pub struct SplatAssets {
/// Low-res 100k splat URL
pub spz_url_100k: Option<String>,
/// Medium-res 500k splat URL
pub spz_url_500k: Option<String>,
/// Full resolution splat URL
pub spz_url_full_res: Option<String>,
pub metric_scale_factor: Option<f64>,
pub ground_plane_offset: Option<f64>,
}
/// GET /marble/v1/operations/{operation_id}
///
/// Poll the status of an async operation.
/// When `done` is true, `response` contains the World object.
/// When `done` is true, `world` contains the structured World object.
pub async fn get_operation(args: GetOperationArgs<'_>) -> Result<GetOperationResponse, WorldLabsError> {
let client = Client::new();
@@ -79,6 +117,16 @@ pub async fn get_operation(args: GetOperationArgs<'_>) -> Result<GetOperationRes
let raw: RawResponse = serde_json::from_str(&response_body)
.map_err(|err| WorldLabsGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?;
let world = raw.response.and_then(|raw_world| {
match try_parse_world(raw_world) {
Some(w) => Some(w),
None => {
warn!("Operation response present but missing world_id — skipping world parse");
None
}
}
});
Ok(GetOperationResponse {
operation_id: raw.operation_id,
done: raw.done.unwrap_or(false),
@@ -90,7 +138,46 @@ pub async fn get_operation(args: GetOperationArgs<'_>) -> Result<GetOperationRes
message: e.message,
}),
metadata: raw.metadata,
response: raw.response,
world,
})
}
/// Try to parse a RawWorld into a strongly-typed World.
/// Returns None if world_id is missing (not a valid World).
fn try_parse_world(raw: RawWorld) -> Option<World> {
let world_id = raw.world_id?;
let assets = raw.assets.map(|a| {
let splats = a.splats.map(|s| {
let semantics = s.semantics_metadata;
SplatAssets {
spz_url_100k: s.spz_urls.as_ref().and_then(|u| u.low.clone()),
spz_url_500k: s.spz_urls.as_ref().and_then(|u| u.medium.clone()),
spz_url_full_res: s.spz_urls.as_ref().and_then(|u| u.full_res.clone()),
metric_scale_factor: semantics.as_ref().and_then(|m| m.metric_scale_factor),
ground_plane_offset: semantics.as_ref().and_then(|m| m.ground_plane_offset),
}
});
WorldAssets {
caption: a.caption,
thumbnail_url: a.thumbnail_url,
pano_url: a.imagery.and_then(|i| i.pano_url),
collider_mesh_url: a.mesh.and_then(|m| m.collider_mesh_url),
splats,
}
});
Some(World {
world_id: WorldId(world_id),
display_name: raw.display_name,
world_marble_url: raw.world_marble_url,
created_at: raw.created_at,
updated_at: raw.updated_at,
model: raw.model,
status: raw.status,
tags: raw.tags,
assets,
})
}
@@ -108,7 +195,6 @@ mod tests {
let creds = get_test_api_key().unwrap();
// Use a known operation_id from a previous generate_world call
let operation_id = OperationId("1fab3bf1-05a1-4929-907e-c6df07c539e2".to_string());
let response = get_operation(GetOperationArgs {
@@ -120,9 +206,46 @@ mod tests {
println!("Operation ID: {}", response.operation_id);
println!("Done: {}", response.done);
println!("Created at: {:?}", response.created_at);
println!("Updated at: {:?}", response.updated_at);
println!("Expires at: {:?}", response.expires_at);
println!("Error: {:?}", response.error);
println!("Metadata: {:?}", response.metadata);
println!("Response: {:?}", response.response);
match &response.world {
None => println!("World: None (not yet complete)"),
Some(world) => {
println!("World:");
println!(" World ID: {}", world.world_id.as_str());
println!(" Display name: {:?}", world.display_name);
println!(" Marble URL: {:?}", world.world_marble_url);
println!(" Created at: {:?}", world.created_at);
println!(" Updated at: {:?}", world.updated_at);
println!(" Model: {:?}", world.model);
println!(" Status: {:?}", world.status);
println!(" Tags: {:?}", world.tags);
match &world.assets {
None => println!(" Assets: None"),
Some(assets) => {
println!(" Assets:");
println!(" Caption: {:?}", assets.caption);
println!(" Thumbnail URL: {:?}", assets.thumbnail_url);
println!(" Pano URL: {:?}", assets.pano_url);
println!(" Collider mesh URL: {:?}", assets.collider_mesh_url);
match &assets.splats {
None => println!(" Splats: None"),
Some(splats) => {
println!(" Splats:");
println!(" SPZ 100k: {:?}", splats.spz_url_100k);
println!(" SPZ 500k: {:?}", splats.spz_url_500k);
println!(" SPZ full res: {:?}", splats.spz_url_full_res);
println!(" Metric scale factor: {:?}", splats.metric_scale_factor);
println!(" Ground plane offset: {:?}", splats.ground_plane_offset);
}
}
}
}
}
}
assert_eq!(1, 2);
}
@@ -9,7 +9,7 @@ pub(crate) struct RawResponse {
pub expires_at: Option<String>,
pub error: Option<RawOperationError>,
pub metadata: Option<serde_json::Value>,
pub response: Option<serde_json::Value>,
pub response: Option<RawWorld>,
}
#[derive(Debug, Clone, Deserialize)]
@@ -17,3 +17,66 @@ pub(crate) struct RawOperationError {
pub code: Option<i32>,
pub message: Option<String>,
}
/// The World object embedded in `response` when the operation completes.
/// All fields are optional to tolerate partial/unexpected shapes.
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct RawWorld {
pub world_id: Option<String>,
pub display_name: Option<String>,
pub world_marble_url: Option<String>,
pub created_at: Option<String>,
pub updated_at: Option<String>,
pub model: Option<String>,
pub status: Option<String>,
pub tags: Option<Vec<String>>,
pub assets: Option<RawWorldAssets>,
pub world_prompt: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct RawWorldAssets {
pub caption: Option<String>,
pub thumbnail_url: Option<String>,
pub imagery: Option<RawImagery>,
pub mesh: Option<RawMesh>,
pub splats: Option<RawSplats>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct RawImagery {
pub pano_url: Option<String>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct RawMesh {
pub collider_mesh_url: Option<String>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct RawSplats {
pub spz_urls: Option<RawSpzUrls>,
pub semantics_metadata: Option<RawSemanticsMetadata>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct RawSemanticsMetadata {
pub metric_scale_factor: Option<f64>,
pub ground_plane_offset: Option<f64>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub(crate) struct RawSpzUrls {
#[serde(rename = "100k")]
pub low: Option<String>,
#[serde(rename = "500k")]
pub medium: Option<String>,
pub full_res: Option<String>,
}
@@ -1,3 +1,4 @@
use crate::api::api_types::world_id::WorldId;
use crate::api::requests::get_world::http_request::RawResponse;
use crate::credentials::world_labs_api_creds::WorldLabsApiCreds;
use crate::error::filter_world_labs_http_error::filter_world_labs_http_error;
@@ -11,12 +12,12 @@ const BASE_URL: &str = "https://api.worldlabs.ai/marble/v1/worlds";
pub struct GetWorldArgs<'a> {
pub creds: &'a WorldLabsApiCreds,
pub world_id: &'a str,
pub world_id: &'a WorldId,
pub request_timeout: Option<Duration>,
}
pub struct GetWorldResponse {
pub world_id: String,
pub world_id: WorldId,
pub display_name: Option<String>,
pub world_marble_url: Option<String>,
pub created_at: Option<String>,
@@ -45,7 +46,7 @@ pub struct WorldAssets {
pub async fn get_world(args: GetWorldArgs<'_>) -> Result<GetWorldResponse, WorldLabsError> {
let client = Client::new();
let url = format!("{}/{}", BASE_URL, args.world_id);
let url = format!("{}/{}", BASE_URL, args.world_id.as_str());
debug!("Requesting URL: {}", url);
@@ -97,7 +98,7 @@ pub async fn get_world(args: GetWorldArgs<'_>) -> Result<GetWorldResponse, World
});
Ok(GetWorldResponse {
world_id: raw.world_id,
world_id: WorldId(raw.world_id),
display_name: raw.display_name,
world_marble_url: raw.world_marble_url,
created_at: raw.created_at,
@@ -125,16 +126,16 @@ mod tests {
let creds = get_test_api_key().unwrap();
// Use a known world_id from a previous generation
let world_id = "0048d009-1c7a-4e13-9881-07e9b6ff32e1";
//let world_id = "c8a89fb1-f8b4-44c6-ae50-525f7205c65f";
let world_id = WorldId::from_str("0048d009-1c7a-4e13-9881-07e9b6ff32e1");
//let world_id = WorldId::from_str("c8a89fb1-f8b4-44c6-ae50-525f7205c65f");
let response = get_world(GetWorldArgs {
creds: &creds,
world_id,
world_id: &world_id,
request_timeout: None,
}).await.unwrap();
println!("World ID: {}", response.world_id);
println!("World ID: {}", response.world_id.as_str());
println!("Display name: {:?}", response.display_name);
println!("Marble URL: {:?}", response.world_marble_url);
println!("Created at: {:?}", response.created_at);
@@ -1,3 +1,4 @@
use crate::api::api_types::world_id::WorldId;
use crate::api::requests::list_worlds::http_request::{RawRequest, RawResponse};
use crate::credentials::world_labs_api_creds::WorldLabsApiCreds;
use crate::error::filter_world_labs_http_error::filter_world_labs_http_error;
@@ -36,7 +37,7 @@ pub struct ListWorldsResponse {
}
pub struct WorldSummary {
pub world_id: String,
pub world_id: WorldId,
pub display_name: Option<String>,
pub world_marble_url: Option<String>,
pub created_at: Option<String>,
@@ -104,7 +105,7 @@ pub async fn list_worlds(args: ListWorldsArgs<'_>) -> Result<ListWorldsResponse,
.map_err(|err| WorldLabsGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?;
let worlds = raw.worlds.into_iter().map(|w| WorldSummary {
world_id: w.world_id,
world_id: WorldId(w.world_id),
display_name: w.display_name,
world_marble_url: w.world_marble_url,
created_at: w.created_at,
@@ -154,7 +155,7 @@ mod tests {
for (i, world) in response.worlds.iter().enumerate() {
println!("--- World {} ---", i + 1);
println!(" World ID: {}", world.world_id);
println!(" World ID: {}", world.world_id.as_str());
println!(" Display name: {:?}", world.display_name);
println!(" Marble URL: {:?}", world.world_marble_url);
println!(" Created at: {:?}", world.created_at);
@@ -5,4 +5,5 @@ pub (crate) mod test_utils;
pub mod api;
pub mod credentials;
pub mod error;
pub mod pricing;
pub mod recipes;
@@ -0,0 +1,133 @@
use crate::api::api_types::world_labs_model::WorldLabsModel;
/// The type of input used to generate a world.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(test, derive(strum::EnumIter, strum::Display))]
pub enum InputType {
Text,
ImageNonPanorama,
ImagePanorama,
MultiImage,
Video,
}
/// Cost breakdown for a World Labs generation request.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WorldLabsCost {
pub worldlabs_credits: u32,
pub us_dollar_cents: u32,
}
/// Calculate the cost of a World Labs generation request.
pub fn calculate_cost(model: WorldLabsModel, input_type: InputType) -> WorldLabsCost {
let worldlabs_credits = credits_for(model, input_type);
let us_dollar_cents = credits_to_us_dollar_cents(worldlabs_credits);
WorldLabsCost { worldlabs_credits, us_dollar_cents }
}
/// Returns the credit cost for a given model and input type,
/// based on the official pricing table at https://docs.worldlabs.ai/api/pricing
fn credits_for(model: WorldLabsModel, input_type: InputType) -> u32 {
match (model, input_type) {
// Marble 0.1-mini
(WorldLabsModel::Marble0p1Mini, InputType::ImagePanorama) => 150,
(WorldLabsModel::Marble0p1Mini, InputType::Text) => 230,
(WorldLabsModel::Marble0p1Mini, InputType::ImageNonPanorama) => 230,
(WorldLabsModel::Marble0p1Mini, InputType::MultiImage) => 250,
(WorldLabsModel::Marble0p1Mini, InputType::Video) => 250,
// Marble 0.1-plus
(WorldLabsModel::Marble0p1Plus, InputType::ImagePanorama) => 1500,
(WorldLabsModel::Marble0p1Plus, InputType::Text) => 1580,
(WorldLabsModel::Marble0p1Plus, InputType::ImageNonPanorama) => 1580,
(WorldLabsModel::Marble0p1Plus, InputType::MultiImage) => 1600,
(WorldLabsModel::Marble0p1Plus, InputType::Video) => 1600,
}
}
/// Converts credits to US dollar cents.
/// Rate: $1.00 USD per 1,250 credits → 100 cents per 1,250 credits.
fn credits_to_us_dollar_cents(credits: u32) -> u32 {
// (credits * 100) / 1250 = (credits * 4) / 50 = (credits * 2) / 25
// Use u64 to avoid overflow on large values.
((credits as u64 * 100) / 1250) as u32
}
#[cfg(test)]
mod tests {
use super::*;
use strum::IntoEnumIterator;
#[test]
fn test_pricing_table() {
let models = [
WorldLabsModel::Marble0p1Mini,
WorldLabsModel::Marble0p1Plus,
];
println!("\n{:<20} {:<20} {:>10} {:>12}", "Model", "Input Type", "Credits", "USD Cents");
println!("{}", "-".repeat(65));
for model in &models {
for input_type in InputType::iter() {
let cost = calculate_cost(*model, input_type);
println!(
"{:<20} {:<20} {:>10} {:>12}",
model.get_model_api_name_str(),
input_type,
cost.worldlabs_credits,
cost.us_dollar_cents,
);
}
}
}
#[test]
fn test_mini_credits() {
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Mini, InputType::Text).worldlabs_credits, 230);
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Mini, InputType::ImageNonPanorama).worldlabs_credits, 230);
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Mini, InputType::ImagePanorama).worldlabs_credits, 150);
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Mini, InputType::MultiImage).worldlabs_credits, 250);
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Mini, InputType::Video).worldlabs_credits, 250);
}
#[test]
fn test_plus_credits() {
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Plus, InputType::Text).worldlabs_credits, 1580);
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Plus, InputType::ImageNonPanorama).worldlabs_credits, 1580);
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Plus, InputType::ImagePanorama).worldlabs_credits, 1500);
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Plus, InputType::MultiImage).worldlabs_credits, 1600);
assert_eq!(calculate_cost(WorldLabsModel::Marble0p1Plus, InputType::Video).worldlabs_credits, 1600);
}
#[test]
fn test_dollar_conversion() {
// 1250 credits = $1.00 = 100 cents
assert_eq!(credits_to_us_dollar_cents(1250), 100);
// 150 credits = $0.12 = 12 cents
assert_eq!(credits_to_us_dollar_cents(150), 12);
// 230 credits = $0.184 = 18 cents (truncated)
assert_eq!(credits_to_us_dollar_cents(230), 18);
// 1580 credits = $1.264 = 126 cents (truncated)
assert_eq!(credits_to_us_dollar_cents(1580), 126);
// 1600 credits = $1.28 = 128 cents
assert_eq!(credits_to_us_dollar_cents(1600), 128);
}
#[test]
fn test_all_combinations_have_costs() {
let models = [
WorldLabsModel::Marble0p1Mini,
WorldLabsModel::Marble0p1Plus,
];
for model in &models {
for input_type in InputType::iter() {
let cost = calculate_cost(*model, input_type);
assert!(cost.worldlabs_credits > 0, "credits should be > 0 for {:?} {:?}", model, input_type);
assert!(cost.us_dollar_cents > 0, "dollar cents should be > 0 for {:?} {:?}", model, input_type);
}
}
}
}
@@ -0,0 +1 @@
pub mod check_pricing;
@@ -1,5 +1,6 @@
use crate::api::api_types::media_asset_id::MediaAssetId;
use crate::api::api_types::operation_id::OperationId;
use crate::api::api_types::world_labs_model::WorldLabsModel;
use crate::api::requests::generate_world::generate_world::{generate_world, GenerateWorldArgs};
use crate::api::requests::generate_world::http_request::{ContentReference, WorldPrompt};
use crate::api::requests::prepare_upload::prepare_upload::{prepare_upload, PrepareUploadArgs, MediaAssetKind};
@@ -16,7 +17,7 @@ pub struct UploadImageAndCreateWorldArgs<'a> {
pub creds: &'a WorldLabsApiCreds,
pub file: FileBytesOrPath,
pub text_prompt: Option<String>,
pub model: Option<String>,
pub model: WorldLabsModel,
pub individual_request_timeout: Option<Duration>,
}
@@ -145,7 +146,7 @@ mod tests {
individual_request_timeout: None,
file: FileBytesOrPath::Bytes(file_bytes),
text_prompt: None,
model: None,
model: WorldLabsModel::Marble0p1Plus,
}).await.unwrap();
println!("Operation ID: {}", results.operation_id.as_str());