mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-02 07:45:41 -05:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
617aa75dc5 |
@@ -1,107 +0,0 @@
|
||||
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 }}
|
||||
@@ -1,96 +0,0 @@
|
||||
name: "Artcraft Windows Publish"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
- artcraft-release
|
||||
- artcraft-windows # Additional branch for Windows testing
|
||||
|
||||
# This workflow creates Windows builds.
|
||||
# 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-windows:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Windows builds
|
||||
- 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
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: |
|
||||
# Navigate to the frontend directory to install dependencies
|
||||
cd ./frontend
|
||||
|
||||
yarn cache clean
|
||||
|
||||
echo "Installing all workspace dependencies..."
|
||||
yarn install --production=false --frozen-lockfile
|
||||
|
||||
echo "=== Version Information ==="
|
||||
echo "Node version:"
|
||||
node --version
|
||||
echo "NPM version:"
|
||||
npm --version
|
||||
echo "Yarn version:"
|
||||
yarn --version
|
||||
|
||||
# Remove mismatched @nx packages and reinstall with correct versions
|
||||
yarn add @nx/remix@20.7.2 @nx/js@20.7.2 @nx/react@20.7.2 @nx/vite@20.7.2 @nx/eslint@20.7.2 --dev -W
|
||||
|
||||
echo "=== Nx versions after adjustment ==="
|
||||
yarn list --pattern "nx" --depth=0
|
||||
|
||||
- name: Pre-build frontend to avoid Tauri build issues
|
||||
run: |
|
||||
# Build the frontend app from the correct directory
|
||||
cd ./frontend
|
||||
|
||||
# Increase Node.js memory limit for the build
|
||||
$env:NODE_OPTIONS="--max-old-space-size=8192"
|
||||
|
||||
# Use npx to ensure we use the local nx version
|
||||
echo "Building with npx nx..."
|
||||
npx nx run editor3d:build
|
||||
|
||||
echo "Checking build output..."
|
||||
dir apps/editor3d/dist/ | Select-Object -First 10
|
||||
|
||||
# Update Tauri config to skip the build (since we already built)
|
||||
cd ${{ github.workspace }}/crates/desktop/artcraft
|
||||
Copy-Item tauri.conf.json tauri.conf.json.bak
|
||||
|
||||
# Set beforeBuildCommand to empty using PowerShell
|
||||
$config = Get-Content tauri.conf.json | ConvertFrom-Json
|
||||
$config.build.beforeBuildCommand = ""
|
||||
$config | ConvertTo-Json -Depth 10 | Set-Content tauri.conf.json
|
||||
|
||||
- name: build and publish
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
projectPath: ./crates/desktop/artcraft
|
||||
tagName: artcraft-windows-v__VERSION__ # the action automatically replaces __VERSION__ with the app version.
|
||||
releaseName: "Artcraft Windows v__VERSION__"
|
||||
releaseBody: "Windows build - See the assets to download this version and install."
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
||||
Generated
+1643
-1972
File diff suppressed because it is too large
Load Diff
+13
-19
@@ -4,18 +4,6 @@ resolver = "2"
|
||||
# This block defines the workspace members.
|
||||
# When we add a library or binary crate, it needs an entry here.
|
||||
members = [
|
||||
# API clients for external services
|
||||
# These might be custom implementations or wrappers over other client libraries.
|
||||
"crates/api_clients/cloud_storage",
|
||||
"crates/api_clients/fal_client",
|
||||
"crates/api_clients/google_drive_common",
|
||||
"crates/api_clients/google_sign_in",
|
||||
"crates/api_clients/openai_sora_client",
|
||||
"crates/api_clients/openart_client",
|
||||
"crates/api_clients/runwayml_client",
|
||||
"crates/api_clients/storyteller_client",
|
||||
"crates/api_clients/thumbnail_generator",
|
||||
|
||||
# CLI tools for operations, etc.
|
||||
"crates/cli/devenv/dev-database-seed",
|
||||
"crates/cli/devenv/dev-upload-media-file",
|
||||
@@ -34,6 +22,15 @@ members = [
|
||||
# Shared libraries
|
||||
"crates/lib/actix_cors_configs",
|
||||
"crates/lib/actix_helpers",
|
||||
"crates/lib/api_clients/cloud_storage",
|
||||
"crates/lib/api_clients/fal_client",
|
||||
"crates/lib/api_clients/google_drive_common",
|
||||
"crates/lib/api_clients/google_sign_in",
|
||||
"crates/lib/api_clients/openai_sora_client",
|
||||
"crates/lib/api_clients/openart_client",
|
||||
"crates/lib/api_clients/runwayml_client",
|
||||
"crates/lib/api_clients/storyteller_client",
|
||||
"crates/lib/api_clients/thumbnail_generator",
|
||||
"crates/lib/bootstrap",
|
||||
"crates/lib/bucket_paths",
|
||||
"crates/lib/build_metadata",
|
||||
@@ -76,7 +73,6 @@ members = [
|
||||
"crates/lib/sqlx_mysql_helpers",
|
||||
"crates/lib/storyteller_root",
|
||||
"crates/lib/subprocess_common",
|
||||
"crates/lib/test_data",
|
||||
"crates/lib/testing",
|
||||
"crates/lib/tts_common",
|
||||
"crates/lib/url_config",
|
||||
@@ -214,12 +210,11 @@ url = { version = "2.5.4", features = ["serde"] }
|
||||
# Internal libs
|
||||
artcraft_api_defs = { path = "crates/schema/public/api/artcraft_api_defs" }
|
||||
build_metadata = { path = "crates/lib/build_metadata" }
|
||||
cloud_storage = { path = "crates/api_clients/cloud_storage" }
|
||||
crockford = { path = "crates/lib/data/crockford" }
|
||||
enums = { path = "crates/schema/public/enums" }
|
||||
enums_public = { path = "crates/schema/public/enums_public" }
|
||||
errors = { path = "crates/lib/errors" }
|
||||
fal_client = { path = "crates/api_clients/fal_client" }
|
||||
fal_client = { path = "crates/lib/api_clients/fal_client" }
|
||||
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
|
||||
filesys = { path = "crates/lib/files/filesys" }
|
||||
idempotency = { path = "crates/lib/idempotency" }
|
||||
@@ -228,11 +223,10 @@ jwt_light = { path = "crates/lib/jwt_light" }
|
||||
memory_caching = { path = "crates/lib/caching/memory_caching" }
|
||||
memory_store = { path = "crates/lib/caching/memory_store" }
|
||||
mimetypes = { path = "crates/lib/files/mimetypes" }
|
||||
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
|
||||
openai_sora_client = { path = "crates/lib/api_clients/openai_sora_client" }
|
||||
primitives = { path = "crates/lib/data/primitives" }
|
||||
shared_service_components = { path = "crates/lib/shared_service_components" }
|
||||
storyteller_client = { path = "crates/api_clients/storyteller_client" }
|
||||
storyteller_client = { path = "crates/lib/api_clients/storyteller_client" }
|
||||
subprocess_common = { path = "crates/lib/subprocess_common" }
|
||||
test_data = { path = "crates/lib/test_data" }
|
||||
tokens = { path = "crates/schema/public/tokens" }
|
||||
web_base64 = { path = "crates/lib/data/web_base64" }
|
||||
web_base64= { path = "crates/lib/data/web_base64" }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
storyteller-rust
|
||||
================
|
||||
|
||||
Our Artcraft monorepo, which serves our desktop application, backend infrastructure, and more.
|
||||
Our Rust monorepo, which serves both our API surface area and our downloadable client applications.
|
||||
|
||||
```
|
||||
."-,.__
|
||||
@@ -45,6 +45,5 @@ Our Artcraft monorepo, which serves our desktop application, backend infrastruct
|
||||
/ ,"'"\,' `/ `-.|"
|
||||
```
|
||||
|
||||
- [developer documentation](./_docs)
|
||||
- [server API reference](https://storyteller-docs.netlify.app/)
|
||||
- [tools, scripts, misc](./script)
|
||||
- [documentation](./_develop/docs)
|
||||
- [tools, misc](./_develop)
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
Local Development Setup Guide (for bare metal)
|
||||
==============================================
|
||||
FakeYou Development Setup (bare metal)
|
||||
======================================
|
||||
|
||||
This document describes how to run the FakeYou infrastructure on your development
|
||||
machine's bare metal (no containers).
|
||||
@@ -1,26 +0,0 @@
|
||||
Code Conventions
|
||||
================
|
||||
|
||||
We use three languages at Artcraft: Rust, TypeScript, and Python.
|
||||
|
||||
## Rust style guide
|
||||
|
||||
We deviate from the official Rust style guide in a few places:
|
||||
|
||||
- We use two spaces for indentation instead of four.
|
||||
- We typically keep one function or type definition per file, though this isn't a steadfast rule.
|
||||
- We use a Cargo workspace to share code within our monorepo.
|
||||
|
||||
### Rust Preferred Libraries
|
||||
|
||||
We use the following Rust libraries, though we are open to migrating to alternatives:
|
||||
|
||||
- `actix-web` for our HTTP web server
|
||||
- `reqwest` for HTTP client requests
|
||||
- `sqlx` for SQL database operations
|
||||
- `tokio` for asynchronous programming
|
||||
|
||||
## TypeScript style guide
|
||||
|
||||
- We use two spaces for indentation instead of four.
|
||||
- We use `nx` for monorepo management
|
||||
@@ -1,42 +0,0 @@
|
||||
[package]
|
||||
name = "cloud_storage"
|
||||
edition = "2018"
|
||||
version = "0.0.1"
|
||||
authors = [
|
||||
"Brandon Thomas <bt@brand.io>",
|
||||
"Brandon Thomas <echelon@gmail.com>",
|
||||
]
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "cloud_storage"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration.workspace = true
|
||||
|
||||
# Internal
|
||||
bucket_paths = { path = "../../lib/bucket_paths" }
|
||||
crockford.workspace = true
|
||||
easyenv = { path = "../../lib/easyenv" }
|
||||
errors.workspace = true
|
||||
filesys.workspace = true
|
||||
hashing = { path = "../../lib/files/hashing" }
|
||||
mimetypes.workspace = true
|
||||
storyteller_root = { path = "../../lib/storyteller_root" }
|
||||
|
||||
# External
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
env_logger = "0.11.3"
|
||||
async-trait = "0.1.75"
|
||||
# The "never-encode-slash" feature seems to have been removed:
|
||||
# https://github.com/durch/rust-s3/pull/254
|
||||
rust-s3 = { version = "0.36.0-beta.2" }
|
||||
|
||||
# Runtime
|
||||
tokio.workspace = true
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod fal_api_key;
|
||||
pub mod open_ai_api_key;
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
/// This is for Fal's OpenAI "Bring Your Own Key" (BYOK) endpoints.
|
||||
#[derive(Clone)]
|
||||
pub struct OpenAiApiKey(pub String);
|
||||
|
||||
impl OpenAiApiKey {
|
||||
pub fn new(api_key: String) -> Self {
|
||||
Self(api_key)
|
||||
}
|
||||
|
||||
pub fn from_str(api_key: &str) -> Self {
|
||||
let api_key = api_key.trim().to_string();
|
||||
Self(api_key)
|
||||
}
|
||||
}
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::creds::open_ai_api_key::OpenAiApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use fal::endpoints::fal_ai::gpt_image_1::edit_image::byok::{gpt_edit_image, GptEditImageRequest};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct GptEditImageByokArgs<'a, V: IntoUrl> {
|
||||
// Request
|
||||
pub image_urls: Vec<String>,
|
||||
pub prompt: &'a str,
|
||||
pub image_size: GptEditImageSize,
|
||||
pub num_images: GptEditImageNumImages,
|
||||
pub quality: GptEditImageQuality,
|
||||
|
||||
// Fulfillment
|
||||
pub api_key: &'a FalApiKey,
|
||||
pub openai_api_key: &'a OpenAiApiKey,
|
||||
pub webhook_url: V,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum GptEditImageSize{
|
||||
Auto,
|
||||
Square,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum GptEditImageQuality {
|
||||
Auto,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum GptEditImageNumImages{
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
pub async fn enqueue_gpt_image_1_edit_image_webhook<V: IntoUrl>(
|
||||
args: GptEditImageByokArgs<'_, V>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
// auto, 1024x1024, 1536x1024, 1024x1536
|
||||
let image_size = match args.image_size {
|
||||
GptEditImageSize::Auto => "auto",
|
||||
GptEditImageSize::Square => "1024x1024",
|
||||
GptEditImageSize::Horizontal => "1536x1024",
|
||||
GptEditImageSize::Vertical => "1024x1536",
|
||||
};
|
||||
|
||||
let quality = match args.quality {
|
||||
GptEditImageQuality::Auto => "auto",
|
||||
GptEditImageQuality::Low => "low",
|
||||
GptEditImageQuality::Medium => "medium",
|
||||
GptEditImageQuality::High => "high",
|
||||
};
|
||||
|
||||
let num_images = match args.num_images {
|
||||
GptEditImageNumImages::One => 1,
|
||||
GptEditImageNumImages::Two => 2,
|
||||
GptEditImageNumImages::Three => 3,
|
||||
GptEditImageNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let request = GptEditImageRequest {
|
||||
image_urls: args.image_urls,
|
||||
prompt: args.prompt.to_string(),
|
||||
image_size: image_size.to_string(),
|
||||
num_images,
|
||||
quality: quality.to_string(),
|
||||
openai_api_key: args.openai_api_key.0.to_string(),
|
||||
};
|
||||
|
||||
let result = gpt_edit_image(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::creds::open_ai_api_key::OpenAiApiKey;
|
||||
use crate::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::{enqueue_gpt_image_1_edit_image_webhook, GptEditImageByokArgs, GptEditImageNumImages, GptEditImageQuality, GptEditImageSize};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, GRASSY_HILL_TRANSPARENT_IMAGE_URL};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test_gpt_image_1_edit_image() -> 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 fal_api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/openai_api_key.txt")?;
|
||||
|
||||
let open_ai_api_key = OpenAiApiKey::from_str(&secret);
|
||||
|
||||
let args = GptEditImageByokArgs {
|
||||
image_urls: vec![
|
||||
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
|
||||
GHOST_IMAGE_URL.to_string(),
|
||||
GRASSY_HILL_TRANSPARENT_IMAGE_URL.to_string(),
|
||||
],
|
||||
prompt: "put the man and the ghost on the grassy hill. the man is scared of the friendly ghost.",
|
||||
api_key: &fal_api_key,
|
||||
openai_api_key: &open_ai_api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
image_size: GptEditImageSize::Horizontal,
|
||||
num_images: GptEditImageNumImages::One,
|
||||
quality: GptEditImageQuality::High,
|
||||
};
|
||||
|
||||
let result = enqueue_gpt_image_1_edit_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
[package]
|
||||
name = "openai_sora_client"
|
||||
edition = "2024"
|
||||
version = "0.0.1"
|
||||
publish = false
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[lib]
|
||||
name = "openai_sora_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration.workspace = true
|
||||
|
||||
# Internal
|
||||
errors.workspace = true
|
||||
idempotency.workspace = true
|
||||
|
||||
# External
|
||||
anyhow.workspace = true
|
||||
base64.workspace = true
|
||||
chrono.workspace = true
|
||||
cookie.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest = { workspace = true, features = ["multipart", "gzip"] }
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha3.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { path = "../../lib/testing" }
|
||||
@@ -1,45 +0,0 @@
|
||||
[package]
|
||||
name = "openart_client"
|
||||
edition = "2024"
|
||||
version = "0.0.1"
|
||||
publish = false
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[lib]
|
||||
name = "openart_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration.workspace = true
|
||||
|
||||
# Internal
|
||||
crockford.workspace = true
|
||||
errors.workspace = true
|
||||
idempotency.workspace = true
|
||||
subprocess_common = { path = "../../lib/subprocess_common" }
|
||||
|
||||
# External
|
||||
anyhow.workspace = true
|
||||
base64.workspace = true
|
||||
chrono.workspace = true
|
||||
cookie.workspace = true
|
||||
hex-literal.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest = { workspace = true, features = ["multipart", "gzip"] }
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha3.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { path = "../../lib/testing" }
|
||||
filesys.workspace = true
|
||||
@@ -1,46 +0,0 @@
|
||||
[package]
|
||||
name = "runwayml_client"
|
||||
edition = "2021"
|
||||
version = "0.0.1"
|
||||
publish = false
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[lib]
|
||||
name = "runwayml_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration.workspace = true
|
||||
|
||||
# Internal
|
||||
crockford.workspace = true
|
||||
errors.workspace = true
|
||||
idempotency.workspace = true
|
||||
jwt_light.workspace = true
|
||||
subprocess_common = { path = "../../lib/subprocess_common" }
|
||||
|
||||
# External
|
||||
anyhow.workspace = true
|
||||
base64.workspace = true
|
||||
chrono.workspace = true
|
||||
cookie.workspace = true
|
||||
hex-literal.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
rand.workspace = true
|
||||
reqwest.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
sha3.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { path = "../../lib/testing" }
|
||||
filesys.workspace = true
|
||||
@@ -1,52 +0,0 @@
|
||||
[package]
|
||||
name = "storyteller_client"
|
||||
edition = "2021"
|
||||
version = "0.0.1"
|
||||
authors = [
|
||||
"Brandon Thomas <bt@brand.io>",
|
||||
"Brandon Thomas <echelon@gmail.com>",
|
||||
]
|
||||
publish = false
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
# [lints.rust]
|
||||
# unused_imports = "forbid"
|
||||
|
||||
[lib]
|
||||
name = "storyteller_client"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration.workspace = true
|
||||
|
||||
# Internal
|
||||
artcraft_api_defs.workspace = true
|
||||
enums.workspace = true
|
||||
enums_public.workspace = true
|
||||
errors.workspace = true
|
||||
idempotency.workspace = true
|
||||
tokens.workspace = true
|
||||
mimetypes.workspace = true
|
||||
|
||||
# External
|
||||
anyhow.workspace = true
|
||||
chrono.workspace = true
|
||||
cookie.workspace = true
|
||||
log.workspace = true
|
||||
reqwest = { workspace = true, features = ["multipart", "gzip"] }
|
||||
url.workspace = true
|
||||
|
||||
# Serialization
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
# HTTP
|
||||
backoff = { version = "0.4.0", features = ["futures"] }
|
||||
uuid = { version = "1.8.0", features = ["v4"] } # Exponential backoff retry
|
||||
|
||||
[dev-dependencies]
|
||||
tokio.workspace = true
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageRequest, GptImage1EditImageResponse, GPT_IMAGE_1_EDIT_IMAGE_PATH};
|
||||
|
||||
|
||||
pub async fn gpt_image_1_edit_image(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GptImage1EditImageRequest,
|
||||
) -> Result<GptImage1EditImageResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GPT_IMAGE_1_EDIT_IMAGE_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod gpt_image_1_edit_image;
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::{GenerateFlux1DevTextToImageRequest, GenerateFlux1DevTextToImageResponse, GENERATE_FLUX_1_DEV_TEXT_TO_IMAGE_PATH};
|
||||
|
||||
|
||||
pub async fn generate_flux_1_dev_text_to_image(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateFlux1DevTextToImageRequest,
|
||||
) -> Result<GenerateFlux1DevTextToImageResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_FLUX_1_DEV_TEXT_TO_IMAGE_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
|
||||
|
||||
|
||||
pub async fn generate_flux_1_schnell_text_to_image(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateFlux1SchnellTextToImageRequest,
|
||||
) -> Result<GenerateFlux1SchnellTextToImageResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::{GenerateFluxPro11TextToImageRequest, GenerateFluxPro11TextToImageResponse, GENERATE_FLUX_PRO_11_TEXT_TO_IMAGE_PATH};
|
||||
|
||||
|
||||
pub async fn generate_flux_pro_11_text_to_image(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateFluxPro11TextToImageRequest,
|
||||
) -> Result<GenerateFluxPro11TextToImageResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_FLUX_PRO_11_TEXT_TO_IMAGE_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::{GenerateFluxPro11UltraTextToImageRequest, GenerateFluxPro11UltraTextToImageResponse, GENERATE_FLUX_PRO_11_ULTRA_TEXT_TO_IMAGE_PATH};
|
||||
|
||||
pub async fn generate_flux_pro_11_ultra_text_to_image(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateFluxPro11UltraTextToImageRequest,
|
||||
) -> Result<GenerateFluxPro11UltraTextToImageResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_FLUX_PRO_11_ULTRA_TEXT_TO_IMAGE_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod edit;
|
||||
pub mod generate_flux_1_dev_text_to_image;
|
||||
pub mod generate_flux_1_schnell_text_to_image;
|
||||
pub mod generate_flux_pro_11_text_to_image;
|
||||
pub mod generate_flux_pro_11_ultra_text_to_image;
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::{GenerateHunyuan20ImageTo3dRequest, GenerateHunyuan20ImageTo3dResponse, GENERATE_HUNYUAN_2_0_IMAGE_TO_3D_URL_PATH};
|
||||
|
||||
pub async fn generate_hunyuan3d_2_0_image_to_3d(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateHunyuan20ImageTo3dRequest,
|
||||
) -> Result<GenerateHunyuan20ImageTo3dResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_HUNYUAN_2_0_IMAGE_TO_3D_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::{GenerateHunyuan21ImageTo3dRequest, GenerateHunyuan21ImageTo3dResponse, GENERATE_HUNYUAN_2_1_IMAGE_TO_3D_URL_PATH};
|
||||
|
||||
pub async fn generate_hunyuan3d_2_1_image_to_3d(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateHunyuan21ImageTo3dRequest,
|
||||
) -> Result<GenerateHunyuan21ImageTo3dResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_HUNYUAN_2_1_IMAGE_TO_3D_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod generate_hunyuan_3d_2_0_image_to_3d;
|
||||
pub mod generate_hunyuan_3d_2_1_image_to_3d;
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::{GenerateKling16ProImageToVideoRequest, GenerateKling16ProImageToVideoResponse, GENERATE_KLING_1_6_PRO_IMAGE_TO_VIDEO_URL_PATH};
|
||||
|
||||
pub async fn generate_kling_16_pro_image_to_video(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateKling16ProImageToVideoRequest,
|
||||
) -> Result<GenerateKling16ProImageToVideoResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_KLING_1_6_PRO_IMAGE_TO_VIDEO_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::generate_kling_2_1_master_image_to_video::{GenerateKling21MasterImageToVideoRequest, GenerateKling21MasterImageToVideoResponse, GENERATE_KLING_2_1_MASTER_IMAGE_TO_VIDEO_URL_PATH};
|
||||
|
||||
pub async fn generate_kling_21_master_image_to_video(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateKling21MasterImageToVideoRequest,
|
||||
) -> Result<GenerateKling21MasterImageToVideoResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_KLING_2_1_MASTER_IMAGE_TO_VIDEO_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::generate_kling_2_1_pro_image_to_video::{GenerateKling21ProImageToVideoRequest, GenerateKling21ProImageToVideoResponse, GENERATE_KLING_2_1_PRO_IMAGE_TO_VIDEO_URL_PATH};
|
||||
|
||||
pub async fn generate_kling_21_pro_image_to_video(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateKling21ProImageToVideoRequest,
|
||||
) -> Result<GenerateKling21ProImageToVideoResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_KLING_2_1_PRO_IMAGE_TO_VIDEO_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::{GenerateSeedance10LiteImageToVideoRequest, GenerateSeedance10LiteImageToVideoResponse, GENERATE_SEEDANCE_1_0_LITE_IMAGE_TO_VIDEO_URL_PATH};
|
||||
|
||||
pub async fn generate_seedance_1_0_lite_image_to_video(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateSeedance10LiteImageToVideoRequest,
|
||||
) -> Result<GenerateSeedance10LiteImageToVideoResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_SEEDANCE_1_0_LITE_IMAGE_TO_VIDEO_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::{GenerateVeo2ImageToVideoRequest, GenerateVeo2ImageToVideoResponse, GENERATE_VEO_2_IMAGE_TO_VIDEO_URL_PATH};
|
||||
|
||||
pub async fn generate_veo_2_image_to_video(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GenerateVeo2ImageToVideoRequest,
|
||||
) -> Result<GenerateVeo2ImageToVideoResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GENERATE_VEO_2_IMAGE_TO_VIDEO_URL_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod generate_kling_16_pro_image_to_video;
|
||||
pub mod generate_kling_21_master_image_to_video;
|
||||
pub mod generate_kling_21_pro_image_to_video;
|
||||
pub mod generate_seedance_1_0_lite_image_to_video;
|
||||
pub mod generate_veo_2_image_to_video;
|
||||
@@ -21,24 +21,23 @@ workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
cloud_storage.workspace = true
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
enums.workspace = true
|
||||
errors.workspace = true
|
||||
filesys.workspace = true
|
||||
enums = { path = "../../../schema/public/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
filesys = { path = "../../../lib/files/filesys" }
|
||||
hashing = { path = "../../../lib/files/hashing" }
|
||||
idempotency.workspace = true
|
||||
mimetypes.workspace = true
|
||||
idempotency = { path = "../../../lib/idempotency" }
|
||||
mimetypes = { path = "../../../lib/files/mimetypes" }
|
||||
mysql_queries = { path = "../../../schema/database/mysql_queries" }
|
||||
storyteller_root = { path = "../../../lib/storyteller_root" }
|
||||
tokens.workspace = true
|
||||
tokens = { path = "../../../schema/public/tokens" }
|
||||
|
||||
# External
|
||||
log.workspace = true
|
||||
log = "0.4.14"
|
||||
clap = "4.4.8"
|
||||
rand.workspace = true
|
||||
|
||||
rand = "0.8.5"
|
||||
# Runtime
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
|
||||
@@ -21,23 +21,23 @@ workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
cloud_storage.workspace = true
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
enums.workspace = true
|
||||
errors.workspace = true
|
||||
filesys.workspace = true
|
||||
mimetypes.workspace = true
|
||||
enums = { path = "../../../schema/public/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
filesys = { path = "../../../lib/files/filesys" }
|
||||
mimetypes = { path = "../../../lib/files/mimetypes" }
|
||||
mysql_queries = { path = "../../../schema/database/mysql_queries" }
|
||||
tokens.workspace = true
|
||||
tokens = { path = "../../../schema/public/tokens" }
|
||||
|
||||
# External
|
||||
clap = "4.4.8"
|
||||
log.workspace = true
|
||||
log = "0.4.14"
|
||||
|
||||
# Runtime
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
tokio.workspace = true
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
|
||||
|
||||
@@ -17,35 +17,35 @@ normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration.workspace = true
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
cloud_storage.workspace = true
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
enums.workspace = true
|
||||
errors.workspace = true
|
||||
filesys.workspace = true
|
||||
enums = { path = "../../../schema/public/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
filesys = { path = "../../../lib/files/filesys" }
|
||||
hashing = { path = "../../../lib/files/hashing" }
|
||||
mysql_queries = { path = "../../../schema/database/mysql_queries" }
|
||||
storyteller_root = { path = "../../../lib/storyteller_root" }
|
||||
tokens.workspace = true
|
||||
tokens = { path = "../../../schema/public/tokens" }
|
||||
|
||||
# External
|
||||
clap = "4.4.8"
|
||||
log.workspace = true
|
||||
log = "0.4.14"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
tempdir.workspace = true
|
||||
tempdir = "0.3.7"
|
||||
|
||||
# Async + clients
|
||||
tokio.workspace = true
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
|
||||
# Serialization
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde = "1.0.125"
|
||||
serde_derive = "1.0.125"
|
||||
serde_json = "1.0.108"
|
||||
|
||||
[dev-dependencies]
|
||||
# None yet
|
||||
|
||||
@@ -9,9 +9,6 @@ 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]
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
"allow": [
|
||||
{
|
||||
"url": "https://*"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:12345"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+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::State;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GetAppPreferencesResponse {
|
||||
|
||||
+2
-1
@@ -4,8 +4,9 @@ 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::State;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
/// For now, we'll only update a single preference at a time.
|
||||
#[derive(Deserialize)]
|
||||
|
||||
+66
-59
@@ -1,28 +1,62 @@
|
||||
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::provider_priority::{Provider, ProviderPriorityStore};
|
||||
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 fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
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::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
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 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 crate::core::commands::enqueue::image::handle_image_artcraft::handle_image_artcraft;
|
||||
use crate::core::commands::enqueue::object::handle_object_artcraft::handle_object_artcraft;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueTextToImageRequest {
|
||||
@@ -30,9 +64,19 @@ pub struct EnqueueTextToImageRequest {
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<ImageModel>,
|
||||
pub model: Option<EnqueueTextToImageModel>,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
@@ -45,8 +89,6 @@ 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
|
||||
@@ -63,8 +105,6 @@ 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>,
|
||||
@@ -78,10 +118,8 @@ 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,
|
||||
@@ -101,11 +139,6 @@ 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;
|
||||
@@ -121,17 +154,7 @@ pub async fn enqueue_text_to_image_command(
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
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(()) => {
|
||||
Ok(EnqueueTextToImageSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
@@ -142,47 +165,31 @@ 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<SuccessEvent, InternalImageError> {
|
||||
) -> Result<(), InternalImageError> {
|
||||
|
||||
match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(ImageModel::GptImage1) => {
|
||||
Some(EnqueueTextToImageModel::GptImage1) => {
|
||||
handle_image_sora(&app, request, sora_creds_manager, sora_task_queue).await?;
|
||||
return Ok(SuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Sora,
|
||||
model: ImageModel::GptImage1,
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
// Fall-through
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
handle_image_fal(&app, request, fal_creds_manager, fal_task_queue).await?;
|
||||
} else {
|
||||
handle_image_artcraft(request, &app, app_data_root, storyteller_creds_manager).await?;
|
||||
}
|
||||
|
||||
Err(InternalImageError::NoProviderAvailable)
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, 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::common::{GenerationAction, GenerationModel, 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::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::core::utils::save_base64_image_to_temp_dir::save_base64_image_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::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
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 artcraft_api_defs::generate::object::generate_hunyuan_2_image_to_3d::GenerateHunyuan2ImageTo3dRequest;
|
||||
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::GenerateKling16ProImageToVideoRequest;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
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 fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
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 log::{error, info, warn};
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
|
||||
use tauri::AppHandle;
|
||||
use storyteller_client::generate::object::generate_hunyuan2_image_to_3d::generate_hunyuan2_image_to_3d;
|
||||
use storyteller_client::generate::video::generate_kling_16_pro_image_to_video::generate_kling_16_pro_image_to_video;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
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> {
|
||||
) -> Result<(), InternalImageError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
@@ -49,96 +54,21 @@ pub async fn handle_image_artcraft(
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let mut selected_model;
|
||||
let mut selected_model = None;
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(ImageModel::GptImage1) => {
|
||||
Some(EnqueueTextToImageModel::GptImage1) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: artcraft is handling sora images")));
|
||||
}
|
||||
Some(ImageModel::Recraft3) => {
|
||||
Some(EnqueueTextToImageModel::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;
|
||||
Some(EnqueueTextToImageModel::FluxProUltra) => {
|
||||
info!("enqueue Flux Pro Ultra");
|
||||
selected_model = Some(GenerationModel::FluxPro11Ultra);
|
||||
let request = GenerateFluxPro11UltraTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
@@ -146,25 +76,22 @@ pub async fn handle_image_artcraft(
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_pro_11_ultra_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux Pro 1.1 Ultra text to image generation");
|
||||
info!("Successfully enqueued Artcraft flux pro 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);
|
||||
error!("Failed to use Artcraft flux pro ultra text to image generation: {:?}", err);
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(SuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Artcraft,
|
||||
model:selected_model,
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, 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::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::creds::fal_api_key::FalApiKey;
|
||||
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};
|
||||
use log::{error, info, warn};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_image_fal(
|
||||
@@ -18,7 +22,7 @@ pub async fn handle_image_fal(
|
||||
request: EnqueueTextToImageRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<SuccessEvent, InternalImageError> {
|
||||
) -> Result<(), InternalImageError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
@@ -43,32 +47,21 @@ pub async fn handle_image_fal(
|
||||
|
||||
let prompt = request.prompt.as_deref().unwrap_or("");
|
||||
|
||||
let model;
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(ImageModel::GptImage1) => {
|
||||
Some(EnqueueTextToImageModel::GptImage1) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: fal is handling sora images")));
|
||||
}
|
||||
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);
|
||||
Some(EnqueueTextToImageModel::FluxProUltra) => {
|
||||
info!("enqueue Flux Pro Ultra text-to-image with prompt: {}", prompt);
|
||||
enqueue_flux_pro_11_ultra_text_to_image(FluxPro11UltraTextToImageArgs {
|
||||
prompt,
|
||||
api_key: &api_key,
|
||||
}).await
|
||||
}
|
||||
Some(ImageModel::Recraft3) => {
|
||||
model = ImageModel::Recraft3;
|
||||
Some(EnqueueTextToImageModel::Recraft3) => {
|
||||
info!("enqueue Recraft v3 text-to-image with prompt: {}", prompt);
|
||||
enqueue_recraft3_text_to_image(Recraft3TextToImageArgs {
|
||||
prompt,
|
||||
@@ -81,15 +74,15 @@ pub async fn handle_image_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);
|
||||
@@ -114,8 +107,5 @@ pub async fn handle_image_fal(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Fal,
|
||||
model,
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, 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_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info};
|
||||
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 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};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
|
||||
@@ -80,5 +90,15 @@ pub async fn handle_image_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(())
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
#[derive(Debug)]
|
||||
pub enum InternalImageError {
|
||||
NoModelSpecified,
|
||||
NoProviderAvailable,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
|
||||
@@ -2,5 +2,4 @@ pub mod enqueue_text_to_image_command;
|
||||
pub mod handle_image_artcraft;
|
||||
pub mod handle_image_fal;
|
||||
pub mod handle_image_sora;
|
||||
pub mod internal_image_error;
|
||||
mod success_event;
|
||||
pub mod internal_image_error;
|
||||
@@ -1,20 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
-217
@@ -1,217 +0,0 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, EnqueueTextToImageSuccessResponse};
|
||||
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_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1::handle_gpt_image_1;
|
||||
use crate::core::commands::enqueue::image_edit::success_event::ContextualEditImageSuccessEvent;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::{Response, ResponseOrErrorType};
|
||||
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_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::model::contextual_image_edit_models::ContextualImageEditModel;
|
||||
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::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
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::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 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::sora_error::SoraError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
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, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EnqueueContextualEditImageCommand {
|
||||
/// The model to use.
|
||||
pub model: Option<ContextualImageEditModel>,
|
||||
|
||||
/// Images to use for the image edit.
|
||||
/// The first image is typically a 2D canvas or 3D stage, but doesn't have to be.
|
||||
/// There must be at least one image.
|
||||
pub image_media_tokens: Vec<MediaFileToken>,
|
||||
|
||||
/// The user's image generation prompt.
|
||||
pub prompt: String,
|
||||
|
||||
/// Turn off the system prompt.
|
||||
pub disable_system_prompt: Option<bool>,
|
||||
|
||||
/// Number of images to generate.
|
||||
pub image_count: Option<u32>,
|
||||
|
||||
/// Aspect ratio.
|
||||
pub aspect_ratio: Option<EditImageSize>,
|
||||
|
||||
/// Image quality.
|
||||
pub image_quality: Option<EditImageQuality>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EditImageSize {
|
||||
Auto,
|
||||
Square,
|
||||
Wide,
|
||||
Tall,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EditImageQuality {
|
||||
Auto,
|
||||
High,
|
||||
Medium,
|
||||
Low,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueContextualEditImageErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
|
||||
/// No model available for image generation
|
||||
NoProviderAvailable,
|
||||
|
||||
/// Generic bad request error
|
||||
BadRequest,
|
||||
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
|
||||
/// The user is sending too many requests
|
||||
TooManyConcurrentTasks,
|
||||
/// User is not logged into Sora!
|
||||
SoraLoginRequired,
|
||||
/// The user needs to create a Sora account
|
||||
SoraUsernameNotYetCreated,
|
||||
/// The Sora service is having problems. Try again soon.
|
||||
SoraIsHavingProblems,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueContextualEditImageSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueContextualEditImageSuccessResponse {}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_contextual_edit_image_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueContextualEditImageCommand,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> ResponseOrErrorType<EnqueueContextualEditImageSuccessResponse, EnqueueContextualEditImageErrorType> {
|
||||
|
||||
info!("enqueue_contextual_edit_image_command called; image media tokens : {:?}, full request: {:?}",
|
||||
&request.image_media_tokens, &request);
|
||||
|
||||
let result = handle_request(
|
||||
&request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&app_env_configs,
|
||||
&provider_priority_store,
|
||||
&storyteller_creds_manager,
|
||||
&fal_creds_manager,
|
||||
&fal_task_queue,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("Error enqueuing contextual edit image: {:?}", err);
|
||||
|
||||
// TODO: Derive from err. Make service provider optional.
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Artcraft, // FIXME: This is wrong.
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Err(err.to_tauri_response())
|
||||
}
|
||||
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(EnqueueContextualEditImageSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_request(
|
||||
request: &EnqueueContextualEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<ContextualEditImageSuccessEvent, InternalContextualEditImageError> {
|
||||
match request.model {
|
||||
None => {
|
||||
Err(InternalContextualEditImageError::NoModelSpecified)
|
||||
}
|
||||
Some(ContextualImageEditModel::GptImage1) => {
|
||||
handle_gpt_image_1(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await
|
||||
}
|
||||
// TODO(bt,2025-07-05): Flux Kontext, etc.
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageErrorType;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use errors::AnyhowError;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use serde::Serialize;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalContextualEditImageError {
|
||||
/// Request did not specify a model.
|
||||
NoModelSpecified,
|
||||
|
||||
/// No provider is configured for the model type.
|
||||
NoProviderAvailable,
|
||||
|
||||
/// No storyteller credentials are set.
|
||||
NeedsStorytellerCredentials,
|
||||
|
||||
/// An invalid number of images was requested.
|
||||
InvalidNumberOfRequestedImages {
|
||||
min: u32,
|
||||
max: u32,
|
||||
requested: u32,
|
||||
},
|
||||
|
||||
/// An invalid number of input images was requested.
|
||||
InvalidNumberOfInputImages {
|
||||
min: u32,
|
||||
max: u32,
|
||||
requested: u32,
|
||||
},
|
||||
|
||||
/// No sora credentials are set.
|
||||
NeedsSoraCredentials,
|
||||
|
||||
SoraError(SoraError),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerError(StorytellerError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InternalContextualEditImageError {
|
||||
fn from(value: AnyhowError) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SoraError> for InternalContextualEditImageError {
|
||||
fn from(value: SoraError) -> Self {
|
||||
Self::SoraError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorytellerError> for InternalContextualEditImageError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalContextualEditImageError {
|
||||
pub fn to_tauri_response<E: Serialize>(&self) -> CommandErrorResponseWrapper<EnqueueContextualEditImageErrorType, E> {
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueContextualEditImageErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.".to_string();
|
||||
|
||||
match self {
|
||||
InternalContextualEditImageError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueContextualEditImageErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation".to_string();
|
||||
}
|
||||
InternalContextualEditImageError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = EnqueueContextualEditImageErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for image generation".to_string();
|
||||
}
|
||||
InternalContextualEditImageError::InvalidNumberOfRequestedImages { min, max, requested } => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueContextualEditImageErrorType::BadRequest;
|
||||
error_message = format!("Invalid number of images requested ({}). Must be between {} and {}", requested, min, max);
|
||||
|
||||
}
|
||||
InternalContextualEditImageError::InvalidNumberOfInputImages { min, max, requested } => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueContextualEditImageErrorType::BadRequest;
|
||||
error_message = format!("Invalid number of input images ({}). Must be between {} and {}", requested, min, max);
|
||||
}
|
||||
InternalContextualEditImageError::SoraError(_) => {}
|
||||
InternalContextualEditImageError::AnyhowError(_) => {}
|
||||
InternalContextualEditImageError::StorytellerError(_) => {}
|
||||
InternalContextualEditImageError::NeedsStorytellerCredentials => {}
|
||||
InternalContextualEditImageError::NeedsSoraCredentials => {}
|
||||
}
|
||||
|
||||
CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_artcraft::handle_gpt_image_1_artcraft;
|
||||
use crate::core::commands::enqueue::image_edit::gpt_image_1::handle_gpt_image_1_sora::handle_gpt_image_1_sora;
|
||||
use crate::core::commands::enqueue::image_edit::success_event::ContextualEditImageSuccessEvent;
|
||||
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::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_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_gpt_image_1(
|
||||
request: &EnqueueContextualEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<ContextualEditImageSuccessEvent, InternalContextualEditImageError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
// TODO: Check if providers are available before proceeding.
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Fal => {
|
||||
// Fallthrough
|
||||
// If in the future we support OpenAI API keys, it's worth considering whether we
|
||||
// send those requests ourselves, or if we use FAL as an intermediary. FAL makes
|
||||
// the API nicer to deal with, but the user needs an additional key.
|
||||
}
|
||||
Provider::Artcraft => {
|
||||
return handle_gpt_image_1_artcraft(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager
|
||||
).await;
|
||||
}
|
||||
Provider::Sora => {
|
||||
return handle_gpt_image_1_sora(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalContextualEditImageError::NoProviderAvailable)
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
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_edit::enqueue_contextual_edit_image_command::{EditImageQuality, EditImageSize, EnqueueContextualEditImageCommand};
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::success_event::ContextualEditImageSuccessEvent;
|
||||
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::contextual_image_edit_models::ContextualImageEditModel;
|
||||
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::provider_priority::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_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 artcraft_api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageImageQuality, GptImage1EditImageImageSize, GptImage1EditImageNumImages, GptImage1EditImageRequest};
|
||||
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 fal_client::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
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_gpt_image_1_artcraft(
|
||||
request: &EnqueueContextualEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<ContextualEditImageSuccessEvent, InternalContextualEditImageError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
return Err(InternalContextualEditImageError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft gpt-image-1 ...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let image_quality = request.image_quality
|
||||
.map(|quality| match quality {
|
||||
EditImageQuality::Auto => GptImage1EditImageImageQuality::Auto,
|
||||
EditImageQuality::High => GptImage1EditImageImageQuality::High,
|
||||
EditImageQuality::Medium => GptImage1EditImageImageQuality::Medium,
|
||||
EditImageQuality::Low => GptImage1EditImageImageQuality::Low,
|
||||
});
|
||||
|
||||
let image_size = request.aspect_ratio
|
||||
.map(|size| match size {
|
||||
EditImageSize::Auto => GptImage1EditImageImageSize::Square,
|
||||
EditImageSize::Square => GptImage1EditImageImageSize::Square,
|
||||
EditImageSize::Tall => GptImage1EditImageImageSize::Vertical,
|
||||
EditImageSize::Wide => GptImage1EditImageImageSize::Horizontal,
|
||||
});
|
||||
|
||||
let num_images = match request.image_count {
|
||||
None => None,
|
||||
Some(1) => Some(GptImage1EditImageNumImages::One),
|
||||
Some(2) => Some(GptImage1EditImageNumImages::Two),
|
||||
Some(3) => Some(GptImage1EditImageNumImages::Three),
|
||||
Some(4) => Some(GptImage1EditImageNumImages::Four),
|
||||
Some(other) => {
|
||||
return Err(InternalContextualEditImageError::InvalidNumberOfRequestedImages {
|
||||
min: 1,
|
||||
max: 4,
|
||||
requested: other,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
let request = GptImage1EditImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: Some(request.prompt.clone()),
|
||||
image_media_tokens: Some(request.image_media_tokens.clone()),
|
||||
image_size,
|
||||
num_images,
|
||||
image_quality,
|
||||
};
|
||||
|
||||
let result = gpt_image_1_edit_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft gpt-image-1. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft gpt-image-1: {:?}", err);
|
||||
return Err(InternalContextualEditImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ContextualEditImageSuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Artcraft,
|
||||
model: ContextualImageEditModel::GptImage1,
|
||||
})
|
||||
}
|
||||
-172
@@ -1,172 +0,0 @@
|
||||
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_edit::enqueue_contextual_edit_image_command::{EditImageSize, EnqueueContextualEditImageCommand, EnqueueContextualEditImageErrorType};
|
||||
use crate::core::commands::enqueue::image_edit::errors::InternalContextualEditImageError;
|
||||
use crate::core::commands::enqueue::image_edit::success_event::ContextualEditImageSuccessEvent;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
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::model::contextual_image_edit_models::ContextualImageEditModel;
|
||||
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::state::provider_priority::ProviderPriorityStore;
|
||||
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::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 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 errors::AnyhowResult;
|
||||
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, 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::sora_error::SoraError;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const SORA_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
const SORA_IMAGE_REMIX_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
const DEFAULT_ASPECT_RATIO : EditImageSize = EditImageSize::Square;
|
||||
|
||||
pub async fn handle_gpt_image_1_sora(
|
||||
request: &EnqueueContextualEditImageCommand,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
|
||||
) -> Result<ContextualEditImageSuccessEvent, InternalContextualEditImageError> {
|
||||
|
||||
let sora_creds = match sora_creds_manager.get_credentials() {
|
||||
Ok(Some(creds)) => creds,
|
||||
Ok(None) => {
|
||||
warn!("No Sora credentials found.");
|
||||
return Err(InternalContextualEditImageError::NeedsSoraCredentials);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to get Sora credentials: {:?}", err);
|
||||
return Err(InternalContextualEditImageError::AnyhowError(err));
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(bt,2025-07-08): Handle many images.
|
||||
// TODO(bt,2025-07-08): Handle authenticated sessions (for private images).
|
||||
// TODO(bt,2025-07-08): Handle HTTP batch get requests.
|
||||
let first_image_token = request.image_media_tokens.first()
|
||||
.ok_or_else(|| InternalContextualEditImageError::InvalidNumberOfInputImages {
|
||||
min: 1,
|
||||
max: 10, // TODO Confirm
|
||||
requested: request.image_media_tokens.len() as u32,
|
||||
})?;
|
||||
|
||||
info!("Calling get media file API: {:?}", app_env_configs.storyteller_host);
|
||||
|
||||
let response = get_media_file(
|
||||
&app_env_configs.storyteller_host,
|
||||
first_image_token
|
||||
).await?;
|
||||
|
||||
let media_file_url = &response.media_file.media_links.cdn_url;
|
||||
let extension_with_dot = get_url_file_extension(media_file_url)
|
||||
.map(|ext| format!(".{}", ext))
|
||||
.unwrap_or_else(|| ".png".to_string());
|
||||
|
||||
let filename = format!("{}{}", response.media_file.token.as_str(), extension_with_dot);
|
||||
let filename = app_data_root.downloads_dir().path().join(&filename);
|
||||
|
||||
simple_http_download(&media_file_url, &filename).await?;
|
||||
|
||||
let files_to_upload = vec![filename];
|
||||
|
||||
let mut creds = sora_creds_manager.get_credentials_required()?;
|
||||
|
||||
let credential_updated = maybe_upgrade_or_renew_session(&mut creds)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upgrade or renew session: {:?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
if credential_updated {
|
||||
info!("Storing updated credentials");
|
||||
sora_creds_manager.set_credentials(&creds)?;
|
||||
}
|
||||
|
||||
let mut sora_media_tokens = Vec::with_capacity(files_to_upload.len());
|
||||
|
||||
for (i, file_path) in files_to_upload.iter().enumerate() {
|
||||
info!("Uploading image {} of {}...", (i+1), files_to_upload.len());
|
||||
|
||||
let (response, maybe_new_credentials) =
|
||||
image_upload_from_file_with_session_auto_renew(ImageUploadFromFileAutoRenewRequest {
|
||||
file_path,
|
||||
credentials: &creds,
|
||||
request_timeout: Some(SORA_IMAGE_UPLOAD_TIMEOUT), // TODO: Centralize and make configurable.
|
||||
}).await?;
|
||||
|
||||
if let Some(new_creds) = maybe_new_credentials {
|
||||
info!("Storing updated credentials.");
|
||||
sora_creds_manager.set_credentials(&new_creds)?;
|
||||
creds = new_creds;
|
||||
}
|
||||
|
||||
sora_media_tokens.push(response.id);
|
||||
}
|
||||
|
||||
let aspect_ratio = request.aspect_ratio.unwrap_or(DEFAULT_ASPECT_RATIO);
|
||||
|
||||
let aspect_ratio = match aspect_ratio {
|
||||
EditImageSize::Auto => ImageSize::Square,
|
||||
EditImageSize::Square => ImageSize::Square,
|
||||
EditImageSize::Wide => ImageSize::Wide,
|
||||
EditImageSize::Tall => ImageSize::Tall,
|
||||
};
|
||||
|
||||
info!("Calling Sora image generation...");
|
||||
|
||||
// TODO(bt,2025-04-21): Download media tokens.
|
||||
// Note: This is incredibly inefficient. We should keep a local cache.
|
||||
// Also, if they've already been uploaded to OpenAI, we shouldn't continue to re-upload.
|
||||
|
||||
let (response, maybe_new_creds) =
|
||||
image_remix_with_session_auto_renew(ImageRemixAutoRenewRequest {
|
||||
prompt: request.prompt.to_string(),
|
||||
num_images: NumImages::One,
|
||||
image_size: aspect_ratio,
|
||||
sora_media_tokens: sora_media_tokens.clone(),
|
||||
credentials: &creds,
|
||||
request_timeout: Some(SORA_IMAGE_REMIX_TIMEOUT),
|
||||
}).await?;
|
||||
|
||||
if let Some(new_creds) = maybe_new_creds {
|
||||
info!("Storing updated credentials.");
|
||||
sora_creds_manager.set_credentials(&new_creds)?;
|
||||
}
|
||||
|
||||
info!("New Sora Task ID: {:?} ", response.task_id);
|
||||
|
||||
sora_task_queue.insert(&response.task_id)?;
|
||||
|
||||
Ok(ContextualEditImageSuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Sora,
|
||||
model: ContextualImageEditModel::GptImage1,
|
||||
})
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod handle_gpt_image_1;
|
||||
pub mod handle_gpt_image_1_artcraft;
|
||||
pub mod handle_gpt_image_1_sora;
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod enqueue_contextual_edit_image_command;
|
||||
pub mod errors;
|
||||
pub mod gpt_image_1;
|
||||
pub mod success_event;
|
||||
@@ -1,16 +0,0 @@
|
||||
use crate::core::events::generation_events::common::{GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::model::contextual_image_edit_models::ContextualImageEditModel;
|
||||
use crate::core::model::image_models::ImageModel;
|
||||
|
||||
pub struct ContextualEditImageSuccessEvent {
|
||||
pub service_provider: GenerationServiceProvider,
|
||||
pub model: ContextualImageEditModel,
|
||||
}
|
||||
|
||||
impl ContextualEditImageSuccessEvent {
|
||||
pub fn tauri_event_model(&self) -> GenerationModel {
|
||||
match self.model {
|
||||
ContextualImageEditModel::GptImage1 => GenerationModel::GptImage1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod image;
|
||||
pub mod image_edit;
|
||||
pub mod object;
|
||||
pub mod video;
|
||||
+52
-53
@@ -4,18 +4,58 @@ use crate::core::commands::enqueue::object::internal_object_error::InternalObjec
|
||||
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::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
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 fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
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 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 serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
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 tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageTo3dObjectRequest {
|
||||
@@ -30,13 +70,8 @@ pub struct EnqueueImageTo3dObjectRequest {
|
||||
#[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)]
|
||||
@@ -50,8 +85,6 @@ impl SerializeMarker for EnqueueImageTo3dObjectSuccessResponse {}
|
||||
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
|
||||
@@ -66,9 +99,7 @@ pub enum EnqueueImageTo3dObjectErrorType {
|
||||
pub async fn enqueue_image_to_3d_object_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
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>,
|
||||
@@ -78,11 +109,9 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
info!("enqueue_image_to_3d_object_command called");
|
||||
|
||||
let result = handle_request(
|
||||
request,
|
||||
&app,
|
||||
&app_env_configs,
|
||||
request,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
@@ -100,12 +129,7 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
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";
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
InternalObjectError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
@@ -135,44 +159,19 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
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_env_configs,
|
||||
app_data_root,
|
||||
storyteller_creds_manager
|
||||
).await?);
|
||||
}
|
||||
Provider::Fal => {
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
return Ok(handle_object_fal(
|
||||
&app,
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
request,
|
||||
fal_creds_manager,
|
||||
fal_task_queue
|
||||
).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
handle_object_fal(&app, app_data_root, request, fal_creds_manager, fal_task_queue).await?;
|
||||
} else {
|
||||
handle_object_artcraft(request, &app, app_data_root, storyteller_creds_manager).await?;
|
||||
}
|
||||
|
||||
Err(InternalObjectError::NoProviderAvailable)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+21
-38
@@ -1,25 +1,33 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
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::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
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::core::utils::save_base64_image_to_temp_dir::save_base64_image_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::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
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 anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_image_to_3d::GenerateHunyuan2ImageTo3dRequest;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
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 fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
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 tauri::AppHandle;
|
||||
use log::{error, info, warn};
|
||||
use storyteller_client::generate::object::generate_hunyuan2_image_to_3d::generate_hunyuan2_image_to_3d;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
pub async fn handle_object_artcraft(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
@@ -55,17 +63,14 @@ pub async fn handle_object_artcraft(
|
||||
None => {
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2 |
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0
|
||||
) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 2.0");
|
||||
let request = GenerateHunyuan20ImageTo3dRequest {
|
||||
Some(EnqueueImageTo3dObjectModel::Hunyuan3d2) => {
|
||||
info!("enqueue Hunyuan 3D 2.0");
|
||||
let request = GenerateHunyuan2ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
};
|
||||
let result = generate_hunyuan3d_2_0_image_to_3d(
|
||||
&app_env_configs.storyteller_host,
|
||||
let result = generate_hunyuan2_image_to_3d(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
@@ -80,28 +85,6 @@ pub async fn handle_object_artcraft(
|
||||
}
|
||||
}
|
||||
}
|
||||
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(
|
||||
&app_env_configs.storyteller_host,
|
||||
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");
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
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::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
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::core::utils::save_base64_image_to_temp_dir::save_base64_image_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::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::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 fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_object_fal(
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
@@ -48,11 +53,8 @@ pub async fn handle_object_fal(
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
temp_download = download_media_file_to_temp_dir(
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
&media_token
|
||||
).await?;
|
||||
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")));
|
||||
}
|
||||
@@ -65,19 +67,13 @@ pub async fn handle_object_fal(
|
||||
None => {
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0 |
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2
|
||||
) => {
|
||||
Some(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 {
|
||||
|
||||
@@ -7,7 +7,6 @@ use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
#[derive(Debug)]
|
||||
pub enum InternalObjectError {
|
||||
NoModelSpecified,
|
||||
NoProviderAvailable,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
|
||||
+62
-50
@@ -1,21 +1,62 @@
|
||||
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::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::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
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 fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
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 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 serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
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 tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -25,7 +66,14 @@ pub struct EnqueueImageToVideoRequest {
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<VideoModel>,
|
||||
pub model: Option<EnqueueImageToVideoModel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageToVideoModel {
|
||||
#[serde(rename = "kling1_6")]
|
||||
Kling16,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -39,8 +87,6 @@ impl SerializeMarker for EnqueueImageToVideoSuccessResponse {}
|
||||
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
|
||||
@@ -55,9 +101,7 @@ pub enum EnqueueImageToVideoErrorType {
|
||||
pub async fn enqueue_image_to_video_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
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>,
|
||||
@@ -67,11 +111,9 @@ pub async fn enqueue_image_to_video_command(
|
||||
info!("enqueue_image_to_video_command called");
|
||||
|
||||
let result = handle_request(
|
||||
request,
|
||||
&app,
|
||||
&app_env_configs,
|
||||
request,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
@@ -89,12 +131,7 @@ pub async fn enqueue_image_to_video_command(
|
||||
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";
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
InternalVideoError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
@@ -124,44 +161,19 @@ pub async fn enqueue_image_to_video_command(
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
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_env_configs,
|
||||
app_data_root,
|
||||
storyteller_creds_manager
|
||||
).await?);
|
||||
}
|
||||
Provider::Fal => {
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
return Ok(handle_video_fal(
|
||||
&app,
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
request,
|
||||
fal_creds_manager,
|
||||
fal_task_queue
|
||||
).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
handle_video_fal(&app, app_data_root, request, fal_creds_manager, fal_task_queue).await?;
|
||||
} else {
|
||||
handle_video_artcraft(request, &app, app_data_root, storyteller_creds_manager).await?;
|
||||
}
|
||||
|
||||
Err(InternalVideoError::NoProviderAvailable)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::{EnqueueImageToVideoModel, 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::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
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::core::utils::save_base64_image_to_temp_dir::save_base64_image_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::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_image_to_3d::GenerateHunyuan2ImageTo3dRequest;
|
||||
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::creds::fal_api_key::FalApiKey;
|
||||
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 fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
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 log::{error, info, warn};
|
||||
use storyteller_client::generate::object::generate_hunyuan2_image_to_3d::generate_hunyuan2_image_to_3d;
|
||||
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;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
pub async fn handle_video_artcraft(
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
@@ -67,8 +69,8 @@ pub async fn handle_video_artcraft(
|
||||
None => {
|
||||
return Err(InternalVideoError::NoModelSpecified);
|
||||
}
|
||||
Some(VideoModel::Kling16Pro) => {
|
||||
info!("enqueue Kling 1.6 Pro with Artcraft API");
|
||||
Some(EnqueueImageToVideoModel::Kling16) => {
|
||||
info!("enqueue Kling 1.6");
|
||||
selected_model = Some(GenerationModel::Kling1_6);
|
||||
let request = GenerateKling16ProImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
@@ -78,7 +80,7 @@ pub async fn handle_video_artcraft(
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_16_pro_image_to_video(
|
||||
&app_env_configs.storyteller_host,
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
@@ -93,110 +95,6 @@ pub async fn handle_video_artcraft(
|
||||
}
|
||||
}
|
||||
}
|
||||
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(
|
||||
&app_env_configs.storyteller_host,
|
||||
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(
|
||||
&app_env_configs.storyteller_host,
|
||||
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(
|
||||
&app_env_configs.storyteller_host,
|
||||
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(
|
||||
&app_env_configs.storyteller_host,
|
||||
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");
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::{EnqueueImageToVideoModel, 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::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
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::core::utils::save_base64_image_to_temp_dir::save_base64_image_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::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::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 fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
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 log::{error, info, warn};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_video_fal(
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
@@ -49,15 +54,14 @@ pub async fn handle_video_fal(
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
temp_download = download_media_file_to_temp_dir(
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
&media_token
|
||||
).await?;
|
||||
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")));
|
||||
}
|
||||
|
||||
info!("Calling FAL image to 3d ...");
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let mut selected_model = None;
|
||||
@@ -66,8 +70,8 @@ pub async fn handle_video_fal(
|
||||
None => {
|
||||
return Err(InternalVideoError::NoModelSpecified);
|
||||
}
|
||||
Some(VideoModel::Kling16Pro) => {
|
||||
info!("enqueue Kling 1.6 image to video with Kling API");
|
||||
Some(EnqueueImageToVideoModel::Kling16) => {
|
||||
info!("enqueue Kling 1.6 image to video");
|
||||
selected_model = Some(GenerationModel::Kling1_6);
|
||||
enqueue_kling_16_pro_image_to_video(Kling16ProArgs {
|
||||
image_path: filename,
|
||||
@@ -77,9 +81,6 @@ pub async fn handle_video_fal(
|
||||
prompt: "",
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("Unsupported model: {:?}", request.model)));
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
|
||||
@@ -7,7 +7,6 @@ use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
#[derive(Debug)]
|
||||
pub enum InternalVideoError {
|
||||
NoModelSpecified,
|
||||
NoProviderAvailable,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
|
||||
+12
-13
@@ -1,41 +1,40 @@
|
||||
use crate::core::commands::response::shorthand::InfallibleResponse;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
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 chrono::{DateTime, Utc};
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::ImageReader;
|
||||
use log::info;
|
||||
use serde_derive::Serialize;
|
||||
use tauri::State;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AppInfoResponse {
|
||||
pub struct BuildInfoResponse {
|
||||
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 AppInfoResponse {}
|
||||
impl SerializeMarker for BuildInfoResponse {}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_app_info_command(
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
) -> InfallibleResponse<AppInfoResponse> {
|
||||
info!("get_app_info_command called...");
|
||||
pub fn get_build_info_command() -> InfallibleResponse<BuildInfoResponse> {
|
||||
info!("get_build_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();
|
||||
|
||||
let storyteller_host = app_env_configs.storyteller_host.to_api_hostname_and_scheme();
|
||||
|
||||
AppInfoResponse {
|
||||
BuildInfoResponse {
|
||||
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,7 +1,6 @@
|
||||
pub mod app_preferences;
|
||||
pub mod enqueue;
|
||||
pub mod flip_image;
|
||||
pub mod get_app_info_command;
|
||||
pub mod get_build_info_command;
|
||||
pub mod platform_info_command;
|
||||
pub mod providers;
|
||||
pub mod response;
|
||||
@@ -1,8 +1,13 @@
|
||||
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 {
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
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())
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod get_provider_order_command;
|
||||
pub mod set_provider_order_command;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user