From 3f85ef088455b2f4377c22cd284e7ca971314533 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 2 Jun 2025 02:23:13 -0400 Subject: [PATCH] enqueuing to webhook works --- Cargo.lock | 7 +++ .../fal_client/src/error/fal_error_plus.rs | 9 +++ .../fal_client/src/requests/mod.rs | 3 +- ...cking_remove_background_rembg_from_file.rs | 1 + .../remove_background_rembg/rembg_request.rs | 32 ++++++++-- .../src/requests/webhook/image/mod.rs | 1 + .../image/remove_background_rembg_webhook.rs | 62 +++++++++++++++++++ .../fal_client/src/requests/webhook/mod.rs | 1 + crates/vendor/fal-rs/Cargo.toml | 1 + crates/vendor/fal-rs/src/lib.rs | 1 + crates/vendor/fal-rs/src/request.rs | 31 +++++++++- crates/vendor/fal-rs/src/webhook.rs | 9 +++ 12 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 crates/lib/api_clients/fal_client/src/requests/webhook/image/mod.rs create mode 100644 crates/lib/api_clients/fal_client/src/requests/webhook/image/remove_background_rembg_webhook.rs create mode 100644 crates/lib/api_clients/fal_client/src/requests/webhook/mod.rs create mode 100644 crates/vendor/fal-rs/src/webhook.rs diff --git a/Cargo.lock b/Cargo.lock index 98efc3e66c..a4530b5302 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3567,6 +3567,7 @@ dependencies = [ "fal-derive", "futures 0.3.31", "image", + "percent-encoding-rfc3986", "reqwest 0.12.15", "serde", "serde_json", @@ -7243,6 +7244,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "percent-encoding-rfc3986" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3637c05577168127568a64e9dc5a6887da720efef07b3d9472d45f63ab191166" + [[package]] name = "phf" version = "0.8.0" diff --git a/crates/lib/api_clients/fal_client/src/error/fal_error_plus.rs b/crates/lib/api_clients/fal_client/src/error/fal_error_plus.rs index 40afb5aa27..fbe333f2ea 100644 --- a/crates/lib/api_clients/fal_client/src/error/fal_error_plus.rs +++ b/crates/lib/api_clients/fal_client/src/error/fal_error_plus.rs @@ -15,6 +15,8 @@ pub enum FalErrorPlus { UrlParseError(url::ParseError), /// An endpoint we don't support yet. UnhandledEndpoint(String), + /// Error from the `reqwest` crate. + ReqwestError(reqwest::Error), } impl Display for FalErrorPlus { @@ -25,6 +27,7 @@ impl Display for FalErrorPlus { Self::AnyhowError(err) => write!(f, "FalErrorPlus::AnyhowError: {:?}", err), Self::UrlParseError(err) => write!(f, "FalErrorPlus::UrlParseError: {:?}", err), Self::UnhandledEndpoint(endpoint) => write!(f, "FalErrorPlus::UnhandledEndpoint: {:?}", endpoint), + Self::ReqwestError(err) => write!(f, "FalErrorPlus::ReqwestError: {:?}", err), } } } @@ -48,3 +51,9 @@ impl From for FalErrorPlus { FalErrorPlus::UrlParseError(err) } } + +impl From for FalErrorPlus { + fn from(err: reqwest::Error) -> Self { + FalErrorPlus::ReqwestError(err) + } +} diff --git a/crates/lib/api_clients/fal_client/src/requests/mod.rs b/crates/lib/api_clients/fal_client/src/requests/mod.rs index 8f2e48eefd..fd1324209e 100644 --- a/crates/lib/api_clients/fal_client/src/requests/mod.rs +++ b/crates/lib/api_clients/fal_client/src/requests/mod.rs @@ -1,4 +1,5 @@ pub mod enqueue_hunyuan2_image_to_3d; pub mod image_gen; pub mod remove_background_rembg; -pub mod video_gen; \ No newline at end of file +pub mod video_gen; +pub mod webhook; \ No newline at end of file diff --git a/crates/lib/api_clients/fal_client/src/requests/remove_background_rembg/async_blocking_remove_background_rembg_from_file.rs b/crates/lib/api_clients/fal_client/src/requests/remove_background_rembg/async_blocking_remove_background_rembg_from_file.rs index 7d9a6edcd1..d227de27a6 100644 --- a/crates/lib/api_clients/fal_client/src/requests/remove_background_rembg/async_blocking_remove_background_rembg_from_file.rs +++ b/crates/lib/api_clients/fal_client/src/requests/remove_background_rembg/async_blocking_remove_background_rembg_from_file.rs @@ -4,6 +4,7 @@ use crate::error::fal_error_plus::FalErrorPlus; use fal::endpoints::fal_ai::imageutils::rembg::{rembg, RemoveBackgroundInput}; use fal::prelude::Status; use fal_client::file_to_base64_url::file_to_base64_url; +use futures::StreamExt; use std::path::Path; use url::Url; diff --git a/crates/lib/api_clients/fal_client/src/requests/remove_background_rembg/rembg_request.rs b/crates/lib/api_clients/fal_client/src/requests/remove_background_rembg/rembg_request.rs index 8be2fc2015..d82f828881 100644 --- a/crates/lib/api_clients/fal_client/src/requests/remove_background_rembg/rembg_request.rs +++ b/crates/lib/api_clients/fal_client/src/requests/remove_background_rembg/rembg_request.rs @@ -2,8 +2,9 @@ use crate::creds::fal_api_key::FalApiKey; use crate::error::classify_fal_error::classify_fal_error; use crate::error::fal_error_plus::FalErrorPlus; use fal::endpoints::fal_ai::imageutils::rembg::{rembg, RemoveBackgroundInput}; +use fal::webhook::WebhookResponse; -pub (super) async fn rembg_request(image_url: String, api_key: &FalApiKey) -> Result<(), FalErrorPlus> { +pub async fn rembg_request(image_url: String, api_key: &FalApiKey) -> Result { let request = RemoveBackgroundInput { image_url, @@ -12,9 +13,8 @@ pub (super) async fn rembg_request(image_url: String, api_key: &FalApiKey) -> Re }; let result = rembg(request) - .with_client() .with_api_key(&api_key.0) - .queue() + .queue_webhook("https://api.storyteller.ai/webhook") .await; let result = match result { @@ -22,7 +22,29 @@ pub (super) async fn rembg_request(image_url: String, api_key: &FalApiKey) -> Re Err(err) => return Err(classify_fal_error(err)), }; - println!("{:?}", result.payload); + println!("{:?}", result); - Ok(()) + Ok(result) +} + +#[cfg(test)] +mod tests { + use crate::creds::fal_api_key::FalApiKey; + use crate::requests::remove_background_rembg::rembg_request::rembg_request; + use std::fs::read_to_string; + + #[tokio::test] + #[ignore] // NB: Manually test, don't run in CI! + async fn test_rembg_request() -> anyhow::Result<()> { + // XXX: Don't commit secrets! + let api_key = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; + let api_key = FalApiKey::from_str(&api_key); + + let response = rembg_request("test_data/juno.jpg".into(), &api_key).await?; + + + assert_eq!(1, 2); + + Ok(()) + } } diff --git a/crates/lib/api_clients/fal_client/src/requests/webhook/image/mod.rs b/crates/lib/api_clients/fal_client/src/requests/webhook/image/mod.rs new file mode 100644 index 0000000000..ea3c2d1bee --- /dev/null +++ b/crates/lib/api_clients/fal_client/src/requests/webhook/image/mod.rs @@ -0,0 +1 @@ +pub mod remove_background_rembg_webhook; \ No newline at end of file diff --git a/crates/lib/api_clients/fal_client/src/requests/webhook/image/remove_background_rembg_webhook.rs b/crates/lib/api_clients/fal_client/src/requests/webhook/image/remove_background_rembg_webhook.rs new file mode 100644 index 0000000000..de0a026812 --- /dev/null +++ b/crates/lib/api_clients/fal_client/src/requests/webhook/image/remove_background_rembg_webhook.rs @@ -0,0 +1,62 @@ +use crate::creds::fal_api_key::FalApiKey; +use crate::error::classify_fal_error::classify_fal_error; +use crate::error::fal_error_plus::FalErrorPlus; +use fal::endpoints::fal_ai::imageutils::rembg::{rembg, RemoveBackgroundInput}; +use fal::webhook::WebhookResponse; +use reqwest::IntoUrl; + +pub struct RemoveBackgroundRembgWebhookArgs<'a, U: IntoUrl, V: IntoUrl> { + pub image_url: U, + pub webhook_url: V, + pub api_key: &'a FalApiKey +} + +pub async fn remove_background_rembg_webhook( + args: RemoveBackgroundRembgWebhookArgs<'_, U, V> +) -> Result { + + let image_url = args.image_url.into_url()?; + + let request = RemoveBackgroundInput { + image_url: image_url.to_string(), + crop_to_bbox: None, + sync_mode: None + }; + + let result = rembg(request) + .with_api_key(&args.api_key.0) + .queue_webhook(args.webhook_url) + .await; + + result.map_err(|err| classify_fal_error(err)) +} + +#[cfg(test)] +mod tests { + use crate::creds::fal_api_key::FalApiKey; + use crate::requests::webhook::image::remove_background_rembg_webhook::remove_background_rembg_webhook; + use crate::requests::webhook::image::remove_background_rembg_webhook::RemoveBackgroundRembgWebhookArgs; + use std::fs::read_to_string; + + #[tokio::test] + #[ignore] // NB: Manually test, don't run in CI! + async fn test_rembg_request() -> anyhow::Result<()> { + // XXX: Don't commit secrets! + let api_key = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; + let api_key = FalApiKey::from_str(&api_key); + + let args = RemoveBackgroundRembgWebhookArgs { + image_url: "https://cdn-2.fakeyou.com/media/b/2/p/b/0/b2pb09bcbxdqjzgbnsapvfs1t897wdnx/image_b2pb09bcbxdqjzgbnsapvfs1t897wdnx.png", + webhook_url: "https://api.storyteller.ai/webhook", + api_key: &api_key, + }; + + let response = remove_background_rembg_webhook(args).await?; + + println!("{:?}", response); + + assert_eq!(1, 2); + + Ok(()) + } +} diff --git a/crates/lib/api_clients/fal_client/src/requests/webhook/mod.rs b/crates/lib/api_clients/fal_client/src/requests/webhook/mod.rs new file mode 100644 index 0000000000..b1b6b4f003 --- /dev/null +++ b/crates/lib/api_clients/fal_client/src/requests/webhook/mod.rs @@ -0,0 +1 @@ +pub mod image; \ No newline at end of file diff --git a/crates/vendor/fal-rs/Cargo.toml b/crates/vendor/fal-rs/Cargo.toml index 6661a145b5..4ce5f77a5e 100644 --- a/crates/vendor/fal-rs/Cargo.toml +++ b/crates/vendor/fal-rs/Cargo.toml @@ -33,6 +33,7 @@ all-features = true eventsource-stream = "0.2" smart-default = "0.7" thiserror = "1" +percent-encoding-rfc3986 = "0.1.3" [dependencies.base64] version = "0.22" diff --git a/crates/vendor/fal-rs/src/lib.rs b/crates/vendor/fal-rs/src/lib.rs index 2146273f0d..0da74c871f 100644 --- a/crates/vendor/fal-rs/src/lib.rs +++ b/crates/vendor/fal-rs/src/lib.rs @@ -7,6 +7,7 @@ pub mod endpoints; pub mod image; pub mod queue; pub mod request; +pub mod webhook; use eventsource_stream::EventStreamError; pub use fal_derive::endpoint; diff --git a/crates/vendor/fal-rs/src/request.rs b/crates/vendor/fal-rs/src/request.rs index 771c191132..a184796816 100644 --- a/crates/vendor/fal-rs/src/request.rs +++ b/crates/vendor/fal-rs/src/request.rs @@ -1,11 +1,13 @@ use std::marker::PhantomData; - +use percent_encoding_rfc3986::{utf8_percent_encode, NON_ALPHANUMERIC}; +use reqwest::IntoUrl; use serde::{de::DeserializeOwned, Serialize}; use crate::{ queue::{Queue, QueueResponse}, FalError, }; +use crate::webhook::WebhookResponse; /// A request to the FAL API /// @@ -129,4 +131,31 @@ impl FalRequest Ok(Queue::new(self.client, self.endpoint, key, payload)) } + + pub async fn queue_webhook(self, url: U) -> Result { + let key = self + .api_key + .expect("No fal API key provided, and FAL_API_KEY environment variable is not set"); + + let url_encoded = url.into_url()?; + let url_encoded = utf8_percent_encode(url_encoded.as_str(), NON_ALPHANUMERIC).to_string(); + + let response = self + .client + .post(format!("https://queue.fal.run/{}?fal_webhook={}", self.endpoint, url_encoded)) + .json(&self.params) + .header("Authorization", format!("Key {}", &key)) + .header("Content-Type", "application/json") + .send() + .await?; + + if response.status() != 200 { + let error = response.text().await?; + return Err(error.into()); + } + + let payload: WebhookResponse = response.error_for_status()?.json().await?; + + Ok(payload) + } } diff --git a/crates/vendor/fal-rs/src/webhook.rs b/crates/vendor/fal-rs/src/webhook.rs new file mode 100644 index 0000000000..d9d3b84ec0 --- /dev/null +++ b/crates/vendor/fal-rs/src/webhook.rs @@ -0,0 +1,9 @@ + +use crate::prelude::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct WebhookResponse { + pub status: Option, + pub request_id: Option, + pub gateway_request_id: Option, +}