Compare commits

..
Author SHA1 Message Date
CrossPr0duct 617aa75dc5 Background removal race condition fixed. 2025-06-19 13:13:30 -04:00
1074 changed files with 22929 additions and 19003 deletions
@@ -1,232 +0,0 @@
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,96 +0,0 @@
name: "Artcraft Windows Publish"
on:
push:
branches:
- release
- artcraft-release
- artcraft-windows # Additional branch for Windows testing
# This workflow creates Windows builds.
# On each push to the release branches it will create or update a GitHub release, build your app, and upload the artifacts to the release.
jobs:
publish-artcraft-windows:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
# Windows builds
- platform: 'windows-latest'
args: ''
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: setup node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: install frontend dependencies
run: |
# Navigate to the frontend directory to install dependencies
cd ./frontend
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 | findstr nx
- name: Pre-build frontend to avoid Tauri build issues
run: |
# Build the frontend app from the correct directory
cd ./frontend
# Increase Node.js memory limit for the build
$env:NODE_OPTIONS="--max-old-space-size=8192"
# Use npx to ensure we use the local nx version
echo "Building with npx nx..."
npx nx run editor3d:build
echo "Checking build output..."
dir apps/editor3d/dist/ | Select-Object -First 10
# Update Tauri config to skip the build (since we already built)
cd ${{ github.workspace }}/crates/desktop/artcraft
Copy-Item tauri.conf.json tauri.conf.json.bak
# Set beforeBuildCommand to empty using PowerShell
$config = Get-Content tauri.conf.json | ConvertFrom-Json
$config.build.beforeBuildCommand = ""
$config | ConvertTo-Json -Depth 10 | Set-Content tauri.conf.json
- name: build and publish
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 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.
releaseName: "Artcraft Windows v__VERSION__"
releaseBody: "Windows build - See the assets to download this version and install."
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
+2 -4
View File
@@ -24,13 +24,11 @@ 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/
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nUPDATE generic_inference_jobs\nSET\n status = 'cancelled_by_system',\n retry_at = NULL,\n internal_debugging_failure_reason = 'reap_stale_fakeyou_jobs'\nWHERE\n job_type != 'fal_queue'\n AND status IN (\n 'pending',\n 'started',\n 'attempt_failed'\n )\n AND created_at > NOW() - INTERVAL 30 MINUTE\nLIMIT 5000\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "19708db99f2b3c6283d2115a511e0eb1c99fab396ad0effd9eb2dd79926e9c93"
}
@@ -1,12 +0,0 @@
{
"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"
}
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n UPDATE tasks\n SET task_status = ?\n WHERE id = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "dc0367e9e43c3d7dda3623e77337ab4529eac2192475be7d08589886703ba1cb"
}
Generated
+1625 -1952
View File
File diff suppressed because it is too large Load Diff
+13 -23
View File
@@ -4,18 +4,6 @@ resolver = "2"
# This block defines the workspace members.
# When we add a library or binary crate, it needs an entry here.
members = [
# API clients for external services
# These might be custom implementations or wrappers over other client libraries.
"crates/api_clients/cloud_storage",
"crates/api_clients/fal_client",
"crates/api_clients/google_drive_common",
"crates/api_clients/google_sign_in",
"crates/api_clients/openai_sora_client",
"crates/api_clients/openart_client",
"crates/api_clients/runwayml_client",
"crates/api_clients/storyteller_client",
"crates/api_clients/thumbnail_generator",
# CLI tools for operations, etc.
"crates/cli/devenv/dev-database-seed",
"crates/cli/devenv/dev-upload-media-file",
@@ -34,13 +22,21 @@ members = [
# Shared libraries
"crates/lib/actix_cors_configs",
"crates/lib/actix_helpers",
"crates/lib/api_clients/cloud_storage",
"crates/lib/api_clients/fal_client",
"crates/lib/api_clients/google_drive_common",
"crates/lib/api_clients/google_sign_in",
"crates/lib/api_clients/openai_sora_client",
"crates/lib/api_clients/openart_client",
"crates/lib/api_clients/runwayml_client",
"crates/lib/api_clients/storyteller_client",
"crates/lib/api_clients/thumbnail_generator",
"crates/lib/bootstrap",
"crates/lib/bucket_paths",
"crates/lib/build_metadata",
"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",
@@ -77,7 +73,6 @@ members = [
"crates/lib/sqlx_mysql_helpers",
"crates/lib/storyteller_root",
"crates/lib/subprocess_common",
"crates/lib/test_data",
"crates/lib/testing",
"crates/lib/tts_common",
"crates/lib/url_config",
@@ -91,7 +86,6 @@ 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.
@@ -216,13 +210,11 @@ url = { version = "2.5.4", features = ["serde"] }
# Internal libs
artcraft_api_defs = { path = "crates/schema/public/api/artcraft_api_defs" }
build_metadata = { path = "crates/lib/build_metadata" }
cloud_storage = { path = "crates/api_clients/cloud_storage" }
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" }
errors = { path = "crates/lib/errors" }
fal_client = { path = "crates/api_clients/fal_client" }
fal_client = { path = "crates/lib/api_clients/fal_client" }
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
filesys = { path = "crates/lib/files/filesys" }
idempotency = { path = "crates/lib/idempotency" }
@@ -231,12 +223,10 @@ jwt_light = { path = "crates/lib/jwt_light" }
memory_caching = { path = "crates/lib/caching/memory_caching" }
memory_store = { path = "crates/lib/caching/memory_store" }
mimetypes = { path = "crates/lib/files/mimetypes" }
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
openai_sora_client = { path = "crates/lib/api_clients/openai_sora_client" }
primitives = { path = "crates/lib/data/primitives" }
shared_service_components = { path = "crates/lib/shared_service_components" }
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
storyteller_client = { path = "crates/api_clients/storyteller_client" }
storyteller_client = { path = "crates/lib/api_clients/storyteller_client" }
subprocess_common = { path = "crates/lib/subprocess_common" }
test_data = { path = "crates/lib/test_data" }
tokens = { path = "crates/schema/public/tokens" }
web_base64 = { path = "crates/lib/data/web_base64" }
web_base64= { path = "crates/lib/data/web_base64" }
+3 -4
View File
@@ -1,7 +1,7 @@
storyteller-rust
================
Our Artcraft monorepo, which serves our desktop application, backend infrastructure, and more.
Our Rust monorepo, which serves both our API surface area and our downloadable client applications.
```
."-,.__
@@ -45,6 +45,5 @@ Our Artcraft monorepo, which serves our desktop application, backend infrastruct
/ ,"'"\,' `/ `-.|"
```
- [developer documentation](./_docs)
- [server API reference](https://storyteller-docs.netlify.app/)
- [tools, scripts, misc](./script)
- [documentation](./_develop/docs)
- [tools, misc](./_develop)
@@ -1,5 +1,5 @@
Local Development Setup Guide (for bare metal)
==============================================
FakeYou Development Setup (bare metal)
======================================
This document describes how to run the FakeYou infrastructure on your development
machine's bare metal (no containers).
-26
View File
@@ -1,26 +0,0 @@
Code Conventions
================
We use three languages at Artcraft: Rust, TypeScript, and Python.
## Rust style guide
We deviate from the official Rust style guide in a few places:
- We use two spaces for indentation instead of four.
- We typically keep one function or type definition per file, though this isn't a steadfast rule.
- We use a Cargo workspace to share code within our monorepo.
### Rust Preferred Libraries
We use the following Rust libraries, though we are open to migrating to alternatives:
- `actix-web` for our HTTP web server
- `reqwest` for HTTP client requests
- `sqlx` for SQL database operations
- `tokio` for asynchronous programming
## TypeScript style guide
- We use two spaces for indentation instead of four.
- We use `nx` for monorepo management
@@ -1,30 +0,0 @@
-- 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);
@@ -1,5 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts DROP COLUMN maybe_model_type;
@@ -1,6 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts
ADD COLUMN maybe_model_type VARCHAR(32) DEFAULT NULL;
@@ -1,5 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts DROP COLUMN maybe_generation_provider;
@@ -1,6 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts
ADD COLUMN maybe_generation_provider VARCHAR(16) DEFAULT NULL;
@@ -1,5 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts DROP COLUMN maybe_anonymous_visitor_token;
@@ -1,6 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE prompts
ADD COLUMN maybe_anonymous_visitor_token VARCHAR(32) DEFAULT NULL;
@@ -1,5 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE prompt_context_items;
@@ -1,30 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- An n:m join table between prompts and media_files.
-- This associates mostly image media files with prompts, though we could conceivably
-- also store 3D sets, etc.
CREATE TABLE prompt_context_items (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- The semantic type of context.
context_semantic_type VARCHAR(16) NOT NULL,
-- Foreign key, many-to-many
-- There is no uniqueness constraint for (prompt_token, media_token) because a user
-- might attach many duplicate images to the same prompt.
prompt_token VARCHAR(32) NOT NULL,
-- Foreign key to the media token
-- There is no uniqueness constraint for (prompt_token, media_token) because a user
-- might attach many duplicate images to the same prompt.
media_token VARCHAR(32) NOT NULL,
-- INDICES --
PRIMARY KEY (id),
KEY index_prompt_token (prompt_token),
KEY index_media_token (media_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -1,5 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE generic_inference_jobs DROP COLUMN maybe_prompt_token;
@@ -1,6 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE generic_inference_jobs
ADD COLUMN maybe_prompt_token VARCHAR(32) DEFAULT NULL;
@@ -1,5 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE media_files DROP COLUMN maybe_generation_provider;
@@ -1,6 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE media_files
ADD COLUMN maybe_generation_provider VARCHAR(16) DEFAULT NULL;
@@ -15,9 +15,9 @@ CREATE TABLE batch_generations (
-- The type of record this is attached to
-- Currently supported values:
-- * 'model_weight' (deprecated)
-- * 'comment' (deprecated)
-- * 'model_weight'
-- * 'media_file'
-- * 'comment'
entity_type VARCHAR(32) NOT NULL,
-- The token for the record.
@@ -37,4 +37,6 @@ CREATE TABLE batch_generations (
-- NB: Realistically, we probably don't need this index until we have a lot of entity types, and the entity_token
-- entropy is probably sufficient enough as-is.
-- KEY index_entity_type_and_token (entity_type, entity_token),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -21,8 +21,6 @@ CREATE TABLE generic_inference_jobs (
-- The new enum for the type of job, which will eventually replace `inference_category` and `maybe_model_type`.
-- This isn't fully supported yet in the inference-job, but we'll start populating it and adding logic around it.
-- We'll start this out as nullable, then migrate the old rows with a default column value.
--
-- Fal jobs have the type `fal_queue`.
job_type VARCHAR(32) DEFAULT NULL,
-- There is an index on this column.
@@ -105,11 +103,6 @@ CREATE TABLE generic_inference_jobs (
-- And furthermore, not all download URLs have a cover image.
maybe_cover_image_media_file_token VARCHAR(32) DEFAULT NULL,
-- If we pre-constructed a prompt, this will be copied to the resultant media generated.
-- This is especially important for Fal-related jobs and endpoints. We need this for keeping
-- track of all the prompt details, context images, etc.
maybe_prompt_token VARCHAR(30) DEFAULT NULL,
-- A migration flag we can control from the backend API service which will tell
-- the worker where to store the results. If false, GCP. If true, AWS.
store_in_aws BOOLEAN NOT NULL DEFAULT FALSE,
-3
View File
@@ -61,9 +61,6 @@ CREATE TABLE media_files (
-- * 'workflow' (DEPRECATED)
origin_product_category VARCHAR(16) NOT NULL DEFAULT "unknown",
-- What service provided the generation (if any).
maybe_generation_provider VARCHAR(16) DEFAULT NULL,
-- For inference that can be tied back to a model, the type of model.
-- There are multiple indices on this column!
-- DO NOT EXPOSE THIS TO USERS VIA THE API, as we may leak secrets in doing so.
@@ -19,25 +19,13 @@ CREATE TABLE prompts (
-- Original product to which the prompt applied.
-- Prompts may or may not be compatible across products.
-- Possible values:
-- * 'artcraft_app'
-- * 'stable_diffusion' (legacy)
-- * 'comfy_ui' (legacy)
-- * 'stable_diffusion'
-- * 'comfy_ui'
prompt_type VARCHAR(16) NOT NULL,
-- What type of model was used to generate the result.
-- NB: We're conflating prompts with outputs, but in a
-- sense the model is inextricably part of the prompt.
maybe_model_type VARCHAR(32) DEFAULT NULL,
-- What service provided the generation.
maybe_generation_provider VARCHAR(16) NOT NULL, -- TODO
-- Whoever created the prompt
-- Whomever created the prompt
maybe_creator_user_token VARCHAR(32) DEFAULT NULL,
-- Whoever created the prompt (anonymous user tracking)
maybe_anonymous_visitor_token VARCHAR(32) DEFAULT NULL,
-- ========== PROMPT DETAILS ==========
-- The positive prompt (optional)
@@ -1,30 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- An n:m join table between prompts and media_files.
-- This associates mostly image media files with prompts, though we could conceivably
-- also store 3D sets, etc.
CREATE TABLE prompt_context_items (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- The semantic type of context.
context_semantic_type VARCHAR(16) NOT NULL,
-- Foreign key, many-to-many
-- There is no uniqueness constraint for (prompt_token, media_token) because a user
-- might attach many duplicate images to the same prompt.
prompt_token VARCHAR(32) NOT NULL,
-- Foreign key to the media token
-- There is no uniqueness constraint for (prompt_token, media_token) because a user
-- might attach many duplicate images to the same prompt.
media_token VARCHAR(32) NOT NULL,
-- INDICES --
PRIMARY KEY (id),
KEY index_prompt_token (prompt_token),
KEY index_media_token (media_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -1,42 +0,0 @@
[package]
name = "cloud_storage"
edition = "2018"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
publish = false
[lib]
name = "cloud_storage"
path = "src/lib.rs"
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
bucket_paths = { path = "../../lib/bucket_paths" }
crockford.workspace = true
easyenv = { path = "../../lib/easyenv" }
errors.workspace = true
filesys.workspace = true
hashing = { path = "../../lib/files/hashing" }
mimetypes.workspace = true
storyteller_root = { path = "../../lib/storyteller_root" }
# External
anyhow.workspace = true
log.workspace = true
env_logger = "0.11.3"
async-trait = "0.1.75"
# The "never-encode-slash" feature seems to have been removed:
# https://github.com/durch/rust-s3/pull/254
rust-s3 = { version = "0.36.0-beta.2" }
# Runtime
tokio.workspace = true
@@ -1,2 +0,0 @@
pub mod fal_api_key;
pub mod open_ai_api_key;
@@ -1,15 +0,0 @@
/// This is for Fal's OpenAI "Bring Your Own Key" (BYOK) endpoints.
#[derive(Clone)]
pub struct OpenAiApiKey(pub String);
impl OpenAiApiKey {
pub fn new(api_key: String) -> Self {
Self(api_key)
}
pub fn from_str(api_key: &str) -> Self {
let api_key = api_key.trim().to_string();
Self(api_key)
}
}
@@ -1,133 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::creds::open_ai_api_key::OpenAiApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::gpt_image_1::edit_image::byok::{gpt_edit_image, GptEditImageRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct GptEditImageByokArgs<'a, V: IntoUrl> {
// Request
pub image_urls: Vec<String>,
pub prompt: &'a str,
pub image_size: GptEditImageSize,
pub num_images: GptEditImageNumImages,
pub quality: GptEditImageQuality,
// Fulfillment
pub api_key: &'a FalApiKey,
pub openai_api_key: &'a OpenAiApiKey,
pub webhook_url: V,
}
#[derive(Copy, Clone, Debug)]
pub enum GptEditImageSize{
Auto,
Square,
Horizontal,
Vertical,
}
#[derive(Copy, Clone, Debug)]
pub enum GptEditImageQuality {
Auto,
Low,
Medium,
High,
}
#[derive(Copy, Clone, Debug)]
pub enum GptEditImageNumImages{
One,
Two,
Three,
Four,
}
pub async fn enqueue_gpt_image_1_edit_image_webhook<V: IntoUrl>(
args: GptEditImageByokArgs<'_, V>
) -> Result<WebhookResponse, FalErrorPlus> {
// auto, 1024x1024, 1536x1024, 1024x1536
let image_size = match args.image_size {
GptEditImageSize::Auto => "auto",
GptEditImageSize::Square => "1024x1024",
GptEditImageSize::Horizontal => "1536x1024",
GptEditImageSize::Vertical => "1024x1536",
};
let quality = match args.quality {
GptEditImageQuality::Auto => "auto",
GptEditImageQuality::Low => "low",
GptEditImageQuality::Medium => "medium",
GptEditImageQuality::High => "high",
};
let num_images = match args.num_images {
GptEditImageNumImages::One => 1,
GptEditImageNumImages::Two => 2,
GptEditImageNumImages::Three => 3,
GptEditImageNumImages::Four => 4,
};
let request = GptEditImageRequest {
image_urls: args.image_urls,
prompt: args.prompt.to_string(),
image_size: image_size.to_string(),
num_images,
quality: quality.to_string(),
openai_api_key: args.openai_api_key.0.to_string(),
};
let result = gpt_edit_image(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::creds::open_ai_api_key::OpenAiApiKey;
use crate::requests::webhook::image::enqueue_gpt_image_1_edit_image_webhook::{enqueue_gpt_image_1_edit_image_webhook, GptEditImageByokArgs, GptEditImageNumImages, GptEditImageQuality, GptEditImageSize};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, GRASSY_HILL_TRANSPARENT_IMAGE_URL};
#[tokio::test]
#[ignore]
async fn test_gpt_image_1_edit_image() -> AnyhowResult<()> {
let image_url = "https://cdn-2.fakeyou.com/media/3/4/h/f/s/34hfsmt8e38rvne6mwa4pwbxr6292sgy/image_34hfsmt8e38rvne6mwa4pwbxr6292sgy.png";
// XXX: Don't commit secrets!
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let fal_api_key = FalApiKey::from_str(&secret);
let secret = read_to_string("/Users/bt/Artcraft/credentials/openai_api_key.txt")?;
let open_ai_api_key = OpenAiApiKey::from_str(&secret);
let args = GptEditImageByokArgs {
image_urls: vec![
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
GHOST_IMAGE_URL.to_string(),
GRASSY_HILL_TRANSPARENT_IMAGE_URL.to_string(),
],
prompt: "put the man and the ghost on the grassy hill. the man is scared of the friendly ghost.",
api_key: &fal_api_key,
openai_api_key: &open_ai_api_key,
webhook_url: "https://example.com/webhook",
image_size: GptEditImageSize::Horizontal,
num_images: GptEditImageNumImages::One,
quality: GptEditImageQuality::High,
};
let result = enqueue_gpt_image_1_edit_image_webhook(args).await?;
Ok(())
}
}
@@ -1,41 +0,0 @@
[package]
name = "openai_sora_client"
edition = "2024"
version = "0.0.1"
publish = false
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[lib]
name = "openai_sora_client"
path = "src/lib.rs"
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
errors.workspace = true
idempotency.workspace = true
# External
anyhow.workspace = true
base64.workspace = true
chrono.workspace = true
cloudflare_errors.workspace = true
cookie.workspace = true
log.workspace = true
once_cell.workspace = true
rand.workspace = true
reqwest = { workspace = true, features = ["multipart", "gzip"] }
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
sha3.workspace = true
thiserror.workspace = true
tokio.workspace = true
toml.workspace = true
[dev-dependencies]
testing = { path = "../../lib/testing" }
@@ -1,45 +0,0 @@
[package]
name = "openart_client"
edition = "2024"
version = "0.0.1"
publish = false
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[lib]
name = "openart_client"
path = "src/lib.rs"
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
crockford.workspace = true
errors.workspace = true
idempotency.workspace = true
subprocess_common = { path = "../../lib/subprocess_common" }
# External
anyhow.workspace = true
base64.workspace = true
chrono.workspace = true
cookie.workspace = true
hex-literal.workspace = true
log.workspace = true
once_cell.workspace = true
rand.workspace = true
reqwest = { workspace = true, features = ["multipart", "gzip"] }
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
sha3.workspace = true
thiserror.workspace = true
tokio.workspace = true
toml.workspace = true
[dev-dependencies]
testing = { path = "../../lib/testing" }
filesys.workspace = true
@@ -1,46 +0,0 @@
[package]
name = "runwayml_client"
edition = "2021"
version = "0.0.1"
publish = false
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
[lib]
name = "runwayml_client"
path = "src/lib.rs"
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
crockford.workspace = true
errors.workspace = true
idempotency.workspace = true
jwt_light.workspace = true
subprocess_common = { path = "../../lib/subprocess_common" }
# External
anyhow.workspace = true
base64.workspace = true
chrono.workspace = true
cookie.workspace = true
hex-literal.workspace = true
log.workspace = true
once_cell.workspace = true
rand.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
sha3.workspace = true
thiserror.workspace = true
tokio.workspace = true
toml.workspace = true
[dev-dependencies]
testing = { path = "../../lib/testing" }
filesys.workspace = true
@@ -1,53 +0,0 @@
[package]
name = "storyteller_client"
edition = "2021"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
publish = false
[package.metadata.cargo-udeps.ignore]
normal = ["workspace-build-acceleration"]
# [lints.rust]
# unused_imports = "forbid"
[lib]
name = "storyteller_client"
path = "src/lib.rs"
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
# Internal
artcraft_api_defs.workspace = true
cloudflare_errors.workspace = true
enums.workspace = true
enums_public.workspace = true
errors.workspace = true
idempotency.workspace = true
mimetypes.workspace = true
tokens.workspace = true
# External
anyhow.workspace = true
chrono.workspace = true
cookie.workspace = true
log.workspace = true
reqwest = { workspace = true, features = ["multipart", "gzip"] }
url.workspace = true
# Serialization
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
# HTTP
backoff = { version = "0.4.0", features = ["futures"] }
uuid = { version = "1.8.0", features = ["v4"] } # Exponential backoff retry
[dev-dependencies]
tokio.workspace = true
@@ -1,19 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::image::edit::gpt_image_1_edit_image::{GptImage1EditImageRequest, GptImage1EditImageResponse, GPT_IMAGE_1_EDIT_IMAGE_PATH};
pub async fn gpt_image_1_edit_image(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GptImage1EditImageRequest,
) -> Result<GptImage1EditImageResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GPT_IMAGE_1_EDIT_IMAGE_PATH,
maybe_creds,
request,
).await?)
}
@@ -1 +0,0 @@
pub mod gpt_image_1_edit_image;
@@ -1,19 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::{GenerateFlux1DevTextToImageRequest, GenerateFlux1DevTextToImageResponse, GENERATE_FLUX_1_DEV_TEXT_TO_IMAGE_PATH};
pub async fn generate_flux_1_dev_text_to_image(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateFlux1DevTextToImageRequest,
) -> Result<GenerateFlux1DevTextToImageResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_FLUX_1_DEV_TEXT_TO_IMAGE_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,19 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
pub async fn generate_flux_1_schnell_text_to_image(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateFlux1SchnellTextToImageRequest,
) -> Result<GenerateFlux1SchnellTextToImageResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,19 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::{GenerateFluxPro11TextToImageRequest, GenerateFluxPro11TextToImageResponse, GENERATE_FLUX_PRO_11_TEXT_TO_IMAGE_PATH};
pub async fn generate_flux_pro_11_text_to_image(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateFluxPro11TextToImageRequest,
) -> Result<GenerateFluxPro11TextToImageResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_FLUX_PRO_11_TEXT_TO_IMAGE_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::{GenerateFluxPro11UltraTextToImageRequest, GenerateFluxPro11UltraTextToImageResponse, GENERATE_FLUX_PRO_11_ULTRA_TEXT_TO_IMAGE_PATH};
pub async fn generate_flux_pro_11_ultra_text_to_image(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateFluxPro11UltraTextToImageRequest,
) -> Result<GenerateFluxPro11UltraTextToImageResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_FLUX_PRO_11_ULTRA_TEXT_TO_IMAGE_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,6 +0,0 @@
pub mod edit;
pub mod generate_flux_1_dev_text_to_image;
pub mod generate_flux_1_schnell_text_to_image;
pub mod generate_flux_pro_11_text_to_image;
pub mod generate_flux_pro_11_ultra_text_to_image;
pub mod remove_image_background;
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::image::remove_image_background::{RemoveImageBackgroundRequest, RemoveImageBackgroundResponse, REMOVE_IMAGE_BACKGROUND_PATH};
pub async fn remove_image_background(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: RemoveImageBackgroundRequest,
) -> Result<RemoveImageBackgroundResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
REMOVE_IMAGE_BACKGROUND_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::{GenerateHunyuan20ImageTo3dRequest, GenerateHunyuan20ImageTo3dResponse, GENERATE_HUNYUAN_2_0_IMAGE_TO_3D_URL_PATH};
pub async fn generate_hunyuan3d_2_0_image_to_3d(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateHunyuan20ImageTo3dRequest,
) -> Result<GenerateHunyuan20ImageTo3dResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_HUNYUAN_2_0_IMAGE_TO_3D_URL_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::{GenerateHunyuan21ImageTo3dRequest, GenerateHunyuan21ImageTo3dResponse, GENERATE_HUNYUAN_2_1_IMAGE_TO_3D_URL_PATH};
pub async fn generate_hunyuan3d_2_1_image_to_3d(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateHunyuan21ImageTo3dRequest,
) -> Result<GenerateHunyuan21ImageTo3dResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_HUNYUAN_2_1_IMAGE_TO_3D_URL_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,2 +0,0 @@
pub mod generate_hunyuan_3d_2_0_image_to_3d;
pub mod generate_hunyuan_3d_2_1_image_to_3d;
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::{GenerateKling16ProImageToVideoRequest, GenerateKling16ProImageToVideoResponse, GENERATE_KLING_1_6_PRO_IMAGE_TO_VIDEO_URL_PATH};
pub async fn generate_kling_16_pro_image_to_video(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateKling16ProImageToVideoRequest,
) -> Result<GenerateKling16ProImageToVideoResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_KLING_1_6_PRO_IMAGE_TO_VIDEO_URL_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::video::generate_kling_2_1_master_image_to_video::{GenerateKling21MasterImageToVideoRequest, GenerateKling21MasterImageToVideoResponse, GENERATE_KLING_2_1_MASTER_IMAGE_TO_VIDEO_URL_PATH};
pub async fn generate_kling_21_master_image_to_video(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateKling21MasterImageToVideoRequest,
) -> Result<GenerateKling21MasterImageToVideoResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_KLING_2_1_MASTER_IMAGE_TO_VIDEO_URL_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::video::generate_kling_2_1_pro_image_to_video::{GenerateKling21ProImageToVideoRequest, GenerateKling21ProImageToVideoResponse, GENERATE_KLING_2_1_PRO_IMAGE_TO_VIDEO_URL_PATH};
pub async fn generate_kling_21_pro_image_to_video(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateKling21ProImageToVideoRequest,
) -> Result<GenerateKling21ProImageToVideoResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_KLING_2_1_PRO_IMAGE_TO_VIDEO_URL_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::{GenerateSeedance10LiteImageToVideoRequest, GenerateSeedance10LiteImageToVideoResponse, GENERATE_SEEDANCE_1_0_LITE_IMAGE_TO_VIDEO_URL_PATH};
pub async fn generate_seedance_1_0_lite_image_to_video(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateSeedance10LiteImageToVideoRequest,
) -> Result<GenerateSeedance10LiteImageToVideoResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_SEEDANCE_1_0_LITE_IMAGE_TO_VIDEO_URL_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::{GenerateVeo2ImageToVideoRequest, GenerateVeo2ImageToVideoResponse, GENERATE_VEO_2_IMAGE_TO_VIDEO_URL_PATH};
pub async fn generate_veo_2_image_to_video(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: GenerateVeo2ImageToVideoRequest,
) -> Result<GenerateVeo2ImageToVideoResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
GENERATE_VEO_2_IMAGE_TO_VIDEO_URL_PATH,
maybe_creds,
request,
).await?)
}
@@ -1,5 +0,0 @@
pub mod generate_kling_16_pro_image_to_video;
pub mod generate_kling_21_master_image_to_video;
pub mod generate_kling_21_pro_image_to_video;
pub mod generate_seedance_1_0_lite_image_to_video;
pub mod generate_veo_2_image_to_video;
@@ -1,43 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_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(",")
}
@@ -1 +0,0 @@
pub mod list_session_jobs;
@@ -1,47 +0,0 @@
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.
/// This function needs to take temporary ownership, but will return it to the caller.
///
/// Example use:
/// let response = client.get().await?;
/// let response = filter_bad_response(response).await?;
/// ... 200 or other valid response
pub async fn filter_bad_response(response: reqwest::Response) -> Result<reqwest::Response, ApiError> {
let status = response.status();
if status.is_success() {
return 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))),
}
}
+10 -11
View File
@@ -21,29 +21,28 @@ workspace-build-acceleration = { workspace = true }
# Internal
bucket_paths = { path = "../../../lib/bucket_paths" }
cloud_storage.workspace = true
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
config = { path = "../../../lib/deprecated/config" }
easyenv = { path = "../../../lib/easyenv" }
enums.workspace = true
errors.workspace = true
filesys.workspace = true
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../../lib/errors" }
filesys = { path = "../../../lib/files/filesys" }
hashing = { path = "../../../lib/files/hashing" }
idempotency.workspace = true
mimetypes.workspace = true
idempotency = { path = "../../../lib/idempotency" }
mimetypes = { path = "../../../lib/files/mimetypes" }
mysql_queries = { path = "../../../schema/database/mysql_queries" }
storyteller_root = { path = "../../../lib/storyteller_root" }
tokens.workspace = true
tokens = { path = "../../../schema/public/tokens" }
# External
log.workspace = true
log = "0.4.14"
clap = "4.4.8"
rand.workspace = true
rand = "0.8.5"
# Runtime
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio = { version = "1.38.0", features = ["macros"] }
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
elasticsearch = "8.5.0-alpha.1"
@@ -21,25 +21,25 @@ workspace-build-acceleration = { workspace = true }
# Internal
bucket_paths = { path = "../../../lib/bucket_paths" }
cloud_storage.workspace = true
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
config = { path = "../../../lib/deprecated/config" }
easyenv = { path = "../../../lib/easyenv" }
enums.workspace = true
errors.workspace = true
filesys.workspace = true
mimetypes.workspace = true
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../../lib/errors" }
filesys = { path = "../../../lib/files/filesys" }
mimetypes = { path = "../../../lib/files/mimetypes" }
mysql_queries = { path = "../../../schema/database/mysql_queries" }
tokens.workspace = true
tokens = { path = "../../../schema/public/tokens" }
# External
clap = "4.4.8"
log.workspace = true
log = "0.4.14"
# Runtime
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio.workspace = true
tokio = { version = "1.38.0", features = ["macros"] }
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -36,7 +36,7 @@ log = "0.4.14"
# Bigger pieces
futures = { version = "0.3.17", features = ["thread-pool"] }
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
tokio = { version = "1.38.0", features = ["macros"] }
# Serialization
+1 -1
View File
@@ -36,7 +36,7 @@ log = "0.4.14"
# Bigger pieces
tokio = { version = "1.38.0", features = ["macros"] }
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
sqlx = { version = "0.7.2", 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.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
elasticsearch = "8.5.0-alpha.1"
# Serialization
+13 -13
View File
@@ -17,35 +17,35 @@ normal = ["workspace-build-acceleration"]
[dependencies]
# Workspace hack (faster compile times)
workspace-build-acceleration.workspace = true
workspace-build-acceleration = { workspace = true }
# Internal
bucket_paths = { path = "../../../lib/bucket_paths" }
cloud_storage.workspace = true
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
config = { path = "../../../lib/deprecated/config" }
easyenv = { path = "../../../lib/easyenv" }
enums.workspace = true
errors.workspace = true
filesys.workspace = true
enums = { path = "../../../schema/public/enums" }
errors = { path = "../../../lib/errors" }
filesys = { path = "../../../lib/files/filesys" }
hashing = { path = "../../../lib/files/hashing" }
mysql_queries = { path = "../../../schema/database/mysql_queries" }
storyteller_root = { path = "../../../lib/storyteller_root" }
tokens.workspace = true
tokens = { path = "../../../schema/public/tokens" }
# External
clap = "4.4.8"
log.workspace = true
log = "0.4.14"
strum = { version = "0.26", features = ["derive"] }
tempdir.workspace = true
tempdir = "0.3.7"
# Async + clients
tokio.workspace = true
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
tokio = { version = "1.38.0", features = ["macros"] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
# Serialization
serde.workspace = true
serde_derive.workspace = true
serde_json.workspace = true
serde = "1.0.125"
serde_derive = "1.0.125"
serde_json = "1.0.108"
[dev-dependencies]
# None yet
+1 -6
View File
@@ -9,9 +9,6 @@ edition = "2021"
rust-version = "1.77.2"
publish = false
# [lints.rust]
# unused_imports = "forbid"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
@@ -28,7 +25,6 @@ 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 }
@@ -37,7 +33,6 @@ 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 }
@@ -60,7 +55,6 @@ 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 }
@@ -75,6 +69,7 @@ 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"
@@ -16,9 +16,6 @@
"allow": [
{
"url": "https://*"
},
{
"url": "http://localhost:12345"
}
]
},
@@ -3,7 +3,7 @@ use crate::core::state::app_preferences::preferred_download_directory::Preferred
use errors::AnyhowResult;
use log::{error, info};
use serde_derive::Serialize;
use tauri::State;
use tauri::{AppHandle, State};
#[derive(Serialize)]
pub struct GetAppPreferencesResponse {
@@ -4,8 +4,9 @@ use crate::core::state::data_dir::app_data_root::AppDataRoot;
use anyhow::anyhow;
use errors::AnyhowResult;
use log::{error, info};
use openai_sora_client::sora_error::SoraError::AnyhowError;
use serde_derive::{Deserialize, Serialize};
use tauri::State;
use tauri::{AppHandle, State};
/// For now, we'll only update a single preference at a time.
#[derive(Deserialize)]

Some files were not shown because too many files have changed in this diff Show More