mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-23 18:19:30 -04:00
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f767ef655 | ||
|
|
da859fc2f8 | ||
|
|
5f832d5d17 | ||
|
|
612e58483c | ||
|
|
11b07b5833 | ||
|
|
a3f3bd8b3f | ||
|
|
a93e921b51 | ||
|
|
58d5a172c4 | ||
|
|
012b7a9567 | ||
|
|
18f4411f2a | ||
|
|
0ed4d58387 | ||
|
|
9782bd33bc | ||
|
|
2c9be7c262 | ||
|
|
a1f38b3695 | ||
|
|
d5566c3466 | ||
|
|
27d2ae47b3 | ||
|
|
926076441b | ||
|
|
64353f1be9 | ||
|
|
a8bb7f2720 | ||
|
|
ec1257ccf6 | ||
|
|
6038fa4fd3 | ||
|
|
8ae6867e05 | ||
|
|
9bc24ac216 | ||
|
|
590c2fbf96 | ||
|
|
212283e8d2 | ||
|
|
40d22b8ad0 |
@@ -0,0 +1,232 @@
|
||||
name: "Artcraft MacOS Publish"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
- artcraft-release
|
||||
- artcraft-macos # Additional branch for MacOS testing
|
||||
|
||||
# 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-xlarge'
|
||||
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-xlarge' && '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: |
|
||||
# Navigate to the frontend directory to install dependencies
|
||||
cd ./frontend
|
||||
|
||||
npm cache clean --force
|
||||
|
||||
echo "Installing all workspace dependencies..."
|
||||
npm ci
|
||||
|
||||
echo "=== Version Information ==="
|
||||
echo "Node version:"
|
||||
node --version
|
||||
echo "NPM version:"
|
||||
npm --version
|
||||
|
||||
# Remove mismatched @nx packages and reinstall with correct versions
|
||||
npm install --save-dev @nx/remix@21.1.1 @nx/js@21.1.1 @nx/react@21.1.1 @nx/vite@21.1.1 @nx/eslint@21.1.1
|
||||
|
||||
echo "=== Nx versions after adjustment ==="
|
||||
npm list --depth=0 | grep nx
|
||||
|
||||
- name: import Apple Developer Certificate
|
||||
if: matrix.platform == 'macos-latest-xlarge'
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
echo "Starting certificate import process..."
|
||||
|
||||
# Create certificate file
|
||||
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
|
||||
|
||||
# Debug: Check if certificate file exists and has content
|
||||
echo "Certificate file info:"
|
||||
ls -la certificate.p12
|
||||
file certificate.p12
|
||||
|
||||
# Debug: Check certificate contents without password (just structure)
|
||||
echo "Certificate structure (may show error if password protected):"
|
||||
openssl pkcs12 -info -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -noout || echo "Could not read certificate structure"
|
||||
|
||||
# Debug: Try to list what's in the p12 file
|
||||
echo "Attempting to list certificate contents:"
|
||||
security cms -D -i certificate.p12 || echo "Not a CMS file, trying PKCS12..."
|
||||
openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -info -nodes 2>&1 | head -20 || echo "Failed to read PKCS12 contents"
|
||||
|
||||
# Debug: Extract certificate details to verify type and expiration
|
||||
echo "Extracting certificate details..."
|
||||
# Extract just the certificate portion and examine it
|
||||
openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(Subject:|Issuer:|Not Before|Not After)" || echo "Failed to parse certificate details"
|
||||
|
||||
# Also try to see what type of certificate this is
|
||||
echo "Certificate type check:"
|
||||
openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -nokeys 2>/dev/null | openssl x509 -text -noout | grep -E "(CN=|OU=)" || echo "Could not determine certificate type"
|
||||
|
||||
# Debug: Check if private key exists
|
||||
echo "Checking for private key..."
|
||||
openssl pkcs12 -in certificate.p12 -passin pass:$APPLE_CERTIFICATE_PASSWORD -nocerts -nodes 2>&1 | grep -E "(PRIVATE KEY|RSA KEY)" | head -5 || echo "No private key found"
|
||||
|
||||
# Create temporary keychain
|
||||
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
|
||||
|
||||
# Import certificate - ensure both certificate and private key are imported
|
||||
echo "Importing certificate into keychain..."
|
||||
# Import the p12 directly - this is the most reliable method
|
||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild
|
||||
|
||||
# Alternative: Try with additional flags if the first import didn't create an identity
|
||||
echo "Verifying import..."
|
||||
if ! security find-identity -v build.keychain | grep -q "1 valid identities found"; then
|
||||
echo "First import didn't create identity, trying with -A flag..."
|
||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -A
|
||||
fi
|
||||
|
||||
# Set trust settings for the certificate
|
||||
echo "Setting trust settings..."
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
|
||||
# Unlock the keychain again to ensure it's accessible
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
|
||||
# Add the keychain to the search list
|
||||
security list-keychains -d user -s build.keychain $(security list-keychains -d user | sed 's/^[[:space:]]*"//;s/"$//')
|
||||
|
||||
# List all certificates to debug
|
||||
echo "Listing all keychains:"
|
||||
security list-keychains
|
||||
|
||||
echo "Listing all certificates in build keychain:"
|
||||
security find-certificate -a -p build.keychain || echo "No certificates found"
|
||||
|
||||
echo "Detailed certificate info:"
|
||||
security find-certificate -c "Learning Machines" build.keychain || echo "Certificate 'Learning Machines' not found"
|
||||
|
||||
# Check certificate trust settings
|
||||
echo "Checking certificate trust settings:"
|
||||
security dump-trust-settings || echo "No custom trust settings"
|
||||
|
||||
# Try to find codesigning identities
|
||||
echo "Finding all identities in keychain:"
|
||||
security find-identity -v build.keychain || echo "No identities found"
|
||||
|
||||
echo "Finding codesigning identities:"
|
||||
security find-identity -v -p codesigning build.keychain || echo "No codesigning identities found"
|
||||
|
||||
# Additional debug: Check if this is a valid Developer ID cert
|
||||
echo "Checking for Developer ID certificates:"
|
||||
security find-certificate -a build.keychain | grep -A 5 -B 5 "Developer ID" || echo "No Developer ID certificates found"
|
||||
|
||||
- name: verify certificate
|
||||
if: matrix.platform == 'macos-latest-xlarge'
|
||||
run: |
|
||||
echo "=== Certificate Verification ==="
|
||||
|
||||
echo "1. Checking codesigning identities:"
|
||||
security find-identity -v -p codesigning build.keychain || echo "No codesigning identities found"
|
||||
|
||||
echo -e "\n2. Checking all identities:"
|
||||
security find-identity -v build.keychain || echo "No identities found"
|
||||
|
||||
echo -e "\n3. Checking certificates in keychain:"
|
||||
security find-certificate -c "Learning Machines" build.keychain -p | openssl x509 -text -noout | grep -E "(Subject:|CN=)" || echo "Could not parse certificate"
|
||||
|
||||
# Set the correct signing identity
|
||||
echo "APPLE_SIGNING_IDENTITY=Developer ID Application: Learning Machines LLC (DJ6XS33FX8)" >> $GITHUB_ENV
|
||||
echo "Proceeding with identity: Developer ID Application: Learning Machines LLC (DJ6XS33FX8)"
|
||||
|
||||
# Also set the team ID
|
||||
echo "APPLE_DEVELOPMENT_TEAM=DJ6XS33FX8" >> $GITHUB_ENV
|
||||
|
||||
- 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
|
||||
export 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..."
|
||||
ls -la apps/editor3d/dist/ | head -10
|
||||
|
||||
# Update Tauri config to skip the build (since we already built)
|
||||
cd ${{ github.workspace }}/crates/desktop/artcraft
|
||||
cp tauri.conf.json tauri.conf.json.bak
|
||||
# Set beforeBuildCommand to empty or a no-op command
|
||||
if command -v jq &> /dev/null; then
|
||||
jq '.build.beforeBuildCommand = ""' tauri.conf.json > tauri.conf.json.tmp
|
||||
mv tauri.conf.json.tmp tauri.conf.json
|
||||
else
|
||||
node -e "const fs = require('fs'); const config = JSON.parse(fs.readFileSync('tauri.conf.json', 'utf8')); config.build.beforeBuildCommand = ''; fs.writeFileSync('tauri.conf.json', JSON.stringify(config, null, 2));"
|
||||
fi
|
||||
|
||||
|
||||
- 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_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_DEVELOPMENT_TEAM: ${{ env.APPLE_DEVELOPMENT_TEAM }}
|
||||
# Use the SQLx query cache for SQLite
|
||||
SQLX_OFFLINE: true
|
||||
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,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 }}
|
||||
@@ -39,24 +39,22 @@ jobs:
|
||||
# Navigate to the frontend directory to install dependencies
|
||||
cd ./frontend
|
||||
|
||||
yarn cache clean
|
||||
npm cache clean --force
|
||||
|
||||
echo "Installing all workspace dependencies..."
|
||||
yarn install --production=false --frozen-lockfile
|
||||
npm ci
|
||||
|
||||
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
|
||||
npm install --save-dev @nx/remix@21.1.1 @nx/js@21.1.1 @nx/react@21.1.1 @nx/vite@21.1.1 @nx/eslint@21.1.1
|
||||
|
||||
echo "=== Nx versions after adjustment ==="
|
||||
yarn list --pattern "nx" --depth=0
|
||||
npm list --depth=0 | findstr nx
|
||||
|
||||
- name: Pre-build frontend to avoid Tauri build issues
|
||||
run: |
|
||||
@@ -76,7 +74,7 @@ jobs:
|
||||
# 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 = ""
|
||||
@@ -86,6 +84,8 @@ jobs:
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Use the SQLx query cache for SQLite
|
||||
SQLX_OFFLINE: true
|
||||
with:
|
||||
projectPath: ./crates/desktop/artcraft
|
||||
tagName: artcraft-windows-v__VERSION__ # the action automatically replaces __VERSION__ with the app version.
|
||||
|
||||
+4
-2
@@ -24,11 +24,13 @@ twitch_secrets.toml
|
||||
*-secrets.env
|
||||
|
||||
# Utilities with secrets
|
||||
script/production_database_secret_backup.sh
|
||||
script/production_database_secret_connect.sh
|
||||
production_database_secret_connect.sh
|
||||
production_database_secret_query.sh
|
||||
production_database_voice_clone_report_download.sh
|
||||
script/artcraft/mac_signing_build.sh
|
||||
script/production_database_secret_backup.sh
|
||||
script/production_database_secret_connect.sh
|
||||
script/socat_dev_database_connect.sh
|
||||
|
||||
# Scratch data
|
||||
scratch/
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO tasks (\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n frontend_subscriber_id,\n frontend_subscriber_payload\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "22b91a21cbb88abc9bc17b338cf2f9418e08e414667d44476623eb141ab17b87"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET task_status = ?\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "dc0367e9e43c3d7dda3623e77337ab4529eac2192475be7d08589886703ba1cb"
|
||||
}
|
||||
Generated
+52
-54
@@ -656,6 +656,7 @@ dependencies = [
|
||||
"directories",
|
||||
"dirs",
|
||||
"discord-rich-presence",
|
||||
"enums",
|
||||
"errors",
|
||||
"fal_client",
|
||||
"filesys",
|
||||
@@ -675,6 +676,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
"sqlite_tasks",
|
||||
"storyteller_client",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
@@ -696,11 +698,13 @@ dependencies = [
|
||||
name = "artcraft_api_defs"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"enums",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tokens",
|
||||
"url",
|
||||
"utoipa",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
@@ -985,16 +989,6 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-write-file"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c232177ba50b16fe7a4588495bd474a62a9e45a8e4ca6fd7d0b7ac29d164631e"
|
||||
dependencies = [
|
||||
"nix 0.26.4",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "attohttpc"
|
||||
version = "0.28.0"
|
||||
@@ -1924,6 +1918,13 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cloudflare_errors"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coarsetime"
|
||||
version = "0.1.34"
|
||||
@@ -2285,7 +2286,7 @@ dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils 0.8.20",
|
||||
"memoffset 0.9.0",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
@@ -3667,7 +3668,7 @@ version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
|
||||
dependencies = [
|
||||
"memoffset 0.9.0",
|
||||
"memoffset",
|
||||
"rustc_version 0.4.0",
|
||||
]
|
||||
|
||||
@@ -5988,15 +5989,6 @@ version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
@@ -6379,19 +6371,6 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"memoffset 0.7.1",
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.28.0"
|
||||
@@ -6402,7 +6381,7 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
"memoffset 0.9.0",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6869,6 +6848,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"cloudflare_errors",
|
||||
"cookie 0.18.1",
|
||||
"errors",
|
||||
"idempotency",
|
||||
@@ -9645,11 +9625,31 @@ dependencies = [
|
||||
"unicode_categories",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlite_tasks"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"enums",
|
||||
"errors",
|
||||
"log",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"tokens",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
|
||||
checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
@@ -9660,9 +9660,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
|
||||
checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"atoi",
|
||||
@@ -9672,7 +9672,6 @@ dependencies = [
|
||||
"chrono",
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"event-listener 2.5.3",
|
||||
"futures-channel",
|
||||
@@ -9706,9 +9705,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
|
||||
checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -9719,11 +9718,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
|
||||
checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
|
||||
dependencies = [
|
||||
"atomic-write-file",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"heck 0.4.1",
|
||||
@@ -9746,9 +9744,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4"
|
||||
checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.7",
|
||||
@@ -9790,9 +9788,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
|
||||
checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.21.7",
|
||||
@@ -9820,7 +9818,6 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1 0.10.6",
|
||||
"sha2 0.10.8",
|
||||
"smallvec 1.13.2",
|
||||
"sqlx-core",
|
||||
@@ -9832,9 +9829,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490"
|
||||
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
@@ -10002,6 +9999,7 @@ dependencies = [
|
||||
"artcraft_api_defs",
|
||||
"backoff",
|
||||
"chrono",
|
||||
"cloudflare_errors",
|
||||
"cookie 0.18.1",
|
||||
"enums",
|
||||
"enums_public",
|
||||
@@ -11589,7 +11587,7 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
|
||||
dependencies = [
|
||||
"memoffset 0.9.0",
|
||||
"memoffset",
|
||||
"tempfile",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
@@ -13174,7 +13172,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"nix 0.28.0",
|
||||
"nix",
|
||||
"ordered-stream",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
|
||||
@@ -40,6 +40,7 @@ members = [
|
||||
"crates/lib/caching/memory_caching",
|
||||
"crates/lib/caching/memory_store",
|
||||
"crates/lib/caching/redis_caching",
|
||||
"crates/lib/cloudflare_errors",
|
||||
"crates/lib/concurrency",
|
||||
"crates/lib/cookies",
|
||||
"crates/lib/data/collections",
|
||||
@@ -90,6 +91,7 @@ members = [
|
||||
"crates/schema/database/mysql_queries",
|
||||
"crates/schema/database/redis_common",
|
||||
"crates/schema/database/redis_schema",
|
||||
"crates/schema/database/sqlite_tasks",
|
||||
#"crates/schema/database/sqlite_queries",
|
||||
|
||||
# Schema (public): common strongly-typed tokens, ids, payloads, etc.
|
||||
@@ -215,6 +217,7 @@ url = { version = "2.5.4", features = ["serde"] }
|
||||
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" }
|
||||
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
|
||||
crockford = { path = "crates/lib/data/crockford" }
|
||||
enums = { path = "crates/schema/public/enums" }
|
||||
enums_public = { path = "crates/schema/public/enums_public" }
|
||||
@@ -231,6 +234,7 @@ mimetypes = { path = "crates/lib/files/mimetypes" }
|
||||
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
|
||||
primitives = { path = "crates/lib/data/primitives" }
|
||||
shared_service_components = { path = "crates/lib/shared_service_components" }
|
||||
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
|
||||
storyteller_client = { path = "crates/api_clients/storyteller_client" }
|
||||
subprocess_common = { path = "crates/lib/subprocess_common" }
|
||||
test_data = { path = "crates/lib/test_data" }
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- Migration history:
|
||||
-- tasks_v1.sqlite - initial version
|
||||
-- tasks_v2.sqlite - added model_type (nullable)
|
||||
|
||||
CREATE TABLE tasks (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
|
||||
task_status TEXT NOT NULL,
|
||||
task_type TEXT NOT NULL,
|
||||
|
||||
model_type TEXT,
|
||||
|
||||
provider TEXT,
|
||||
provider_job_id TEXT,
|
||||
|
||||
frontend_subscriber_id TEXT,
|
||||
frontend_subscriber_payload TEXT,
|
||||
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
|
||||
completed_at INTEGER DEFAULT NULL
|
||||
);
|
||||
|
||||
-- Unique Indices
|
||||
CREATE UNIQUE INDEX idx_tasks_on_provider_job_id ON tasks(provider_job_id);
|
||||
|
||||
-- Indices
|
||||
CREATE INDEX idx_tasks_on_status ON tasks(task_status);
|
||||
CREATE INDEX idx_tasks_on_provider ON tasks(provider);
|
||||
+4
-4
@@ -37,10 +37,10 @@ pub async fn enqueue_veo_2_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
let duration = match args.duration {
|
||||
Veo2Duration::Default => None,
|
||||
Veo2Duration::FiveSeconds => Some("5".to_string()),
|
||||
Veo2Duration::SixSeconds => Some("6".to_string()),
|
||||
Veo2Duration::SevenSeconds => Some("7".to_string()),
|
||||
Veo2Duration::EightSeconds => Some("8".to_string()),
|
||||
Veo2Duration::FiveSeconds => Some("5s".to_string()),
|
||||
Veo2Duration::SixSeconds => Some("6s".to_string()),
|
||||
Veo2Duration::SevenSeconds => Some("7s".to_string()),
|
||||
Veo2Duration::EightSeconds => Some("8s".to_string()),
|
||||
};
|
||||
|
||||
let aspect_ratio = match args.aspect_ratio {
|
||||
|
||||
@@ -23,6 +23,7 @@ idempotency.workspace = true
|
||||
anyhow.workspace = true
|
||||
base64.workspace = true
|
||||
chrono.workspace = true
|
||||
cloudflare_errors.workspace = true
|
||||
cookie.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use cloudflare_errors::cloudflare_error::CloudflareError;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
@@ -42,15 +43,9 @@ pub enum SoraError {
|
||||
/// Typically served from Cloudflare
|
||||
/// We preserve the message for debugging.
|
||||
BadGateway(String),
|
||||
|
||||
/// HTTP 403 + Cloudflare Challenge Interstitial
|
||||
/// Cloudflare decided to block the request and present a challenge.
|
||||
CloudFlareChallenge,
|
||||
|
||||
/// HTTP 524 (Cloudflare-specific)
|
||||
/// Cloudflare formed a TCP connection to Sora, but no payload was delivered before timeout
|
||||
/// We preserve the message for debugging
|
||||
CloudFlareTimeout(String),
|
||||
|
||||
/// Specific Cloudflare errors.
|
||||
CloudflareError(CloudflareError),
|
||||
|
||||
/// Another error occurred.
|
||||
OtherBadStatus(anyhow::Error),
|
||||
@@ -72,6 +67,17 @@ pub enum SoraError {
|
||||
JsonErrorWithBody(serde_json::Error, String),
|
||||
}
|
||||
|
||||
impl SoraError {
|
||||
pub fn is_sora_having_downtime_issues(&self) -> bool {
|
||||
match self {
|
||||
Self::BadGateway(_) => true,
|
||||
Self::CloudflareError(CloudflareError::GatewayTimeout504) => true,
|
||||
Self::CloudflareError(CloudflareError::TimeoutOccurred524) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SoraError {}
|
||||
|
||||
impl Display for SoraError {
|
||||
@@ -98,11 +104,8 @@ impl Display for SoraError {
|
||||
Self::BadGateway(message) => {
|
||||
write!(f, "Bad Gateway; Sora is likely having issues: {:?}", message)
|
||||
}
|
||||
Self::CloudFlareChallenge => {
|
||||
write!(f, "Cloudflare Challenge (403); Cloudflare wants to verify the request with a CAPTCHA challenge.")
|
||||
}
|
||||
Self::CloudFlareTimeout(message) => {
|
||||
write!(f, "Cloudflare Timeout (524); Sora is likely having issues: {:?}", message)
|
||||
Self::CloudflareError(error) => {
|
||||
write!(f, "Cloudflare error: {}", error)
|
||||
}
|
||||
Self::OtherBadStatus(err) => {
|
||||
write!(f, "Other error: {}", err)
|
||||
@@ -149,3 +152,9 @@ impl From<serde_json::Error> for SoraError {
|
||||
Self::JsonError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CloudflareError> for SoraError {
|
||||
fn from(err: CloudflareError) -> Self {
|
||||
Self::CloudflareError(err)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-23
@@ -1,6 +1,6 @@
|
||||
use crate::sora_error::SoraError;
|
||||
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
|
||||
use reqwest::StatusCode;
|
||||
|
||||
/*
|
||||
|
||||
Old cookie -
|
||||
@@ -50,32 +50,15 @@ pub async fn classify_general_http_status_code_and_body(status: StatusCode, resp
|
||||
}
|
||||
|
||||
let status_code = status.as_u16();
|
||||
|
||||
|
||||
if let Err(err) = filter_cloudflare_errors(status_code, &response_body) {
|
||||
return SoraError::CloudflareError(err);
|
||||
}
|
||||
|
||||
match status_code {
|
||||
403 => {
|
||||
if message.contains("challenge-platform")
|
||||
|| message.contains("challenge-error-text")
|
||||
|| message.contains("cType: 'managed'")
|
||||
|| message.contains("Just a moment...") {
|
||||
return SoraError::CloudFlareChallenge;
|
||||
}
|
||||
}
|
||||
502 => {
|
||||
return SoraError::BadGateway(message);
|
||||
}
|
||||
504 => {
|
||||
if message.contains("cloudflare")
|
||||
|| message.contains("Cloudflare") {
|
||||
return SoraError::CloudFlareTimeout(message);
|
||||
}
|
||||
}
|
||||
524 => {
|
||||
// NB: 524 is a cloudflare specific gateway timeout error with slightly different semantics than 504.
|
||||
if message.contains("cloudflare")
|
||||
|| message.contains("Cloudflare") {
|
||||
return SoraError::CloudFlareTimeout(message);
|
||||
}
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,13 @@ workspace-build-acceleration.workspace = true
|
||||
|
||||
# Internal
|
||||
artcraft_api_defs.workspace = true
|
||||
cloudflare_errors.workspace = true
|
||||
enums.workspace = true
|
||||
enums_public.workspace = true
|
||||
errors.workspace = true
|
||||
idempotency.workspace = true
|
||||
tokens.workspace = true
|
||||
mimetypes.workspace = true
|
||||
tokens.workspace = true
|
||||
|
||||
# External
|
||||
anyhow.workspace = true
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use cloudflare_errors::cloudflare_error::CloudflareError;
|
||||
use errors::AnyhowError;
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io;
|
||||
use errors::AnyhowError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ApiError {
|
||||
@@ -26,6 +27,9 @@ pub enum ApiError {
|
||||
backend_hostname: Option<String>,
|
||||
},
|
||||
|
||||
/// Cloudflare errors.
|
||||
CloudflareError(CloudflareError),
|
||||
|
||||
/// A deserialization error with the response.
|
||||
DeserializationError(serde_json::Error),
|
||||
|
||||
@@ -63,6 +67,8 @@ impl Display for ApiError {
|
||||
// Network errors
|
||||
ApiError::Timeout(msg) => write!(f, "Timeout: {}", msg),
|
||||
ApiError::NetworkError(msg) => write!(f, "Network error: {}", msg),
|
||||
// Cloudflare errors
|
||||
ApiError::CloudflareError(error) => write!(f, "Cloudflare Error: {}", error),
|
||||
// I/O errors
|
||||
ApiError::IoError(error) => write!(f, "IO error: {}", error),
|
||||
// Other
|
||||
@@ -95,3 +101,9 @@ impl From<io::Error> for ApiError {
|
||||
ApiError::IoError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CloudflareError> for ApiError {
|
||||
fn from(error: CloudflareError) -> Self {
|
||||
ApiError::CloudflareError(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_get_request::basic_json_get_request;
|
||||
use artcraft_api_defs::jobs::list_session_jobs::{ListSessionJobsSuccessResponse, LIST_SESSION_JOBS_URL_PATH};
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum States {
|
||||
All,
|
||||
Include(HashSet<JobStatusPlus>),
|
||||
Exclude(HashSet<JobStatusPlus>),
|
||||
}
|
||||
|
||||
pub async fn list_session_jobs(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
job_states: States,
|
||||
) -> Result<ListSessionJobsSuccessResponse, StorytellerError> {
|
||||
|
||||
let url = match job_states {
|
||||
States::All =>
|
||||
LIST_SESSION_JOBS_URL_PATH.to_string(),
|
||||
States::Include(states) =>
|
||||
format!("{}?include_states={}", LIST_SESSION_JOBS_URL_PATH, states_string(&states)),
|
||||
States::Exclude(states) =>
|
||||
format!("{}?exclude_states={}", LIST_SESSION_JOBS_URL_PATH, states_string(&states)),
|
||||
};
|
||||
|
||||
Ok(basic_json_get_request(
|
||||
api_host,
|
||||
&url,
|
||||
maybe_creds,
|
||||
).await?)
|
||||
}
|
||||
|
||||
fn states_string(states: &HashSet<JobStatusPlus>) -> String {
|
||||
states.iter()
|
||||
.map(|state| state.to_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod list_session_jobs;
|
||||
@@ -3,6 +3,7 @@
|
||||
pub mod credentials;
|
||||
pub mod error;
|
||||
pub mod generate;
|
||||
pub mod jobs;
|
||||
pub mod media_files;
|
||||
pub mod recipes;
|
||||
pub mod shared_response_types;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::client_error::ClientError;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
|
||||
use log::debug;
|
||||
use reqwest::Client;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub async fn basic_json_get_request<Res: DeserializeOwned>(
|
||||
api_host: &ApiHost,
|
||||
route_path: &str,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
) -> Result<Res, StorytellerError> {
|
||||
|
||||
let url = get_route(api_host, route_path);
|
||||
|
||||
debug!("Requesting {:?}", &url);
|
||||
|
||||
let client = Client::builder()
|
||||
.gzip(true)
|
||||
.build()
|
||||
.map_err(|err| StorytellerError::Client(ClientError::from(err)))?;
|
||||
|
||||
let mut request_builder = client.get(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", APPLICATION_JSON);
|
||||
|
||||
if let Some(creds) = maybe_creds {
|
||||
if let Some(header) = &creds.maybe_as_cookie_header() {
|
||||
request_builder = request_builder.header("Cookie", header);
|
||||
}
|
||||
}
|
||||
|
||||
let response = request_builder
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
let response = filter_bad_response(response)
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
let response_body = &response.text()
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
debug!("Response body: {:?}", response_body);
|
||||
|
||||
let response = serde_json::from_str(&response_body)
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn get_route(api_host: &ApiHost, route_path: &str) -> String {
|
||||
let api_hostname = api_host.to_api_hostname_and_scheme();
|
||||
format!("{}{}", api_hostname, route_path)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::utils::status_codes::*;
|
||||
use anyhow::anyhow;
|
||||
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
|
||||
use log::error;
|
||||
|
||||
/// Pass request errors to this method for standard error handling behavior.
|
||||
@@ -13,30 +14,34 @@ use log::error;
|
||||
pub async fn filter_bad_response(response: reqwest::Response) -> Result<reqwest::Response, ApiError> {
|
||||
let status = response.status();
|
||||
|
||||
if !status.is_success() {
|
||||
// The host in the fleet that handled the request.
|
||||
let backend_hostname= response.headers()
|
||||
.get("x-backend-hostname")
|
||||
.map(|header| header.to_str().unwrap_or_else(|err| {
|
||||
error!("Failed to parse x-backend-hostname header: {:?}", err);
|
||||
"unknown"
|
||||
}))
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let response_body = match response.text().await {
|
||||
Ok(text) => text.to_string(),
|
||||
Err(err) => format!("Could not read response body: {:?}", err),
|
||||
};
|
||||
|
||||
return match status.as_u16() {
|
||||
STATUS_401_UNAUTHORIZED => Err(ApiError::Unauthorized(response_body)),
|
||||
STATUS_403_FORBIDDEN => Err(ApiError::Forbidden(response_body)),
|
||||
STATUS_404_NOT_FOUND => Err(ApiError::NotFound(response_body)),
|
||||
STATUS_429_TOO_MANY_REQUESTS => Err(ApiError::TooManyRequests(response_body)),
|
||||
STATUS_500_INTERNAL_SERVER_ERROR => Err(ApiError::InternalServerError { body: response_body, backend_hostname }),
|
||||
_ => Err(ApiError::Other(anyhow!("Bad status code: {}; message: {:?}", status, response_body))),
|
||||
}
|
||||
if status.is_success() {
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
// The host in the fleet that handled the request.
|
||||
let backend_hostname= response.headers()
|
||||
.get("x-backend-hostname")
|
||||
.map(|header| header.to_str().unwrap_or_else(|err| {
|
||||
error!("Failed to parse x-backend-hostname header: {:?}", err);
|
||||
"unknown"
|
||||
}))
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let response_body = match response.text().await {
|
||||
Ok(text) => text.to_string(),
|
||||
Err(err) => format!("Could not read response body: {:?}", err),
|
||||
};
|
||||
|
||||
let status_code = status.as_u16();
|
||||
|
||||
filter_cloudflare_errors(status_code, &response_body)?;
|
||||
|
||||
match status_code {
|
||||
STATUS_401_UNAUTHORIZED => Err(ApiError::Unauthorized(response_body)),
|
||||
STATUS_403_FORBIDDEN => Err(ApiError::Forbidden(response_body)),
|
||||
STATUS_404_NOT_FOUND => Err(ApiError::NotFound(response_body)),
|
||||
STATUS_429_TOO_MANY_REQUESTS => Err(ApiError::TooManyRequests(response_body)),
|
||||
STATUS_500_INTERNAL_SERVER_ERROR => Err(ApiError::InternalServerError { body: response_body, backend_hostname }),
|
||||
_ => Err(ApiError::Other(anyhow!("Bad status code: {}; message: {:?}", status, response_body))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod api_host;
|
||||
pub mod basic_json_get_request;
|
||||
pub mod basic_json_post_request;
|
||||
pub mod constants;
|
||||
pub mod filter_bad_response;
|
||||
|
||||
@@ -43,7 +43,7 @@ rand.workspace = true
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
|
||||
elasticsearch = "8.5.0-alpha.1"
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ log.workspace = true
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
tokio.workspace = true
|
||||
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
# None yet
|
||||
|
||||
@@ -36,7 +36,7 @@ log = "0.4.14"
|
||||
|
||||
# Bigger pieces
|
||||
futures = { version = "0.3.17", features = ["thread-pool"] }
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
|
||||
# Serialization
|
||||
|
||||
@@ -36,7 +36,7 @@ log = "0.4.14"
|
||||
|
||||
# Bigger pieces
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
|
||||
# Serialization
|
||||
serde = "1.0.125"
|
||||
|
||||
@@ -38,7 +38,7 @@ strum = { version = "0.26", features = ["derive"] }
|
||||
|
||||
# Bigger pieces
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
elasticsearch = "8.5.0-alpha.1"
|
||||
|
||||
# Serialization
|
||||
|
||||
@@ -40,7 +40,7 @@ tempdir.workspace = true
|
||||
|
||||
# Async + clients
|
||||
tokio.workspace = true
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
|
||||
# Serialization
|
||||
serde.workspace = true
|
||||
|
||||
@@ -28,6 +28,7 @@ workspace-build-acceleration = { workspace = true }
|
||||
# Internal
|
||||
artcraft_api_defs = { workspace = true }
|
||||
build_metadata = { workspace = true }
|
||||
enums = { workspace = true }
|
||||
errors = { workspace = true }
|
||||
fal_client = { workspace = true }
|
||||
filesys = { workspace = true }
|
||||
@@ -36,6 +37,7 @@ memory_store = { workspace = true }
|
||||
mimetypes = { workspace = true }
|
||||
openai_sora_client = { workspace = true }
|
||||
primitives = { workspace = true }
|
||||
sqlite_tasks.workspace = true
|
||||
storyteller_client = { workspace = true }
|
||||
tokens = { workspace = true }
|
||||
web_base64 = { workspace = true }
|
||||
@@ -58,6 +60,7 @@ serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-log = "2.3.1"
|
||||
tauri-plugin-upload = "2.2.1"
|
||||
tempdir = { workspace = true }
|
||||
@@ -72,7 +75,6 @@ tauri-plugin-http = { path = "../../vendor/tauri-plugin-http" }
|
||||
# NB: The version of the `discord-rich-presence` crate (0.2.5) last published has non-Send/Sync error
|
||||
# types and can't do threading. We need to wait for a new version to unpin from Git.
|
||||
discord-rich-presence = { git = "https://github.com/vionya/discord-rich-presence" }
|
||||
tauri-plugin-dialog = "2"
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "3.2.0"
|
||||
|
||||
+80
-16
@@ -1,8 +1,8 @@
|
||||
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::generic::handle_image_artcraft::handle_image_artcraft;
|
||||
use crate::core::commands::enqueue::image::generic::handle_image_fal::handle_image_fal;
|
||||
use crate::core::commands::enqueue::image::gpt_image_1::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::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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;
|
||||
@@ -14,14 +14,19 @@ 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::task_database::TaskDatabase;
|
||||
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 enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
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 sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -31,8 +36,25 @@ pub struct EnqueueTextToImageRequest {
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<ImageModel>,
|
||||
|
||||
/// Aspect ratio.
|
||||
pub aspect_ratio: Option<TextToImageSize>,
|
||||
}
|
||||
|
||||
// TODO(bt,2025-07-14): Support other aspect ratios / resolutions -
|
||||
// Flux Dev has: 4:3, 16:9, 3:4, 9:16, and custom.
|
||||
// Flux Schnell has: 4:3, 16:9, 3:4, 9:16, and custom.
|
||||
// Flux Pro has: 4:3, 16:9, 3:4, 9:16, and custom.
|
||||
// Flux Pro Ulra has: 4:3, 16:9, 3:4, 9:16, and custom.
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TextToImageSize {
|
||||
Auto,
|
||||
Square,
|
||||
Wide,
|
||||
Tall,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueTextToImageSuccessResponse {
|
||||
@@ -60,11 +82,12 @@ pub enum EnqueueTextToImageErrorType {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_text_to_image_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
app: AppHandle,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
@@ -75,10 +98,11 @@ pub async fn enqueue_text_to_image_command(
|
||||
info!("enqueue_text_to_image called");
|
||||
|
||||
let result = handle_request(
|
||||
&app,
|
||||
request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
@@ -123,9 +147,9 @@ pub async fn enqueue_text_to_image_command(
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: event.service_provider,
|
||||
model: Some(event.tauri_event_model()),
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: event.model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
@@ -139,8 +163,52 @@ pub async fn enqueue_text_to_image_command(
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
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<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let result = dispatch_request(
|
||||
request,
|
||||
&app,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
&fal_task_queue,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
|
||||
let success_event = match result {
|
||||
Err(err) => return Err(err),
|
||||
Ok(event) => event,
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database(task_database)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
pub async fn dispatch_request(
|
||||
request: EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
@@ -149,18 +217,14 @@ pub async fn handle_request(
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<SuccessEvent, InternalImageError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(ImageModel::GptImage1) => {
|
||||
handle_image_sora(&app, request, sora_creds_manager, sora_task_queue).await?;
|
||||
return Ok(SuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Sora,
|
||||
model: ImageModel::GptImage1,
|
||||
});
|
||||
return handle_image_sora(&app, request, sora_creds_manager, sora_task_queue).await;
|
||||
}
|
||||
_ => {
|
||||
// Fall-through
|
||||
|
||||
+55
-19
@@ -1,18 +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::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image::success_event::SuccessEvent;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::GenerateFluxPro11TextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::{GenerateFlux1DevTextToImageAspectRatio, GenerateFlux1DevTextToImageRequest};
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageAspectRatio, GenerateFlux1SchnellTextToImageRequest};
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::{GenerateFluxPro11TextToImageAspectRatio, GenerateFluxPro11TextToImageRequest};
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::{GenerateFluxPro11UltraTextToImageAspectRatio, GenerateFluxPro11UltraTextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
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};
|
||||
@@ -28,7 +30,7 @@ pub async fn handle_image_artcraft(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<SuccessEvent, InternalImageError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
@@ -63,11 +65,19 @@ pub async fn handle_image_artcraft(
|
||||
}
|
||||
Some(ImageModel::Flux1Dev) => {
|
||||
info!("enqueue Flux 1 Dev");
|
||||
selected_model = ImageModel::Flux1Dev;
|
||||
selected_model = GenerationModel::Flux1Dev;
|
||||
let aspect_ratio = request.aspect_ratio
|
||||
.map(|aspect| match aspect {
|
||||
// TODO(bt,2025-07-14): Support other aspect ratios.
|
||||
TextToImageSize::Tall => GenerateFlux1DevTextToImageAspectRatio::PortraitNineBySixteen,
|
||||
TextToImageSize::Wide => GenerateFlux1DevTextToImageAspectRatio::LandscapeSixteenByNine,
|
||||
TextToImageSize::Auto => GenerateFlux1DevTextToImageAspectRatio::SquareHd,
|
||||
TextToImageSize::Square => GenerateFlux1DevTextToImageAspectRatio::SquareHd,
|
||||
});
|
||||
let request = GenerateFlux1DevTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
aspect_ratio,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_1_dev_text_to_image(
|
||||
@@ -88,11 +98,19 @@ pub async fn handle_image_artcraft(
|
||||
}
|
||||
Some(ImageModel::Flux1Schnell) => {
|
||||
info!("enqueue Flux 1 Schnell");
|
||||
selected_model = ImageModel::Flux1Schnell;
|
||||
selected_model = GenerationModel::Flux1Schnell;
|
||||
let aspect_ratio = request.aspect_ratio
|
||||
.map(|aspect| match aspect {
|
||||
// TODO(bt,2025-07-14): Support other aspect ratios.
|
||||
TextToImageSize::Tall => GenerateFlux1SchnellTextToImageAspectRatio::PortraitNineBySixteen,
|
||||
TextToImageSize::Wide => GenerateFlux1SchnellTextToImageAspectRatio::LandscapeSixteenByNine,
|
||||
TextToImageSize::Auto => GenerateFlux1SchnellTextToImageAspectRatio::SquareHd,
|
||||
TextToImageSize::Square => GenerateFlux1SchnellTextToImageAspectRatio::SquareHd,
|
||||
});
|
||||
let request = GenerateFlux1SchnellTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
aspect_ratio,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_1_schnell_text_to_image(
|
||||
@@ -113,11 +131,19 @@ pub async fn handle_image_artcraft(
|
||||
}
|
||||
Some(ImageModel::FluxPro11) => {
|
||||
info!("enqueue Flux Pro 1.1");
|
||||
selected_model = ImageModel::FluxPro11;
|
||||
selected_model = GenerationModel::FluxPro11;
|
||||
let aspect_ratio = request.aspect_ratio
|
||||
.map(|aspect| match aspect {
|
||||
// TODO(bt,2025-07-14): Support other aspect ratios.
|
||||
TextToImageSize::Tall => GenerateFluxPro11TextToImageAspectRatio::PortraitNineBySixteen,
|
||||
TextToImageSize::Wide => GenerateFluxPro11TextToImageAspectRatio::LandscapeSixteenByNine,
|
||||
TextToImageSize::Auto => GenerateFluxPro11TextToImageAspectRatio::Square,
|
||||
TextToImageSize::Square => GenerateFluxPro11TextToImageAspectRatio::Square,
|
||||
});
|
||||
let request = GenerateFluxPro11TextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
aspect_ratio,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_pro_11_text_to_image(
|
||||
@@ -138,11 +164,19 @@ pub async fn handle_image_artcraft(
|
||||
}
|
||||
Some(ImageModel::FluxPro11Ultra) => {
|
||||
info!("enqueue Flux Pro 1.1 Ultra");
|
||||
selected_model = ImageModel::FluxPro11Ultra;
|
||||
selected_model = GenerationModel::FluxPro11Ultra;
|
||||
let aspect_ratio = request.aspect_ratio
|
||||
.map(|aspect| match aspect {
|
||||
// TODO(bt,2025-07-14): Support other aspect ratios.
|
||||
TextToImageSize::Tall => GenerateFluxPro11UltraTextToImageAspectRatio::PortraitNineBySixteen,
|
||||
TextToImageSize::Wide => GenerateFluxPro11UltraTextToImageAspectRatio::LandscapeSixteenByNine,
|
||||
TextToImageSize::Auto => GenerateFluxPro11UltraTextToImageAspectRatio::Square,
|
||||
TextToImageSize::Square => GenerateFluxPro11UltraTextToImageAspectRatio::Square,
|
||||
});
|
||||
let request = GenerateFluxPro11UltraTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
aspect_ratio,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_pro_11_ultra_text_to_image(
|
||||
@@ -163,8 +197,10 @@ pub async fn handle_image_artcraft(
|
||||
}
|
||||
};
|
||||
|
||||
Ok(SuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Artcraft,
|
||||
model:selected_model,
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(selected_model),
|
||||
provider_job_id: Some(job_token.to_string()),
|
||||
})
|
||||
}
|
||||
+16
-10
@@ -1,13 +1,15 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image::success_event::SuccessEvent;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use anyhow::anyhow;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
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};
|
||||
@@ -18,7 +20,7 @@ pub async fn handle_image_fal(
|
||||
request: EnqueueTextToImageRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<SuccessEvent, InternalImageError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
@@ -60,7 +62,7 @@ pub async fn handle_image_fal(
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented: {:?}", request.model)));
|
||||
}
|
||||
Some(ImageModel::FluxPro11Ultra) => {
|
||||
model = ImageModel::FluxPro11Ultra;
|
||||
model = GenerationModel::FluxPro11Ultra;
|
||||
info!("enqueue Flux Pro 1.1 Ultra text-to-image with prompt: {}", prompt);
|
||||
enqueue_flux_pro_11_ultra_text_to_image(FluxPro11UltraTextToImageArgs {
|
||||
prompt,
|
||||
@@ -68,7 +70,7 @@ pub async fn handle_image_fal(
|
||||
}).await
|
||||
}
|
||||
Some(ImageModel::Recraft3) => {
|
||||
model = ImageModel::Recraft3;
|
||||
model = GenerationModel::Recraft3;
|
||||
info!("enqueue Recraft v3 text-to-image with prompt: {}", prompt);
|
||||
enqueue_recraft3_text_to_image(Recraft3TextToImageArgs {
|
||||
prompt,
|
||||
@@ -77,7 +79,7 @@ pub async fn handle_image_fal(
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
let success_result = match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued text to image");
|
||||
|
||||
@@ -95,6 +97,8 @@ pub async fn handle_image_fal(
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
|
||||
enqueued
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue text to image: {:?}", err);
|
||||
@@ -112,10 +116,12 @@ pub async fn handle_image_fal(
|
||||
|
||||
return Err(InternalImageError::FalError(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(SuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Fal,
|
||||
model,
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Fal,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(model),
|
||||
provider_job_id: Some(success_result.request_id.to_string()),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod handle_image_artcraft;
|
||||
pub mod handle_image_fal;
|
||||
+22
-5
@@ -1,10 +1,14 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
@@ -20,7 +24,7 @@ pub async fn handle_image_sora(
|
||||
request: EnqueueTextToImageRequest,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<(), InternalImageError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalImageError> {
|
||||
|
||||
let mut creds = sora_creds_manager
|
||||
.get_credentials_required()
|
||||
@@ -42,12 +46,20 @@ pub async fn handle_image_sora(
|
||||
}
|
||||
|
||||
let prompt = request.prompt.as_deref().unwrap_or("");
|
||||
|
||||
let size = match request.aspect_ratio {
|
||||
None => ImageSize::Square,
|
||||
Some(TextToImageSize::Auto) => ImageSize::Square,
|
||||
Some(TextToImageSize::Square) => ImageSize::Square,
|
||||
Some(TextToImageSize::Tall) => ImageSize::Tall,
|
||||
Some(TextToImageSize::Wide) => ImageSize::Wide,
|
||||
};
|
||||
|
||||
let result =
|
||||
simple_image_gen_with_session_auto_renew(SimpleImageGenAutoRenewRequest {
|
||||
prompt: prompt.to_string(),
|
||||
num_images: NumImages::One,
|
||||
image_size: ImageSize::Square,
|
||||
image_size: size,
|
||||
credentials: &creds,
|
||||
request_timeout: Some(SORA_SIMPLE_IMAGE_GEN_TIMEOUT),
|
||||
}).await;
|
||||
@@ -80,5 +92,10 @@ pub async fn handle_image_sora(
|
||||
|
||||
sora_task_queue.insert(&response.task_id)?;
|
||||
|
||||
Ok(())
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Sora,
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(GenerationModel::GptImage1),
|
||||
provider_job_id: Some(response.task_id.to_string()),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod handle_image_sora;
|
||||
@@ -1,6 +1,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 generic;
|
||||
pub mod internal_image_error;
|
||||
mod success_event;
|
||||
mod gpt_image_1;
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-10
@@ -1,14 +1,10 @@
|
||||
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_bg_removal::errors::InternalBgRemovalError;
|
||||
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal::handle_generic_bg_removal;
|
||||
use crate::core::commands::enqueue::image_bg_removal::success_event::EnqueueImageBgRemovalSuccessEvent;
|
||||
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::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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;
|
||||
@@ -23,6 +19,7 @@ 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::state::task_database::TaskDatabase;
|
||||
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;
|
||||
@@ -30,6 +27,8 @@ 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 enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
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};
|
||||
@@ -38,6 +37,7 @@ use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_o
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
@@ -89,6 +89,7 @@ pub async fn enqueue_image_bg_removal_command(
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
@@ -102,6 +103,7 @@ pub async fn enqueue_image_bg_removal_command(
|
||||
&app_data_root,
|
||||
&app_env_configs,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
&fal_creds_manager,
|
||||
&fal_task_queue,
|
||||
@@ -127,8 +129,8 @@ pub async fn enqueue_image_bg_removal_command(
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::RemoveBackground,
|
||||
service: event.service_provider,
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: None, // FIXME: This isn't right, though we probably don't care for simple bg removal.
|
||||
};
|
||||
|
||||
@@ -147,14 +149,15 @@ pub async fn handle_request(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<EnqueueImageBgRemovalSuccessEvent, InternalBgRemovalError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
|
||||
|
||||
// TODO(bt,2025-07-07): Other model/provider routing...
|
||||
|
||||
handle_generic_bg_removal(
|
||||
let success_event = handle_generic_bg_removal(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
@@ -163,5 +166,16 @@ pub async fn handle_request(
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
).await
|
||||
).await?;
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database(task_database)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
+2
-3
@@ -2,12 +2,11 @@ use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_c
|
||||
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
|
||||
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal_artcraft::handle_generic_bg_removal_artcraft;
|
||||
use crate::core::commands::enqueue::image_bg_removal::generic::handle_generic_bg_removal_fal::handle_generic_bg_removal_fal;
|
||||
use crate::core::commands::enqueue::image_bg_removal::success_event::EnqueueImageBgRemovalSuccessEvent;
|
||||
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::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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};
|
||||
@@ -28,7 +27,7 @@ pub async fn handle_generic_bg_removal(
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<EnqueueImageBgRemovalSuccessEvent, InternalBgRemovalError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
|
||||
+12
-7
@@ -2,10 +2,9 @@ use crate::core::commands::enqueue::image::enqueue_text_to_image_command::Enqueu
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
|
||||
use crate::core::commands::enqueue::image_bg_removal::success_event::EnqueueImageBgRemovalSuccessEvent;
|
||||
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::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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;
|
||||
@@ -27,6 +26,8 @@ use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::G
|
||||
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::image::remove_image_background::RemoveImageBackgroundRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
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;
|
||||
@@ -48,7 +49,7 @@ pub async fn handle_generic_bg_removal_artcraft(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<EnqueueImageBgRemovalSuccessEvent, InternalBgRemovalError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
@@ -79,20 +80,24 @@ pub async fn handle_generic_bg_removal_artcraft(
|
||||
request,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
let success_result = match result {
|
||||
Ok(enqueued) => {
|
||||
// TODO(bt,2025-07-05): Enqueue job token?
|
||||
info!("Successfully enqueued Artcraft background removal. Job token: {}",
|
||||
enqueued.inference_job_token);
|
||||
enqueued
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft background removal: {:?}", err);
|
||||
return Err(InternalBgRemovalError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(EnqueueImageBgRemovalSuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Artcraft,
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
task_type: TaskType::BackgroundRemoval,
|
||||
provider_job_id: Some(success_result.inference_job_token.to_string()),
|
||||
model: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::EnqueueImageBgRemovalCommand;
|
||||
use crate::core::commands::enqueue::image_bg_removal::errors::InternalBgRemovalError;
|
||||
use crate::core::commands::enqueue::image_bg_removal::success_event::EnqueueImageBgRemovalSuccessEvent;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
@@ -14,6 +14,6 @@ pub async fn handle_generic_bg_removal_fal(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<EnqueueImageBgRemovalSuccessEvent, InternalBgRemovalError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalBgRemovalError> {
|
||||
unimplemented!("TODO: Implement fal background removal handling");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod enqueue_image_bg_removal_command;
|
||||
pub mod errors;
|
||||
pub mod generic;
|
||||
pub mod success_event;
|
||||
pub mod generic;
|
||||
@@ -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 EnqueueImageBgRemovalSuccessEvent {
|
||||
pub service_provider: GenerationServiceProvider,
|
||||
//pub model: ContextualImageEditModel,
|
||||
}
|
||||
|
||||
impl EnqueueImageBgRemovalSuccessEvent {
|
||||
//pub fn tauri_event_model(&self) -> GenerationModel {
|
||||
// match self.model {
|
||||
// ContextualImageEditModel::GptImage1 => GenerationModel::GptImage1,
|
||||
// }
|
||||
//}
|
||||
}
|
||||
+28
-13
@@ -1,11 +1,8 @@
|
||||
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::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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;
|
||||
@@ -19,7 +16,8 @@ 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::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
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;
|
||||
@@ -27,6 +25,9 @@ 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 enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
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};
|
||||
@@ -35,6 +36,7 @@ use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_o
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
@@ -42,7 +44,6 @@ 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.
|
||||
@@ -130,6 +131,7 @@ pub async fn enqueue_contextual_edit_image_command(
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
@@ -146,6 +148,7 @@ pub async fn enqueue_contextual_edit_image_command(
|
||||
&app_data_root,
|
||||
&app_env_configs,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
&fal_creds_manager,
|
||||
&fal_task_queue,
|
||||
@@ -173,9 +176,9 @@ pub async fn enqueue_contextual_edit_image_command(
|
||||
}
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: event.service_provider,
|
||||
model: Some(event.tauri_event_model()),
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: event.model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
@@ -193,15 +196,16 @@ pub async fn handle_request(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
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 {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
let success_event= match request.model {
|
||||
None => {
|
||||
Err(InternalContextualEditImageError::NoModelSpecified)
|
||||
return Err(InternalContextualEditImageError::NoModelSpecified)
|
||||
}
|
||||
Some(ContextualImageEditModel::GptImage1) => {
|
||||
handle_gpt_image_1(
|
||||
@@ -215,8 +219,19 @@ pub async fn handle_request(
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await
|
||||
).await?
|
||||
}
|
||||
// TODO(bt,2025-07-05): Flux Kontext, etc.
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database(task_database)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_co
|
||||
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::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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};
|
||||
@@ -27,7 +27,7 @@ pub async fn handle_gpt_image_1(
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<ContextualEditImageSuccessEvent, InternalContextualEditImageError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
|
||||
+13
-8
@@ -3,9 +3,9 @@ use crate::core::commands::enqueue::image::internal_image_error::InternalImageEr
|
||||
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::gpt_image_1::handle_gpt_image_1::MAX_IMAGES;
|
||||
use crate::core::commands::enqueue::image_edit::success_event::ContextualEditImageSuccessEvent;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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::contextual_image_edit_models::ContextualImageEditModel;
|
||||
use crate::core::model::image_models::ImageModel;
|
||||
@@ -33,6 +33,8 @@ use storyteller_client::generate::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;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
|
||||
pub async fn handle_gpt_image_1_artcraft(
|
||||
request: &EnqueueContextualEditImageCommand,
|
||||
@@ -40,7 +42,7 @@ pub async fn handle_gpt_image_1_artcraft(
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<ContextualEditImageSuccessEvent, InternalContextualEditImageError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
@@ -117,20 +119,23 @@ pub async fn handle_gpt_image_1_artcraft(
|
||||
request,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
let job_id = 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);
|
||||
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,
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Artcraft,
|
||||
model: Some(GenerationModel::GptImage1),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::ImageGeneration,
|
||||
})
|
||||
}
|
||||
|
||||
+10
-7
@@ -3,10 +3,10 @@ use crate::core::commands::enqueue::image::internal_image_error::InternalImageEr
|
||||
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::gpt_image_1::handle_gpt_image_1::MAX_IMAGES;
|
||||
use crate::core::commands::enqueue::image_edit::success_event::ContextualEditImageSuccessEvent;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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::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::contextual_image_edit_models::ContextualImageEditModel;
|
||||
@@ -27,6 +27,8 @@ use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::Gener
|
||||
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 enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
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;
|
||||
@@ -53,8 +55,7 @@ pub async fn handle_gpt_image_1_sora(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
|
||||
) -> Result<ContextualEditImageSuccessEvent, InternalContextualEditImageError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalContextualEditImageError> {
|
||||
|
||||
let sora_creds = match sora_creds_manager.get_credentials() {
|
||||
Ok(Some(creds)) => creds,
|
||||
@@ -182,8 +183,10 @@ pub async fn handle_gpt_image_1_sora(
|
||||
|
||||
sora_task_queue.insert(&response.task_id)?;
|
||||
|
||||
Ok(ContextualEditImageSuccessEvent {
|
||||
service_provider: GenerationServiceProvider::Sora,
|
||||
model: ContextualImageEditModel::GptImage1,
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::ImageGeneration,
|
||||
model: Some(GenerationModel::GptImage1),
|
||||
provider: GenerationProvider::Sora,
|
||||
provider_job_id: Some(response.task_id.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod enqueue_contextual_edit_image_command;
|
||||
pub mod errors;
|
||||
pub mod gpt_image_1;
|
||||
pub mod success_event;
|
||||
pub mod gpt_image_1;
|
||||
@@ -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,5 +1,6 @@
|
||||
pub mod image;
|
||||
pub mod image_bg_removal;
|
||||
pub mod image_edit;
|
||||
pub mod task_enqueue_success;
|
||||
pub mod object;
|
||||
pub mod video;
|
||||
+45
-32
@@ -1,9 +1,10 @@
|
||||
use crate::core::commands::enqueue::object::handle_object_artcraft::handle_object_artcraft;
|
||||
use crate::core::commands::enqueue::object::handle_object_fal::handle_object_fal;
|
||||
use crate::core::commands::enqueue::object::generic::handle_object::handle_object;
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::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;
|
||||
@@ -15,7 +16,9 @@ use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageTo3dObjectRequest {
|
||||
@@ -69,6 +72,7 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
@@ -83,6 +87,7 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
@@ -127,7 +132,17 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(()) => {
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: event.model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Ok(EnqueueImageTo3dObjectSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
@@ -140,39 +155,37 @@ pub async fn handle_request(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
let result = handle_object(
|
||||
request,
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
let success_event = match result {
|
||||
Err(err) => return Err(err),
|
||||
Ok(event) => event,
|
||||
};
|
||||
|
||||
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?);
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = success_event
|
||||
.insert_into_task_database(task_database)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
Err(InternalObjectError::NoProviderAvailable)
|
||||
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::EnqueueImageTo3dObjectRequest;
|
||||
use crate::core::commands::enqueue::object::generic::handle_object_artcraft::handle_object_artcraft;
|
||||
use crate::core::commands::enqueue::object::generic::handle_object_fal::handle_object_fal;
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_object(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, 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?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalObjectError::NoProviderAvailable)
|
||||
}
|
||||
+23
-45
@@ -1,7 +1,8 @@
|
||||
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::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
@@ -9,6 +10,8 @@ use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::GenerateHunyuan20ImageTo3dRequest;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::GenerateHunyuan21ImageTo3dRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
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};
|
||||
@@ -22,7 +25,7 @@ pub async fn handle_object_artcraft(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
@@ -39,18 +42,12 @@ pub async fn handle_object_artcraft(
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalObjectError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
|
||||
info!("Calling Artcraft image to 3d ...");
|
||||
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let mut used_model = None;
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
@@ -60,6 +57,7 @@ pub async fn handle_object_artcraft(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0
|
||||
) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 2.0");
|
||||
used_model = Some(GenerationModel::Hunyuan3d2_0);
|
||||
let request = GenerateHunyuan20ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
@@ -82,6 +80,7 @@ pub async fn handle_object_artcraft(
|
||||
}
|
||||
Some(EnqueueImageTo3dObjectModel::Hunyuan3d2_1) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 2.1");
|
||||
used_model = Some(GenerationModel::Hunyuan3d2_1);
|
||||
let request = GenerateHunyuan21ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
@@ -106,41 +105,20 @@ pub async fn handle_object_artcraft(
|
||||
|
||||
info!("Successfully enqueued 3D generation");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
//if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
// error!("Failed to enqueue task: {:?}", err);
|
||||
// return Err(InternalObjectError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
//let event = GenerationEnqueueSuccessEvent {
|
||||
// action: GenerationAction::ImageTo3d,
|
||||
// service: GenerationServiceProvider::Artcraft,
|
||||
// model: None,
|
||||
//};
|
||||
//
|
||||
//if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
//}
|
||||
|
||||
// match result {
|
||||
// Ok(enqueued) => {
|
||||
// }
|
||||
// Err(err) => {
|
||||
// error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
//
|
||||
// let event = GenerationEnqueueFailureEvent {
|
||||
// action: GenerationAction::ImageTo3d,
|
||||
// service: GenerationServiceProvider::Fal,
|
||||
// model: None,
|
||||
// reason: None,
|
||||
// };
|
||||
//
|
||||
// if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
// }
|
||||
//
|
||||
// return Err(InternalObjectError::FalError(err));
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::ObjectGeneration,
|
||||
model: used_model,
|
||||
provider: GenerationProvider::Artcraft,
|
||||
provider_job_id: Some(job_token.to_string()),
|
||||
})
|
||||
}
|
||||
+18
-3
@@ -1,7 +1,8 @@
|
||||
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::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
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::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
@@ -10,6 +11,8 @@ use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use anyhow::anyhow;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
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};
|
||||
@@ -22,7 +25,7 @@ pub async fn handle_object_fal(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalObjectError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
@@ -60,6 +63,8 @@ pub async fn handle_object_fal(
|
||||
info!("Calling FAL image to 3d ...");
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let mut used_model = None;
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
@@ -69,6 +74,7 @@ pub async fn handle_object_fal(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0 |
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2
|
||||
) => {
|
||||
used_model = Some(GenerationModel::Hunyuan3d2_0);
|
||||
info!("enqueue Hunyuan 3D 2.0");
|
||||
enqueue_hunyuan2_image_to_3d(Hunyuan2Args {
|
||||
image_path: filename,
|
||||
@@ -80,9 +86,13 @@ pub async fn handle_object_fal(
|
||||
}
|
||||
};
|
||||
|
||||
let mut job_id = None;
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued text to image");
|
||||
|
||||
job_id = Some(enqueued.request_id.to_string());
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
@@ -117,5 +127,10 @@ pub async fn handle_object_fal(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::ObjectGeneration,
|
||||
model: used_model,
|
||||
provider: GenerationProvider::Fal,
|
||||
provider_job_id: job_id,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub (super) mod handle_object;
|
||||
pub (super) mod handle_object_artcraft;
|
||||
pub (super) mod handle_object_fal;
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod enqueue_image_to_3d_object_command;
|
||||
pub mod handle_object_artcraft;
|
||||
pub mod handle_object_fal;
|
||||
pub mod internal_object_error;
|
||||
pub mod internal_object_error;
|
||||
mod generic;
|
||||
@@ -0,0 +1,83 @@
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_model_type::TaskModelType;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use sqlite_tasks::error::SqliteTasksError;
|
||||
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use tokens::tokens::sqlite::tasks::TaskId;
|
||||
|
||||
pub struct TaskEnqueueSuccess {
|
||||
pub task_type: TaskType,
|
||||
pub model: Option<GenerationModel>,
|
||||
pub provider: GenerationProvider,
|
||||
pub provider_job_id: Option<String>,
|
||||
// TODO: We may want to change the `model` type - this has weird ownership and semantics
|
||||
}
|
||||
|
||||
impl TaskEnqueueSuccess{
|
||||
pub fn to_frontend_event_action(&self) -> GenerationAction {
|
||||
match self.task_type {
|
||||
TaskType::ImageGeneration => GenerationAction::GenerateImage,
|
||||
TaskType::VideoGeneration => GenerationAction::GenerateVideo,
|
||||
TaskType::BackgroundRemoval => GenerationAction::RemoveBackground,
|
||||
TaskType::ObjectGeneration => GenerationAction::ImageTo3d,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_frontend_event_service(&self) -> GenerationServiceProvider {
|
||||
match self.provider {
|
||||
GenerationProvider::Artcraft => GenerationServiceProvider::Artcraft,
|
||||
GenerationProvider::Fal => GenerationServiceProvider::Fal,
|
||||
GenerationProvider::Sora => GenerationServiceProvider::Sora,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn insert_into_task_database(&self, task_database: &TaskDatabase) -> Result<TaskId, SqliteTasksError> {
|
||||
// TODO: Move this mapping elsewhere, or remove the other models.
|
||||
let model_type = match self.model {
|
||||
None => None,
|
||||
Some(GenerationModel::Flux1Dev) => Some(TaskModelType::Flux1Dev),
|
||||
Some(GenerationModel::Flux1Schnell) => Some(TaskModelType::Flux1Schnell),
|
||||
Some(GenerationModel::FluxPro11) => Some(TaskModelType::FluxPro11),
|
||||
Some(GenerationModel::FluxPro11Ultra) => Some(TaskModelType::FluxPro11Ultra),
|
||||
Some(GenerationModel::GptImage1) => Some(TaskModelType::GptImage1),
|
||||
Some(GenerationModel::Recraft3) => Some(TaskModelType::Recraft3),
|
||||
Some(GenerationModel::Kling21Pro) => Some(TaskModelType::Kling21Pro),
|
||||
Some(GenerationModel::Kling21Master) => Some(TaskModelType::Kling21Master),
|
||||
Some(GenerationModel::Seedance10Lite) => Some(TaskModelType::Seedance10Lite),
|
||||
Some(GenerationModel::Veo2) => Some(TaskModelType::Veo2),
|
||||
Some(GenerationModel::Hunyuan3d2_0) => Some(TaskModelType::Hunyuan3d2_0),
|
||||
Some(GenerationModel::Hunyuan3d2_1) => Some(TaskModelType::Hunyuan3d2_1),
|
||||
|
||||
// TODO: These seem wrong -
|
||||
Some(GenerationModel::Kling1_6) => Some(TaskModelType::Kling16Pro), // NB: `VideoModel::Kling16Pro`.
|
||||
Some(GenerationModel::Kling2_0) => None, // TODO: unused elsewhere?
|
||||
Some(GenerationModel::Sora) => None, // TODO: unused elsewhere?
|
||||
};
|
||||
|
||||
create_task(CreateTaskArgs {
|
||||
db: task_database.get_connection(),
|
||||
status: TaskStatus::Pending,
|
||||
task_type: self.task_type,
|
||||
model_type: model_type,
|
||||
provider: self.provider,
|
||||
provider_job_id: self.provider_job_id.as_deref(),
|
||||
frontend_subscriber_id: None,
|
||||
frontend_subscriber_payload: None,
|
||||
}).await
|
||||
}
|
||||
|
||||
// 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,
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
+47
-32
@@ -1,13 +1,16 @@
|
||||
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::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::video::generic::handle_video::handle_video;
|
||||
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::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
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::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
@@ -26,6 +29,9 @@ pub struct EnqueueImageToVideoRequest {
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<VideoModel>,
|
||||
|
||||
/// Optional text prompt.
|
||||
pub prompt: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -58,6 +64,7 @@ pub async fn enqueue_image_to_video_command(
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
@@ -72,6 +79,7 @@ pub async fn enqueue_image_to_video_command(
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
@@ -116,7 +124,17 @@ pub async fn enqueue_image_to_video_command(
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(()) => {
|
||||
Ok(event) => {
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: event.to_frontend_event_action(),
|
||||
service: event.to_frontend_event_service(),
|
||||
model: event.model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Ok(EnqueueImageToVideoSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
@@ -129,39 +147,36 @@ pub async fn handle_request(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
) -> Result<TaskEnqueueSuccess, 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?);
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = handle_video(
|
||||
request,
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
let success_event = match result {
|
||||
Err(err) => return Err(err),
|
||||
Ok(event) => event,
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database(task_database)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to create task in database: {:?}", err);
|
||||
// NB: Fail open, but find a way to flag this.
|
||||
}
|
||||
|
||||
Err(InternalVideoError::NoProviderAvailable)
|
||||
Ok(success_event)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::video::generic::handle_video_artcraft::handle_video_artcraft;
|
||||
use crate::core::commands::enqueue::video::generic::handle_video_fal::handle_video_fal;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
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::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_video(
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, 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?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalVideoError::NoProviderAvailable)
|
||||
}
|
||||
+14
-42
@@ -1,3 +1,4 @@
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
@@ -13,6 +14,8 @@ use artcraft_api_defs::generate::video::generate_kling_2_1_master_image_to_video
|
||||
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 enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::Kling16ProAspectRatio;
|
||||
use fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::Veo2AspectRatio;
|
||||
@@ -32,7 +35,7 @@ pub async fn handle_video_artcraft(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
@@ -74,7 +77,7 @@ pub async fn handle_video_artcraft(
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateKling16ProAspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
prompt: request.prompt,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_16_pro_image_to_video(
|
||||
@@ -100,7 +103,7 @@ pub async fn handle_video_artcraft(
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateKling21MasterAspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
prompt: request.prompt,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_21_master_image_to_video(
|
||||
@@ -126,7 +129,7 @@ pub async fn handle_video_artcraft(
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateKling21ProAspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
prompt: request.prompt,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_21_pro_image_to_video(
|
||||
@@ -151,7 +154,7 @@ pub async fn handle_video_artcraft(
|
||||
let request = GenerateSeedance10LiteImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
prompt: None,
|
||||
prompt: request.prompt,
|
||||
resolution: None,
|
||||
duration: None,
|
||||
};
|
||||
@@ -178,7 +181,7 @@ pub async fn handle_video_artcraft(
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateVeo2AspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
prompt: request.prompt,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_veo_2_image_to_video(
|
||||
@@ -201,41 +204,10 @@ pub async fn handle_video_artcraft(
|
||||
|
||||
info!("Successfully enqueued video generation");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::VideoGeneration,
|
||||
model: selected_model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
//if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
// error!("Failed to enqueue task: {:?}", err);
|
||||
// return Err(InternalVideoError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
//}
|
||||
|
||||
// match result {
|
||||
// Ok(enqueued) => {
|
||||
// }
|
||||
// Err(err) => {
|
||||
// error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
//
|
||||
// let event = GenerationEnqueueFailureEvent {
|
||||
// action: GenerationAction::ImageTo3d,
|
||||
// service: GenerationServiceProvider::Fal,
|
||||
// model: None,
|
||||
// reason: None,
|
||||
// };
|
||||
//
|
||||
// if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
// }
|
||||
//
|
||||
// return Err(InternalVideoError::FalError(err));
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
provider: GenerationProvider::Artcraft,
|
||||
provider_job_id: Some(job_token.to_string()),
|
||||
})
|
||||
}
|
||||
+15
-13
@@ -1,3 +1,4 @@
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
@@ -11,6 +12,8 @@ use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use anyhow::anyhow;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::{enqueue_kling_16_pro_image_to_video, Kling16ProArgs, Kling16ProAspectRatio, Kling16ProDuration};
|
||||
use log::{error, info};
|
||||
@@ -23,7 +26,7 @@ pub async fn handle_video_fal(
|
||||
request: EnqueueImageToVideoRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
) -> Result<TaskEnqueueSuccess, InternalVideoError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
@@ -74,28 +77,22 @@ pub async fn handle_video_fal(
|
||||
api_key: &api_key,
|
||||
duration: Kling16ProDuration::Default,
|
||||
aspect_ratio: Kling16ProAspectRatio::WideSixteenNine,
|
||||
prompt: "",
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("Unsupported model: {:?}", request.model)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut job_id = None;
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Kling 1.6 image to video");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: selected_model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
job_id = Some(enqueued.request_id.to_string());
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
@@ -119,5 +116,10 @@ pub async fn handle_video_fal(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::VideoGeneration,
|
||||
model: selected_model,
|
||||
provider: GenerationProvider::Fal,
|
||||
provider_job_id: job_id,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub (super) mod handle_video;
|
||||
pub (super) mod handle_video_artcraft;
|
||||
pub (super) mod handle_video_fal;
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod enqueue_image_to_video_command;
|
||||
pub mod handle_video_artcraft;
|
||||
pub mod handle_video_fal;
|
||||
pub mod internal_video_error;
|
||||
pub mod internal_video_error;
|
||||
mod generic;
|
||||
@@ -21,8 +21,15 @@ pub enum GenerationModel {
|
||||
FluxPro11Ultra,
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
#[serde(rename = "hunyuan_3d_2_0")]
|
||||
Hunyuan3d2_0,
|
||||
#[serde(rename = "hunyuan_3d_2_1")]
|
||||
Hunyuan3d2_1,
|
||||
|
||||
// TODO: Should be Kling16Pro
|
||||
#[serde(rename = "kling_1.6")]
|
||||
Kling1_6,
|
||||
|
||||
#[serde(rename = "kling_2.0")]
|
||||
Kling2_0,
|
||||
#[serde(rename = "kling_2.1_master")]
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod startup;
|
||||
@@ -0,0 +1,80 @@
|
||||
use crate::core::lifecycle::startup::tasks::bootstrap_task_database::bootstrap_task_database;
|
||||
use crate::core::lifecycle::startup::tasks::load_provider_priority_state::load_provider_priority_state;
|
||||
use crate::core::lifecycle::startup::tasks::set_app_log_level::set_app_log_level;
|
||||
use crate::core::lifecycle::startup::tasks::initially_size_and_position_windows::initially_size_and_position_windows;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_discord_presence_thread::spawn_discord_presence_thread;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_fal_task_polling_thread::spawn_fal_task_polling_thread;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_main_window_thread::spawn_main_window_thread;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_sora_task_polling_thread::spawn_sora_task_polling_thread;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_storytoller_task_polling_thread::spawn_storyteller_task_polling_thread;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
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 errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub async fn handle_tauri_startup(
|
||||
app: AppHandle,
|
||||
root: AppDataRoot,
|
||||
app_env_configs: AppEnvConfigs,
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
sora_credential_manager: SoraCredentialManager,
|
||||
sora_task_queue: SoraTaskQueue,
|
||||
fal_credential_manager: FalCredentialManager,
|
||||
fal_task_queue: FalTaskQueue,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
set_app_log_level(
|
||||
&app,
|
||||
&root,
|
||||
)?;
|
||||
|
||||
let task_database =
|
||||
bootstrap_task_database(&app, &root).await?;
|
||||
|
||||
load_provider_priority_state(
|
||||
&app,
|
||||
&root,
|
||||
)?;
|
||||
|
||||
spawn_main_window_thread(
|
||||
&app,
|
||||
&root,
|
||||
&storyteller_creds_manager,
|
||||
)?;
|
||||
|
||||
spawn_storyteller_task_polling_thread(
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
)?;
|
||||
|
||||
spawn_sora_task_polling_thread(
|
||||
&app,
|
||||
&root,
|
||||
&app_env_configs,
|
||||
&sora_credential_manager,
|
||||
&storyteller_creds_manager,
|
||||
&sora_task_queue,
|
||||
)?;
|
||||
|
||||
spawn_fal_task_polling_thread(
|
||||
&app,
|
||||
&root,
|
||||
&app_env_configs,
|
||||
&fal_credential_manager,
|
||||
&fal_task_queue,
|
||||
&storyteller_creds_manager,
|
||||
)?;
|
||||
|
||||
spawn_discord_presence_thread()?;
|
||||
|
||||
initially_size_and_position_windows(&app, &root);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod handle_tauri_startup;
|
||||
mod tasks;
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub async fn bootstrap_task_database(app: &AppHandle, root: &AppDataRoot) -> AnyhowResult<TaskDatabase> {
|
||||
let task_database = TaskDatabase::connect(root).await?;
|
||||
app.manage(task_database.clone());
|
||||
Ok(task_database)
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::window::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::window::main_window_size::MainWindowSize;
|
||||
use crate::core::utils::window::position_main_window::position_main_window;
|
||||
use crate::core::utils::window::resize_main_window::resize_main_window;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub fn initially_size_and_position_windows(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
) {
|
||||
|
||||
println!("Sizing and positioning window...");
|
||||
|
||||
match MainWindowSize::from_filesystem_configs(&root) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(size)) => {
|
||||
println!("Resizing window to: {:?}", size);
|
||||
let result = resize_main_window(app, &size);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Could not set window size: {:?}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read window size from disk: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match MainWindowPosition::from_filesystem_configs(&root) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(pos)) => {
|
||||
println!("Moving window to: {:?}", pos);
|
||||
let result = position_main_window(app, &pos);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Could not set window position: {:?}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read window position from disk: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub fn load_provider_priority_state(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
) -> AnyhowResult<()> {
|
||||
let provider_priority = match ProviderPriorityStore::from_filesystem_configs(root) {
|
||||
Ok(Some(priority)) => {
|
||||
println!("Loaded provider priority from disk: {:?}", priority.get_priority());
|
||||
priority
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("No provider priority found on disk, using default.");
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read provider priority from disk: {:?}", err);
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
};
|
||||
|
||||
let success = app.manage(provider_priority);
|
||||
|
||||
if !success {
|
||||
eprintln!("Failed to manage provider priority state.");
|
||||
return Err(anyhow!("Failed to manage provider priority state."));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub (super) mod bootstrap_task_database;
|
||||
pub (super) mod set_app_log_level;
|
||||
pub (super) mod initially_size_and_position_windows;
|
||||
pub (super) mod spawn_discord_presence_thread;
|
||||
pub (super) mod spawn_fal_task_polling_thread;
|
||||
pub (super) mod spawn_main_window_thread;
|
||||
pub (super) mod spawn_sora_task_polling_thread;
|
||||
pub (super) mod spawn_storytoller_task_polling_thread;
|
||||
pub (super) mod load_provider_priority_state;
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use log::LevelFilter;
|
||||
use std::env;
|
||||
use std::env::VarError;
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_log::{Target, TargetKind};
|
||||
|
||||
const RUST_LOG: &str = "RUST_LOG";
|
||||
|
||||
pub fn set_app_log_level(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
//print_env_var(RUST_LOG);
|
||||
|
||||
// NB: The semantics of this do not match `RUST_LOG` exactly as it's not set per-package.
|
||||
let maybe_level = env::var(RUST_LOG)
|
||||
.map(|value| value.trim().to_ascii_lowercase());
|
||||
|
||||
let mut log_level = LevelFilter::Info;
|
||||
|
||||
match maybe_level {
|
||||
Ok(level) => {
|
||||
println!("The `RUST_LOG` environment variable was set to: `{}`.", level);
|
||||
|
||||
if level.contains("debug") {
|
||||
log_level = LevelFilter::Debug;
|
||||
} else if level.contains("info") {
|
||||
log_level = LevelFilter::Info;
|
||||
} else if level.contains("warn") {
|
||||
log_level = LevelFilter::Warn;
|
||||
} else if level.contains("error") {
|
||||
log_level = LevelFilter::Error;
|
||||
} else if level.contains("off") {
|
||||
log_level = LevelFilter::Off;
|
||||
}
|
||||
}
|
||||
Err(VarError::NotPresent) => {
|
||||
// NB: This demonstrably tests stderr output in the default case.
|
||||
eprintln!("No `RUST_LOG` environment variable found.");
|
||||
}
|
||||
Err(VarError::NotUnicode(_)) => {
|
||||
// NB: This demonstrably tests stderr output in the default case.
|
||||
eprintln!("Could not parse `RUST_LOG` environment variable; not unicode.");
|
||||
}
|
||||
}
|
||||
|
||||
println!("Setting app log level to: {:?}", log_level);
|
||||
|
||||
app.plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log_level)
|
||||
.targets(vec![
|
||||
Target::new(TargetKind::Stdout),
|
||||
Target::new(TargetKind::LogDir {
|
||||
file_name: Some(root.log_file_name_str().to_string())
|
||||
}),
|
||||
])
|
||||
.build(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn print_env_var(var_name: &str) {
|
||||
// match env::var(var_name) {
|
||||
// Ok(value) => println!("Environment variable `{}` = {}", var_name, value),
|
||||
// Err(VarError::NotPresent) => println!("Environment variable `{}` is not set.", var_name),
|
||||
// Err(VarError::NotUnicode(_)) => println!("Environment variable `{}` is not a valid Unicode string", var_name),
|
||||
// }
|
||||
// }
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
use crate::core::threads::discord_presence_thread::discord_presence_thread;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
pub fn spawn_discord_presence_thread(
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
tauri::async_runtime::spawn(discord_presence_thread());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
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::fal::threads::fal_task_polling_thread::fal_task_polling_thread;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub fn spawn_fal_task_polling_thread(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_credential_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
tauri::async_runtime::spawn(fal_task_polling_thread(
|
||||
app.clone(),
|
||||
app_env_configs.clone(),
|
||||
root.clone(),
|
||||
fal_credential_manager.clone(),
|
||||
fal_task_queue.clone(),
|
||||
storyteller_creds_manager.clone(),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::threads::main_window_thread::main_window_thread::main_window_thread;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub fn spawn_main_window_thread(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
tauri::async_runtime::spawn(main_window_thread(
|
||||
app.clone(),
|
||||
root.clone(),
|
||||
storyteller_creds_manager.clone(),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::sora::threads::sora_task_polling_thread::sora_task_polling_thread;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub fn spawn_sora_task_polling_thread(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
sora_credential_manager: &SoraCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
tauri::async_runtime::spawn(sora_task_polling_thread(
|
||||
app.clone(),
|
||||
app_env_configs.clone(),
|
||||
root.clone(),
|
||||
sora_credential_manager.clone(),
|
||||
storyteller_creds_manager.clone(),
|
||||
sora_task_queue.clone(),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::services::storyteller::threads::storyteller_task_polling_thread::storyteller_task_polling_thread;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub fn spawn_storyteller_task_polling_thread(
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
tauri::async_runtime::spawn(storyteller_task_polling_thread(
|
||||
app.clone(),
|
||||
app_env_configs.clone(),
|
||||
task_database.clone(),
|
||||
storyteller_creds_manager.clone(),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod artcraft_error;
|
||||
pub mod commands;
|
||||
pub mod events;
|
||||
pub mod lifecycle;
|
||||
pub mod model;
|
||||
pub mod state;
|
||||
pub mod threads;
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::core::state::data_dir::app_assets_dir::AppAssetsDir;
|
||||
use crate::core::state::data_dir::app_credentials_dir::AppCredentialsDir;
|
||||
use crate::core::state::data_dir::app_downloads_dir::AppDownloadsDir;
|
||||
use crate::core::state::data_dir::app_settings_dir::AppSettingsDir;
|
||||
use crate::core::state::data_dir::app_weights_dir::AppWeightsDir;
|
||||
use crate::core::state::data_dir::app_state_dir::AppStateDir;
|
||||
use crate::core::state::data_dir::temporary_dir::TemporaryDir;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::state::expanduser::expanduser;
|
||||
@@ -20,14 +20,16 @@ const LOG_FILE_NAME : &str = "application_debug";
|
||||
#[derive(Clone)]
|
||||
pub struct AppDataRoot {
|
||||
path: PathBuf,
|
||||
|
||||
log_file_name: PathBuf,
|
||||
log_file_name_string: String,
|
||||
|
||||
assets_dir: AppAssetsDir,
|
||||
credentials_dir: AppCredentialsDir,
|
||||
downloads_dir: AppDownloadsDir,
|
||||
settings_dir: AppSettingsDir,
|
||||
weights_dir: AppWeightsDir,
|
||||
state_dir: AppStateDir,
|
||||
temp_dir: TemporaryDir,
|
||||
log_file_name: PathBuf,
|
||||
log_file_name_string: String,
|
||||
}
|
||||
|
||||
impl AppDataRoot {
|
||||
@@ -65,8 +67,8 @@ impl AppDataRoot {
|
||||
let credentials_dir = AppCredentialsDir::get_or_create_in_root_dir(&dir)?;
|
||||
let downloads_dir = AppDownloadsDir::get_or_create_in_root_dir(&dir)?;
|
||||
let settings_dir = AppSettingsDir::get_or_create_in_root_dir(&dir)?;
|
||||
let state_dir = AppStateDir::get_or_create_in_root_dir(&dir)?;
|
||||
let temp_dir = TemporaryDir::get_or_create_in_root_dir(&dir)?;
|
||||
let weights_dir = AppWeightsDir::get_or_create_in_root_dir(&dir)?;
|
||||
let log_file_name = dir.join(LOG_FILE_NAME);
|
||||
let log_file_name_string = log_file_name
|
||||
.to_str()
|
||||
@@ -75,14 +77,14 @@ impl AppDataRoot {
|
||||
|
||||
Ok(Self {
|
||||
path: dir,
|
||||
log_file_name,
|
||||
log_file_name_string,
|
||||
assets_dir,
|
||||
credentials_dir,
|
||||
downloads_dir,
|
||||
settings_dir,
|
||||
weights_dir,
|
||||
state_dir,
|
||||
temp_dir,
|
||||
log_file_name,
|
||||
log_file_name_string,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -101,9 +103,9 @@ impl AppDataRoot {
|
||||
pub fn settings_dir(&self) -> &AppSettingsDir {
|
||||
&self.settings_dir
|
||||
}
|
||||
|
||||
pub fn weights_dir(&self) -> &AppWeightsDir {
|
||||
&self.weights_dir
|
||||
|
||||
pub fn state_dir(&self) -> &AppStateDir {
|
||||
&self.state_dir
|
||||
}
|
||||
|
||||
pub fn temp_dir(&self) -> &TemporaryDir {
|
||||
@@ -135,15 +137,15 @@ impl AppDataRoot {
|
||||
}
|
||||
|
||||
pub fn get_window_size_config_file(&self) -> PathBuf {
|
||||
self.path.join("window_size.json")
|
||||
self.state_dir.get_window_size_config_file()
|
||||
}
|
||||
|
||||
pub fn get_window_position_config_file(&self) -> PathBuf {
|
||||
self.path.join("window_position.json")
|
||||
self.state_dir.get_window_position_config_file()
|
||||
}
|
||||
}
|
||||
|
||||
// eg. /home/bob/artcraft, /Users/bob/artcraft, or C:\Users\Alice\artcraft
|
||||
// eg. /home/bob/artcraft, /Users/alice/artcraft, or C:\Users\Taylor\artcraft
|
||||
fn get_default_data_dir() -> anyhow::Result<PathBuf> {
|
||||
Ok(UserDirs::new()
|
||||
.ok_or_else(|| anyhow!("could not determine user home directory"))?
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppStateDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DataSubdir for AppStateDir{
|
||||
const DIRECTORY_NAME: &'static str = "state";
|
||||
|
||||
fn new_from<P: AsRef<Path>>(dir: P) -> Self {
|
||||
Self {
|
||||
path: dir.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl AppStateDir {
|
||||
pub fn get_tasks_sqlite_database_path(&self) -> PathBuf {
|
||||
self.path.join("tasks_v2.sqlite")
|
||||
}
|
||||
|
||||
pub fn get_window_size_config_file(&self) -> PathBuf {
|
||||
self.path.join("window_size.json")
|
||||
}
|
||||
|
||||
pub fn get_window_position_config_file(&self) -> PathBuf {
|
||||
self.path.join("window_position.json")
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub mod app_credentials_dir;
|
||||
pub mod app_data_root;
|
||||
pub mod app_downloads_dir;
|
||||
pub mod app_settings_dir;
|
||||
pub mod app_state_dir;
|
||||
pub mod app_weights_dir;
|
||||
pub mod temporary_dir;
|
||||
pub mod trait_data_subdir;
|
||||
@@ -3,7 +3,7 @@ pub mod app_preferences;
|
||||
pub mod app_startup_time;
|
||||
pub mod data_dir;
|
||||
pub mod expanduser;
|
||||
pub mod main_window_position;
|
||||
pub mod main_window_size;
|
||||
pub mod os_platform;
|
||||
pub mod provider_priority;
|
||||
pub mod provider_priority;
|
||||
pub mod task_database;
|
||||
pub mod window;
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use sqlite_tasks::connection::TaskDbConnection;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TaskDatabase {
|
||||
connection: TaskDbConnection,
|
||||
}
|
||||
|
||||
impl TaskDatabase {
|
||||
pub async fn connect(root: &AppDataRoot) -> AnyhowResult<Self> {
|
||||
let path = root.state_dir().get_tasks_sqlite_database_path();
|
||||
let connection = TaskDbConnection::connect_and_migrate(path).await?;
|
||||
Ok(Self { connection})
|
||||
}
|
||||
|
||||
pub fn get_connection(&self) -> &TaskDbConnection {
|
||||
&self.connection
|
||||
}
|
||||
}
|
||||
-8
@@ -42,14 +42,6 @@ impl MainWindowPosition {
|
||||
Ok(Some(pos))
|
||||
}
|
||||
|
||||
pub fn apply_to_main_window(&self, app: &AppHandle) -> AnyhowResult<()> {
|
||||
let windows = app.windows();
|
||||
let window = windows.get(MAIN_WINDOW_NAME)
|
||||
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
|
||||
window.set_position(PhysicalPosition::new(self.x, self.y))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn persist_to_filesystem(&self, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
let filename = app_data_root.get_window_position_config_file();
|
||||
let json = serde_json::to_string(self)?;
|
||||
+19
-11
@@ -1,12 +1,13 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::best_window_size_heuristic::best_window_size_heuristic;
|
||||
use crate::core::windows::main_window::constants::MAIN_WINDOW_NAME;
|
||||
use crate::core::utils::window::get_window_size_heuristic::get_window_size_heuristic;
|
||||
use crate::core::windows::main_window::constants::{MAIN_WINDOW_MIN_HEIGHT, MAIN_WINDOW_MIN_WIDTH, MAIN_WINDOW_NAME};
|
||||
use errors::AnyhowResult;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use tauri::{AppHandle, Manager, PhysicalSize, Window};
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct MainWindowSize {
|
||||
pub width: u32,
|
||||
@@ -26,7 +27,7 @@ impl MainWindowSize {
|
||||
}
|
||||
|
||||
pub fn from_window(window: &Window) -> AnyhowResult<Self> {
|
||||
let size = best_window_size_heuristic(window)?;
|
||||
let size = get_window_size_heuristic(window)?;
|
||||
Ok(Self {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
@@ -43,14 +44,6 @@ impl MainWindowSize {
|
||||
Ok(Some(size))
|
||||
}
|
||||
|
||||
pub fn apply_to_main_window(&self, app: &AppHandle) -> AnyhowResult<()> {
|
||||
let windows = app.windows();
|
||||
let window = windows.get(MAIN_WINDOW_NAME)
|
||||
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
|
||||
window.set_size(PhysicalSize::new(self.width, self.height))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn persist_to_filesystem(&self, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
let filename = app_data_root.get_window_size_config_file();
|
||||
let json = serde_json::to_string(self)?;
|
||||
@@ -64,6 +57,21 @@ impl MainWindowSize {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Window size is not wide enough to persist or apply.
|
||||
pub fn is_not_wide_enough(&self) -> bool {
|
||||
self.width < MAIN_WINDOW_MIN_WIDTH
|
||||
}
|
||||
|
||||
/// Window size is not tall enough to persist or apply.
|
||||
pub fn is_not_tall_enough(&self) -> bool {
|
||||
self.height < MAIN_WINDOW_MIN_HEIGHT
|
||||
}
|
||||
|
||||
/// Window size is not big enough (tall or wide) to persist or apply.
|
||||
pub fn is_not_big_enough(&self) -> bool {
|
||||
self.is_not_wide_enough() || self.is_not_tall_enough()
|
||||
}
|
||||
|
||||
pub fn to_physical_size(&self) -> PhysicalSize<u32> {
|
||||
PhysicalSize::new(self.width, self.height)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod main_window_position;
|
||||
pub mod main_window_size;
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::core::state::app_startup_time::AppStartupTime;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::main_window_size::MainWindowSize;
|
||||
use crate::core::state::window::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::window::main_window_size::MainWindowSize;
|
||||
use crate::core::threads::main_window_thread::persist_storyteller_cookies_task::persist_storyteller_cookies_task;
|
||||
use crate::core::threads::main_window_thread::persist_window_position_task::persist_window_position_task;
|
||||
use crate::core::threads::main_window_thread::persist_window_resize_task::persist_window_resize_task;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::window::main_window_position::MainWindowPosition;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use memory_store::clone_slot::CloneSlot;
|
||||
|
||||
+7
-3
@@ -1,6 +1,6 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_size::MainWindowSize;
|
||||
use crate::core::utils::best_window_size_heuristic::best_window_size_heuristic;
|
||||
use crate::core::state::window::main_window_size::MainWindowSize;
|
||||
use crate::core::utils::window::get_window_size_heuristic::get_window_size_heuristic;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use memory_store::clone_slot::CloneSlot;
|
||||
@@ -13,7 +13,7 @@ pub async fn persist_window_resize_task(
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
let current_window_size = MainWindowSize::from_window(window)?;
|
||||
let current_physical_size = best_window_size_heuristic(window)?;
|
||||
let current_physical_size = get_window_size_heuristic(window)?;
|
||||
|
||||
let mut save_size_to_disk = true;
|
||||
|
||||
@@ -22,6 +22,10 @@ pub async fn persist_window_resize_task(
|
||||
save_size_to_disk = false;
|
||||
}
|
||||
}
|
||||
|
||||
if current_window_size.is_not_big_enough() {
|
||||
save_size_to_disk = false;
|
||||
}
|
||||
|
||||
if save_size_to_disk {
|
||||
info!("Saving window size configs to disk...");
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
use crate::core::events::generation_events::common::GenerationServiceProvider;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
|
||||
// TODO(bt,2025-07-15): Get rid of GenerationServiceProvider
|
||||
pub fn to_generation_service_provider(provider: GenerationProvider) -> GenerationServiceProvider {
|
||||
match provider {
|
||||
GenerationProvider::Artcraft => GenerationServiceProvider::Artcraft,
|
||||
GenerationProvider::Fal => GenerationServiceProvider::Fal,
|
||||
GenerationProvider::Sora => GenerationServiceProvider::Sora,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod generation_provider;
|
||||
pub mod task_type;
|
||||
@@ -0,0 +1,11 @@
|
||||
use crate::core::events::generation_events::common::GenerationAction;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
|
||||
pub fn to_generation_action(task_type: TaskType) -> GenerationAction {
|
||||
match task_type {
|
||||
TaskType::ImageGeneration => GenerationAction::GenerateImage,
|
||||
TaskType::VideoGeneration => GenerationAction::GenerateVideo,
|
||||
TaskType::BackgroundRemoval => GenerationAction::RemoveBackground,
|
||||
TaskType::ObjectGeneration => GenerationAction::ImageTo3d,
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod best_window_size_heuristic;
|
||||
pub mod clear_all_webview_cookies;
|
||||
pub mod download_media_file_to_temp_dir;
|
||||
pub mod download_url_to_temp_dir;
|
||||
pub mod enum_conversion;
|
||||
pub mod get_url_file_extension;
|
||||
pub mod image;
|
||||
pub mod large_file_transfer;
|
||||
@@ -9,4 +9,4 @@ pub mod object_store;
|
||||
pub mod save_base64_image_to_temp_dir;
|
||||
pub mod simple_http_download;
|
||||
pub mod simple_http_download_to_tempfile;
|
||||
pub mod webview_unsafe;
|
||||
pub mod window;
|
||||
@@ -1,38 +0,0 @@
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
const MAIN_WINDOW_NAME: &str = "main";
|
||||
|
||||
pub fn webview_unsafe_for_app(app: &AppHandle) -> AnyhowResult<()> {
|
||||
let windows = app.windows();
|
||||
let maybe_window = windows.get(MAIN_WINDOW_NAME);
|
||||
|
||||
/*if let Some(window) = maybe_window {
|
||||
let webview = window.get_webview(MAIN_WINDOW_NAME);
|
||||
if let Some(webview) = webview {
|
||||
webview_unsafe(&webview);
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Main window not found"));
|
||||
}*/
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*pub fn webview_unsafe(webview: &Webview) {
|
||||
let r = webview.with_webview(|webview| {
|
||||
webview.controller();
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe {
|
||||
let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();
|
||||
let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();
|
||||
let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();
|
||||
|
||||
//view.setPageZoom(40.);
|
||||
//controller.removeAllUserScripts();
|
||||
let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(1.0, 0.2, 0.4, 1.);
|
||||
window.setBackgroundColor(Some(&bg_color));
|
||||
}
|
||||
|
||||
});
|
||||
}*/
|
||||
+4
-4
@@ -4,18 +4,18 @@ use tauri::{PhysicalSize, Window};
|
||||
// A typical height difference when dev tools are open:
|
||||
// Window inner size = PhysicalSize { width: 3704, height: 732 }
|
||||
// Window outer size = PhysicalSize { width: 3704, height: 1476 }
|
||||
const HEIGHT_DIFFERENCE_THRESHOLD: u32 = 300;
|
||||
const DEV_TOOLS_HEIGHT_DIFFERENCE_THRESHOLD: u32 = 300;
|
||||
|
||||
/// Get the best window size
|
||||
pub fn best_window_size_heuristic(window: &Window) -> AnyhowResult<PhysicalSize<u32>> {
|
||||
pub fn get_window_size_heuristic(window: &Window) -> AnyhowResult<PhysicalSize<u32>> {
|
||||
// NB: Ordinarily we'd use inner size, as the outer size includes the window decorations.
|
||||
// However, dev tools contract the inner size, and in the event they're open, we should
|
||||
// prefer the outer size.
|
||||
let inner = window.inner_size()?;
|
||||
let outer = window.outer_size()?;
|
||||
|
||||
|
||||
let height_delta = outer.height.saturating_sub(inner.height);
|
||||
if height_delta > HEIGHT_DIFFERENCE_THRESHOLD {
|
||||
if height_delta > DEV_TOOLS_HEIGHT_DIFFERENCE_THRESHOLD {
|
||||
// Dev tools are open, use the outer size
|
||||
Ok(outer)
|
||||
} else {
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod get_window_size_heuristic;
|
||||
pub mod position_main_window;
|
||||
pub mod resize_main_window;
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::core::state::window::main_window_position::MainWindowPosition;
|
||||
use crate::core::windows::main_window::constants::MAIN_WINDOW_NAME;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager, PhysicalPosition};
|
||||
|
||||
/// Try to position the main window at startup.
|
||||
/// If it doesn't meet certain placement heuristics, abandon manual placement and let the OS
|
||||
/// decide where to place the app.
|
||||
pub fn position_main_window(
|
||||
app: &AppHandle,
|
||||
position: &MainWindowPosition,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
// NB: This non-negative position heuristic is good for MacBook (single screen).
|
||||
// Not sure how this fares on Windows, Linux, or multiscreen setups.
|
||||
// On Mac, this prevents the windows from being positioned off to the top-left,
|
||||
// as (0,0) is the top left directly under the top menu bar.
|
||||
if position.x < 0 {
|
||||
// TODO(bt,2025-07-16): just in the case of x/width, allow slight placement off
|
||||
// screen as long as the app is wide enough.
|
||||
return Err(anyhow::anyhow!("X position must be non-negative"));
|
||||
} else if position.y < 0 {
|
||||
return Err(anyhow::anyhow!("Y position must be non-negative"));
|
||||
}
|
||||
|
||||
let windows = app.windows();
|
||||
|
||||
let window = windows.get(MAIN_WINDOW_NAME)
|
||||
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
|
||||
|
||||
if let Ok(Some(monitor)) = window.current_monitor() {
|
||||
// Reports the size of the monitor, e.g. PhysicalSize { width: 4112, height: 2658 }
|
||||
// This is not impacted by `scale_factor`, even if that's a multiple like "2".
|
||||
// i.e., positioning the app at `monitor.size().width/2` and `monitor.size().height/2`
|
||||
// will place the **top left corner of the app** in roughly the center of the monitor.
|
||||
let monitor_size = monitor.size();
|
||||
|
||||
println!("Current monitor - size: {:?}", monitor_size);
|
||||
println!("Current monitor - scale factor: {:?}", monitor.scale_factor());
|
||||
println!("Current monitor - position: {:?}", monitor.position());
|
||||
|
||||
let width_padding = monitor_size.width.div_ceil(6);
|
||||
let height_padding = monitor_size.height.div_ceil(6);
|
||||
|
||||
let max_width = monitor_size.width.saturating_sub(width_padding);
|
||||
let max_height = monitor_size.height.saturating_sub(height_padding);
|
||||
|
||||
let max_width = i32::try_from(max_width)?;
|
||||
let max_height = i32::try_from(max_height)?;
|
||||
|
||||
// Prevent the window from being positioned off the screen to the bottom, right, and bottom right.
|
||||
// This works on MacBook (single screen), but should be tested on Windows, Linux, and multiscreen setups.
|
||||
if position.x > max_width {
|
||||
return Err(anyhow::anyhow!("X position must be less than or equal to {} (monitor width {})",
|
||||
max_width, monitor_size.width));
|
||||
} else if position.y > max_height {
|
||||
return Err(anyhow::anyhow!("Y position must be less than or equal to {} (monitor height {})",
|
||||
max_height, monitor_size.height));
|
||||
}
|
||||
}
|
||||
|
||||
window.set_position(PhysicalPosition::new(position.x, position.y))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use crate::core::state::window::main_window_size::MainWindowSize;
|
||||
use crate::core::windows::main_window::constants::{MAIN_WINDOW_MIN_HEIGHT, MAIN_WINDOW_MIN_WIDTH, MAIN_WINDOW_NAME};
|
||||
use tauri::{AppHandle, Manager, PhysicalSize};
|
||||
|
||||
/// Try to resize the main window at startup.
|
||||
/// If it doesn't meet certain sizing heuristics, abandon manual resize and let the OS
|
||||
/// decide how to size the app.
|
||||
pub fn resize_main_window(
|
||||
app: &AppHandle,
|
||||
size: &MainWindowSize,
|
||||
) -> errors::AnyhowResult<()> {
|
||||
|
||||
let mut resize_width = size.width;
|
||||
let mut resize_height = size.height;
|
||||
let mut resize_was_corrected = false;
|
||||
|
||||
if resize_width < MAIN_WINDOW_MIN_WIDTH {
|
||||
resize_width = MAIN_WINDOW_MIN_WIDTH;
|
||||
resize_was_corrected = true;
|
||||
}
|
||||
if resize_height < MAIN_WINDOW_MIN_HEIGHT {
|
||||
resize_height = MAIN_WINDOW_MIN_HEIGHT;
|
||||
resize_was_corrected = true;
|
||||
}
|
||||
|
||||
let windows = app.windows();
|
||||
let window = windows.get(MAIN_WINDOW_NAME)
|
||||
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
|
||||
|
||||
if let Ok(Some(monitor)) = window.current_monitor() {
|
||||
let scale_factor = monitor.scale_factor();
|
||||
|
||||
if scale_factor > 1.0 {
|
||||
|
||||
let min_width = MAIN_WINDOW_MIN_WIDTH as f64 * scale_factor;
|
||||
let min_height = MAIN_WINDOW_MIN_HEIGHT as f64 * scale_factor;
|
||||
|
||||
// Is this safe? https://users.rust-lang.org/t/convert-an-f64-to-an-i32/50991/4
|
||||
let min_width = min_width as u32;
|
||||
let min_height = min_height as u32;
|
||||
|
||||
if resize_width < min_width {
|
||||
resize_width = min_width;
|
||||
resize_was_corrected = true;
|
||||
}
|
||||
if resize_height < min_height {
|
||||
resize_height = min_height;
|
||||
resize_was_corrected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if resize_was_corrected {
|
||||
println!("Resizing window to: {}x{} (corrected from {}x{})",
|
||||
resize_width, resize_height, size.width, size.height);
|
||||
} else {
|
||||
println!("Resizing window to: {}x{} (no correction needed)", resize_width, resize_height);
|
||||
}
|
||||
|
||||
window.set_size(PhysicalSize::new(resize_width, resize_height))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,3 +2,11 @@
|
||||
/// The name of the main application window.
|
||||
/// This is the Artcraft application UX itself.
|
||||
pub const MAIN_WINDOW_NAME : &str = "main";
|
||||
|
||||
// TODO: handle pixel density, eg. retina displays
|
||||
/// This is the minimum width to resize the main window in an automated fashion
|
||||
pub const MAIN_WINDOW_MIN_WIDTH : u32 = 800;
|
||||
|
||||
// TODO: handle pixel density, eg. retina displays
|
||||
/// This is the minimum height to resize the main window in an automated fashion
|
||||
pub const MAIN_WINDOW_MIN_HEIGHT : u32 = 600;
|
||||
|
||||
@@ -15,15 +15,13 @@ use crate::core::commands::get_app_info_command::get_app_info_command;
|
||||
use crate::core::commands::platform_info_command::platform_info_command;
|
||||
use crate::core::commands::providers::get_provider_order_command::get_provider_order_command;
|
||||
use crate::core::commands::providers::set_provider_order_command::set_provider_order_command;
|
||||
use crate::core::lifecycle::startup::handle_tauri_startup::handle_tauri_startup;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::load_app_preferences_or_default;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::main_window_size::MainWindowSize;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::threads::discord_presence_thread::discord_presence_thread;
|
||||
use crate::core::threads::main_window_thread::main_window_thread::main_window_thread;
|
||||
use crate::core::utils::webview_unsafe::webview_unsafe_for_app;
|
||||
use crate::services::fal::commands::fal_background_removal_command::fal_background_removal_command;
|
||||
use crate::services::fal::commands::fal_hunyuan_image_to_3d_command::fal_hunyuan_image_to_3d_command;
|
||||
use crate::services::fal::commands::fal_kling_image_to_video_command::fal_kling_image_to_video_command;
|
||||
@@ -41,6 +39,7 @@ use crate::services::sora::state::sora_credential_manager::SoraCredentialManager
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::sora::threads::sora_task_polling_thread::sora_task_polling_thread;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use log::error;
|
||||
|
||||
use tauri_plugin_dialog;
|
||||
use tauri_plugin_http;
|
||||
@@ -85,36 +84,12 @@ pub fn run() {
|
||||
|
||||
let app_env_configs_2 = app_env_configs.clone();
|
||||
|
||||
let provider_priority = match ProviderPriorityStore::from_filesystem_configs(&app_data_root) {
|
||||
Ok(Some(priority)) => {
|
||||
println!("Loaded provider priority from disk: {:?}", priority.get_priority());
|
||||
priority
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("No provider priority found on disk, using default.");
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read provider priority from disk: {:?}", err);
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
};
|
||||
|
||||
println!("Initializing backend runtime...");
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_upload::init())
|
||||
.plugin(tauri_plugin_log::Builder::new()
|
||||
.level(log::LevelFilter::Info)
|
||||
.targets(vec![
|
||||
Target::new(TargetKind::Stdout),
|
||||
Target::new(TargetKind::LogDir {
|
||||
file_name: Some(app_data_root.log_file_name_str().to_string())
|
||||
}),
|
||||
])
|
||||
.build())
|
||||
.setup(move |app| {
|
||||
// TODO(bt): This is broken on windows
|
||||
// log_environment_details();
|
||||
@@ -127,59 +102,32 @@ pub fn run() {
|
||||
// )?;
|
||||
//}
|
||||
let app = app.handle().clone();
|
||||
|
||||
let result = webview_unsafe_for_app(&app);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Error setting webview unsafe: {:?}", err);
|
||||
}
|
||||
let handle = app.clone();
|
||||
let root = app_data_root_2.clone();
|
||||
let env_config = app_env_configs_2.clone();
|
||||
let storyteller_creds = storyteller_creds_manager_2.clone();
|
||||
let sora_creds = sora_creds_manager_2.clone();
|
||||
let sora_tasks = sora_task_queue_2.clone();
|
||||
let fal_creds = fal_creds_manager_2.clone();
|
||||
let fal_tasks = fal_task_queue_2.clone();
|
||||
|
||||
// TODO(bt): Clean this up. We can just clone at the callsite. Also clean initialization
|
||||
let app_2 = app.clone();
|
||||
let app_3 = app.clone();
|
||||
let app_4 = app.clone();
|
||||
let app_5 = app.clone();
|
||||
let app_env_configs_3 = app_env_configs_2.clone();
|
||||
let app_data_root_3 = app_data_root_2.clone();
|
||||
let app_data_root_4 = app_data_root_2.clone();
|
||||
let app_data_root_5 = app_data_root_2.clone();
|
||||
let sora_creds_manager_3 = sora_creds_manager_2.clone();
|
||||
let storyteller_creds_manager_3 = storyteller_creds_manager.clone();
|
||||
let storyteller_creds_manager_4 = storyteller_creds_manager.clone();
|
||||
let fal_creds_manager_3 = fal_creds_manager_2.clone();
|
||||
let fal_task_queue_3 = fal_task_queue_2.clone();
|
||||
tauri::async_runtime::block_on(async move {
|
||||
let result = handle_tauri_startup(
|
||||
handle,
|
||||
root,
|
||||
env_config,
|
||||
storyteller_creds,
|
||||
sora_creds,
|
||||
sora_tasks,
|
||||
fal_creds,
|
||||
fal_tasks,
|
||||
).await;
|
||||
|
||||
match MainWindowSize::from_filesystem_configs(&app_data_root_3) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(size)) => {
|
||||
println!("Resizing window to: {:?}", size);
|
||||
let result = size.apply_to_main_window(&app);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Could not set window size: {:?}", err);
|
||||
}
|
||||
if let Err(err) = result {
|
||||
error!("Failed to handle Tauri startup: {:?}", err);
|
||||
panic!("Failed to handle Tauri startup: {:?}", err);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read window size from disk: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match MainWindowPosition::from_filesystem_configs(&app_data_root_3) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(pos)) => {
|
||||
println!("Moving window to: {:?}", pos);
|
||||
let result = pos.apply_to_main_window(&app);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Could not set window position: {:?}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read window position from disk: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
tauri::async_runtime::spawn(main_window_thread(app_3, app_data_root_3, storyteller_creds_manager_2));
|
||||
tauri::async_runtime::spawn(sora_task_polling_thread(app_4, app_env_configs_2, app_data_root_4, sora_creds_manager_3, storyteller_creds_manager_3, sora_task_queue_2));
|
||||
tauri::async_runtime::spawn(discord_presence_thread());
|
||||
tauri::async_runtime::spawn(fal_task_polling_thread(app_5, app_env_configs_3, app_data_root_5, fal_creds_manager_3, fal_task_queue_3, storyteller_creds_manager_4));
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
@@ -188,7 +136,6 @@ pub fn run() {
|
||||
.manage(app_preferences)
|
||||
.manage(fal_creds_manager)
|
||||
.manage(fal_task_queue)
|
||||
.manage(provider_priority)
|
||||
.manage(sora_creds_manager)
|
||||
.manage(sora_task_queue)
|
||||
.manage(storyteller_creds_manager_3)
|
||||
|
||||
@@ -145,10 +145,12 @@ pub async fn sora_image_remix_command(
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_message = "Your Sora username is not yet created. Please visit the Sora.com website to create it first.";
|
||||
},
|
||||
InnerError::SoraError(SoraError::BadGateway(_) | SoraError::CloudFlareTimeout(_)) => {
|
||||
error_type = SoraImageRemixErrorType::SoraIsHavingProblems;
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_message = "Sora is having problems. Please try again later.";
|
||||
InnerError::SoraError(err) => {
|
||||
if err.is_sora_having_downtime_issues() {
|
||||
error_type = SoraImageRemixErrorType::SoraIsHavingProblems;
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_message = "Sora is having problems. Please try again later.";
|
||||
}
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user