don't need the queue pieces for now

This commit is contained in:
Brandon Thomas committed 2026-03-19 18:53:27 -04:00
1 parent 29294365ce
commit facf8a3df2
13 files changed
+4 -580

No files matched your search

@@ -1,4 +1,5 @@
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error::FalError;
use std::error::Error;
use std::fmt::{Display, Formatter};
@@ -37,8 +38,8 @@ impl Display for FalErrorPlus {
impl Error for FalErrorPlus {}
impl From<crate::error::fal_error::FalError> for FalErrorPlus {
fn from(err: crate::error::fal_error::FalError) -> Self {
impl From<FalError> for FalErrorPlus {
fn from(err: FalError) -> Self {
classify_fal_error(err)
}
}
@@ -60,19 +61,3 @@ impl From<reqwest::Error> for FalErrorPlus {
FalErrorPlus::ReqwestError(err)
}
}
// Temporary: support conversion from the vendored fal crate's FalError.
// This can be removed once all `requests/queue/*` and `utils/*` files are migrated off fal::.
impl From<fal::FalError> for FalErrorPlus {
fn from(err: fal::FalError) -> Self {
// Convert the vendored FalError into our local FalError, then classify it.
let local_err = match err {
fal::FalError::RequestError(e) => crate::error::fal_error::FalError::RequestError(e),
fal::FalError::SerializeError(e) => crate::error::fal_error::FalError::SerializeError(e),
fal::FalError::Other(s) => crate::error::fal_error::FalError::Other(s),
// ImageError and StreamError don't exist in our local FalError; stringify them.
other => crate::error::fal_error::FalError::Other(format!("{}", other)),
};
classify_fal_error(local_err)
}
}
@@ -1,5 +1,4 @@
pub mod api;
pub mod http;
pub mod queue;
pub mod traits;
pub mod webhook;
@@ -1,68 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::hunyuan3d::v2::v2;
use fal::endpoints::fal_ai::hunyuan3d::v2::Hunyuan3DInput;
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::io::Write;
use std::path::Path;
pub struct Hunyuan2Args<'a, P: AsRef<Path>> {
pub image_path: P,
pub api_key: &'a FalApiKey,
}
pub async fn enqueue_hunyuan2_image_to_3d<P: AsRef<Path>>(args: Hunyuan2Args<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
let image_url = file_to_base64_url(args.image_path)?;
let request = Hunyuan3DInput {
input_image_url: image_url,
textured_mesh: Some(true),
guidance_scale: None,
num_inference_steps: None,
octree_resolution: None,
seed: None,
};
let result = v2(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(FalErrorPlus::from(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_utils::test_file_path::test_file_path;
#[tokio::test]
#[ignore]
async fn test() -> AnyhowResult<()> {
let image = test_file_path("test_data/image/juno.jpg")?;
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Hunyuan2Args {
image_path: image,
api_key: &api_key,
};
let result = enqueue_hunyuan2_image_to_3d(args).await?;
Ok(())
}
}
@@ -1,68 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::v1_1_ultra;
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::FluxProUltraTextToImageInput;
use futures::StreamExt;
use std::io::Write;
pub struct FluxPro11UltraTextToImageArgs<'a> {
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
// TODO: Size parameter
}
pub async fn enqueue_flux_pro_11_ultra_text_to_image(args: FluxPro11UltraTextToImageArgs<'_>) -> Result<EnqueuedRequest, FalErrorPlus> {
let request = FluxProUltraTextToImageInput {
prompt: args.prompt.to_string(),
// Maybe expose
aspect_ratio: None, // Default is "16:9"
num_images: Some(1), // Default is 1
seed: None,
raw: Some(true), // Generate less processed, more natural-looking images. Default is false.
// Maybe abstract
enable_safety_checker: Some(false),
safety_tolerance: Some("5".to_string()), // 1 is most strict, 5 is most permissive
// Constants
output_format: Some("png".to_string()),
sync_mode: None, // Synchronous / slow
};
let result = v1_1_ultra(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(FalErrorPlus::from(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_flux_pro() -> AnyhowResult<()> {
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = FluxPro11UltraTextToImageArgs {
prompt: "a corgi smiles at the camera, inside retro video game store, lots of sci-fi props from Cowboy Bebop, photorealistic",
api_key: &api_key,
};
let result = enqueue_flux_pro_11_ultra_text_to_image(args).await?;
Ok(())
}
}
@@ -1,62 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::recraft_v3::recraft_v3;
use fal::endpoints::fal_ai::recraft_v3::ImageSizeProperty;
use fal::endpoints::fal_ai::recraft_v3::TextToImageInput;
use futures::StreamExt;
use std::io::Write;
pub struct Recraft3TextToImageArgs<'a> {
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
// TODO: Size parameter
}
pub async fn enqueue_recraft3_text_to_image(args: Recraft3TextToImageArgs<'_>) -> Result<EnqueuedRequest, FalErrorPlus> {
let request = TextToImageInput {
image_size: Some(ImageSizeProperty::SquareHd),
prompt: args.prompt.to_string(),
style: None, // eg. "realistic_image"
style_id: None, // LoRA.
colors: None, // Preferable colors. We can offer this later.
};
let result = recraft_v3(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(FalErrorPlus::from(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
use errors::AnyhowResult;
use std::fs::read_to_string;
#[tokio::test]
#[ignore]
async fn test_recraft3() -> AnyhowResult<()> {
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Recraft3TextToImageArgs {
prompt: "a corgi looks out over the water",
api_key: &api_key,
};
let result = enqueue_recraft3_text_to_image(args).await?;
Ok(())
}
}
@@ -1,2 +0,0 @@
pub mod enqueue_flux_pro_11_ultra_text_to_image;
pub mod enqueue_recraft3_text_to_image;
@@ -1,4 +0,0 @@
pub mod enqueue_hunyuan2_image_to_3d;
pub mod image_gen;
pub mod remove_background_rembg;
pub mod video_gen;
@@ -1,74 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::imageutils::rembg::RemoveBackgroundInput;
use fal::prelude::fal_ai::imageutils::rembg::rembg;
use fal::prelude::*;
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::path::Path;
use url::Url;
pub struct RemBgResponse {
pub image_url: Url,
}
/// remove background using `fal_ai/imageutils/rembg`
pub async fn remove_background_rembg<P: AsRef<Path>>(image_path: P, api_key: &FalApiKey) -> Result<RemBgResponse, FalErrorPlus> {
let image_url = file_to_base64_url(image_path)?;
let request = RemoveBackgroundInput {
image_url,
crop_to_bbox: None,
sync_mode: None
};
let result = rembg(request)
.with_api_key(&api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(FalErrorPlus::from(err)),
};
let mut stream = result.stream(true).await?;
while let Some(status) = stream.next().await {
let status = status?;
if status.status == Status::Completed {
break;
}
}
let output = result.response().await?;
let url = Url::parse(&output.image.url)?;
Ok(RemBgResponse {
image_url: url,
})
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::remove_background_rembg::remove_background_rembg;
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_utils::test_file_path::test_file_path;
#[tokio::test]
#[ignore]
async fn test_remove_background_rembg() -> AnyhowResult<()> {
let image = test_file_path("test_data/image/juno.jpg")?;
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let result = remove_background_rembg(image, &api_key).await?;
Ok(())
}
}
@@ -1,101 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::kling_video::v1_6::pro::image_to_video::{image_to_video, ProImageToVideoRequest};
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::io::Write;
use std::path::Path;
pub struct Kling16ProArgs<'a, P: AsRef<Path>> {
pub image_path: P,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub duration: Kling16ProDuration,
pub aspect_ratio: Kling16ProAspectRatio,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling16ProDuration {
Default,
FiveSeconds,
TenSeconds,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling16ProAspectRatio {
Square, // 1:1
WideSixteenNine, // 16:9
TallNineSixteen, // 9:16
}
pub async fn enqueue_kling_16_pro_image_to_video<P: AsRef<Path>>(args: Kling16ProArgs<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
let image_url = file_to_base64_url(args.image_path)?;
let duration = match args.duration{
Kling16ProDuration::Default => None,
Kling16ProDuration::FiveSeconds => Some("5".to_string()), // Gross...
Kling16ProDuration::TenSeconds => Some("10".to_string()),
};
let aspect_ratio = match args.aspect_ratio {
Kling16ProAspectRatio::Square => Some("1:1".to_string()),
Kling16ProAspectRatio::WideSixteenNine => Some("16:9".to_string()),
Kling16ProAspectRatio::TallNineSixteen => Some("9:16".to_string()),
};
let request = ProImageToVideoRequest {
image_url,
prompt: args.prompt.to_string(),
aspect_ratio,
duration,
// Maybe expose these later
cfg_scale: None,
negative_prompt: None,
tail_image_url: None,
};
let result = image_to_video(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(FalErrorPlus::from(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::{enqueue_kling_16_pro_image_to_video, Kling16ProArgs, Kling16ProAspectRatio, Kling16ProDuration};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_utils::test_file_path::test_file_path;
#[tokio::test]
#[ignore]
async fn test_kling16_video() -> AnyhowResult<()> {
let image = test_file_path("test_data/image/juno.jpg")?;
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Kling16ProArgs {
image_path: image,
prompt: "a corgi looks out over the water",
api_key: &api_key,
duration: Kling16ProDuration::Default,
aspect_ratio: Kling16ProAspectRatio::Square,
};
let result = enqueue_kling_16_pro_image_to_video(args).await?;
Ok(())
}
}
@@ -1,69 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use fal::endpoints::fal_ai::minimax::image_to_video::image_to_video;
use fal::endpoints::fal_ai::minimax::image_to_video::ImageToVideoRequest;
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::io::Write;
use std::path::Path;
pub struct Minimax01Args<'a, P: AsRef<Path>> {
pub image_path: P,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
}
pub async fn enqueue_minimax_01_image_to_video<P: AsRef<Path>>(args: Minimax01Args<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
let image_url = file_to_base64_url(args.image_path)?;
let request = ImageToVideoRequest {
image_url,
prompt: args.prompt.to_string(),
prompt_optimizer: None,
};
let result = image_to_video(request)
.with_api_key(&args.api_key.0)
.queue()
.await;
let result = match result {
Ok(result) => result,
Err(err) => return Err(FalErrorPlus::from(err)),
};
Ok(EnqueuedRequest::from_queue_response(&result)?)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::queue::video_gen::enqueue_minimax_01_image_to_video::{enqueue_minimax_01_image_to_video, Minimax01Args};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_utils::test_file_path::test_file_path;
#[tokio::test]
#[ignore]
async fn test_minimax_video() -> AnyhowResult<()> {
let image = test_file_path("test_data/image/juno.jpg")?;
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&secret);
let args = Minimax01Args {
image_path: image,
prompt: "a dog jumps into the water",
api_key: &api_key,
};
let result = enqueue_minimax_01_image_to_video(args).await?;
println!("Request ID: {:?}", result.request_id);
Ok(())
}
}
@@ -1,2 +0,0 @@
pub mod enqueue_kling_16_pro_image_to_video;
pub mod enqueue_minimax_01_image_to_video;
@@ -1,3 +1,2 @@
pub mod clone_queue_payload;
pub mod clone_queue_response;
pub mod queue_status_checker;
pub mod clone_queue_response;
@@ -1,109 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::model::enqueued_request::EnqueuedRequest;
use crate::model::fal_endpoint::FalEndpoint;
use anyhow::anyhow;
use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::Output;
use fal::endpoints::fal_ai::hunyuan3d::v2::ObjectOutput;
use fal::endpoints::fal_ai::kling_video::v1_6::pro::effects::I2VOutput;
use fal::endpoints::fal_ai::minimax::image_to_video::VideoOutput;
use fal::endpoints::fal_ai::recraft_v3::TextToImageOutput;
use fal::prelude::QueueResponse;
use fal::queue::{Queue, QueueStatus};
use reqwest::Client;
use serde::de::DeserializeOwned;
pub struct QueueStatusChecker {
api_key: FalApiKey,
}
impl QueueStatusChecker {
pub fn new(api_key: FalApiKey) -> Self {
Self { api_key }
}
pub async fn check_status(&self, request: &EnqueuedRequest) -> Result<QueueStatus, FalErrorPlus> {
match &request.fal_endpoint {
FalEndpoint::FluxProUltraTextToImage => {
let queue = self.make_queue::<Output>(&request);
let status = queue.status(false).await?;
Ok(status)
}
FalEndpoint::Hunyuan3d2Base => {
let queue = self.make_queue::<ObjectOutput>(&request);
let status = queue.status(false).await?;
Ok(status)
}
FalEndpoint::Kling16ImageToVideo => {
let queue = self.make_queue::<I2VOutput>(&request);
let status = queue.status(false).await?;
Ok(status)
}
FalEndpoint::Minimax01ImageToVideo => {
let queue = self.make_queue::<VideoOutput>(&request); // Assuming Minimax uses the same output type
let status = queue.status(false).await?;
Ok(status)
}
FalEndpoint::RecraftV3TextToImage => {
let queue = self.make_queue::<TextToImageOutput>(&request);
let status = queue.status(false).await?;
Ok(status)
}
FalEndpoint::Other(endpoint) => {
Err(FalErrorPlus::UnhandledEndpoint(format!("Unsupported endpoint: {}", endpoint)))
}
}
}
pub async fn get_download_url(&self, request: &EnqueuedRequest) -> Result<String, FalErrorPlus> {
match &request.fal_endpoint {
FalEndpoint::FluxProUltraTextToImage => {
let queue = self.make_queue::<Output>(&request);
let response = queue.response().await?;
// TODO(bt,2025-05-26): Handle multiple images
Ok(response.images.first()
.ok_or_else(|| FalErrorPlus::AnyhowError(anyhow!("No images returned")))?
.url.clone())
}
FalEndpoint::Hunyuan3d2Base => {
let queue = self.make_queue::<ObjectOutput>(&request);
let response = queue.response().await?;
Ok(response.model_mesh.url)
}
FalEndpoint::Kling16ImageToVideo => {
let queue = self.make_queue::<I2VOutput>(&request);
let response = queue.response().await?;
Ok(response.video.url)
}
FalEndpoint::Minimax01ImageToVideo => {
let queue = self.make_queue::<VideoOutput>(&request); // Assuming Minimax uses the same output type
let response = queue.response().await?;
Ok(response.video.url)
}
FalEndpoint::RecraftV3TextToImage => {
let queue = self.make_queue::<TextToImageOutput>(&request);
let response = queue.response().await?;
Ok(response.images.first()
.ok_or_else(|| FalErrorPlus::AnyhowError(anyhow!("No images returned")))?
.url.clone())
}
FalEndpoint::Other(endpoint) => {
Err(FalErrorPlus::UnhandledEndpoint(format!("Unsupported endpoint: {}", endpoint)))
}
}
}
pub fn make_queue<T: DeserializeOwned>(&self, request: &EnqueuedRequest) -> Queue<T> {
let client = Client::new();
let endpoint = request.fal_endpoint.url();
let payload = QueueResponse {
request_id: request.request_id.0.clone(),
response_url: request.response_url.clone(),
status_url: request.status_url.clone(),
cancel_url: request.cancel_url.clone(),
};
Queue::new(client, endpoint, self.api_key.0.clone(), payload)
}
}