diff --git a/crates/api_clients/world_labs_client/src/api/api_types/mod.rs b/crates/api_clients/world_labs_client/src/api/api_types/mod.rs index 98f3688dc0..6367d2527b 100644 --- a/crates/api_clients/world_labs_client/src/api/api_types/mod.rs +++ b/crates/api_clients/world_labs_client/src/api/api_types/mod.rs @@ -1,3 +1,4 @@ pub mod object_id; pub mod upload_id; pub mod upload_mime_type; +pub mod world_id; diff --git a/crates/api_clients/world_labs_client/src/api/api_types/world_id.rs b/crates/api_clients/world_labs_client/src/api/api_types/world_id.rs new file mode 100644 index 0000000000..921ea44877 --- /dev/null +++ b/crates/api_clients/world_labs_client/src/api/api_types/world_id.rs @@ -0,0 +1,11 @@ +/// Type for World IDs. +/// These are used to refer to worlds +/// These appear to be bare UUIDs. +#[derive(Clone, Debug)] +pub struct WorldId(pub String); + +impl WorldId { + pub fn as_str(&self) -> &str { + &self.0 + } +} diff --git a/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r8_worlds_request_payload.json b/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r8_worlds_request_payload.json new file mode 100644 index 0000000000..4d2992a5d0 --- /dev/null +++ b/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r8_worlds_request_payload.json @@ -0,0 +1,18 @@ +{ + "permission": { + "public": true + }, + "generation_input": { + "model": "Marble 0.1-plus", + "prompt": { + "type": "image", + "image_prompt": { + "uri": "https://cdn.marble.worldlabs.ai/object/cf326bd0-b7eb-454c-b433-54f36eb4d5a6/asset.jpg" + }, + "text_prompt": "The scene is a cozy A-frame cabin interior, rendered in a realistic style, exuding warmth and rustic charm, creating a tranquil and inviting atmosphere. A prominent wooden A-frame structure defines the architecture, featuring angled timber beams that converge towards the peak of the ceiling. The walls and ceiling are paneled with warm-toned wooden planks, creating a consistent and enveloping aesthetic throughout the space. A large, floor-to-ceiling window dominates one wall, offering an expansive view of a lush forest with tall trees and autumnal foliage outside, suggesting the cabin is nestled deep within nature. A plush bed occupies a significant portion of the floor space, adorned with a dark grey duvet, numerous pillows, and a rust-colored throw blanket, inviting comfort and relaxation. A cream-colored faux fur rug rests on the wooden floor beside the bed, adding texture and a soft landing. A black wood-burning stove with a visible fire provides heat and a focal point for the room, situated near the large window. The floor is made of polished light wood planks. On the other side of the cabin, a continuation of the wooden paneling and A-frame structure mirrors the visible portion, maintaining architectural consistency. Another large window, identical in style and size to the visible one, would likely offer a similar forest view on the opposite side of the cabin, bathing the entire space in natural light filtered through the trees. A small, rustic armchair with a woven texture would be positioned near the stove on the opposite side, providing an additional seating area for enjoying the warmth. A stack of neatly chopped firewood is arranged next to the stove, ready for use, emphasizing the cabin's rustic functionality. " + } + }, + "context": { + "run_id": "a37a82a4-0b39-47e1-acf9-07a65888d5be" + } +} diff --git a/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r8_worlds_response.json b/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r8_worlds_response.json new file mode 100644 index 0000000000..8ef012b79e --- /dev/null +++ b/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r8_worlds_response.json @@ -0,0 +1,37 @@ +{ + "context": { + "run_id": "a37a82a4-0b39-47e1-acf9-07a65888d5be", + "project_id": null + }, + "id": "3e8d210f-28ea-4375-92d8-3ee6f14db61d", + "display_name": null, + "status": "INITIALIZING", + "error": null, + "owner_id": "sVgAQDyH3BfYOBfF6epWTjLHygX2", + "created_at": 1765325564, + "updated_at": 1765325564, + "permission": { + "public": true, + "allowed_readers": [], + "allowed_writers": [] + }, + "stats": null, + "application_data": null, + "generation_input": { + "model": "Marble 0.1-plus", + "seed": null, + "prompt": { + "type": "image", + "text_prompt": "The scene is a cozy A-frame cabin interior, rendered in a realistic style, exuding warmth and rustic charm, creating a tranquil and inviting atmosphere. A prominent wooden A-frame structure defines the architecture, featuring angled timber beams that converge towards the peak of the ceiling. The walls and ceiling are paneled with warm-toned wooden planks, creating a consistent and enveloping aesthetic throughout the space. A large, floor-to-ceiling window dominates one wall, offering an expansive view of a lush forest with tall trees and autumnal foliage outside, suggesting the cabin is nestled deep within nature. A plush bed occupies a significant portion of the floor space, adorned with a dark grey duvet, numerous pillows, and a rust-colored throw blanket, inviting comfort and relaxation. A cream-colored faux fur rug rests on the wooden floor beside the bed, adding texture and a soft landing. A black wood-burning stove with a visible fire provides heat and a focal point for the room, situated near the large window. The floor is made of polished light wood planks. On the other side of the cabin, a continuation of the wooden paneling and A-frame structure mirrors the visible portion, maintaining architectural consistency. Another large window, identical in style and size to the visible one, would likely offer a similar forest view on the opposite side of the cabin, bathing the entire space in natural light filtered through the trees. A small, rustic armchair with a woven texture would be positioned near the stove on the opposite side, providing an additional seating area for enjoying the warmth. A stack of neatly chopped firewood is arranged next to the stove, ready for use, emphasizing the cabin's rustic functionality. ", + "image_prompt": { + "uri": "https://cdn.marble.worldlabs.ai/3e8d210f-28ea-4375-92d8-3ee6f14db61d/5c5474e3_image_prompt_sanitized.png", + "data_base64": null, + "extension": "png" + }, + "is_pano": null + } + }, + "tags": null, + "hq_mesh_export_op_id": null, + "generation_output": null +} diff --git a/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r8_worlds_response.txt b/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r8_worlds_response.txt deleted file mode 100644 index 0a1051eeae..0000000000 --- a/crates/api_clients/world_labs_client/src/api/requests/_metadata/request_flow_3_firefox/r8_worlds_response.txt +++ /dev/null @@ -1 +0,0 @@ -{"context":{"run_id":"a37a82a4-0b39-47e1-acf9-07a65888d5be","project_id":null},"id":"3e8d210f-28ea-4375-92d8-3ee6f14db61d","display_name":null,"status":"INITIALIZING","error":null,"owner_id":"sVgAQDyH3BfYOBfF6epWTjLHygX2","created_at":1765325564,"updated_at":1765325564,"permission":{"public":true,"allowed_readers":[],"allowed_writers":[]},"stats":null,"application_data":null,"generation_input":{"model":"Marble 0.1-plus","seed":null,"prompt":{"type":"image","text_prompt":"The scene is a cozy A-frame cabin interior, rendered in a realistic style, exuding warmth and rustic charm, creating a tranquil and inviting atmosphere. A prominent wooden A-frame structure defines the architecture, featuring angled timber beams that converge towards the peak of the ceiling. The walls and ceiling are paneled with warm-toned wooden planks, creating a consistent and enveloping aesthetic throughout the space. A large, floor-to-ceiling window dominates one wall, offering an expansive view of a lush forest with tall trees and autumnal foliage outside, suggesting the cabin is nestled deep within nature. A plush bed occupies a significant portion of the floor space, adorned with a dark grey duvet, numerous pillows, and a rust-colored throw blanket, inviting comfort and relaxation. A cream-colored faux fur rug rests on the wooden floor beside the bed, adding texture and a soft landing. A black wood-burning stove with a visible fire provides heat and a focal point for the room, situated near the large window. The floor is made of polished light wood planks. On the other side of the cabin, a continuation of the wooden paneling and A-frame structure mirrors the visible portion, maintaining architectural consistency. Another large window, identical in style and size to the visible one, would likely offer a similar forest view on the opposite side of the cabin, bathing the entire space in natural light filtered through the trees. A small, rustic armchair with a woven texture would be positioned near the stove on the opposite side, providing an additional seating area for enjoying the warmth. A stack of neatly chopped firewood is arranged next to the stove, ready for use, emphasizing the cabin's rustic functionality. ","image_prompt":{"uri":"https://cdn.marble.worldlabs.ai/3e8d210f-28ea-4375-92d8-3ee6f14db61d/5c5474e3_image_prompt_sanitized.png","data_base64":null,"extension":"png"},"is_pano":null}},"tags":null,"hq_mesh_export_op_id":null,"generation_output":null} \ No newline at end of file diff --git a/crates/api_clients/world_labs_client/src/api/requests/mod.rs b/crates/api_clients/world_labs_client/src/api/requests/mod.rs index d51e7af04e..8e2bbb2889 100644 --- a/crates/api_clients/world_labs_client/src/api/requests/mod.rs +++ b/crates/api_clients/world_labs_client/src/api/requests/mod.rs @@ -1,3 +1,4 @@ pub mod google_upload; pub mod objects; -pub mod recaption; \ No newline at end of file +pub mod recaption; +pub mod worlds; \ No newline at end of file diff --git a/crates/api_clients/world_labs_client/src/api/requests/worlds/create_world.rs b/crates/api_clients/world_labs_client/src/api/requests/worlds/create_world.rs new file mode 100644 index 0000000000..7de8ca9e19 --- /dev/null +++ b/crates/api_clients/world_labs_client/src/api/requests/worlds/create_world.rs @@ -0,0 +1,185 @@ +use crate::api::api_types::object_id::ObjectId; +use crate::api::api_types::upload_id::UploadId; +use crate::api::api_types::upload_mime_type::UploadMimeType; +use crate::api::api_types::world_id::WorldId; +use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE}; +use crate::api::utils::upload_id_to_image_url::upload_id_to_image_url; +use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken; +use crate::credentials::world_labs_cookies::WorldLabsCookies; +use crate::error::world_labs_client_error::WorldLabsClientError; +use crate::error::world_labs_error::WorldLabsError; +use crate::error::world_labs_generic_api_error::WorldLabsGenericApiError; +use http_headers::names::{PRIORITY, SEC_FETCH_DEST, SEC_FETCH_MODE, SEC_FETCH_SITE, SEC_GPC}; +use http_headers::values::accept::ACCEPT_ALL; +use http_headers::values::cache_control::CACHE_CONTROL_NO_CACHE; +use http_headers::values::connection::CONNECTION_KEEP_ALIVE; +use http_headers::values::content_type::CONTENT_TYPE_APPLICATION_JSON; +use http_headers::values::pragma::PRAGMA_NO_CACHE; +use http_headers::values::priority::PRIORITY_4; +use http_headers::values::sec::{SEC_FETCH_DEST_EMPTY, SEC_FETCH_MODE_CORS, SEC_FETCH_SITE_CROSS_SITE}; +use http_headers::values::te::TE_TRAILERS; +use log::{debug, error}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_TYPE, ORIGIN, PRAGMA, REFERER, TE}; +use wreq::Client; +use wreq_util::Emulation; + +const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/worlds"; + +pub struct CreateWorldArgs<'a> { + pub cookies: &'a WorldLabsCookies, + pub bearer_token: &'a WorldLabsBearerToken, + pub upload_id: &'a UploadId, + pub upload_mime_type: UploadMimeType, + pub run_id: &'a ObjectId, + pub text_prompt: &'a str, + pub request_timeout: Option, +} + +pub struct CreateWorldResponse { + pub world_id: WorldId, +} + +/// Marble Image-to-World +/// This request starts world generation +/// Request #7 (of ~10) +pub async fn create_world(args: CreateWorldArgs<'_>) -> Result { + let client = Client::builder() + .emulation(Emulation::Firefox143) + .build() + .map_err(|err| WorldLabsClientError::WreqClientError(err))?; + + debug!("Requesting URL: {}", URL); + + let mut request_builder = client.post(URL) + .header(ACCEPT, ACCEPT_ALL) + .header(ACCEPT_LANGUAGE, "en-US,en;q=0.5") + .header(ACCEPT_ENCODING, "gzip, deflate, br, zstd") + .header(REFERER, REFERER_VALUE) + .header(CONTENT_TYPE, CONTENT_TYPE_APPLICATION_JSON) + .header(AUTHORIZATION, args.bearer_token.to_bearer_token_string()) + .header(ORIGIN, ORIGIN_VALUE) + .header(SEC_GPC, "1") + .header(CONNECTION, CONNECTION_KEEP_ALIVE) + .header(SEC_FETCH_DEST, SEC_FETCH_DEST_EMPTY) + .header(SEC_FETCH_MODE, SEC_FETCH_MODE_CORS) + .header(SEC_FETCH_SITE, SEC_FETCH_SITE_CROSS_SITE) + .header(PRIORITY, PRIORITY_4) + .header(PRAGMA, PRAGMA_NO_CACHE) + .header(CACHE_CONTROL, CACHE_CONTROL_NO_CACHE) + .header(TE, TE_TRAILERS); + + if let Some(timeout) = args.request_timeout { + request_builder = request_builder.timeout(timeout); + } + + let request_payload = RawRequest::for_image_and_run(args.upload_id, args.upload_mime_type, args.run_id, args.text_prompt); + + let http_request = request_builder.json(&request_payload) + .build() + .map_err(|err| { + error!("Error building request: {:?}", err); + WorldLabsClientError::WreqClientError(err) + })?; + + let response = client.execute(http_request) + .await + .map_err(|err| { + error!("Error during request execution: {:?}", err); + WorldLabsGenericApiError::WreqError(err) + })?; + + let status = response.status(); + + let response_body = response.text() + .await + .map_err(|err| { + error!("Error reading response body: {:?}", err); + WorldLabsGenericApiError::WreqError(err) + })?; + + // TODO: Handle errors (Cloudflare, Grok, etc.) + if !status.is_success() { + error!("Request returned an error (code {}) : {:?}", status.as_u16(), response_body); + //return Err(classify_general_http_status_code_and_body(status, response_body)); + return Err(WorldLabsGenericApiError::UncategorizedBadResponseWithStatusAndBody { status_code: status, body: response_body}.into()) + } + + debug!("Response body (200): {}", response_body); + + let response : RawResponse = serde_json::from_str(&response_body) + .map_err(|err| WorldLabsGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?; + + Ok(CreateWorldResponse { + world_id: WorldId(response.id), + }) +} + +#[derive(Serialize)] +struct RawRequest { + pub permission: Permission, + pub generation_input: GenerationInput, + pub context: Context, +} + +#[derive(Serialize)] +struct Permission { + pub public: bool, +} + +#[derive(Serialize)] +struct GenerationInput { + pub model: String, + pub prompt: Prompt, +} + +#[derive(Serialize)] +struct Prompt { + pub r#type: String, + pub image_prompt: ImagePrompt, + pub text_prompt: String, +} + +#[derive(Serialize)] +struct ImagePrompt { + pub uri: String, +} + +#[derive(Serialize)] +struct Context { + pub run_id: String, +} + +impl RawRequest { + pub fn for_image_and_run(image_upload_id: &UploadId, image_mime_type: UploadMimeType, run_id: &ObjectId, text_prompt: &str) -> Self { + let image_url = upload_id_to_image_url(image_upload_id, image_mime_type); + Self { + permission: Permission { + public: true, + }, + generation_input: GenerationInput { + model: "Marble 0.1-plus".to_string(), + prompt: Prompt { + r#type: "image".to_string(), + image_prompt: ImagePrompt { + uri: image_url, + }, + text_prompt: text_prompt.to_string(), + }, + }, + context: Context { + run_id: run_id.0.clone(), + } + } + } +} + +#[derive(Deserialize)] +struct RawResponse { + pub id: String, +} + +#[cfg(test)] +mod tests { +} diff --git a/crates/api_clients/world_labs_client/src/api/requests/worlds/mod.rs b/crates/api_clients/world_labs_client/src/api/requests/worlds/mod.rs new file mode 100644 index 0000000000..5a153a220a --- /dev/null +++ b/crates/api_clients/world_labs_client/src/api/requests/worlds/mod.rs @@ -0,0 +1 @@ +pub mod create_world; \ No newline at end of file