mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-31 07:45:55 -04:00
Compare commits
51
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69ffe4a045 | ||
|
|
dda397ff6b | ||
|
|
d8b8b4a037 | ||
|
|
a842df3774 | ||
|
|
4b3df535b5 | ||
|
|
3d06f89477 | ||
|
|
d007cb81d5 | ||
|
|
221d8bb95a | ||
|
|
760639d071 | ||
|
|
4e68169faf | ||
|
|
8090cdb526 | ||
|
|
ada4c77093 | ||
|
|
7bb14e1b17 | ||
|
|
f9c8343165 | ||
|
|
53eb46d2b6 | ||
|
|
f5a3ff67b3 | ||
|
|
6b262d9fcb | ||
|
|
744df6bcb8 | ||
|
|
9a5ab77d3c | ||
|
|
3e42ccc5e5 | ||
|
|
81bf7d712c | ||
|
|
e9fe8bdf0d | ||
|
|
627fd6beeb | ||
|
|
77788a74e9 | ||
|
|
691f27eb23 | ||
|
|
9008e1f4f4 | ||
|
|
c11894692c | ||
|
|
e7d8613d09 | ||
|
|
55832bdfc2 | ||
|
|
49f18d7616 | ||
|
|
10603f7c86 | ||
|
|
5e8c524f84 | ||
|
|
05f0fe1216 | ||
|
|
1f44f7e929 | ||
|
|
c56cc8700c | ||
|
|
caaff569a6 | ||
|
|
3b0ed7a8ba | ||
|
|
ea1905cfcf | ||
|
|
d1f28877ca | ||
|
|
1b8d8cbd97 | ||
|
|
8be5013260 | ||
|
|
3c9b81c3ea | ||
|
|
9d1b7d22ee | ||
|
|
b046f6ff2a | ||
|
|
df0b98ec63 | ||
|
|
f214577af1 | ||
|
|
3efe9db4ca | ||
|
|
8208206e5a | ||
|
|
4f33e74e3a | ||
|
|
f75b210dcd | ||
|
|
7a07f196d1 |
@@ -0,0 +1,107 @@
|
||||
name: "Artcraft Publish"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
- artcraft-release
|
||||
|
||||
# This workflow creates Universal macOS builds with signing certificate support.
|
||||
# On each push to the release branches it will create or update a GitHub release, build your app, and upload the artifacts to the release.
|
||||
|
||||
jobs:
|
||||
publish-artcraft:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Universal macOS builds (arm64 and x86_64)
|
||||
- platform: 'macos-latest'
|
||||
args: '--target universal-apple-darwin'
|
||||
# Uncomment below for additional platforms
|
||||
# - platform: 'ubuntu-22.04'
|
||||
# args: ''
|
||||
# - platform: 'windows-latest'
|
||||
# args: ''
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: |
|
||||
cd ./crates/desktop/artcraft
|
||||
yarn install
|
||||
|
||||
- name: import Apple Developer Certificate
|
||||
if: matrix.platform == 'macos-latest'
|
||||
# Prevents keychain from locking automatically for 3600 seconds.
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security set-keychain-settings -t 3600 -u build.keychain
|
||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security find-identity -v -p codesigning build.keychain
|
||||
|
||||
- name: verify certificate
|
||||
if: matrix.platform == 'macos-latest'
|
||||
run: |
|
||||
echo "Available certificates:"
|
||||
security find-identity -v -p codesigning build.keychain
|
||||
|
||||
# Try multiple certificate patterns
|
||||
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep -E "(Developer ID Application|Apple Development|Storyteller Developer)" | head -1)
|
||||
|
||||
if [ -z "$CERT_INFO" ]; then
|
||||
echo "No matching certificate found. Using first available certificate."
|
||||
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep -v "Policy" | head -1)
|
||||
fi
|
||||
|
||||
CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
|
||||
echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
|
||||
echo "Using certificate: $CERT_ID"
|
||||
|
||||
- name: build and publish
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
|
||||
with:
|
||||
projectPath: ./crates/desktop/artcraft
|
||||
tagName: artcraft-v__VERSION__ # the action automatically replaces __VERSION__ with the app version.
|
||||
releaseName: "Artcraft v__VERSION__"
|
||||
releaseBody: "See the assets to download this version and install."
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n is_batch_generated = ?,\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_mod_user_token = ?, \n is_generated_on_prem = ?, \n generated_by_worker = ?, \n generated_by_cluster = ?\n ",
|
||||
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n is_batch_generated = ?,\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n \n maybe_engine_category = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_mod_user_token = ?, \n is_generated_on_prem = ?, \n generated_by_worker = ?, \n generated_by_cluster = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 38
|
||||
"Right": 39
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c58f414b5e4d0f0420d1894e2f40311e5613919f49f3ac91f81f5fc2bef1bea8"
|
||||
"hash": "b4470e607c005421dd8504b9f1d695080a581936ac6ce00b6f437b2764e741be"
|
||||
}
|
||||
Generated
+13
@@ -648,6 +648,7 @@ name = "artcraft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"artcraft_api_defs",
|
||||
"base64 0.22.1",
|
||||
"build_metadata",
|
||||
"bytes 1.10.1",
|
||||
@@ -659,6 +660,7 @@ dependencies = [
|
||||
"fal_client",
|
||||
"filesys",
|
||||
"futures 0.3.31",
|
||||
"idempotency",
|
||||
"image",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
@@ -686,6 +688,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"url",
|
||||
"web_base64",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
@@ -3595,6 +3598,7 @@ dependencies = [
|
||||
"fal",
|
||||
"file_to_base64_url",
|
||||
"futures 0.3.31",
|
||||
"log",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"testing",
|
||||
@@ -9994,6 +9998,7 @@ name = "storyteller_client"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"artcraft_api_defs",
|
||||
"backoff",
|
||||
"chrono",
|
||||
"cookie 0.18.1",
|
||||
@@ -12207,6 +12212,14 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web_base64"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web_scrapers"
|
||||
version = "0.0.1"
|
||||
|
||||
@@ -43,6 +43,7 @@ members = [
|
||||
"crates/lib/data/crockford",
|
||||
"crates/lib/data/datetimes",
|
||||
"crates/lib/data/primitives",
|
||||
"crates/lib/data/web_base64",
|
||||
"crates/lib/deprecated/config", # TODO(bt, 2023-02-13): Deprecate or repurpose
|
||||
"crates/lib/deprecated/http_server_common", # TODO(bt, 2023-03-20): Move useful pieces to actix_helpers
|
||||
"crates/lib/dimensions",
|
||||
@@ -228,3 +229,4 @@ shared_service_components = { path = "crates/lib/shared_service_components" }
|
||||
storyteller_client = { path = "crates/lib/api_clients/storyteller_client" }
|
||||
subprocess_common = { path = "crates/lib/subprocess_common" }
|
||||
tokens = { path = "crates/schema/public/tokens" }
|
||||
web_base64= { path = "crates/lib/data/web_base64" }
|
||||
|
||||
@@ -9,6 +9,9 @@ edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
publish = false
|
||||
|
||||
# [lints.rust]
|
||||
# unused_imports = "forbid"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
@@ -23,16 +26,19 @@ tauri-build = { version = "2.1.1", features = [] }
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
artcraft_api_defs = { workspace = true }
|
||||
build_metadata = { workspace = true }
|
||||
errors = { workspace = true }
|
||||
fal_client = { workspace = true }
|
||||
filesys = { workspace = true }
|
||||
idempotency = { workspace = true }
|
||||
memory_store = { workspace = true }
|
||||
mimetypes = { workspace = true }
|
||||
openai_sora_client = { workspace = true }
|
||||
primitives = { workspace = true }
|
||||
storyteller_client = { workspace = true }
|
||||
tokens = { workspace = true }
|
||||
web_base64 = { workspace = true }
|
||||
|
||||
# External
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
"allow": [
|
||||
{
|
||||
"url": "https://*"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:12345"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ArtcraftError {
|
||||
AnyhowError(anyhow::Error),
|
||||
DecodeError(base64::DecodeError),
|
||||
IoError(std::io::Error),
|
||||
StorytellerError(StorytellerError),
|
||||
}
|
||||
|
||||
impl Error for ArtcraftError {}
|
||||
|
||||
impl Display for ArtcraftError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::AnyhowError(e) => write!(f, "AnyhowError: {:?}", e),
|
||||
Self::DecodeError(e) => write!(f, "DecodeError: {:?}", e),
|
||||
Self::IoError(e) => write!(f, "IoError: {:?}", e),
|
||||
Self::StorytellerError(e) => write!(f, "StorytellerError: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for ArtcraftError {
|
||||
fn from(value: anyhow::Error) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for ArtcraftError {
|
||||
fn from(value: base64::DecodeError) -> Self {
|
||||
Self::DecodeError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ArtcraftError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorytellerError> for ArtcraftError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ use crate::core::state::app_preferences::preferred_download_directory::Preferred
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use serde_derive::Serialize;
|
||||
use tauri::{AppHandle, State};
|
||||
use tauri::State;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GetAppPreferencesResponse {
|
||||
|
||||
+1
-2
@@ -4,9 +4,8 @@ use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::sora_error::SoraError::AnyhowError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use tauri::State;
|
||||
|
||||
/// For now, we'll only update a single preference at a time.
|
||||
#[derive(Deserialize)]
|
||||
|
||||
+68
-64
@@ -1,60 +1,28 @@
|
||||
use crate::core::commands::enqueue::image::handle_fal::handle_fal;
|
||||
use crate::core::commands::enqueue::image::handle_sora::handle_sora;
|
||||
use crate::core::commands::enqueue::image::handle_image_artcraft::handle_image_artcraft;
|
||||
use crate::core::commands::enqueue::image::handle_image_fal::handle_image_fal;
|
||||
use crate::core::commands::enqueue::image::handle_image_sora::handle_image_sora;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image::success_event::SuccessEvent;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::model::image_models::ImageModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
use log::{debug, error, info, warn};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use reqwest::Url;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::read_to_string;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::ops::Add;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use tempfile::NamedTempFile;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueTextToImageRequest {
|
||||
@@ -62,19 +30,9 @@ pub struct EnqueueTextToImageRequest {
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<EnqueueTextToImageModel>,
|
||||
pub model: Option<ImageModel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueTextToImageModel {
|
||||
#[serde(rename = "flux_pro_ultra")]
|
||||
FluxProUltra,
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
#[serde(rename = "recraft_3")]
|
||||
Recraft3,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueTextToImageSuccessResponse {
|
||||
@@ -87,12 +45,16 @@ impl SerializeMarker for EnqueueTextToImageSuccessResponse {}
|
||||
pub enum EnqueueTextToImageErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// No model available for image generation
|
||||
NoProviderAvailable,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// No Fal API key available
|
||||
NeedsFalApiKey,
|
||||
/// Fal had an API error
|
||||
FalError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +63,8 @@ pub async fn enqueue_text_to_image_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
@@ -114,8 +78,10 @@ pub async fn enqueue_text_to_image_command(
|
||||
&app,
|
||||
request,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
&fal_task_queue,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
@@ -135,6 +101,11 @@ pub async fn enqueue_text_to_image_command(
|
||||
error_type = EnqueueTextToImageErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
InternalImageError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueTextToImageErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for image generation";
|
||||
}
|
||||
InternalImageError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueTextToImageErrorType::NeedsFalApiKey;
|
||||
@@ -150,7 +121,17 @@ pub async fn enqueue_text_to_image_command(
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(()) => {
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: event.service_provider,
|
||||
model: Some(event.tauri_event_model()),
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Ok(EnqueueTextToImageSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
@@ -161,24 +142,47 @@ pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<(), InternalImageError> {
|
||||
) -> Result<SuccessEvent, InternalImageError> {
|
||||
|
||||
match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(EnqueueTextToImageModel::FluxProUltra) | Some(EnqueueTextToImageModel::Recraft3) => {
|
||||
handle_fal(&app, request, fal_creds_manager, fal_task_queue).await?;
|
||||
},
|
||||
Some(EnqueueTextToImageModel::GptImage1) => {
|
||||
handle_sora(&app, request, sora_creds_manager, sora_task_queue).await?;
|
||||
Some(ImageModel::GptImage1) => {
|
||||
handle_image_sora(&app, request, sora_creds_manager, sora_task_queue).await?;
|
||||
return Ok(SuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Sora,
|
||||
model: ImageModel::GptImage1,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
// Fall-through
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Sora => {} // Fallthrough
|
||||
Provider::Artcraft => {
|
||||
return handle_image_artcraft(
|
||||
request, &app, app_env_configs, app_data_root, storyteller_creds_manager).await;
|
||||
}
|
||||
Provider::Fal => {
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
return handle_image_fal(&app, request, fal_creds_manager, fal_task_queue).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalImageError::NoProviderAvailable)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image::success_event::SuccessEvent;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::model::image_models::ImageModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::GenerateFluxPro11TextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::generate::image::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_image_artcraft(
|
||||
request: EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<SuccessEvent, InternalImageError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
error!("No Artcraft credentials are set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::GenerateImage);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalImageError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft text to image...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let mut selected_model;
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(ImageModel::GptImage1) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: artcraft is handling sora images")));
|
||||
}
|
||||
Some(ImageModel::Recraft3) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented in Artcraft")));
|
||||
}
|
||||
Some(ImageModel::Flux1Dev) => {
|
||||
info!("enqueue Flux 1 Dev");
|
||||
selected_model = ImageModel::Flux1Dev;
|
||||
let request = GenerateFlux1DevTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_1_dev_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux 1 Dev text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux 1 Dev text to image generation: {:?}", err);
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(ImageModel::Flux1Schnell) => {
|
||||
info!("enqueue Flux 1 Schnell");
|
||||
selected_model = ImageModel::Flux1Schnell;
|
||||
let request = GenerateFlux1SchnellTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_1_schnell_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux 1 Schnell text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux 1 Schnell text to image generation: {:?}", err);
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(ImageModel::FluxPro11) => {
|
||||
info!("enqueue Flux Pro 1.1");
|
||||
selected_model = ImageModel::FluxPro11;
|
||||
let request = GenerateFluxPro11TextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_pro_11_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux Pro 1.1 text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux Pro 1.1 text to image generation: {:?}", err);
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(ImageModel::FluxPro11Ultra) => {
|
||||
info!("enqueue Flux Pro 1.1 Ultra");
|
||||
selected_model = ImageModel::FluxPro11Ultra;
|
||||
let request = GenerateFluxPro11UltraTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_pro_11_ultra_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux Pro 1.1 Ultra text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux Pro 1.1 Ultra text to image generation: {:?}", err);
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(SuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Artcraft,
|
||||
model:selected_model,
|
||||
})
|
||||
}
|
||||
+35
-25
@@ -1,28 +1,24 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image::success_event::SuccessEvent;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::model::image_models::ImageModel;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use log::{error, info};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_fal(
|
||||
pub async fn handle_image_fal(
|
||||
app: &AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalImageError> {
|
||||
) -> Result<SuccessEvent, InternalImageError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
@@ -47,21 +43,32 @@ pub async fn handle_fal(
|
||||
|
||||
let prompt = request.prompt.as_deref().unwrap_or("");
|
||||
|
||||
let model;
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(EnqueueTextToImageModel::GptImage1) => {
|
||||
Some(ImageModel::GptImage1) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: fal is handling sora images")));
|
||||
}
|
||||
Some(EnqueueTextToImageModel::FluxProUltra) => {
|
||||
info!("enqueue Flux Pro Ultra text-to-image with prompt: {}", prompt);
|
||||
enqueue_flux_pro_ultra_text_to_image(FluxProUltraTextToImageArgs {
|
||||
Some(
|
||||
ImageModel::Flux1Dev |
|
||||
ImageModel::Flux1Schnell |
|
||||
ImageModel::FluxPro11
|
||||
) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented: {:?}", request.model)));
|
||||
}
|
||||
Some(ImageModel::FluxPro11Ultra) => {
|
||||
model = ImageModel::FluxPro11Ultra;
|
||||
info!("enqueue Flux Pro 1.1 Ultra text-to-image with prompt: {}", prompt);
|
||||
enqueue_flux_pro_11_ultra_text_to_image(FluxPro11UltraTextToImageArgs {
|
||||
prompt,
|
||||
api_key: &api_key,
|
||||
}).await
|
||||
}
|
||||
Some(EnqueueTextToImageModel::Recraft3) => {
|
||||
Some(ImageModel::Recraft3) => {
|
||||
model = ImageModel::Recraft3;
|
||||
info!("enqueue Recraft v3 text-to-image with prompt: {}", prompt);
|
||||
enqueue_recraft3_text_to_image(Recraft3TextToImageArgs {
|
||||
prompt,
|
||||
@@ -74,15 +81,15 @@ pub async fn handle_fal(
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued text to image");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
//let event = GenerationEnqueueSuccessEvent {
|
||||
// action: GenerationAction::GenerateImage,
|
||||
// service: GenerationServiceProvider::Fal,
|
||||
// model: None,
|
||||
//};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
//if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
//}
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
@@ -107,5 +114,8 @@ pub async fn handle_fal(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(SuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Fal,
|
||||
model,
|
||||
})
|
||||
}
|
||||
+5
-25
@@ -1,31 +1,21 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::creds::sora_credential_set::SoraCredentialSet;
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const SORA_SIMPLE_IMAGE_GEN_TIMEOUT : Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
pub async fn handle_sora(
|
||||
pub async fn handle_image_sora(
|
||||
app: &AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
@@ -90,15 +80,5 @@ pub async fn handle_sora(
|
||||
|
||||
sora_task_queue.insert(&response.task_id)?;
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,20 +1,24 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalImageError {
|
||||
NoModelSpecified,
|
||||
NoProviderAvailable,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
FalError(FalErrorPlus),
|
||||
SoraError(SoraError),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerApiError(ApiError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InternalImageError {
|
||||
@@ -35,9 +39,9 @@ impl From<SoraError> for InternalImageError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InternalImageError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
impl From<StorytellerError> for InternalImageError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,3 +56,15 @@ impl From<std::io::Error> for InternalImageError {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InternalImageError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod enqueue_text_to_image_command;
|
||||
pub mod handle_fal;
|
||||
pub mod handle_sora;
|
||||
pub mod internal_image_error;
|
||||
pub mod handle_image_artcraft;
|
||||
pub mod handle_image_fal;
|
||||
pub mod handle_image_sora;
|
||||
pub mod internal_image_error;
|
||||
mod success_event;
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::core::events::generation_events::common::{GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::model::image_models::ImageModel;
|
||||
|
||||
pub struct SuccessEvent {
|
||||
pub service_provider: GenerationServiceProvider,
|
||||
pub model: ImageModel,
|
||||
}
|
||||
|
||||
impl SuccessEvent {
|
||||
pub fn tauri_event_model(&self) -> GenerationModel {
|
||||
match self.model {
|
||||
ImageModel::Flux1Dev => GenerationModel::Flux1Dev,
|
||||
ImageModel::Flux1Schnell => GenerationModel::Flux1Schnell,
|
||||
ImageModel::FluxPro11 => GenerationModel::FluxPro11,
|
||||
ImageModel::FluxPro11Ultra => GenerationModel::FluxPro11Ultra,
|
||||
ImageModel::GptImage1 => GenerationModel::GptImage1,
|
||||
ImageModel::Recraft3 => GenerationModel::Recraft3,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
pub mod image;
|
||||
pub mod image;
|
||||
pub mod object;
|
||||
pub mod video;
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
use crate::core::commands::enqueue::object::handle_object_artcraft::handle_object_artcraft;
|
||||
use crate::core::commands::enqueue::object::handle_object_fal::handle_object_fal;
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageTo3dObjectRequest {
|
||||
/// Image media file; the image to remove the background from.
|
||||
/// TODO: In the future we may support base64 images, URLs, or file paths here.
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<EnqueueImageTo3dObjectModel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageTo3dObjectModel {
|
||||
#[deprecated(note="Use `hunyuan_3d_2_0` instead")]
|
||||
#[serde(rename = "hunyuan_3d_2")]
|
||||
Hunyuan3d2,
|
||||
#[serde(rename = "hunyuan_3d_2_0")]
|
||||
Hunyuan3d2_0,
|
||||
#[serde(rename = "hunyuan_3d_2_1")]
|
||||
Hunyuan3d2_1,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueImageTo3dObjectSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueImageTo3dObjectSuccessResponse {}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageTo3dObjectErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// No model available for object generation
|
||||
NoProviderAvailable,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// No Fal API key available
|
||||
NeedsFalApiKey,
|
||||
/// Fal had an API error
|
||||
FalError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_image_to_3d_object_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueImageTo3dObjectSuccessResponse, EnqueueImageTo3dObjectErrorType, ()> {
|
||||
|
||||
info!("enqueue_image_to_3d_object_command called");
|
||||
|
||||
let result = handle_request(
|
||||
&app,
|
||||
request,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueImageTo3dObjectErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match err {
|
||||
InternalObjectError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for object generation";
|
||||
}
|
||||
InternalObjectError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for object generation";
|
||||
}
|
||||
InternalObjectError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
InternalObjectError::NeedsStorytellerCredentials => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsStorytellerCredentials;
|
||||
error_message = "You need to be logged into Artcraft.";
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(()) => {
|
||||
Ok(EnqueueImageTo3dObjectSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Sora => {} // Fallthrough
|
||||
Provider::Artcraft => {
|
||||
return Ok(handle_object_artcraft(
|
||||
request, &app, app_data_root, storyteller_creds_manager).await?);
|
||||
}
|
||||
Provider::Fal => {
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
return Ok(handle_object_fal(
|
||||
&app, app_data_root, request, fal_creds_manager, fal_task_queue).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalObjectError::NoProviderAvailable)
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::GenerateHunyuan20ImageTo3dRequest;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::GenerateHunyuan21ImageTo3dRequest;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::generate::object::generate_hunyuan_3d_2_0_image_to_3d::generate_hunyuan3d_2_0_image_to_3d;
|
||||
use storyteller_client::generate::object::generate_hunyuan_3d_2_1_image_to_3d::generate_hunyuan3d_2_1_image_to_3d;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_object_artcraft(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
error!("No Artcraft credentials are set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::ImageTo3d);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalObjectError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalObjectError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
|
||||
info!("Calling Artcraft image to 3d ...");
|
||||
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2 |
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0
|
||||
) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 2.0");
|
||||
let request = GenerateHunyuan20ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
};
|
||||
let result = generate_hunyuan3d_2_0_image_to_3d(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Hunyuan 3D 2.0");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Hunyuan 3D 2.0: {:?}", err);
|
||||
return Err(InternalObjectError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(EnqueueImageTo3dObjectModel::Hunyuan3d2_1) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 2.1");
|
||||
let request = GenerateHunyuan21ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
};
|
||||
let result = generate_hunyuan3d_2_1_image_to_3d(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Hunyuan 3D 2.1");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Hunyuan 3D 2.1: {:?}", err);
|
||||
return Err(InternalObjectError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued 3D generation");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
//if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
// error!("Failed to enqueue task: {:?}", err);
|
||||
// return Err(InternalObjectError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
//}
|
||||
|
||||
// match result {
|
||||
// Ok(enqueued) => {
|
||||
// }
|
||||
// Err(err) => {
|
||||
// error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
//
|
||||
// let event = GenerationEnqueueFailureEvent {
|
||||
// action: GenerationAction::ImageTo3d,
|
||||
// service: GenerationServiceProvider::Fal,
|
||||
// model: None,
|
||||
// reason: None,
|
||||
// };
|
||||
//
|
||||
// if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
// }
|
||||
//
|
||||
// return Err(InternalObjectError::FalError(err));
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use anyhow::anyhow;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_object_fal(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
error!("No FAL API key is set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::ImageTo3d);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalObjectError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalObjectError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
temp_download = download_media_file_to_temp_dir(&app_data_root, &media_token).await?;
|
||||
|
||||
} else {
|
||||
return Err(InternalObjectError::AnyhowError(anyhow!("No image media token provided")));
|
||||
}
|
||||
|
||||
info!("Calling FAL image to 3d ...");
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0 |
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2
|
||||
) => {
|
||||
info!("enqueue Hunyuan 3D 2.0");
|
||||
enqueue_hunyuan2_image_to_3d(Hunyuan2Args {
|
||||
image_path: filename,
|
||||
api_key: &api_key,
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
return Err(InternalObjectError::AnyhowError(anyhow!("Wrong model specified: {:?}", request.model)));
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued text to image");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(InternalObjectError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalObjectError::FalError(err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalObjectError {
|
||||
NoModelSpecified,
|
||||
NoProviderAvailable,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InternalObjectError {
|
||||
fn from(value: AnyhowError) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FalErrorPlus> for InternalObjectError {
|
||||
fn from(value: FalErrorPlus) -> Self {
|
||||
Self::FalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorytellerError> for InternalObjectError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for InternalObjectError {
|
||||
fn from(value: DecodeError) -> Self {
|
||||
Self::DecodeError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for InternalObjectError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InternalObjectError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod enqueue_image_to_3d_object_command;
|
||||
pub mod handle_object_artcraft;
|
||||
pub mod handle_object_fal;
|
||||
pub mod internal_object_error;
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
use crate::core::commands::enqueue::video::handle_video_artcraft::handle_video_artcraft;
|
||||
use crate::core::commands::enqueue::video::handle_video_fal::handle_video_fal;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::model::video_models::VideoModel;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageToVideoRequest {
|
||||
/// Image media file; the image to remove the background from.
|
||||
/// TODO: In the future we may support base64 images, URLs, or file paths here.
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<VideoModel>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueImageToVideoSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueImageToVideoSuccessResponse {}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageToVideoErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// No model available for video generation
|
||||
NoProviderAvailable,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// No Fal API key available
|
||||
NeedsFalApiKey,
|
||||
/// Fal had an API error
|
||||
FalError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_image_to_video_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueImageToVideoSuccessResponse, EnqueueImageToVideoErrorType, ()> {
|
||||
|
||||
info!("enqueue_image_to_video_command called");
|
||||
|
||||
let result = handle_request(
|
||||
&app,
|
||||
request,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueImageToVideoErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match err {
|
||||
InternalVideoError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueImageToVideoErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for video generation";
|
||||
}
|
||||
InternalVideoError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueImageToVideoErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for video generation";
|
||||
}
|
||||
InternalVideoError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageToVideoErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
InternalVideoError::NeedsStorytellerCredentials => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageToVideoErrorType::NeedsStorytellerCredentials;
|
||||
error_message = "You need to be logged into Artcraft.";
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(()) => {
|
||||
Ok(EnqueueImageToVideoSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Sora => {} // Fallthrough
|
||||
Provider::Artcraft => {
|
||||
return Ok(handle_video_artcraft(
|
||||
request, &app, app_data_root, storyteller_creds_manager).await?);
|
||||
}
|
||||
Provider::Fal => {
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
return Ok(handle_video_fal(
|
||||
&app, app_data_root, request, fal_creds_manager, fal_task_queue).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalVideoError::NoProviderAvailable)
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::model::video_models::VideoModel;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::{GenerateKling16ProAspectRatio, GenerateKling16ProImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_kling_2_1_master_image_to_video::{GenerateKling21MasterAspectRatio, GenerateKling21MasterImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_kling_2_1_pro_image_to_video::{GenerateKling21ProAspectRatio, GenerateKling21ProImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteImageToVideoRequest;
|
||||
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::{GenerateVeo2AspectRatio, GenerateVeo2ImageToVideoRequest};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::Kling16ProAspectRatio;
|
||||
use fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::Veo2AspectRatio;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::generate::video::generate_kling_16_pro_image_to_video::generate_kling_16_pro_image_to_video;
|
||||
use storyteller_client::generate::video::generate_kling_21_master_image_to_video::generate_kling_21_master_image_to_video;
|
||||
use storyteller_client::generate::video::generate_kling_21_pro_image_to_video::generate_kling_21_pro_image_to_video;
|
||||
use storyteller_client::generate::video::generate_seedance_1_0_lite_image_to_video::generate_seedance_1_0_lite_image_to_video;
|
||||
use storyteller_client::generate::video::generate_veo_2_image_to_video::generate_veo_2_image_to_video;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_video_artcraft(
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
error!("No Artcraft credentials are set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::GenerateVideo);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalVideoError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalVideoError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
|
||||
info!("Calling Artcraft image to video...");
|
||||
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let mut selected_model = None;
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(InternalVideoError::NoModelSpecified);
|
||||
}
|
||||
Some(VideoModel::Kling16Pro) => {
|
||||
info!("enqueue Kling 1.6 Pro with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Kling1_6);
|
||||
let request = GenerateKling16ProImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateKling16ProAspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_16_pro_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Kling 1.6 video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Kling 1.6 video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Kling21Master) => {
|
||||
info!("enqueue Kling 2.1 Master with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Kling21Master);
|
||||
let request = GenerateKling21MasterImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateKling21MasterAspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_21_master_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Kling 2.1 Master video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Kling 2.1 Master video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Kling21Pro) => {
|
||||
info!("enqueue Kling 2.1 Pro with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Kling21Pro);
|
||||
let request = GenerateKling21ProImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateKling21ProAspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_21_pro_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Kling 2.1 Pro video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Kling 2.1 Pro video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Seedance10Lite) => {
|
||||
info!("enqueue Seedance 1.0 Lite with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Seedance10Lite);
|
||||
let request = GenerateSeedance10LiteImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
prompt: None,
|
||||
resolution: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_seedance_1_0_lite_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Seedance 1.0 Lite video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Seedance 1.0 Lite video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Veo2) => {
|
||||
info!("enqueue Veo 2 with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Veo2);
|
||||
let request = GenerateVeo2ImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateVeo2AspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_veo_2_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Veo 2 video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Veo 2 video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued video generation");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
model: selected_model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
//if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
// error!("Failed to enqueue task: {:?}", err);
|
||||
// return Err(InternalVideoError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
//}
|
||||
|
||||
// match result {
|
||||
// Ok(enqueued) => {
|
||||
// }
|
||||
// Err(err) => {
|
||||
// error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
//
|
||||
// let event = GenerationEnqueueFailureEvent {
|
||||
// action: GenerationAction::ImageTo3d,
|
||||
// service: GenerationServiceProvider::Fal,
|
||||
// model: None,
|
||||
// reason: None,
|
||||
// };
|
||||
//
|
||||
// if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
// }
|
||||
//
|
||||
// return Err(InternalVideoError::FalError(err));
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::model::video_models::VideoModel;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use anyhow::anyhow;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::{enqueue_kling_16_pro_image_to_video, Kling16ProArgs, Kling16ProAspectRatio, Kling16ProDuration};
|
||||
use log::{error, info};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_video_fal(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
error!("No FAL API key is set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::GenerateVideo);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalVideoError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalVideoError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
temp_download = download_media_file_to_temp_dir(&app_data_root, &media_token).await?;
|
||||
|
||||
} else {
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("No image media token provided")));
|
||||
}
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let mut selected_model = None;
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(InternalVideoError::NoModelSpecified);
|
||||
}
|
||||
Some(VideoModel::Kling16Pro) => {
|
||||
info!("enqueue Kling 1.6 image to video with Kling API");
|
||||
selected_model = Some(GenerationModel::Kling1_6);
|
||||
enqueue_kling_16_pro_image_to_video(Kling16ProArgs {
|
||||
image_path: filename,
|
||||
api_key: &api_key,
|
||||
duration: Kling16ProDuration::Default,
|
||||
aspect_ratio: Kling16ProAspectRatio::WideSixteenNine,
|
||||
prompt: "",
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("Unsupported model: {:?}", request.model)));
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Kling 1.6 image to video");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: selected_model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue image to video: {:?}", err);
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: selected_model,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalVideoError::FalError(err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalVideoError {
|
||||
NoModelSpecified,
|
||||
NoProviderAvailable,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InternalVideoError {
|
||||
fn from(value: AnyhowError) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FalErrorPlus> for InternalVideoError {
|
||||
fn from(value: FalErrorPlus) -> Self {
|
||||
Self::FalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorytellerError> for InternalVideoError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for InternalVideoError {
|
||||
fn from(value: DecodeError) -> Self {
|
||||
Self::DecodeError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for InternalVideoError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InternalVideoError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod enqueue_image_to_video_command;
|
||||
pub mod handle_video_artcraft;
|
||||
pub mod handle_video_fal;
|
||||
pub mod internal_video_error;
|
||||
+13
-12
@@ -1,40 +1,41 @@
|
||||
use crate::core::commands::response::shorthand::InfallibleResponse;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::os_platform::OsPlatform;
|
||||
use crate::services::fal::commands::fal_background_removal_command::FalBackgroundRemovalSuccessResponse;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use chrono::{DateTime, Utc};
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::ImageReader;
|
||||
use log::info;
|
||||
use serde_derive::Serialize;
|
||||
use std::io::Cursor;
|
||||
use tauri::State;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BuildInfoResponse {
|
||||
pub struct AppInfoResponse {
|
||||
pub build_timestamp: DateTime<Utc>,
|
||||
pub git_commit_id: Option<String>,
|
||||
pub git_commit_short_id: Option<String>,
|
||||
pub git_commit_timestamp: Option<DateTime<Utc>>,
|
||||
pub storyteller_host: String,
|
||||
}
|
||||
|
||||
impl SerializeMarker for BuildInfoResponse {}
|
||||
impl SerializeMarker for AppInfoResponse {}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_build_info_command() -> InfallibleResponse<BuildInfoResponse> {
|
||||
info!("get_build_info_command called...");
|
||||
pub fn get_app_info_command(
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
) -> InfallibleResponse<AppInfoResponse> {
|
||||
info!("get_app_info_command called...");
|
||||
|
||||
let build_timestamp = build_metadata::build_timestamp();
|
||||
let git_commit_id = build_metadata::git_commit_id();
|
||||
let git_commit_short_id = build_metadata::git_commit_short_id();
|
||||
let git_commit_timestamp = build_metadata::git_commit_timestamp();
|
||||
|
||||
BuildInfoResponse {
|
||||
let storyteller_host = app_env_configs.storyteller_host.to_api_hostname();
|
||||
|
||||
AppInfoResponse {
|
||||
build_timestamp,
|
||||
git_commit_id: git_commit_id.map(|s| s.to_string()),
|
||||
git_commit_short_id: git_commit_short_id.map(|s| s.to_string()),
|
||||
git_commit_timestamp,
|
||||
storyteller_host,
|
||||
}.into()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod app_preferences;
|
||||
pub mod enqueue;
|
||||
pub mod flip_image;
|
||||
pub mod get_build_info_command;
|
||||
pub mod get_app_info_command;
|
||||
pub mod platform_info_command;
|
||||
pub mod providers;
|
||||
pub mod response;
|
||||
@@ -1,13 +1,8 @@
|
||||
use crate::core::commands::response::shorthand::InfallibleResponse;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::os_platform::OsPlatform;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::ImageReader;
|
||||
use log::info;
|
||||
use serde_derive::Serialize;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PlatformInfoResponse {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
use crate::core::commands::response::shorthand::ResponseOrErrorMessage;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use log::{error, info};
|
||||
use serde_derive::Serialize;
|
||||
use tauri::State;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GetProviderOrderResponse {
|
||||
pub providers: Vec<Provider>,
|
||||
}
|
||||
|
||||
impl SerializeMarker for GetProviderOrderResponse{}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_provider_order_command(
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
) -> ResponseOrErrorMessage<GetProviderOrderResponse> {
|
||||
info!("get_provider_order_command called");
|
||||
|
||||
let providers = provider_priority_store.get_priority()
|
||||
.map_err(|err| {
|
||||
error!("Failed to get provider order: {:?}", err);
|
||||
"Failed to retrieve provider order"
|
||||
})?;
|
||||
|
||||
Ok(GetProviderOrderResponse {
|
||||
providers,
|
||||
}.into())
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod get_provider_order_command;
|
||||
pub mod set_provider_order_command;
|
||||
@@ -0,0 +1,34 @@
|
||||
use crate::core::commands::response::shorthand::SuccessOrErrorMessage;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use log::{error, info};
|
||||
use serde_derive::Deserialize;
|
||||
use tauri::State;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SetProviderOrderRequest {
|
||||
pub providers: Vec<Provider>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_provider_order_command(
|
||||
request: SetProviderOrderRequest,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
) -> SuccessOrErrorMessage {
|
||||
info!("set_provider_order_command called");
|
||||
|
||||
provider_priority_store.set_priority(&request.providers)
|
||||
.map_err(|err| {
|
||||
error!("Failed to set provider order: {:?}", err);
|
||||
"Failed to set provider order"
|
||||
})?;
|
||||
|
||||
provider_priority_store.persist_to_filesystem(&app_data_root)
|
||||
.map_err(|err| {
|
||||
error!("Failed to persist provider order to filesystem: {:?}", err);
|
||||
"Failed to persist provider order"
|
||||
})?;
|
||||
|
||||
Ok(().into())
|
||||
}
|
||||
@@ -30,3 +30,19 @@ pub type Response<SuccessPayload, ErrType, ErrPayload> =
|
||||
|
||||
/// No inner payloads for this type. Just strings as messages.
|
||||
pub type SimpleResponse = Response<(), (), ()>;
|
||||
|
||||
/// Either an empty success payload or error message.
|
||||
pub type SuccessOrErrorMessage =
|
||||
Result<CommandSuccessResponseWrapper<()>, CommandErrorResponseWrapper<(), ()>>;
|
||||
|
||||
/// Either a success or error message.
|
||||
pub type ResponseOrErrorMessage<SuccessPayload> =
|
||||
Result<CommandSuccessResponseWrapper<SuccessPayload>, CommandErrorResponseWrapper<(), ()>>;
|
||||
|
||||
/// Either a success or error type.
|
||||
pub type ResponseOrErrorType<SuccessPayload, ErrType> =
|
||||
Result<CommandSuccessResponseWrapper<SuccessPayload>, CommandErrorResponseWrapper<ErrType, ()>>;
|
||||
|
||||
/// Either a success or error payload.
|
||||
pub type ResponseOrError<SuccessPayload, ErrPayload> =
|
||||
Result<CommandSuccessResponseWrapper<SuccessPayload>, CommandErrorResponseWrapper<(), ErrPayload>>;
|
||||
|
||||
@@ -3,18 +3,40 @@ use serde::Serialize;
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerationServiceProvider {
|
||||
Sora,
|
||||
Artcraft,
|
||||
Fal,
|
||||
Sora,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerationModel {
|
||||
#[serde(rename = "flux_1_dev")]
|
||||
Flux1Dev,
|
||||
#[serde(rename = "flux_1_schnell")]
|
||||
Flux1Schnell,
|
||||
#[serde(rename = "flux_pro_1.1")]
|
||||
FluxPro11,
|
||||
#[serde(rename = "flux_pro_1.1_ultra")]
|
||||
FluxPro11Ultra,
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
#[serde(rename = "kling_1.6")]
|
||||
Kling1_6,
|
||||
#[serde(rename = "kling_2.0")]
|
||||
Kling2_0,
|
||||
#[serde(rename = "kling_2.1_master")]
|
||||
Kling21Master,
|
||||
#[serde(rename = "kling_2.1_pro")]
|
||||
Kling21Pro,
|
||||
#[serde(rename = "recraft_3")]
|
||||
Recraft3,
|
||||
#[serde(rename = "sora")]
|
||||
Sora,
|
||||
#[serde(rename = "seedance_1.0_lite")]
|
||||
Seedance10Lite,
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
|
||||
+9
@@ -19,6 +19,15 @@ impl BasicSendableEvent for GenerationEnqueueFailureEvent {
|
||||
}
|
||||
|
||||
impl GenerationEnqueueFailureEvent {
|
||||
pub fn no_artcraft_credentials(action: GenerationAction) -> Self {
|
||||
Self {
|
||||
action,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
model: None,
|
||||
reason: Some("Please log into Artcraft.".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_fal_api_key(action: GenerationAction) -> Self {
|
||||
Self {
|
||||
action,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod artcraft_error;
|
||||
pub mod commands;
|
||||
pub mod events;
|
||||
pub mod model;
|
||||
pub mod state;
|
||||
pub mod threads;
|
||||
pub mod utils;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ImageModel {
|
||||
#[serde(rename = "flux_1_dev")]
|
||||
Flux1Dev,
|
||||
#[serde(rename = "flux_1_schnell")]
|
||||
Flux1Schnell,
|
||||
#[serde(rename = "flux_pro_11")]
|
||||
FluxPro11,
|
||||
#[serde(rename = "flux_pro_11_ultra")]
|
||||
FluxPro11Ultra,
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
#[serde(rename = "recraft_3")]
|
||||
Recraft3,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod image_models;
|
||||
pub mod video_models;
|
||||
@@ -0,0 +1,22 @@
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum VideoModel {
|
||||
#[serde(rename = "kling_1.6_pro")]
|
||||
Kling16Pro,
|
||||
|
||||
#[serde(rename = "kling_2.1_pro")]
|
||||
Kling21Pro,
|
||||
|
||||
#[serde(rename = "kling_2.1_master")]
|
||||
Kling21Master,
|
||||
|
||||
#[serde(rename = "seedance_1.0_lite")]
|
||||
Seedance10Lite,
|
||||
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use crate::core::state::app_env_configs::app_env_configs_serializeable::{AppEnvConfigsSerializable, StorytellerApiHost};
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppEnvConfigs {
|
||||
pub storyteller_host: ApiHost,
|
||||
}
|
||||
|
||||
impl AppEnvConfigs {
|
||||
|
||||
pub fn load_from_filesystem(root: &AppDataRoot) -> AnyhowResult<Self> {
|
||||
println!("Loading app environmental configs from filesystem...");
|
||||
let input = AppEnvConfigsSerializable::load_from_filesystem(root)?;
|
||||
|
||||
let storyteller = input.as_ref()
|
||||
.map(|i| i.storyteller_host)
|
||||
.flatten();
|
||||
|
||||
let storyteller_api = match storyteller {
|
||||
Some(StorytellerApiHost::Localhost) => ApiHost::Localhost {
|
||||
port: input.as_ref()
|
||||
.and_then(|i| i.storyteller_port)
|
||||
.unwrap_or(12345)
|
||||
},
|
||||
Some(StorytellerApiHost::Production) => ApiHost::Storyteller,
|
||||
_ => ApiHost::Storyteller,
|
||||
};
|
||||
|
||||
println!("Using storyteller API host: {:?}", storyteller_api);
|
||||
|
||||
Ok(Self {
|
||||
storyteller_host: storyteller_api,
|
||||
})
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
const CURRENT_VERSION: &str = "1";
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StorytellerApiHost {
|
||||
Production,
|
||||
Localhost,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct AppEnvConfigsSerializable {
|
||||
/// Versioning string.
|
||||
pub version: Option<String>,
|
||||
pub storyteller_port: Option<u32>,
|
||||
pub storyteller_host: Option<StorytellerApiHost>,
|
||||
}
|
||||
|
||||
impl AppEnvConfigsSerializable {
|
||||
pub fn load_from_filesystem(app_data_root: &AppDataRoot) -> AnyhowResult<Option<Self>> {
|
||||
let filename = app_data_root.settings_dir().get_app_env_configs_path();
|
||||
if !filename.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let contents = std::fs::read_to_string(filename)?;
|
||||
let data: Self = serde_json::from_str(&contents)?;
|
||||
Ok(Some(data))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod app_env_configs;
|
||||
pub mod app_env_configs_serializeable;
|
||||
+1
-1
@@ -60,7 +60,7 @@ impl Display for SystemDownloadDirectory {
|
||||
mod tests {
|
||||
use crate::core::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use crate::core::state::app_preferences::preferred_download_directory::SystemDownloadDirectory;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
||||
mod json {
|
||||
use super::*;
|
||||
|
||||
@@ -33,6 +33,7 @@ pub struct AppDataRoot {
|
||||
impl AppDataRoot {
|
||||
pub fn create_default() -> anyhow::Result<Self> {
|
||||
let directory = get_default_data_dir()?;
|
||||
println!("App data directory: {:?}", directory);
|
||||
Self::create_existing(directory)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,15 @@ impl AppSettingsDir {
|
||||
pub fn get_app_preferences_path(&self) -> PathBuf {
|
||||
self.path.join("app_preferences.json")
|
||||
}
|
||||
|
||||
|
||||
pub fn get_app_env_configs_path(&self) -> PathBuf {
|
||||
self.path.join("env_configs.json")
|
||||
}
|
||||
|
||||
pub fn get_provider_preferences_path(&self) -> PathBuf {
|
||||
self.path.join("provider_preferences.json")
|
||||
}
|
||||
|
||||
pub fn write_app_preferences(&self, prefs: &AppPreferences) -> AnyhowResult<()> {
|
||||
let serializable = prefs.to_serializable();
|
||||
|
||||
|
||||
@@ -43,11 +43,11 @@ pub fn home_dir() -> io::Result<PathBuf> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::core::state::expanduser::expanduser;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use crate::core::state::expanduser::expanduser;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
#[test]
|
||||
#[serial_test::serial]
|
||||
fn test_success() {
|
||||
let old_home = env::var("HOME").expect("no home dir set");
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
pub mod app_env_configs;
|
||||
pub mod app_preferences;
|
||||
pub mod app_startup_time;
|
||||
pub mod data_dir;
|
||||
pub mod expanduser;
|
||||
pub mod main_window_position;
|
||||
pub mod main_window_size;
|
||||
pub mod os_platform;
|
||||
pub mod os_platform;
|
||||
pub mod provider_priority;
|
||||
@@ -0,0 +1,162 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Provider {
|
||||
Artcraft,
|
||||
Fal,
|
||||
Sora,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ProviderPriority {
|
||||
/// Providers may occur zero or one time.
|
||||
/// They are ordered by priority, highest to lowest.
|
||||
provider_priority: Vec<Provider>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ProviderPriorityStore {
|
||||
provider_priority: Arc<RwLock<ProviderPriority>>,
|
||||
}
|
||||
|
||||
impl ProviderPriority {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
provider_priority: vec![
|
||||
Provider::Artcraft,
|
||||
Provider::Sora,
|
||||
Provider::Fal,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_priority(&mut self, ordered_list: &[Provider]) -> AnyhowResult<()> {
|
||||
let set : HashSet<&Provider> = HashSet::from_iter(ordered_list.iter());
|
||||
|
||||
if set.len() != ordered_list.len() {
|
||||
return Err(anyhow::anyhow!("Duplicate providers in the ordered list"));
|
||||
}
|
||||
|
||||
self.provider_priority = ordered_list.to_vec();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_priority(&self) -> &[Provider] {
|
||||
&self.provider_priority
|
||||
}
|
||||
|
||||
pub fn from_filesystem_configs(app_data_root: &AppDataRoot) -> AnyhowResult<Option<Self>> {
|
||||
let filename = app_data_root.settings_dir().get_provider_preferences_path();
|
||||
if !filename.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
let contents = std::fs::read_to_string(filename)?;
|
||||
let providers : Self = serde_json::from_str(&contents)?;
|
||||
Ok(Some(providers))
|
||||
}
|
||||
|
||||
pub fn persist_to_filesystem(&self, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
let filename = app_data_root.settings_dir().get_provider_preferences_path();
|
||||
let json = serde_json::to_string(self)?;
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(filename)?;
|
||||
file.write_all(json.as_bytes())?;
|
||||
file.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ProviderPriorityStore {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
provider_priority: Arc::new(RwLock::new(ProviderPriority::default())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_priority(&self, ordered_list: &[Provider]) -> AnyhowResult<()> {
|
||||
match self.provider_priority.write() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire write lock on provider priority: {}", err))
|
||||
}
|
||||
Ok(mut providers) => {
|
||||
providers.set_priority(ordered_list)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_priority(&self) -> AnyhowResult<Vec<Provider>> {
|
||||
match self.provider_priority.read() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire read lock on provider priority: {}", err))
|
||||
}
|
||||
Ok(providers) => {
|
||||
Ok(providers.get_priority().to_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_filesystem_configs(app_data_root: &AppDataRoot) -> AnyhowResult<Option<Self>> {
|
||||
match ProviderPriority::from_filesystem_configs(app_data_root) {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to read provider preferences from filesystem: {}", err))
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Ok(Some(providers)) => {
|
||||
Ok(Some(Self {
|
||||
provider_priority: Arc::new(RwLock::new(providers)),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn persist_to_filesystem(&self, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
match self.provider_priority.read() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire read lock on provider priority: {}", err))
|
||||
}
|
||||
Ok(providers) => {
|
||||
providers.persist_to_filesystem(app_data_root)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_priority() {
|
||||
let priority = ProviderPriority::default();
|
||||
assert_eq!(priority.get_priority(), &[Provider::Artcraft, Provider::Sora, Provider::Fal]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_priority() -> AnyhowResult<()> {
|
||||
let mut priority = ProviderPriority::default();
|
||||
priority.set_priority(&[Provider::Fal, Provider::Artcraft])?;
|
||||
assert_eq!(priority.get_priority(), &[Provider::Fal, Provider::Artcraft]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_priority_with_duplicates() {
|
||||
let mut priority = ProviderPriority::default();
|
||||
let result = priority.set_priority(&[Provider::Artcraft, Provider::Artcraft]);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,14 @@ use crate::core::state::app_startup_time::AppStartupTime;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::main_window_size::MainWindowSize;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::core::threads::main_window_thread::persist_storyteller_cookies_task::persist_storyteller_cookies_task;
|
||||
use crate::core::threads::main_window_thread::persist_window_position_task::persist_window_position_task;
|
||||
use crate::core::threads::main_window_thread::persist_window_resize_task::persist_window_resize_task;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use log::{error, info, warn};
|
||||
use memory_store::clone_slot::CloneSlot;
|
||||
use tauri::{AppHandle, Manager, Webview, Window};
|
||||
use tauri::{AppHandle, Manager, Window};
|
||||
|
||||
const MAIN_WINDOW_NAME : &str = "main";
|
||||
|
||||
@@ -31,6 +31,8 @@ pub async fn main_window_thread(
|
||||
.unwrap_or_else(|| "unknown".to_string()));
|
||||
info!("build timestamp: {:?}", build_metadata::build_timestamp()
|
||||
.to_string());
|
||||
|
||||
// debug_try_clear_all_webview_data(&app);
|
||||
|
||||
loop {
|
||||
for (window_name, window) in app.windows() {
|
||||
@@ -75,3 +77,15 @@ pub fn log_errors<T>(result: AnyhowResult<T>) {
|
||||
error!("Error persisting window size: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_try_clear_all_webview_data(app: &AppHandle) {
|
||||
warn!("[!!!] THIS IS ONLY FOR DEBUGGING PURPOSES: Attempting to clear webview data...");
|
||||
|
||||
for (name, webview) in app.webviews() {
|
||||
if let Err(err) = webview.clear_all_browsing_data() {
|
||||
error!("Failed to clear cookies for '{}' window webview: {:?}", name, err);
|
||||
} else {
|
||||
warn!("Successfully cleared cookies for '{}' window webview", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -1,18 +1,15 @@
|
||||
use crate::core::state::app_startup_time::AppStartupTime;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use chrono::TimeDelta;
|
||||
use errors::AnyhowResult;
|
||||
use log::{debug, error, info};
|
||||
use log::{error, info};
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::Url;
|
||||
use std::fs;
|
||||
use std::sync::LockResult;
|
||||
use storyteller_client::credentials::storyteller_avt_cookie::StorytellerAvtCookie;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::credentials::storyteller_session_cookie::StorytellerSessionCookie;
|
||||
use tauri::{AppHandle, Manager, Webview, Window};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri_plugin_http::reqwest_cookie_store::CookieStore;
|
||||
use tauri_plugin_http::Http;
|
||||
|
||||
|
||||
-2
@@ -1,7 +1,5 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::main_window_size::MainWindowSize;
|
||||
use crate::core::utils::best_window_size_heuristic::best_window_size_heuristic;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use memory_store::clone_slot::CloneSlot;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use tauri::{PhysicalSize, Window};
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{PhysicalSize, Window};
|
||||
|
||||
// A typical height difference when dev tools are open:
|
||||
// Window inner size = PhysicalSize { width: 3704, height: 732 }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use tauri::Webview;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::Webview;
|
||||
|
||||
pub fn clear_all_webview_cookies(webview: &Webview) -> AnyhowResult<()> {
|
||||
webview.clear_all_browsing_data()?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use errors::AnyhowResult;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tempfile::NamedTempFile;
|
||||
@@ -9,7 +9,7 @@ use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
// TODO: Better more concrete error handling
|
||||
|
||||
pub async fn download_media_file_to_temp_dir(app_data_root: &AppDataRoot, token: &MediaFileToken) -> AnyhowResult<NamedTempFile> {
|
||||
pub async fn download_media_file_to_temp_dir(app_data_root: &AppDataRoot, token: &MediaFileToken) -> Result<NamedTempFile, ArtcraftError> {
|
||||
let response = get_media_file(&ApiHost::Storyteller, token).await?;
|
||||
|
||||
let media_file_url = &response.media_file.media_links.cdn_url;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::path::Path;
|
||||
use reqwest::Url;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn get_url_file_extension(url: &Url) -> Option<&str> {
|
||||
Path::new(url.path())
|
||||
|
||||
@@ -11,8 +11,6 @@ use reqwest::header::{
|
||||
};
|
||||
use reqwest::Url;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use std::fs::remove_file;
|
||||
use std::io::SeekFrom;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Implemented from https://github.com/huggingface/hf_transfer/blob/main/src/lib.rs
|
||||
|
||||
use std::fmt::Display;
|
||||
use reqwest::header::ToStrError;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
|
||||
@@ -6,7 +6,6 @@ use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use reqwest::header::CONTENT_LENGTH;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use std::io::SeekFrom;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Implemented from https://github.com/huggingface/hf_transfer/blob/main/src/lib.rs
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::fmt::Display;
|
||||
|
||||
pub const BASE_WAIT_TIME: usize = 300;
|
||||
pub const MAX_WAIT_TIME: usize = 10_000;
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use errors::AnyhowResult;
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use std::io::Write;
|
||||
use tempfile::NamedTempFile;
|
||||
use web_base64::web_base64_decode::web_base64_decode;
|
||||
|
||||
// TODO: Retain error types (io/decode)
|
||||
|
||||
pub async fn save_base64_image_to_temp_dir(app_data_root: &AppDataRoot, base64_image: String) -> AnyhowResult<NamedTempFile> {
|
||||
let bytes = BASE64_STANDARD.decode(base64_image)?;
|
||||
pub async fn save_base64_image_to_temp_dir(app_data_root: &AppDataRoot, base64_image: String) -> Result<NamedTempFile, ArtcraftError> {
|
||||
let bytes = web_base64_decode(&base64_image)?;
|
||||
|
||||
let extension = MimetypeInfo::get_for_bytes(&bytes)
|
||||
.map(|info| info.file_extension())
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use errors::AnyhowResult;
|
||||
use reqwest::Client;
|
||||
use reqwest::Url;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use reqwest::Url;
|
||||
use reqwest::Client;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
const USER_AGENT: &str = "storyteller-client/1.0";
|
||||
|
||||
|
||||
@@ -6,13 +6,19 @@ use tauri::Manager;
|
||||
use crate::core::commands::app_preferences::get_app_preferences_command::get_app_preferences_command;
|
||||
use crate::core::commands::app_preferences::update_app_preference_command::update_app_preferences_command;
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::enqueue_text_to_image_command;
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::enqueue_image_to_3d_object_command;
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::enqueue_image_to_video_command;
|
||||
use crate::core::commands::flip_image::flip_image;
|
||||
use crate::core::commands::get_build_info_command::get_build_info_command;
|
||||
use crate::core::commands::get_app_info_command::get_app_info_command;
|
||||
use crate::core::commands::platform_info_command::platform_info_command;
|
||||
use crate::core::commands::providers::get_provider_order_command::get_provider_order_command;
|
||||
use crate::core::commands::providers::set_provider_order_command::set_provider_order_command;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::load_app_preferences_or_default;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::main_window_size::MainWindowSize;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::threads::discord_presence_thread::discord_presence_thread;
|
||||
use crate::core::threads::main_window_thread::main_window_thread::main_window_thread;
|
||||
use crate::core::utils::webview_unsafe::webview_unsafe_for_app;
|
||||
@@ -50,8 +56,10 @@ pub fn run() {
|
||||
println!("Loading app preferences...");
|
||||
let app_preferences = load_app_preferences_or_default(&app_data_root);
|
||||
|
||||
println!("Attempting to read existing artcraft credentials...");
|
||||
let storyteller_creds_manager = StorytellerCredentialManager::initialize_from_disk_infallible(&app_data_root);
|
||||
// NB: tauri-plugin-http stores the credentials on disk, so we can defer to that for now.
|
||||
// println!("Attempting to read existing artcraft credentials...");
|
||||
// let storyteller_creds_manager = StorytellerCredentialManager::initialize_from_disk_infallible(&app_data_root);
|
||||
let storyteller_creds_manager = StorytellerCredentialManager::initialize_empty(&app_data_root);
|
||||
let storyteller_creds_manager_2 = storyteller_creds_manager.clone();
|
||||
let storyteller_creds_manager_3 = storyteller_creds_manager.clone();
|
||||
|
||||
@@ -69,6 +77,24 @@ pub fn run() {
|
||||
|
||||
let fal_task_queue = FalTaskQueue::new();
|
||||
let fal_task_queue_2 = fal_task_queue.clone();
|
||||
|
||||
let app_env_configs = AppEnvConfigs::load_from_filesystem(&app_data_root)
|
||||
.expect("AppEnvConfigs should be loaded from disk");
|
||||
|
||||
let provider_priority = match ProviderPriorityStore::from_filesystem_configs(&app_data_root) {
|
||||
Ok(Some(priority)) => {
|
||||
println!("Loaded provider priority from disk: {:?}", priority.get_priority());
|
||||
priority
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("No provider priority found on disk, using default.");
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read provider priority from disk: {:?}", err);
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
};
|
||||
|
||||
println!("Initializing backend runtime...");
|
||||
|
||||
@@ -153,22 +179,28 @@ pub fn run() {
|
||||
Ok(())
|
||||
})
|
||||
.manage(app_data_root)
|
||||
.manage(app_env_configs)
|
||||
.manage(app_preferences)
|
||||
.manage(fal_creds_manager)
|
||||
.manage(fal_task_queue)
|
||||
.manage(provider_priority)
|
||||
.manage(sora_creds_manager)
|
||||
.manage(sora_task_queue)
|
||||
.manage(storyteller_creds_manager_3)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
check_sora_session_command,
|
||||
enqueue_image_to_3d_object_command,
|
||||
enqueue_image_to_video_command,
|
||||
enqueue_text_to_image_command,
|
||||
fal_background_removal_command,
|
||||
fal_hunyuan_image_to_3d_command,
|
||||
fal_kling_image_to_video_command,
|
||||
flip_image,
|
||||
get_app_info_command,
|
||||
get_app_preferences_command,
|
||||
get_build_info_command,
|
||||
get_fal_api_key_command,
|
||||
get_provider_order_command,
|
||||
set_provider_order_command,
|
||||
open_sora_login_command,
|
||||
platform_info_command,
|
||||
set_fal_api_key_command,
|
||||
|
||||
+26
-33
@@ -1,3 +1,4 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
@@ -11,49 +12,25 @@ use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::queue::remove_background_rembg::remove_background_rembg;
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
use log::{debug, error, info, warn};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use image::{EncodableLayout, ImageReader};
|
||||
use log::{error, info, warn};
|
||||
use reqwest::Url;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::read_to_string;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::ops::Add;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use tempfile::NamedTempFile;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -176,9 +153,10 @@ pub async fn fal_background_removal_command(
|
||||
enum InnerError {
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerApiError(ApiError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InnerError {
|
||||
@@ -193,9 +171,9 @@ impl From<FalErrorPlus> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InnerError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
impl From<StorytellerError> for InnerError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +189,17 @@ impl From<std::io::Error> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InnerError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_background(
|
||||
request: FalBackgroundRemovalRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
@@ -224,9 +213,11 @@ pub async fn remove_background(
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
info!("From media token: {:?}", &media_token);
|
||||
temp_download = download_media_file_to_temp_dir(&app_data_root, &media_token).await?;
|
||||
|
||||
} else if let Some(base64_bytes) = request.base64_image {
|
||||
info!("From base64 bytes...");
|
||||
temp_download = save_base64_image_to_temp_dir(&app_data_root, base64_bytes).await?;
|
||||
} else {
|
||||
return Err(InnerError::AnyhowError(anyhow!("No image media token or base64 image provided")));
|
||||
@@ -237,6 +228,8 @@ pub async fn remove_background(
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let result = remove_background_rembg(filename, &api_key).await?;
|
||||
|
||||
info!("Unpacking FAL result...");
|
||||
|
||||
let extension_with_dot = get_url_file_extension(&result.image_url)
|
||||
.map(|ext| format!(".{}", ext))
|
||||
|
||||
+24
-43
@@ -1,3 +1,4 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
@@ -8,52 +9,19 @@ use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
use log::{debug, error, info, warn};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use reqwest::Url;
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::read_to_string;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::ops::Add;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use tempfile::NamedTempFile;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -152,9 +120,10 @@ pub async fn fal_hunyuan_image_to_3d_command(
|
||||
enum InnerError {
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerApiError(ApiError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InnerError {
|
||||
@@ -169,9 +138,9 @@ impl From<FalErrorPlus> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InnerError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
impl From<StorytellerError> for InnerError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +156,19 @@ impl From<std::io::Error> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InnerError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn image_to_3d(
|
||||
request: FalHunyuanImageTo3dRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
@@ -196,7 +178,6 @@ pub async fn image_to_3d(
|
||||
) -> Result<(), InnerError> {
|
||||
|
||||
let api_key = fal_creds_manager.get_key_required()?;
|
||||
let creds = storyteller_creds_manager.get_credentials_required()?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
|
||||
+27
-45
@@ -1,3 +1,4 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
@@ -8,52 +9,19 @@ use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
use log::{debug, error, info, warn};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use reqwest::Url;
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::{enqueue_kling_16_pro_image_to_video, Kling16ProArgs, Kling16ProAspectRatio, Kling16ProDuration};
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::read_to_string;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::ops::Add;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use tempfile::NamedTempFile;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -157,9 +125,10 @@ pub async fn fal_kling_image_to_video_command(
|
||||
enum InnerError {
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerApiError(ApiError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InnerError {
|
||||
@@ -174,9 +143,9 @@ impl From<FalErrorPlus> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InnerError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
impl From<StorytellerError> for InnerError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +161,18 @@ impl From<std::io::Error> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InnerError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn image_to_video(
|
||||
request: FalKlingImageToVideoRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
@@ -218,10 +199,11 @@ pub async fn image_to_video(
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let enqueued = enqueue_kling_16_image_to_video(Kling16Args {
|
||||
let enqueued = enqueue_kling_16_pro_image_to_video(Kling16ProArgs {
|
||||
image_path: filename,
|
||||
api_key: &api_key,
|
||||
duration: Kling16Duration::Default,
|
||||
duration: Kling16ProDuration::Default,
|
||||
aspect_ratio: Kling16ProAspectRatio::WideSixteenNine,
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
}).await?;
|
||||
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::core::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::sora_error::SoraError::AnyhowError;
|
||||
use primitives::traits::trim_or_emptyable::TrimOrEmptyable;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use log::error;
|
||||
use serde_derive::Serialize;
|
||||
use tauri::State;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GetFalApiKeyResponse {
|
||||
|
||||
@@ -2,4 +2,6 @@ pub mod fal_background_removal_command;
|
||||
pub mod fal_hunyuan_image_to_3d_command;
|
||||
pub mod fal_kling_image_to_video_command;
|
||||
pub mod get_fal_api_key_command;
|
||||
pub mod set_fal_api_key_command;
|
||||
pub mod set_fal_api_key_command;
|
||||
// pub mod get_open_ai_api_key_command;
|
||||
// pub mod set_open_ai_api_key_command;
|
||||
@@ -1,15 +1,10 @@
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::core::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::sora_error::SoraError::AnyhowError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use serde_derive::Deserialize;
|
||||
use tauri::State;
|
||||
|
||||
/// For now, we'll only update a single preference at a time.
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use crate::services::fal::state::read_fal_api_key_from_disk::read_fal_api_key_from_disk;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::read_fal_api_key_from_disk::read_fal_api_key_from_disk;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use memory_store::clone_slot::CloneSlot;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::{fs, io};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -42,6 +41,9 @@ impl FalCredentialManager {
|
||||
}
|
||||
|
||||
pub fn set_key(&self, key: &FalApiKey) -> AnyhowResult<()> {
|
||||
if key.0.trim().is_empty() {
|
||||
return Err(anyhow!("FAL API key cannot be empty")); // TODO: Also handle upstream.
|
||||
}
|
||||
self.key.set_clone(key)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,20 @@
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::common::GenerationServiceProvider;
|
||||
use crate::core::events::generation_events::generation_complete_event::GenerationCompleteEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::download_url_to_temp_dir::download_url_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::export::queue::Status;
|
||||
use fal_client::model::fal_request_id::FalRequestId;
|
||||
use fal_client::utils::queue_status_checker::QueueStatusChecker;
|
||||
use log::{error, info, warn};
|
||||
use mimetypes::mimetype_for_file::get_mimetype_for_file;
|
||||
use mimetypes::mimetype_info::file_extension::FileExtension;
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use openai_sora_client::requests::image_gen::image_gen_status::{Generation, TaskId, TaskStatus};
|
||||
use serde_derive::Serialize;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::media_files::upload_new_engine_asset_from_file::upload_new_engine_asset_from_file;
|
||||
use storyteller_client::media_files::upload_video_media_file_from_file::{upload_video_media_file_from_file, UploadVideoMediaFileSuccessResponse};
|
||||
use storyteller_client::recipes::upload_media_file_from_file::upload_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tempdir::TempDir;
|
||||
use tempfile::NamedTempFile;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use url::Url;
|
||||
|
||||
pub async fn fal_task_polling_thread(
|
||||
app_handle: AppHandle,
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
use crate::core::commands::app_preferences::get_app_preferences_command::AppPreferencesPayload;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::windows::sora_login_window::open_sora_login_window::open_sora_login_window;
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use chrono::{TimeDelta, Utc};
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use once_cell::sync::Lazy;
|
||||
use openai_sora_client::creds::sora_credential_set::SoraCredentialSet;
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::list_media::list_media::list_media;
|
||||
use serde_derive::Serialize;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::windows::sora_login_window::open_sora_login_window::open_sora_login_window;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use once_cell::sync::Lazy;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use errors::AnyhowResult;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, ImageReader};
|
||||
use log::{error, info};
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use std::fs::read_to_string;
|
||||
use std::io::Cursor;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
const SORA_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
|
||||
@@ -9,36 +9,21 @@ use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, ImageReader};
|
||||
use log::{debug, error, info, warn};
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
|
||||
use errors::AnyhowError;
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::read_to_string;
|
||||
use std::io::Cursor;
|
||||
use std::ops::Add;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
const SORA_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
@@ -199,7 +184,7 @@ pub async fn sora_image_remix_command(
|
||||
enum InnerError {
|
||||
SoraError(SoraError),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerApiError(ApiError),
|
||||
StorytellerError(StorytellerError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InnerError {
|
||||
@@ -214,9 +199,9 @@ impl From<SoraError> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InnerError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
impl From<StorytellerError> for InnerError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
use crate::core::commands::app_preferences::get_app_preferences_command::AppPreferencesPayload;
|
||||
use crate::core::commands::response::shorthand::SimpleResponse;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::windows::sora_login_window::open_sora_login_window::open_sora_login_window;
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use log::{error, info};
|
||||
use once_cell::sync::Lazy;
|
||||
use openai_sora_client::creds::sora_credential_set::SoraCredentialSet;
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::list_media::list_media::list_media;
|
||||
use serde_derive::Serialize;
|
||||
use std::fmt::format;
|
||||
use tauri::{AppHandle, State};
|
||||
use tauri::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sora_logout_command(
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_stats::SoraCredentialStats;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info, warn};
|
||||
use memory_store::clone_cell::CloneCell;
|
||||
|
||||
@@ -13,15 +13,13 @@ use log::{error, info};
|
||||
use openai_sora_client::recipes::list_sora_task_status_with_session_auto_renew::{list_sora_task_status_with_session_auto_renew, StatusRequestArgs};
|
||||
use openai_sora_client::requests::image_gen::image_gen_status::{Generation, TaskId, TaskStatus};
|
||||
use reqwest::Url;
|
||||
use serde_derive::Serialize;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tauri::AppHandle;
|
||||
use tempdir::TempDir;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
pub async fn sora_task_polling_thread(
|
||||
app_handle: AppHandle,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use tauri::Webview;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
// This is just so we have a way to clear browsing data while debugging / building.
|
||||
pub fn clear_browsing_data_on_test_domain(webview: &Webview) -> AnyhowResult<()> {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::core::utils::clear_all_webview_cookies::clear_all_webview_cookies;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::windows::sora_login_window::sora_login_thread::sora_login_thread;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
+3
-12
@@ -1,25 +1,16 @@
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::events::sora_login_success_event::SoraLoginSuccessEvent;
|
||||
use crate::services::sora::state::initialize_sora_jwt_bearer_token::initialize_sora_jwt_bearer_token;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::windows::sora_login_window::extract_sora_webview_cookies::extract_sora_webview_cookies;
|
||||
use crate::services::sora::windows::sora_login_window::open_sora_login_window::LOGIN_WINDOW_NAME;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, NaiveDateTime, TimeDelta};
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::creds::sora_credential_set::SoraCredentialSet;
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::utils::has_session_cookie::{has_session_cookie, SessionCookiePresence};
|
||||
use reqwest::Url;
|
||||
use std::fs;
|
||||
use std::fs::{read_to_string, OpenOptions};
|
||||
use std::io::Write;
|
||||
use std::ops::Sub;
|
||||
use tauri::{AppHandle, Manager, Webview, WebviewWindow};
|
||||
use tauri::{AppHandle, Manager, WebviewWindow};
|
||||
|
||||
pub async fn sora_login_thread(
|
||||
app: AppHandle,
|
||||
|
||||
+8
-4
@@ -1,11 +1,8 @@
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::read_storyteller_credentials_from_disk::read_storyteller_credentials_from_disk;
|
||||
use crate::services::storyteller::state::storyteller_credential_holder::StorytellerCredentialHolder;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info, warn};
|
||||
use log::{info, warn};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use storyteller_client::credentials::storyteller_avt_cookie::StorytellerAvtCookie;
|
||||
@@ -20,6 +17,13 @@ pub struct StorytellerCredentialManager {
|
||||
|
||||
impl StorytellerCredentialManager {
|
||||
|
||||
pub fn initialize_empty(app_data_root: &AppDataRoot) -> Self {
|
||||
Self {
|
||||
holder: StorytellerCredentialHolder::new(),
|
||||
app_data_root: app_data_root.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_from_disk_infallible(app_data_root: &AppDataRoot) -> Self {
|
||||
let holder = StorytellerCredentialHolder::new();
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"width": 2400,
|
||||
"height": 1300,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
"fullscreen": false,
|
||||
"dragDropEnabled": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
@@ -33,7 +34,7 @@
|
||||
"https://www.googletagmanager.com",
|
||||
"ipc://localhost",
|
||||
"tauri://localhost"
|
||||
],
|
||||
],
|
||||
"connect-src": [
|
||||
"'self'",
|
||||
"'unsafe-eval'",
|
||||
|
||||
@@ -23,6 +23,7 @@ errors = { workspace = true }
|
||||
# External
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
log = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
+13
-10
@@ -7,22 +7,25 @@ use fal::endpoints::fal_ai::flux_pro::v1_1_ultra::FluxProUltraTextToImageInput;
|
||||
use futures::StreamExt;
|
||||
use std::io::Write;
|
||||
|
||||
pub struct FluxProUltraTextToImageArgs<'a> {
|
||||
pub struct FluxPro11UltraTextToImageArgs<'a> {
|
||||
pub prompt: &'a str,
|
||||
pub api_key: &'a FalApiKey,
|
||||
// TODO: Size parameter
|
||||
}
|
||||
|
||||
pub async fn enqueue_flux_pro_ultra_text_to_image(args: FluxProUltraTextToImageArgs<'_>) -> Result<EnqueuedRequest, FalErrorPlus> {
|
||||
pub async fn enqueue_flux_pro_11_ultra_text_to_image(args: FluxPro11UltraTextToImageArgs<'_>) -> Result<EnqueuedRequest, FalErrorPlus> {
|
||||
let request = FluxProUltraTextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
safety_tolerance: Some("5".to_string()), // 1 is most strict, 5 is most permissive
|
||||
enable_safety_checker: Some(false),
|
||||
num_images: Some(1), // Default is 1
|
||||
output_format: Some("png".to_string()),
|
||||
raw: Some(true), // Generate less processed, more natural-looking images. Default is false.
|
||||
// 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
|
||||
};
|
||||
|
||||
@@ -42,7 +45,7 @@ pub async fn enqueue_flux_pro_ultra_text_to_image(args: FluxProUltraTextToImageA
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::queue::image_gen::enqueue_flux_pro_ultra_text_to_image::{enqueue_flux_pro_ultra_text_to_image, FluxProUltraTextToImageArgs};
|
||||
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;
|
||||
|
||||
@@ -54,12 +57,12 @@ mod tests {
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = FluxProUltraTextToImageArgs {
|
||||
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_ultra_text_to_image(args).await?;
|
||||
let result = enqueue_flux_pro_11_ultra_text_to_image(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod enqueue_flux_pro_ultra_text_to_image;
|
||||
pub mod enqueue_flux_pro_11_ultra_text_to_image;
|
||||
pub mod enqueue_recraft3_text_to_image;
|
||||
@@ -22,7 +22,7 @@ pub async fn remove_background_rembg<P: AsRef<Path>>(image_path: P, api_key: &Fa
|
||||
crop_to_bbox: None,
|
||||
sync_mode: None
|
||||
};
|
||||
|
||||
|
||||
let result = rembg(request)
|
||||
.with_api_key(&api_key.0)
|
||||
.queue()
|
||||
@@ -56,6 +56,7 @@ 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 testing::test_file_path::test_file_path;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -63,9 +64,9 @@ mod tests {
|
||||
async fn test_remove_background_rembg() -> AnyhowResult<()> {
|
||||
let image = test_file_path("test_data/image/juno.jpg")?;
|
||||
|
||||
// TODO: DO NOT COMMIT SECRET !
|
||||
const KEY : &str = "DO NOT COMMIT";
|
||||
let api_key = FalApiKey::from_str(KEY);
|
||||
// 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?;
|
||||
|
||||
|
||||
+29
-13
@@ -8,35 +8,50 @@ use futures::StreamExt;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
pub struct Kling16Args<'a, P: AsRef<Path>> {
|
||||
pub struct Kling16ProArgs<'a, P: AsRef<Path>> {
|
||||
pub image_path: P,
|
||||
pub prompt: &'a str,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub duration: Kling16Duration,
|
||||
pub duration: Kling16ProDuration,
|
||||
pub aspect_ratio: Kling16ProAspectRatio,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Kling16Duration {
|
||||
pub enum Kling16ProDuration {
|
||||
Default,
|
||||
FiveSeconds,
|
||||
TenSeconds,
|
||||
}
|
||||
|
||||
pub async fn enqueue_kling_16_image_to_video<P: AsRef<Path>>(args: Kling16Args<'_, P>) -> Result<EnqueuedRequest, FalErrorPlus> {
|
||||
#[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{
|
||||
Kling16Duration::Default => None,
|
||||
Kling16Duration::FiveSeconds => Some("5".to_string()), // Gross...
|
||||
Kling16Duration::TenSeconds => Some("10".to_string()),
|
||||
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: None,
|
||||
cfg_scale: None,
|
||||
aspect_ratio,
|
||||
duration,
|
||||
// Maybe expose these later
|
||||
cfg_scale: None,
|
||||
negative_prompt: None,
|
||||
tail_image_url: None,
|
||||
};
|
||||
@@ -57,7 +72,7 @@ pub async fn enqueue_kling_16_image_to_video<P: AsRef<Path>>(args: Kling16Args<'
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::queue::video_gen::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
|
||||
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 testing::test_file_path::test_file_path;
|
||||
@@ -72,14 +87,15 @@ mod tests {
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = Kling16Args {
|
||||
let args = Kling16ProArgs {
|
||||
image_path: image,
|
||||
prompt: "a corgi looks out over the water",
|
||||
api_key: &api_key,
|
||||
duration: Kling16Duration::Default,
|
||||
duration: Kling16ProDuration::Default,
|
||||
aspect_ratio: Kling16ProAspectRatio::Square,
|
||||
};
|
||||
|
||||
let result = enqueue_kling_16_image_to_video(args).await?;
|
||||
let result = enqueue_kling_16_pro_image_to_video(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod enqueue_kling_16_image_to_video;
|
||||
pub mod enqueue_kling_16_pro_image_to_video;
|
||||
pub mod enqueue_minimax_01_image_to_video;
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
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::flux::dev::dev;
|
||||
use fal::endpoints::fal_ai::flux::dev::DevTextToImageInput;
|
||||
use fal::prelude::fal_ai::flux::dev::ImageSizeProperty;
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Flux1DevArgs<'a, U: IntoUrl> {
|
||||
pub prompt: &'a str,
|
||||
pub webhook_url: U,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub aspect_ratio: Flux1DevAspectRatio,
|
||||
pub num_images: Flux1DevNumImages,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Flux1DevAspectRatio {
|
||||
Square, // 1:1
|
||||
SquareHd, // 1:1
|
||||
LandscapeFourByThree, // 4:3
|
||||
LandscapeSixteenByNine, // 16:9
|
||||
PortraitThreeByFour, // 3:4
|
||||
PortraitNineBySixteen, // 9:16
|
||||
//Custom { width: u32, height: u32 }, // TODO
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Flux1DevNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
pub async fn enqueue_flux_1_dev_text_to_image_webhook<U: IntoUrl>(
|
||||
args: Flux1DevArgs<'_, U>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
Flux1DevNumImages::One => 1,
|
||||
Flux1DevNumImages::Two => 2,
|
||||
Flux1DevNumImages::Three => 3,
|
||||
Flux1DevNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let image_size = match args.aspect_ratio {
|
||||
Flux1DevAspectRatio::Square => ImageSizeProperty::Square,
|
||||
Flux1DevAspectRatio::SquareHd => ImageSizeProperty::SquareHd,
|
||||
Flux1DevAspectRatio::LandscapeFourByThree => ImageSizeProperty::Landscape43,
|
||||
Flux1DevAspectRatio::LandscapeSixteenByNine => ImageSizeProperty::Landscape169,
|
||||
Flux1DevAspectRatio::PortraitThreeByFour => ImageSizeProperty::Portrait43,
|
||||
Flux1DevAspectRatio::PortraitNineBySixteen => ImageSizeProperty::Portrait169,
|
||||
};
|
||||
|
||||
let request = DevTextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
num_images: Some(num_images),
|
||||
image_size: Some(image_size),
|
||||
// Maybe abstract
|
||||
enable_safety_checker: Some(false),
|
||||
// Maybe expose
|
||||
seed: None,
|
||||
guidance_scale: None,
|
||||
num_inference_steps: None,
|
||||
// Constants
|
||||
sync_mode: None, // Synchronous / slow
|
||||
};
|
||||
|
||||
let result = dev(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::enqueue_flux_1_dev_text_to_image_webhook::{enqueue_flux_1_dev_text_to_image_webhook, Flux1DevArgs, Flux1DevAspectRatio, Flux1DevNumImages};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_flux_1_dev() -> 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 = Flux1DevArgs {
|
||||
prompt: "a giant robot fighting a dragon in a futuristic city",
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
num_images: Flux1DevNumImages::One,
|
||||
aspect_ratio: Flux1DevAspectRatio::LandscapeFourByThree
|
||||
};
|
||||
|
||||
let result = enqueue_flux_1_dev_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
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::flux::schnell::schnell;
|
||||
use fal::endpoints::fal_ai::flux::schnell::SchnellTextToImageInput;
|
||||
use fal::prelude::fal_ai::flux::schnell::ImageSizeProperty;
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Flux1SchnellArgs<'a, U: IntoUrl> {
|
||||
pub prompt: &'a str,
|
||||
pub webhook_url: U,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub aspect_ratio: Flux1SchnellAspectRatio,
|
||||
pub num_images: Flux1SchnellNumImages,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Flux1SchnellAspectRatio {
|
||||
Square, // 1:1
|
||||
SquareHd, // 1:1
|
||||
LandscapeFourByThree, // 4:3
|
||||
LandscapeSixteenByNine, // 16:9
|
||||
PortraitThreeByFour, // 3:4
|
||||
PortraitNineBySixteen, // 9:16
|
||||
//Custom { width: u32, height: u32 }, // TODO
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Flux1SchnellNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
pub async fn enqueue_flux_1_schnell_text_to_image_webhook<U: IntoUrl>(
|
||||
args: Flux1SchnellArgs<'_, U>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
Flux1SchnellNumImages::One => 1,
|
||||
Flux1SchnellNumImages::Two => 2,
|
||||
Flux1SchnellNumImages::Three => 3,
|
||||
Flux1SchnellNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let image_size = match args.aspect_ratio {
|
||||
Flux1SchnellAspectRatio::Square => ImageSizeProperty::Square,
|
||||
Flux1SchnellAspectRatio::SquareHd => ImageSizeProperty::SquareHd,
|
||||
Flux1SchnellAspectRatio::LandscapeFourByThree => ImageSizeProperty::Landscape43,
|
||||
Flux1SchnellAspectRatio::LandscapeSixteenByNine => ImageSizeProperty::Landscape169,
|
||||
Flux1SchnellAspectRatio::PortraitThreeByFour => ImageSizeProperty::Portrait43,
|
||||
Flux1SchnellAspectRatio::PortraitNineBySixteen => ImageSizeProperty::Portrait169,
|
||||
};
|
||||
|
||||
let request = SchnellTextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
num_images: Some(num_images),
|
||||
image_size: Some(image_size),
|
||||
// Maybe abstract
|
||||
enable_safety_checker: Some(false),
|
||||
// Maybe expose
|
||||
seed: None,
|
||||
num_inference_steps: None,
|
||||
// Constants
|
||||
sync_mode: None, // Synchronous / slow
|
||||
};
|
||||
|
||||
let result = schnell(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::enqueue_flux_1_schnell_text_to_image_webhook::{enqueue_flux_1_schnell_text_to_image_webhook, Flux1SchnellArgs, Flux1SchnellAspectRatio, Flux1SchnellNumImages};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_flux_1_schnell() -> 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 = Flux1SchnellArgs {
|
||||
prompt: "a giant robot fighting a dragon in a futuristic city",
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
num_images: Flux1SchnellNumImages::One,
|
||||
aspect_ratio: Flux1SchnellAspectRatio::LandscapeSixteenByNine,
|
||||
};
|
||||
|
||||
let result = enqueue_flux_1_schnell_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
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::flux_pro::v1_1::{v1_1, FluxProPlusTextToImageInput, ImageSizeProperty};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct FluxPro11Args<'a, U: IntoUrl> {
|
||||
pub prompt: &'a str,
|
||||
pub webhook_url: U,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub aspect_ratio: FluxPro11AspectRatio,
|
||||
pub num_images: FluxPro11NumImages,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FluxPro11AspectRatio {
|
||||
Square, // 1:1
|
||||
SquareHd, // 1:1
|
||||
LandscapeFourByThree, // 4:3
|
||||
LandscapeSixteenByNine, // 16:9
|
||||
PortraitThreeByFour, // 3:4
|
||||
PortraitNineBySixteen, // 9:16
|
||||
//Custom { width: u32, height: u32 }, // TODO
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FluxPro11NumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
pub async fn enqueue_flux_pro_11_text_to_image_webhook<U: IntoUrl>(
|
||||
args: FluxPro11Args<'_, U>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
FluxPro11NumImages::One => 1,
|
||||
FluxPro11NumImages::Two => 2,
|
||||
FluxPro11NumImages::Three => 3,
|
||||
FluxPro11NumImages::Four => 4,
|
||||
};
|
||||
|
||||
let image_size = match args.aspect_ratio {
|
||||
FluxPro11AspectRatio::Square => ImageSizeProperty::Square,
|
||||
FluxPro11AspectRatio::SquareHd => ImageSizeProperty::SquareHd,
|
||||
FluxPro11AspectRatio::LandscapeFourByThree => ImageSizeProperty::Landscape43,
|
||||
FluxPro11AspectRatio::LandscapeSixteenByNine => ImageSizeProperty::Landscape169,
|
||||
FluxPro11AspectRatio::PortraitThreeByFour => ImageSizeProperty::Portrait43,
|
||||
FluxPro11AspectRatio::PortraitNineBySixteen => ImageSizeProperty::Portrait169,
|
||||
};
|
||||
|
||||
let request = FluxProPlusTextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
num_images: Some(num_images),
|
||||
image_size: Some(image_size),
|
||||
// Maybe expose
|
||||
seed: None,
|
||||
// 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(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::enqueue_flux_pro_11_text_to_image_webhook::{enqueue_flux_pro_11_text_to_image_webhook, FluxPro11Args, FluxPro11AspectRatio, FluxPro11NumImages};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_flux_11_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 = FluxPro11Args {
|
||||
prompt: "a giant red panda fighting a dragon in a futuristic city",
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
num_images: FluxPro11NumImages::One,
|
||||
aspect_ratio: FluxPro11AspectRatio::LandscapeSixteenByNine,
|
||||
};
|
||||
|
||||
let result = enqueue_flux_pro_11_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
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::flux_pro::v1_1_ultra::{v1_1_ultra, AspectRatioProperty, FluxProUltraTextToImageInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct FluxPro11UltraArgs<'a, U: IntoUrl> {
|
||||
pub prompt: &'a str,
|
||||
pub webhook_url: U,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub aspect_ratio: FluxPro11UltraAspectRatio,
|
||||
pub num_images: FluxPro11UltraNumImages,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FluxPro11UltraAspectRatio {
|
||||
Square, // 1:1
|
||||
LandscapeFourByThree, // 4:3
|
||||
LandscapeSixteenByNine, // 16:9
|
||||
PortraitThreeByFour, // 3:4
|
||||
PortraitNineBySixteen, // 9:16
|
||||
//Custom { width: u32, height: u32 }, // TODO
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FluxPro11UltraNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
pub async fn enqueue_flux_pro_11_ultra_text_to_image_webhook<U: IntoUrl>(
|
||||
args: FluxPro11UltraArgs<'_, U>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
FluxPro11UltraNumImages::One => 1,
|
||||
FluxPro11UltraNumImages::Two => 2,
|
||||
FluxPro11UltraNumImages::Three => 3,
|
||||
FluxPro11UltraNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let aspect_ratio = match args.aspect_ratio {
|
||||
FluxPro11UltraAspectRatio::Square => AspectRatioProperty::Property_1_1,
|
||||
FluxPro11UltraAspectRatio::LandscapeFourByThree => AspectRatioProperty::Property_4_3,
|
||||
FluxPro11UltraAspectRatio::LandscapeSixteenByNine => AspectRatioProperty::Property_16_9,
|
||||
FluxPro11UltraAspectRatio::PortraitThreeByFour => AspectRatioProperty::Property_3_4,
|
||||
FluxPro11UltraAspectRatio::PortraitNineBySixteen => AspectRatioProperty::Property_9_16,
|
||||
};
|
||||
|
||||
let request = FluxProUltraTextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
num_images: Some(num_images),
|
||||
aspect_ratio: Some(aspect_ratio),
|
||||
// Maybe expose
|
||||
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_webhook(args.webhook_url)
|
||||
.await;
|
||||
|
||||
result.map_err(|err| classify_fal_error(err))
|
||||
}
|
||||
@@ -1 +1,5 @@
|
||||
pub mod enqueue_flux_1_dev_text_to_image_webhook;
|
||||
pub mod enqueue_flux_1_schnell_text_to_image_webhook;
|
||||
pub mod enqueue_flux_pro_11_text_to_image_webhook;
|
||||
pub mod enqueue_flux_pro_11_ultra_text_to_image_webhook;
|
||||
pub mod remove_background_rembg_webhook;
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
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::prelude::fal_ai::hunyuan3d::v21::{v21, Hunyuan3DInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Hunyuan3d21Args<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub webhook_url: V,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
pub async fn enqueue_hunyuan_3d_2_1_image_to_3d_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Hunyuan3d21Args<'_, U, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let image_url = args.image_url.as_str().to_string();
|
||||
|
||||
let request = Hunyuan3DInput {
|
||||
input_image_url: image_url,
|
||||
textured_mesh: Some(true),
|
||||
// TODO: Maybe expose these later
|
||||
guidance_scale: None,
|
||||
num_inference_steps: None,
|
||||
octree_resolution: None,
|
||||
seed: None,
|
||||
};
|
||||
|
||||
let result = v21(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::object::enqueue_hunyuan_3d_21_image_to_3d_webhook::{enqueue_hunyuan_3d_2_1_image_to_3d_webhook, Hunyuan3d21Args};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_hunyuan3d_21() -> AnyhowResult<()> {
|
||||
let image_url = "https://cdn-2.fakeyou.com/media/p/a/c/7/j/pac7jgp2tkehm7j7sm4sky1fpnkrnbve/image_pac7jgp2tkehm7j7sm4sky1fpnkrnbve.png";
|
||||
|
||||
// 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 = Hunyuan3d21Args {
|
||||
image_url: image_url,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_hunyuan_3d_2_1_image_to_3d_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+3
-10
@@ -5,21 +5,14 @@ use fal::endpoints::fal_ai::hunyuan3d::v2::{v2, Hunyuan3DInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Hunyuan2Args<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub struct Hunyuan3d2Args<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub webhook_url: V,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Kling16Duration {
|
||||
Default,
|
||||
FiveSeconds,
|
||||
TenSeconds,
|
||||
}
|
||||
|
||||
pub async fn enqueue_hunyuan2_image_to_3d_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Hunyuan2Args<'_, U, V>
|
||||
pub async fn enqueue_hunyuan_3d_2_image_to_3d_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Hunyuan3d2Args<'_, U, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let image_url = args.image_url.as_str().to_string();
|
||||
@@ -1 +1,2 @@
|
||||
pub mod enqueue_hunyuan2_image_to_3d_webhook;
|
||||
pub mod enqueue_hunyuan_3d_2_image_to_3d_webhook;
|
||||
pub mod enqueue_hunyuan_3d_21_image_to_3d_webhook;
|
||||
+20
-5
@@ -5,12 +5,13 @@ use fal::endpoints::fal_ai::kling_video::v1_6::pro::image_to_video::{image_to_vi
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Kling16Args<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub struct Kling16ProArgs<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub webhook_url: V,
|
||||
pub prompt: &'a str,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub duration: Kling16Duration,
|
||||
pub aspect_ratio: Kling16ProAspectRatio,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -20,8 +21,15 @@ pub enum Kling16Duration {
|
||||
TenSeconds,
|
||||
}
|
||||
|
||||
pub async fn enqueue_kling_16_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Kling16Args<'_, U, V>
|
||||
#[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_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Kling16ProArgs<'_, U, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
let duration = match args.duration {
|
||||
Kling16Duration::Default => None,
|
||||
@@ -29,14 +37,21 @@ pub async fn enqueue_kling_16_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
Kling16Duration::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 image_url = args.image_url.as_str().to_string();
|
||||
|
||||
let request = ProImageToVideoRequest {
|
||||
image_url,
|
||||
prompt: args.prompt.to_string(),
|
||||
aspect_ratio: None,
|
||||
cfg_scale: None,
|
||||
aspect_ratio,
|
||||
duration,
|
||||
// Maybe expose these later
|
||||
cfg_scale: None,
|
||||
negative_prompt: None,
|
||||
tail_image_url: None,
|
||||
};
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
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::kling_video::v2_1::master::image_to_video::{image_to_video, MasterImageToVideoRequest};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Kling21MasterArgs<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub webhook_url: V,
|
||||
pub prompt: &'a str,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub duration: Kling21MasterDuration,
|
||||
pub aspect_ratio: Kling21MasterAspectRatio,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Kling21MasterDuration {
|
||||
Default,
|
||||
FiveSeconds,
|
||||
TenSeconds,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Kling21MasterAspectRatio {
|
||||
Square, // 1:1
|
||||
WideSixteenNine, // 16:9
|
||||
TallNineSixteen, // 9:16
|
||||
}
|
||||
|
||||
pub async fn enqueue_kling_21_master_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Kling21MasterArgs<'_, U, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
let duration = match args.duration {
|
||||
Kling21MasterDuration::Default => None,
|
||||
Kling21MasterDuration::FiveSeconds => Some("5".to_string()), // Gross...
|
||||
Kling21MasterDuration::TenSeconds => Some("10".to_string()),
|
||||
};
|
||||
|
||||
let aspect_ratio = match args.aspect_ratio {
|
||||
Kling21MasterAspectRatio::Square => Some("1:1".to_string()),
|
||||
Kling21MasterAspectRatio::WideSixteenNine => Some("16:9".to_string()),
|
||||
Kling21MasterAspectRatio::TallNineSixteen => Some("9:16".to_string()),
|
||||
};
|
||||
|
||||
let image_url = args.image_url.as_str().to_string();
|
||||
|
||||
let request = MasterImageToVideoRequest {
|
||||
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_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::video::enqueue_kling_21_master_image_to_video_webhook::{enqueue_kling_21_master_image_to_video_webhook, Kling21MasterArgs, Kling21MasterAspectRatio, Kling21MasterDuration};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_kling21_master_video() -> AnyhowResult<()> {
|
||||
let image_url = "https://cdn-2.fakeyou.com/media/3/4/h/f/s/34hfsmt8e38rvne6mwa4pwbxr6292sgy/image_34hfsmt8e38rvne6mwa4pwbxr6292sgy.png";
|
||||
|
||||
// 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 = Kling21MasterArgs {
|
||||
image_url: image_url,
|
||||
prompt: "a shot of the mountains, the camera rotates around to show the mountain range, the sun begins to set, and the moon becomes visible",
|
||||
api_key: &api_key,
|
||||
duration: Kling21MasterDuration::Default,
|
||||
aspect_ratio: Kling21MasterAspectRatio::WideSixteenNine,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_kling_21_master_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
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::kling_video::v2_1::pro::image_to_video::{image_to_video, ProImageToVideoRequest};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Kling21ProArgs<'a, U: IntoUrl, V: IntoUrl> {
|
||||
pub image_url: U,
|
||||
pub webhook_url: V,
|
||||
pub prompt: &'a str,
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub duration: Kling21ProDuration,
|
||||
pub aspect_ratio: Kling21ProAspectRatio,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Kling21ProDuration {
|
||||
Default,
|
||||
FiveSeconds,
|
||||
TenSeconds,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Kling21ProAspectRatio {
|
||||
Square, // 1:1
|
||||
WideSixteenNine, // 16:9
|
||||
TallNineSixteen, // 9:16
|
||||
}
|
||||
|
||||
pub async fn enqueue_kling_21_pro_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
args: Kling21ProArgs<'_, U, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
let duration = match args.duration {
|
||||
Kling21ProDuration::Default => None,
|
||||
Kling21ProDuration::FiveSeconds => Some("5".to_string()), // Gross...
|
||||
Kling21ProDuration::TenSeconds => Some("10".to_string()),
|
||||
};
|
||||
|
||||
let aspect_ratio = match args.aspect_ratio {
|
||||
Kling21ProAspectRatio::Square => Some("1:1".to_string()),
|
||||
Kling21ProAspectRatio::WideSixteenNine => Some("16:9".to_string()),
|
||||
Kling21ProAspectRatio::TallNineSixteen => Some("9:16".to_string()),
|
||||
};
|
||||
|
||||
let image_url = args.image_url.as_str().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_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::video::enqueue_kling_21_pro_image_to_video_webhook::{enqueue_kling_21_pro_image_to_video_webhook, Kling21ProArgs, Kling21ProAspectRatio, Kling21ProDuration};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_kling21_pro_video() -> AnyhowResult<()> {
|
||||
let image_url = "https://cdn-2.fakeyou.com/media/3/4/h/f/s/34hfsmt8e38rvne6mwa4pwbxr6292sgy/image_34hfsmt8e38rvne6mwa4pwbxr6292sgy.png";
|
||||
|
||||
// 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 = Kling21ProArgs {
|
||||
image_url: image_url,
|
||||
prompt: "a shot of the mountains, the camera rotates around to show the mountain range",
|
||||
api_key: &api_key,
|
||||
duration: Kling21ProDuration::Default,
|
||||
aspect_ratio: Kling21ProAspectRatio::WideSixteenNine,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_kling_21_pro_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user