diff --git a/crates/api_clients/grok_client/src/datatypes/api/mod.rs b/crates/api_clients/grok_client/src/datatypes/api/mod.rs index 927ec4a6cf..4fe3408938 100644 --- a/crates/api_clients/grok_client/src/datatypes/api/mod.rs +++ b/crates/api_clients/grok_client/src/datatypes/api/mod.rs @@ -1,6 +1,7 @@ pub mod aspect_ratio; pub mod baggage; pub mod file_id; +pub mod video_generation_mode; pub mod post_id; pub mod request_id; pub mod sentry_trace; diff --git a/crates/api_clients/grok_client/src/datatypes/api/video_generation_mode.rs b/crates/api_clients/grok_client/src/datatypes/api/video_generation_mode.rs new file mode 100644 index 0000000000..3036b34114 --- /dev/null +++ b/crates/api_clients/grok_client/src/datatypes/api/video_generation_mode.rs @@ -0,0 +1,27 @@ + +/// The video generation mode selector. +#[derive(Copy, Clone, Debug)] +pub enum VideoGenerationMode { + /// Supposedly the "NSFW" mode. + Spicy, + + Fun, + + Normal, + + /// This might be the only mode that allows for a custom text prompt + Custom, +} + +impl VideoGenerationMode { + + /// This is the value for `--mode` in video generation chats. + pub fn as_api_mode_arg(&self) -> &'static str { + match self { + Self::Spicy => "extremely-spicy-or-crazy", + Self::Fun => "extremely-crazy", + Self::Normal => "normal", + Self::Custom => "custom", + } + } +} diff --git a/crates/api_clients/grok_client/src/recipes/prompt_websocket_image_with_retry.rs b/crates/api_clients/grok_client/src/recipes/prompt_websocket_image_with_retry.rs index 8d2d90ee01..f6cea7cb1f 100644 --- a/crates/api_clients/grok_client/src/recipes/prompt_websocket_image_with_retry.rs +++ b/crates/api_clients/grok_client/src/recipes/prompt_websocket_image_with_retry.rs @@ -92,6 +92,8 @@ mod tests { cookies: cookies.as_str(), }).await?; + let prompt = "A dinosaur on stilts walking on the beach"; + //let websocket = GrokWrappedWebsocket::new(websocket); let mut websocket = GrokWebsocket::new(websocket); @@ -106,7 +108,7 @@ mod tests { let result = prompt_websocket_image_with_retry(PromptWebsocketImageWithRetryArgs { websocket: &mut websocket, - prompt: "a dog riding a motorcycle", + prompt, aspect_ratio: ClientMessageAspectRatio::WideThreeByTwo, cookies: &cookies, }).await?; @@ -152,9 +154,19 @@ mod tests { match message { WebsocketServerMessage::Image(image) => { println!("IMAGE: {:?}", image.percentage_complete); + if let Some(percent) = image.percentage_complete { + if percent > 90.0 { + println!("Image: {:?}", image); + } + } } WebsocketServerMessage::Json(json) => { println!("JSON : {:?}", json.percentage_complete); + if let Some(percent) = json.percentage_complete { + if percent > 90.0 { + println!("Image: {:?}", json); + } + } } WebsocketServerMessage::Unknown(unknown) => { //let typ = unknown.get("type"); diff --git a/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video.rs b/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video.rs index d062b7d618..01e8421010 100644 --- a/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video.rs +++ b/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video.rs @@ -2,6 +2,7 @@ use crate::credentials::grok_full_credentials::GrokFullCredentials; use crate::datatypes::api::aspect_ratio::AspectRatio; use crate::datatypes::api::file_id::FileId; use crate::datatypes::api::post_id::PostId; +use crate::datatypes::api::video_generation_mode::VideoGenerationMode; use crate::datatypes::file_upload_spec::FileUploadSpec; use crate::error::grok_error::GrokError; use crate::error::grok_generic_api_error::GrokGenericApiError; @@ -27,6 +28,9 @@ pub struct UploadImageAndGenerateVideo<'a, P: AsRef> { /// Aspect ratio for the video. pub aspect_ratio: Option, + /// Mode to generate the video: normal, fun, spicy, etc. + pub mode: Option, + /// Wait for the full video to be generated before returning /// By default, the endpoint stays open for 30-ish seconds while /// the video generates and streams back JSON progress updates. @@ -116,6 +120,7 @@ pub async fn upload_image_and_generate_video>(args: UploadImageAn media_type: VideoMediaPostType::UserUploadedImage, cookie: args.full_credentials.cookies.as_str(), prompt: args.prompt, + mode: args.mode, aspect_ratio: args.aspect_ratio, request_timeout: args.individual_request_timeout, baggage: &args.full_credentials.client_secrets.baggage, @@ -178,6 +183,7 @@ pub async fn upload_image_and_generate_video>(args: UploadImageAn #[cfg(test)] mod tests { use crate::credentials::grok_full_credentials::GrokFullCredentials; + use crate::datatypes::api::aspect_ratio::AspectRatio; use crate::datatypes::file_upload_spec::FileUploadSpec; use crate::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs}; use crate::recipes::upload_image_and_generate_video::{upload_image_and_generate_video, UploadImageAndGenerateVideo}; @@ -185,7 +191,6 @@ mod tests { use crate::test_utils::setup_test_logging::setup_test_logging; use errors::AnyhowResult; use log::LevelFilter; - use crate::datatypes::api::aspect_ratio::AspectRatio; // Result: GrokUploadFileResponse { file_metadata_id: // Some("acdee48f-9d6f-4bc6-9d06-fcc97dd4418a"), file_uri: // Some("users/85980643-ffab-4984-a3de-59a608c47d7f/acdee48f-9d6f-4bc6-9d06-fcc97dd4418a/content") } @@ -220,6 +225,7 @@ mod tests { file: FileUploadSpec::Path(image_path), prompt: maybe_prompt, aspect_ratio: Some(AspectRatio::Square), + mode: None, individual_request_timeout: None, wait_for_generation: false, }).await?; diff --git a/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video_with_retry.rs b/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video_with_retry.rs index 4e981563d3..856d97338a 100644 --- a/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video_with_retry.rs +++ b/crates/api_clients/grok_client/src/recipes/upload_image_and_generate_video_with_retry.rs @@ -1,6 +1,7 @@ use crate::credentials::grok_client_secrets::GrokClientSecrets; use crate::credentials::grok_full_credentials::GrokFullCredentials; use crate::datatypes::api::aspect_ratio::AspectRatio; +use crate::datatypes::api::video_generation_mode::VideoGenerationMode; use crate::datatypes::file_upload_spec::FileUploadSpec; use crate::error::grok_client_error::GrokClientError; use crate::error::grok_error::GrokError; @@ -30,6 +31,9 @@ pub struct UploadImageAndGenerateVideoWithRetry<'a, P: AsRef> { /// Aspect ratio for the video. pub aspect_ratio: Option, + /// Mode to generate the video: normal, fun, spicy, etc. + pub mode: Option, + /// Wait for the full video to be generated before returning /// By default, the endpoint stays open for 30-ish seconds while /// the video generates and streams back JSON progress updates. @@ -116,6 +120,7 @@ pub async fn upload_image_and_generate_video_with_retry>( media_type: VideoMediaPostType::UserUploadedImage, cookie: current_full_credentials_ref.cookies.as_str(), prompt: args.prompt, + mode: args.mode, aspect_ratio: args.aspect_ratio, request_timeout: args.individual_request_timeout, baggage: ¤t_full_credentials_ref.client_secrets.baggage, @@ -271,6 +276,7 @@ pub async fn upload_image_and_generate_video_with_retry>( mod tests { use crate::credentials::grok_full_credentials::GrokFullCredentials; use crate::datatypes::api::aspect_ratio::AspectRatio; + use crate::datatypes::api::video_generation_mode::VideoGenerationMode; use crate::datatypes::file_upload_spec::FileUploadSpec; use crate::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs}; use crate::recipes::upload_image_and_generate_video_with_retry::{upload_image_and_generate_video_with_retry, UploadImageAndGenerateVideoWithRetry}; @@ -310,7 +316,8 @@ mod tests { credentials: &credentials, file: FileUploadSpec::Path(image_path), prompt: maybe_prompt, - aspect_ratio: Some(AspectRatio::Square), + mode: Some(VideoGenerationMode::Custom), + aspect_ratio: Some(AspectRatio::TallTwoByThree), individual_request_timeout: None, wait_for_generation: false, }).await?; diff --git a/crates/api_clients/grok_client/src/requests/video_chat/_metadata/dec_1/fun_video_gen_request.txt b/crates/api_clients/grok_client/src/requests/video_chat/_metadata/dec_1/fun_video_gen_request.txt new file mode 100644 index 0000000000..42da28ae85 --- /dev/null +++ b/crates/api_clients/grok_client/src/requests/video_chat/_metadata/dec_1/fun_video_gen_request.txt @@ -0,0 +1,45 @@ +curl 'https://grok.com/rest/app-chat/conversations/new' \ + -H 'accept: */*' \ + -H 'accept-language: en-US,en;q=0.9' \ + -H 'baggage: sentry-environment=production,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c,sentry-trace_id=7798e88fd61243ba8985ca9f589f5113,sentry-org_id=4508179396558848,sentry-transaction=%2F%3Aslug*%3F,sentry-sampled=false,sentry-sample_rand=0.01740155041905489,sentry-sample_rate=0' \ + -H 'cache-control: no-cache' \ + -H 'content-type: application/json' \ + -b '_ga=GA1.1.1232202746.1760710013; i18nextLng=en; sso=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; sso-rw=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; stblid=b3331fc1-45d7-466b-83df-67427c0b2367; x-userid=85980643-ffab-4984-a3de-59a608c47d7f; mp_ea93da913ddb66b6372b89d97b1029ac_mixpanel=%7B%22distinct_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%2C%22%24device_id%22%3A%2279ce237a-a0f3-4913-bf4b-519ac8a98263%22%2C%22%24initial_referrer%22%3A%22%24direct%22%2C%22%24initial_referring_domain%22%3A%22%24direct%22%2C%22__mps%22%3A%7B%7D%2C%22__mpso%22%3A%7B%7D%2C%22__mpus%22%3A%7B%7D%2C%22__mpa%22%3A%7B%7D%2C%22__mpu%22%3A%7B%7D%2C%22__mpr%22%3A%5B%5D%2C%22__mpap%22%3A%5B%5D%2C%22%24user_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%7D; cf_clearance=5QPOzLWH8eEniaDlJR23k9VB4XjRPHdR.kfVR15BRkI-1764645588-1.2.1.1-WBy8wpvS9.Q.ofWBIMTLHDrKRIPt0Xrn5kAQ6I7xf4u0JSbB.E4CRd.fK6kEZo8OTXMAdt.F0YkoLJzB0TufxM79dMu63TKLmorKB.gSgUWJcea1eQcLJ1WG80uhv4VCMrSJ_HHAaq6fqiNNvS7pArgMT4tvoaQXShfDuV8ivYsRp31LYMgNnRFdtiCiJ5mxhf7uUMpSS_UbvCSaYe7CysUr472alMjEYUd.QhKpb8E; _ga_8FEWB057YH=GS2.1.s1764645588$o22$g1$t1764646543$j57$l0$h0' \ + -H 'origin: https://grok.com' \ + -H 'pragma: no-cache' \ + -H 'priority: u=1, i' \ + -H 'referer: https://grok.com/imagine/post/463eb5f6-7485-495a-a344-70ab4fe454a9' \ + -H 'sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' \ + -H 'sec-ch-ua-mobile: ?0' \ + -H 'sec-ch-ua-platform: "macOS"' \ + -H 'sec-fetch-dest: empty' \ + -H 'sec-fetch-mode: cors' \ + -H 'sec-fetch-site: same-origin' \ + -H 'sentry-trace: 7798e88fd61243ba8985ca9f589f5113-889cac2356ac270b-0' \ + -H 'traceparent: 00-9698441276035e5f4414a5c7b7cde4e7-043b158b0070e175-00' \ + -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \ + -H 'x-statsig-id: eVTlFkyAcCo5RWxUQOUfzewYmzWMb/GbAFj4n2ymSal+UQwbGGxtFMSDrd+DdsVB0EaFp33Dd+aDMEmMn9Ay0MCCa8Bqeg' \ + -H 'x-xai-request-id: ac5e2fc3-daed-4967-a9ff-1d12b60ee0e1' \ + --data-raw '{"temporary":true,"modelName":"grok-3","message":"https://imagine-public.x.ai/imagine-public/images/463eb5f6-7485-495a-a344-70ab4fe454a9.png --mode=extremely-crazy","toolOverrides":{"videoGen":true},"responseMetadata":{"modelConfigOverride":{"modelMap":{"videoGenModelConfig":{"parentPostId":"463eb5f6-7485-495a-a344-70ab4fe454a9","aspectRatio":"2:3","videoLength":6}}}}}' + + +--data-raw + { + "temporary":true, + "modelName":"grok-3", + "message":"https://imagine-public.x.ai/imagine-public/images/463eb5f6-7485-495a-a344-70ab4fe454a9.png --mode=extremely-crazy", + "toolOverrides": { + "videoGen":true + }, + "responseMetadata": { + "modelConfigOverride": { + "modelMap": { + "videoGenModelConfig": { + "parentPostId":"463eb5f6-7485-495a-a344-70ab4fe454a9", + "aspectRatio": "2:3", + "videoLength":6 + } + } + } + } + } diff --git a/crates/api_clients/grok_client/src/requests/video_chat/_metadata/dec_1/normal_video_gen_request.txt b/crates/api_clients/grok_client/src/requests/video_chat/_metadata/dec_1/normal_video_gen_request.txt new file mode 100644 index 0000000000..360d234a7a --- /dev/null +++ b/crates/api_clients/grok_client/src/requests/video_chat/_metadata/dec_1/normal_video_gen_request.txt @@ -0,0 +1,23 @@ +curl 'https://grok.com/rest/app-chat/conversations/new' \ + -H 'accept: */*' \ + -H 'accept-language: en-US,en;q=0.9' \ + -H 'baggage: sentry-environment=production,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c,sentry-trace_id=7798e88fd61243ba8985ca9f589f5113,sentry-org_id=4508179396558848,sentry-transaction=%2F%3Aslug*%3F,sentry-sampled=false,sentry-sample_rand=0.01740155041905489,sentry-sample_rate=0' \ + -H 'cache-control: no-cache' \ + -H 'content-type: application/json' \ + -b '_ga=GA1.1.1232202746.1760710013; i18nextLng=en; sso=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; sso-rw=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; stblid=b3331fc1-45d7-466b-83df-67427c0b2367; x-userid=85980643-ffab-4984-a3de-59a608c47d7f; mp_ea93da913ddb66b6372b89d97b1029ac_mixpanel=%7B%22distinct_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%2C%22%24device_id%22%3A%2279ce237a-a0f3-4913-bf4b-519ac8a98263%22%2C%22%24initial_referrer%22%3A%22%24direct%22%2C%22%24initial_referring_domain%22%3A%22%24direct%22%2C%22__mps%22%3A%7B%7D%2C%22__mpso%22%3A%7B%7D%2C%22__mpus%22%3A%7B%7D%2C%22__mpa%22%3A%7B%7D%2C%22__mpu%22%3A%7B%7D%2C%22__mpr%22%3A%5B%5D%2C%22__mpap%22%3A%5B%5D%2C%22%24user_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%7D; cf_clearance=5QPOzLWH8eEniaDlJR23k9VB4XjRPHdR.kfVR15BRkI-1764645588-1.2.1.1-WBy8wpvS9.Q.ofWBIMTLHDrKRIPt0Xrn5kAQ6I7xf4u0JSbB.E4CRd.fK6kEZo8OTXMAdt.F0YkoLJzB0TufxM79dMu63TKLmorKB.gSgUWJcea1eQcLJ1WG80uhv4VCMrSJ_HHAaq6fqiNNvS7pArgMT4tvoaQXShfDuV8ivYsRp31LYMgNnRFdtiCiJ5mxhf7uUMpSS_UbvCSaYe7CysUr472alMjEYUd.QhKpb8E; _ga_8FEWB057YH=GS2.1.s1764645588$o22$g1$t1764646543$j57$l0$h0' \ + -H 'origin: https://grok.com' \ + -H 'pragma: no-cache' \ + -H 'priority: u=1, i' \ + -H 'referer: https://grok.com/imagine/post/463eb5f6-7485-495a-a344-70ab4fe454a9' \ + -H 'sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' \ + -H 'sec-ch-ua-mobile: ?0' \ + -H 'sec-ch-ua-platform: "macOS"' \ + -H 'sec-fetch-dest: empty' \ + -H 'sec-fetch-mode: cors' \ + -H 'sec-fetch-site: same-origin' \ + -H 'sentry-trace: 7798e88fd61243ba8985ca9f589f5113-ac16ba34e7a9e3c2-0' \ + -H 'traceparent: 00-017b734e99d0585d4e05bf0376ef9ffb-c5854dfb0968df83-00' \ + -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \ + -H 'x-statsig-id: 8dxtnsQI+KKxzeTcyG2XRWSQE70E53kTiNBwF+QuwSH22YSTkOTlnEwLJVcL/k3JWIkNL/XNm7U/U87VShlyvfCIJ01k8g' \ + -H 'x-xai-request-id: c2eab60f-859f-4963-8ef8-b5eefaaf5966' \ + --data-raw '{"temporary":true,"modelName":"grok-3","message":"https://imagine-public.x.ai/imagine-public/images/463eb5f6-7485-495a-a344-70ab4fe454a9.png --mode=normal","toolOverrides":{"videoGen":true},"responseMetadata":{"modelConfigOverride":{"modelMap":{"videoGenModelConfig":{"parentPostId":"463eb5f6-7485-495a-a344-70ab4fe454a9","aspectRatio":"2:3","videoLength":6}}}}}' \ No newline at end of file diff --git a/crates/api_clients/grok_client/src/requests/video_chat/_metadata/dec_1/spicy_video_gen_request.txt b/crates/api_clients/grok_client/src/requests/video_chat/_metadata/dec_1/spicy_video_gen_request.txt new file mode 100644 index 0000000000..1922f7d4f1 --- /dev/null +++ b/crates/api_clients/grok_client/src/requests/video_chat/_metadata/dec_1/spicy_video_gen_request.txt @@ -0,0 +1,46 @@ +curl 'https://grok.com/rest/app-chat/conversations/new' \ + -H 'accept: */*' \ + -H 'accept-language: en-US,en;q=0.9' \ + -H 'baggage: sentry-environment=production,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c,sentry-trace_id=277d6351c5063fd659b51b77b50bb453,sentry-org_id=4508179396558848,sentry-sampled=false,sentry-sample_rand=0.9765826569629004,sentry-sample_rate=0' \ + -H 'cache-control: no-cache' \ + -H 'content-type: application/json' \ + -b '_ga=GA1.1.1232202746.1760710013; i18nextLng=en; sso=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; sso-rw=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; stblid=b3331fc1-45d7-466b-83df-67427c0b2367; x-userid=85980643-ffab-4984-a3de-59a608c47d7f; mp_ea93da913ddb66b6372b89d97b1029ac_mixpanel=%7B%22distinct_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%2C%22%24device_id%22%3A%2279ce237a-a0f3-4913-bf4b-519ac8a98263%22%2C%22%24initial_referrer%22%3A%22%24direct%22%2C%22%24initial_referring_domain%22%3A%22%24direct%22%2C%22__mps%22%3A%7B%7D%2C%22__mpso%22%3A%7B%7D%2C%22__mpus%22%3A%7B%7D%2C%22__mpa%22%3A%7B%7D%2C%22__mpu%22%3A%7B%7D%2C%22__mpr%22%3A%5B%5D%2C%22__mpap%22%3A%5B%5D%2C%22%24user_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%7D; cf_clearance=5QPOzLWH8eEniaDlJR23k9VB4XjRPHdR.kfVR15BRkI-1764645588-1.2.1.1-WBy8wpvS9.Q.ofWBIMTLHDrKRIPt0Xrn5kAQ6I7xf4u0JSbB.E4CRd.fK6kEZo8OTXMAdt.F0YkoLJzB0TufxM79dMu63TKLmorKB.gSgUWJcea1eQcLJ1WG80uhv4VCMrSJ_HHAaq6fqiNNvS7pArgMT4tvoaQXShfDuV8ivYsRp31LYMgNnRFdtiCiJ5mxhf7uUMpSS_UbvCSaYe7CysUr472alMjEYUd.QhKpb8E; _ga_8FEWB057YH=GS2.1.s1764645588$o22$g1$t1764645617$j31$l0$h0' \ + -H 'origin: https://grok.com' \ + -H 'pragma: no-cache' \ + -H 'priority: u=1, i' \ + -H 'referer: https://grok.com/imagine/post/6136f31a-4aaf-48e0-ab86-fe9871feca91' \ + -H 'sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' \ + -H 'sec-ch-ua-mobile: ?0' \ + -H 'sec-ch-ua-platform: "macOS"' \ + -H 'sec-fetch-dest: empty' \ + -H 'sec-fetch-mode: cors' \ + -H 'sec-fetch-site: same-origin' \ + -H 'sentry-trace: 277d6351c5063fd659b51b77b50bb453-928bfb8d1221fde1-0' \ + -H 'traceparent: 00-b0bd9846c2990e253ecca887fdfba8a2-c415282f4b74bf60-00' \ + -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \ + -H 'x-statsig-id: HjOCcSvnF01eIgszJ4J4qot//FLrCJb8Zz+f+AvBLs4ZNmt8fwsKc6PkyrjkEaImt9npwBpAUCrYQeeYTG/5jRWOKfLdHQ' \ + -H 'x-xai-request-id: d5ac2ce6-ed77-4a5d-8905-613a498035d3' \ + --data-raw '{"temporary":true,"modelName":"grok-3","message":"https://imagine-public.x.ai/imagine-public/images/6136f31a-4aaf-48e0-ab86-fe9871feca91.png --mode=extremely-spicy-or-crazy","toolOverrides":{"videoGen":true},"responseMetadata":{"modelConfigOverride":{"modelMap":{"videoGenModelConfig":{"parentPostId":"6136f31a-4aaf-48e0-ab86-fe9871feca91","aspectRatio":"2:3","videoLength":6}}}}}' + + +--data-raw + { + "temporary":true, + "modelName":"grok-3", + "message":"https://imagine-public.x.ai/imagine-public/images/6136f31a-4aaf-48e0-ab86-fe9871feca91.png --mode=extremely-spicy-or-crazy", + "toolOverrides": { + "videoGen":true + }, + "responseMetadata": { + "modelConfigOverride": { + "modelMap": { + "videoGenModelConfig": { + "parentPostId": "6136f31a-4aaf-48e0-ab86-fe9871feca91", + "aspectRatio": "2:3", + "videoLength":6 + } + } + } + } + } + diff --git a/crates/api_clients/grok_client/src/requests/video_chat/grok_video_gen_chat_conversation.rs b/crates/api_clients/grok_client/src/requests/video_chat/grok_video_gen_chat_conversation.rs index 0a6c4b35e6..10b7b50f47 100644 --- a/crates/api_clients/grok_client/src/requests/video_chat/grok_video_gen_chat_conversation.rs +++ b/crates/api_clients/grok_client/src/requests/video_chat/grok_video_gen_chat_conversation.rs @@ -6,6 +6,7 @@ use crate::datatypes::api::sentry_trace::SentryTrace; use crate::datatypes::api::svg_path_data::SvgPathData; use crate::datatypes::api::user_id::UserId; use crate::datatypes::api::verification_token::VerificationToken; +use crate::datatypes::api::video_generation_mode::VideoGenerationMode; use crate::datatypes::api::xsid_numbers::XsidNumbers; use crate::error::categorize_grok_http_error::categorize_grok_http_error; use crate::error::grok_client_error::GrokClientError; @@ -33,6 +34,7 @@ pub struct GrokVideoGenChatConversationBuilder<'a> { pub file_id: &'a FileId, pub media_type: VideoMediaPostType, pub prompt: Option<&'a str>, + pub mode: Option, pub aspect_ratio: Option, pub cookie: &'a str, @@ -232,12 +234,23 @@ impl <'a> GrokVideoGenChatConversationBuilder<'a> { VideoMediaPostType::Video => unimplemented!("implement for videos"), }; - let mut prompt = format!("{media_url} --mode=normal"); + let mode_string = self.mode + .unwrap_or(VideoGenerationMode::Normal) + .as_api_mode_arg(); + + let mut prompt = format!("{media_url} --mode={mode_string}"); if let Some(user_prompt) = self.prompt { - prompt = format!("{media_url} {user_prompt} --mode=custom"); + // If a text prompt is set, mode seems to typically be "custom". + let mode_string = self.mode + .unwrap_or(VideoGenerationMode::Custom) + .as_api_mode_arg(); + + prompt = format!("{media_url} {user_prompt} --mode={mode_string}"); } + info!("Grok video prompt: {prompt}"); + let aspect_ratio = match self.aspect_ratio { Some(AspectRatio::Square) => VideoGenAspectRatio::Square, Some(AspectRatio::WideThreeByTwo) => VideoGenAspectRatio::WideThreeByTwo, @@ -351,6 +364,7 @@ mod tests { file_id: &file_id, media_type: VideoMediaPostType::UserUploadedImage, prompt: maybe_prompt, + mode: Some(VideoGenerationMode::Custom), aspect_ratio: Some(AspectRatio::TallTwoByThree), cookie: cookies.as_str(), @@ -414,6 +428,7 @@ mod tests { file_id: &file_id, media_type: VideoMediaPostType::UserUploadedImage, prompt: maybe_prompt, + mode: None, aspect_ratio: Some(AspectRatio::Square), cookie: cookies.as_str(), diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/mod.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/mod.rs index 1374972ee0..8afb9e70fc 100644 --- a/crates/vendor/fal-rs/src/endpoints/fal_ai/mod.rs +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/mod.rs @@ -2408,3 +2408,4 @@ pub mod yue; pub mod zonos; pub mod qwen_image_edit; +pub mod nano_banana_pro; diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/mod.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/mod.rs new file mode 100644 index 0000000000..d3ef4e8e3c --- /dev/null +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/mod.rs @@ -0,0 +1,2 @@ +pub mod nano_banana_pro_text_to_image; +pub mod nano_banana_pro_image_edit; \ No newline at end of file diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_image_edit.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_image_edit.rs new file mode 100644 index 0000000000..a51ec7fa5f --- /dev/null +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_image_edit.rs @@ -0,0 +1,69 @@ +use crate::prelude::{Deserialize, FalRequest, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct NanoBananaProImageEditInput { + pub prompt: String, + + pub image_urls: Vec, + + /// Eg. "16:9", "1:1", and a ton of other options. So many options. + pub aspect_ratio: String, + + /// Eg. "1K", "2K", "4K" + pub resolution: String, + + /// 1 - 4 + #[serde(skip_serializing_if = "Option::is_none")] + pub num_images: Option, + + /// "jpeg", "png", "webp" + #[serde(skip_serializing_if = "Option::is_none")] + pub output_format: Option, +} + +/* +{ + "prompt": "make a photo of the man driving the car down the california coastline", + "num_images": 3, + "aspect_ratio": "16:9", + "output_format": "png", + "image_urls": [ + "https://storage.googleapis.com/falserverless/example_inputs/nano-banana-edit-input.png", + "https://storage.googleapis.com/falserverless/example_inputs/nano-banana-edit-input-2.png" + ], + "resolution": "1K" +} + */ + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct File { + /// The URL where the file can be downloaded from. + pub url: String, + + // /// The mime type of the file. + // /// "image/png" + // #[serde(skip_serializing_if = "Option::is_none")] + // pub content_type: Option, + // /// File data + // #[serde(skip_serializing_if = "Option::is_none")] + // pub file_data: Option, + // /// The name of the file. It will be auto-generated if not provided. + // /// "z9RV14K95DvU.png" + // #[serde(skip_serializing_if = "Option::is_none")] + // pub file_name: Option, + // /// The size of the file in bytes. + // /// 4404019 + // #[serde(skip_serializing_if = "Option::is_none")] + // pub file_size: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Gemini25FlashImageEditOutput { + pub images: Vec, +} + +pub fn gemini_25_flash_image_edit( + params: Gemini25FlashImageEditInput, +) -> FalRequest { + FalRequest::new("fal-ai/gemini-25-flash-image/edit", params) +} diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_text_to_image.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_text_to_image.rs new file mode 100644 index 0000000000..0a07eaa3ee --- /dev/null +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/nano_banana_pro/nano_banana_pro_text_to_image.rs @@ -0,0 +1,53 @@ +use crate::prelude::{Deserialize, FalRequest, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct Gemini25FlashImageEditInput { + pub prompt: String, + + pub image_urls: Vec, + + /// 1 - 4 + #[serde(skip_serializing_if = "Option::is_none")] + pub num_images: Option, + + /// "jpeg" or "png" + #[serde(skip_serializing_if = "Option::is_none")] + pub output_format: Option, + + // /// Resolution, eg. "720p" + // #[serde(skip_serializing_if = "Option::is_none")] + // pub resolution: Option, +} + +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct File { + /// The URL where the file can be downloaded from. + pub url: String, + + // /// The mime type of the file. + // /// "image/png" + // #[serde(skip_serializing_if = "Option::is_none")] + // pub content_type: Option, + // /// File data + // #[serde(skip_serializing_if = "Option::is_none")] + // pub file_data: Option, + // /// The name of the file. It will be auto-generated if not provided. + // /// "z9RV14K95DvU.png" + // #[serde(skip_serializing_if = "Option::is_none")] + // pub file_name: Option, + // /// The size of the file in bytes. + // /// 4404019 + // #[serde(skip_serializing_if = "Option::is_none")] + // pub file_size: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Gemini25FlashImageEditOutput { + pub images: Vec, +} + +pub fn gemini_25_flash_image_edit( + params: Gemini25FlashImageEditInput, +) -> FalRequest { + FalRequest::new("fal-ai/gemini-25-flash-image/edit", params) +}