mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 14:24:53 -05:00
Compare commits
106
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
558b822fe0 | ||
|
|
627fd6beeb | ||
|
|
1820043cde | ||
|
|
2629635c6a | ||
|
|
03365687f9 | ||
|
|
77788a74e9 | ||
|
|
691f27eb23 | ||
|
|
9008e1f4f4 | ||
|
|
c11894692c | ||
|
|
e7d8613d09 | ||
|
|
55832bdfc2 | ||
|
|
49f18d7616 | ||
|
|
10603f7c86 | ||
|
|
5e8c524f84 | ||
|
|
05f0fe1216 | ||
|
|
1f44f7e929 | ||
|
|
c56cc8700c | ||
|
|
caaff569a6 | ||
|
|
3b0ed7a8ba | ||
|
|
ea1905cfcf | ||
|
|
d1f28877ca | ||
|
|
1b8d8cbd97 | ||
|
|
8be5013260 | ||
|
|
3c9b81c3ea | ||
|
|
9d1b7d22ee | ||
|
|
b046f6ff2a | ||
|
|
df0b98ec63 | ||
|
|
f214577af1 | ||
|
|
3efe9db4ca | ||
|
|
8208206e5a | ||
|
|
4f33e74e3a | ||
|
|
f75b210dcd | ||
|
|
7a07f196d1 | ||
|
|
a609282d8a | ||
|
|
0a32e06cd5 | ||
|
|
f1563feb91 | ||
|
|
cc73ffab5d | ||
|
|
d374d9339c | ||
|
|
53681eac3f | ||
|
|
f22369ea4a | ||
|
|
f7060c0278 | ||
|
|
2af8fbd9e1 | ||
|
|
a4eb74292f | ||
|
|
78b8c56066 | ||
|
|
221aa347bf | ||
|
|
14a27ee803 | ||
|
|
36e35dab0b | ||
|
|
cb868c7275 | ||
|
|
b756f81421 | ||
|
|
0a1ad3d814 | ||
|
|
9df8c981cd | ||
|
|
e7622318c4 | ||
|
|
352c7517cc | ||
|
|
010a2f043a | ||
|
|
cbb32f8275 | ||
|
|
0aecc97ede | ||
|
|
72a1bb07e0 | ||
|
|
6a01e4bba0 | ||
|
|
90d741323c | ||
|
|
066d1c8bfb | ||
|
|
3536736f53 | ||
|
|
76d1f65164 | ||
|
|
ab72551ee7 | ||
|
|
9cf87cda5b | ||
|
|
5163f357af | ||
|
|
42dfa9263d | ||
|
|
f4d9385668 | ||
|
|
189d6a6df8 | ||
|
|
89cdc8492f | ||
|
|
46d2944823 | ||
|
|
216e091257 | ||
|
|
f8bfa3ab2b | ||
|
|
569622bcc0 | ||
|
|
7aad79d16b | ||
|
|
d70f8ac41e | ||
|
|
7097144e19 | ||
|
|
709af560a5 | ||
|
|
0f1cf93252 | ||
|
|
4a10581e9a | ||
|
|
447a70b890 | ||
|
|
f4e998d945 | ||
|
|
a954859ac2 | ||
|
|
2d8e835401 | ||
|
|
c23380af90 | ||
|
|
c34f75d071 | ||
|
|
8a886e9033 | ||
|
|
3101df8211 | ||
|
|
a4c264ebbf | ||
|
|
3d807fdeb2 | ||
|
|
b3a62cce64 | ||
|
|
42ccc25e4d | ||
|
|
6595518b35 | ||
|
|
618cbc8f0e | ||
|
|
b31a152c1a | ||
|
|
0e2169b902 | ||
|
|
0ee9b7c442 | ||
|
|
17495276e7 | ||
|
|
2d78fd5a6f | ||
|
|
98e513e917 | ||
|
|
97eb1c65ca | ||
|
|
ba82a438dd | ||
|
|
de7b990ec5 | ||
|
|
7b90c59675 | ||
|
|
11f1a5f41a | ||
|
|
b92cd58f81 | ||
|
|
914c26b4c6 |
@@ -0,0 +1,107 @@
|
||||
name: "Artcraft Publish"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
- artcraft-release
|
||||
|
||||
# This workflow creates Universal macOS builds with signing certificate support.
|
||||
# On each push to the release branches it will create or update a GitHub release, build your app, and upload the artifacts to the release.
|
||||
|
||||
jobs:
|
||||
publish-artcraft:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Universal macOS builds (arm64 and x86_64)
|
||||
- platform: 'macos-latest'
|
||||
args: '--target universal-apple-darwin'
|
||||
# Uncomment below for additional platforms
|
||||
# - platform: 'ubuntu-22.04'
|
||||
# args: ''
|
||||
# - platform: 'windows-latest'
|
||||
# args: ''
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: |
|
||||
cd ./crates/desktop/artcraft
|
||||
yarn install
|
||||
|
||||
- name: import Apple Developer Certificate
|
||||
if: matrix.platform == 'macos-latest'
|
||||
# Prevents keychain from locking automatically for 3600 seconds.
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security set-keychain-settings -t 3600 -u build.keychain
|
||||
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security find-identity -v -p codesigning build.keychain
|
||||
|
||||
- name: verify certificate
|
||||
if: matrix.platform == 'macos-latest'
|
||||
run: |
|
||||
echo "Available certificates:"
|
||||
security find-identity -v -p codesigning build.keychain
|
||||
|
||||
# Try multiple certificate patterns
|
||||
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep -E "(Developer ID Application|Apple Development|Storyteller Developer)" | head -1)
|
||||
|
||||
if [ -z "$CERT_INFO" ]; then
|
||||
echo "No matching certificate found. Using first available certificate."
|
||||
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep -v "Policy" | head -1)
|
||||
fi
|
||||
|
||||
CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
|
||||
echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
|
||||
echo "Using certificate: $CERT_ID"
|
||||
|
||||
- name: build and publish
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
|
||||
with:
|
||||
projectPath: ./crates/desktop/artcraft
|
||||
tagName: artcraft-v__VERSION__ # the action automatically replaces __VERSION__ with the app version.
|
||||
releaseName: "Artcraft v__VERSION__"
|
||||
releaseBody: "See the assets to download this version and install."
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = NULL,\n maybe_model_token = NULL,\n\n maybe_input_source_token = NULL,\n maybe_input_source_token_type = NULL,\n\n maybe_download_url = NULL,\n maybe_cover_image_media_file_token = NULL,\n\n maybe_raw_inference_text = NULL,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n priority_level = 0,\n is_keepalive_required = FALSE,\n max_duration_seconds = 0,\n\n is_debug_request = FALSE,\n maybe_routing_tag = NULL,\n\n status = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 13
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0fa824822cd94024dce21243f41e69e4a3e8685c4de676834a7c0ee14982c976"
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n \n jobs.maybe_external_third_party as `external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty`,\n jobs.maybe_external_third_party_id as `external_third_party_id`,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.created_at,\n jobs.updated_at\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\nAND jobs.maybe_external_third_party_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "status: enums::common::job_status_plus::JobStatusPlus",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 76
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "external_third_party: enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "external_third_party_id",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 256
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "creator_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "64dfacc1f0552d539f009fea91e902a2fe296c06ad539808596bbf2c8fc9eaa0"
|
||||
}
|
||||
+1
-1
@@ -48,7 +48,7 @@
|
||||
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n is_batch_generated = ?,\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_mod_user_token = ?, \n is_generated_on_prem = ?, \n generated_by_worker = ?, \n generated_by_cluster = ?\n ",
|
||||
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n\n media_class = ?,\n media_type = ?,\n\n is_user_upload = ?,\n is_intermediate_system_file = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n is_batch_generated = ?,\n maybe_batch_token = ?,\n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?,\n maybe_prompt_token = ?,\n checksum_sha2 = ?,\n \n maybe_engine_category = ?,\n\n maybe_title = ?,\n maybe_text_transcript = ?,\n\n maybe_scene_source_media_file_token = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n extra_file_modification_info = ?,\n\n maybe_mod_user_token = ?, \n is_generated_on_prem = ?, \n generated_by_worker = ?, \n generated_by_cluster = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 38
|
||||
"Right": 39
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c58f414b5e4d0f0420d1894e2f40311e5613919f49f3ac91f81f5fc2bef1bea8"
|
||||
"hash": "b4470e607c005421dd8504b9f1d695080a581936ac6ce00b6f437b2764e741be"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE generic_inference_jobs\nSET\n status = ?,\n on_success_result_entity_type = ?,\n on_success_result_entity_token = ?,\n failure_reason = NULL,\n internal_debugging_failure_reason = NULL,\n success_execution_millis = NULL,\n success_inference_execution_millis = NULL,\n retry_at = NULL,\n successfully_completed_at = NOW()\nWHERE token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f68a8917e639418a0bb952bef606c96299001e143c745ee30df99b72d1235d35"
|
||||
}
|
||||
Vendored
+13
-4
@@ -1,6 +1,15 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/*~": true,
|
||||
"frontend_old/**": true
|
||||
}
|
||||
"files.exclude": {
|
||||
"**/*~": true,
|
||||
".sqlx/**": true,
|
||||
"_elasticsearch/**": true,
|
||||
"_sql/**": true,
|
||||
"frontend_old/**": true,
|
||||
"includes/**": true,
|
||||
"target/**": true,
|
||||
"test_data/**": true,
|
||||
"tmp/**": true,
|
||||
"virtualenv/**": true,
|
||||
"windows_dlls/**": true,
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+368
-2796
File diff suppressed because it is too large
Load Diff
+56
-29
@@ -4,13 +4,6 @@ resolver = "2"
|
||||
# This block defines the workspace members.
|
||||
# When we add a library or binary crate, it needs an entry here.
|
||||
members = [
|
||||
# Types: common strongly-typed tokens, ids, payloads, etc.
|
||||
"crates/_types/composite_identifiers",
|
||||
"crates/_types/enums",
|
||||
"crates/_types/enums_public",
|
||||
"crates/_types/reusable_types", # TODO(bt, 2022-12-21): Deprecate
|
||||
"crates/_types/tokens",
|
||||
|
||||
# CLI tools for operations, etc.
|
||||
"crates/cli/devenv/dev-database-seed",
|
||||
"crates/cli/devenv/dev-upload-media-file",
|
||||
@@ -34,11 +27,13 @@ members = [
|
||||
"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",
|
||||
@@ -48,11 +43,11 @@ members = [
|
||||
"crates/lib/data/crockford",
|
||||
"crates/lib/data/datetimes",
|
||||
"crates/lib/data/primitives",
|
||||
"crates/lib/data/web_base64",
|
||||
"crates/lib/deprecated/config", # TODO(bt, 2023-02-13): Deprecate or repurpose
|
||||
"crates/lib/deprecated/http_server_common", # TODO(bt, 2023-03-20): Move useful pieces to actix_helpers
|
||||
"crates/lib/dimensions",
|
||||
"crates/lib/easyenv", # NB: Vendored version of https://github.com/echelon/easyenv.rs that we'll improve.
|
||||
"crates/lib/elasticsearch_schema",
|
||||
"crates/lib/email_sender",
|
||||
"crates/lib/errors",
|
||||
"crates/lib/files/archives",
|
||||
@@ -68,15 +63,11 @@ members = [
|
||||
"crates/lib/jobs_common",
|
||||
"crates/lib/jwt_light",
|
||||
"crates/lib/logging",
|
||||
"crates/lib/machine_learning/ml_models",
|
||||
"crates/lib/machine_learning/ml_weights_registry",
|
||||
"crates/lib/machine_learning/prompt_engineering",
|
||||
"crates/lib/markdown",
|
||||
"crates/lib/migration",
|
||||
"crates/lib/mysql_queries",
|
||||
"crates/lib/password",
|
||||
"crates/lib/redis_common",
|
||||
"crates/lib/redis_schema",
|
||||
"crates/lib/server_environment",
|
||||
"crates/lib/shared_service_components",
|
||||
"crates/lib/sqlx_mysql_helpers",
|
||||
@@ -88,7 +79,22 @@ members = [
|
||||
"crates/lib/user_input_common",
|
||||
"crates/lib/web_scrapers",
|
||||
#"crates/lib/api_clients/fakeyou_client",
|
||||
#"crates/lib/sqlite_queries",
|
||||
#"crates/lib/machine_learning/ml_models",
|
||||
|
||||
# Schema (database): queries, database layouts, Redis keys, etc.
|
||||
"crates/schema/database/elasticsearch_schema",
|
||||
"crates/schema/database/mysql_queries",
|
||||
"crates/schema/database/redis_common",
|
||||
"crates/schema/database/redis_schema",
|
||||
#"crates/schema/database/sqlite_queries",
|
||||
|
||||
# Schema (public): common strongly-typed tokens, ids, payloads, etc.
|
||||
"crates/schema/public/api/artcraft_api_defs",
|
||||
"crates/schema/public/composite_identifiers",
|
||||
"crates/schema/public/enums",
|
||||
"crates/schema/public/enums_public",
|
||||
"crates/schema/public/reusable_types", # TODO(bt, 2022-12-21): Deprecate
|
||||
"crates/schema/public/tokens",
|
||||
|
||||
# Services: API servers, jobs, workers, etc.
|
||||
"crates/service/job/analytics_job",
|
||||
@@ -109,19 +115,23 @@ members = [
|
||||
"crates/service/plugins/user_traits_component",
|
||||
|
||||
# Vendored code
|
||||
"crates/vendor/candle/candle-core",
|
||||
"crates/vendor/candle/candle-datasets",
|
||||
"crates/vendor/candle/candle-examples",
|
||||
"crates/vendor/candle/candle-flash-attn",
|
||||
"crates/vendor/candle/candle-kernels",
|
||||
"crates/vendor/candle/candle-metal-kernels",
|
||||
"crates/vendor/candle/candle-nn",
|
||||
"crates/vendor/candle/candle-onnx",
|
||||
"crates/vendor/candle/candle-pyo3",
|
||||
"crates/vendor/candle/candle-transformers",
|
||||
"crates/vendor/candle/tensor-tools",
|
||||
"crates/vendor/fal-rs",
|
||||
"crates/vendor/fal-rs/fal-derive",
|
||||
"crates/vendor/fal-rs/generate",
|
||||
"crates/vendor/limitation",
|
||||
"crates/vendor/tauri-plugin-http",
|
||||
# NB(bt,2025-05-25): Too many failures on Mac. Disable for now.
|
||||
#"crates/vendor/candle/candle-core",
|
||||
#"crates/vendor/candle/candle-datasets",
|
||||
#"crates/vendor/candle/candle-examples",
|
||||
#"crates/vendor/candle/candle-flash-attn",
|
||||
#"crates/vendor/candle/candle-kernels",
|
||||
#"crates/vendor/candle/candle-metal-kernels",
|
||||
#"crates/vendor/candle/candle-nn",
|
||||
#"crates/vendor/candle/candle-onnx",
|
||||
#"crates/vendor/candle/candle-pyo3",
|
||||
#"crates/vendor/candle/candle-transformers",
|
||||
#"crates/vendor/candle/tensor-tools",
|
||||
# NB(bt,2023-12-12): Newrelic keeps breaking with upgrades. Kill for now.
|
||||
#"crates/vendor/newrelic-telemetry-sdk-rust",
|
||||
|
||||
@@ -161,8 +171,19 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
|
||||
# External libs
|
||||
# Centrally pinned versions so that we can uniformly upgrade/downgrade versions across the workspace
|
||||
# If a crate needs to use an older/newer version, the dependency should be managed in that crate's own Cargo.toml
|
||||
actix = "0.13.5"
|
||||
actix-cors = "0.6.4" # TODO(bt): Carefully upgrade this. CORS broke last time.
|
||||
actix-http = "3.7.0"
|
||||
actix-multipart = "0.6.2"
|
||||
actix-rt = "2.10.0"
|
||||
actix-service = "2.0.3"
|
||||
actix-utils = "3.0.1"
|
||||
actix-web = "4.7.0" # TODO(bt): We're behind and need to bump versions.
|
||||
actix-web-actors = "4.3.1+deprecated"
|
||||
actix-web-lab = "0.20.2"
|
||||
anyhow = "1.0.98"
|
||||
base64 = "0.22.1"
|
||||
bytes = "1.10.1"
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
cookie = "0.18.1"
|
||||
futures = { version = "0.3.31" }
|
||||
@@ -176,20 +197,25 @@ reqwest = { version = "0.12.15", features = ["json", "cookies"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_derive = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
serde_yaml = "0.9.1"
|
||||
serde_yaml = "0.9.34+deprecated"
|
||||
sha3 = "0.10.8"
|
||||
tauri = { version = "2.4.1", features = ["unstable", "devtools"] } # NB: "devtools" prevents App Store distribution
|
||||
tempdir = "0.3.7"
|
||||
tempfile = "3.20.0"
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.45.0", features = ["macros"] }
|
||||
tokio = { version = "1.45.1", features = ["macros"] }
|
||||
toml = "0.8.22"
|
||||
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" }
|
||||
crockford = { path = "crates/lib/data/crockford" }
|
||||
enums = { path = "crates/_types/enums" }
|
||||
enums_public = { path = "crates/_types/enums_public" }
|
||||
enums = { path = "crates/schema/public/enums" }
|
||||
enums_public = { path = "crates/schema/public/enums_public" }
|
||||
errors = { path = "crates/lib/errors" }
|
||||
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" }
|
||||
images = { path = "crates/lib/files/images" }
|
||||
@@ -202,4 +228,5 @@ primitives = { path = "crates/lib/data/primitives" }
|
||||
shared_service_components = { path = "crates/lib/shared_service_components" }
|
||||
storyteller_client = { path = "crates/lib/api_clients/storyteller_client" }
|
||||
subprocess_common = { path = "crates/lib/subprocess_common" }
|
||||
tokens = { path = "crates/_types/tokens" }
|
||||
tokens = { path = "crates/schema/public/tokens" }
|
||||
web_base64= { path = "crates/lib/data/web_base64" }
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs DROP COLUMN maybe_external_third_party_id;
|
||||
ALTER TABLE generic_inference_jobs DROP COLUMN maybe_external_third_party;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD COLUMN maybe_external_third_party VARCHAR(16) DEFAULT NULL;
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD COLUMN maybe_external_third_party_id VARCHAR(64) DEFAULT NULL;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP INDEX `index_maybe_external_third_party_id`
|
||||
ON `generic_inference_jobs`;
|
||||
|
||||
DROP INDEX `index_maybe_external_third_party_and_id`
|
||||
ON `generic_inference_jobs`;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD UNIQUE INDEX index_maybe_external_third_party_and_id (maybe_external_third_party, maybe_external_third_party_id),
|
||||
ALGORITHM=INPLACE, LOCK=NONE;
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD INDEX index_maybe_external_third_party_id (maybe_external_third_party_id),
|
||||
ALGORITHM=INPLACE, LOCK=NONE;
|
||||
@@ -39,8 +39,20 @@ CREATE TABLE generic_inference_jobs (
|
||||
-- Examples (may not be up to date):
|
||||
-- * text_to_speech
|
||||
-- * voice_conversion
|
||||
-- * image_generation
|
||||
-- * video_generation
|
||||
-- * background_removal
|
||||
inference_category VARCHAR(32) NOT NULL,
|
||||
|
||||
-- If the job is externally enqueued, this is the third-party service
|
||||
-- responsible for the job. Typically, we won't run an inference job for
|
||||
-- jobs being run on a third party.
|
||||
maybe_external_third_party VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- If the job is externally enqueued, this is the ID the third-party
|
||||
-- service uses.
|
||||
maybe_external_third_party_id VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- There is an index on this column.
|
||||
-- NB: See notes on inference_category. This is becoming a problematic field. We're using
|
||||
-- this to load the container spin-up arguments and dependencies, but the former is being
|
||||
@@ -287,11 +299,13 @@ CREATE TABLE generic_inference_jobs (
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (uuid_idempotency_token),
|
||||
UNIQUE KEY (maybe_external_third_party, maybe_external_third_party_id),
|
||||
KEY index_job_type (job_type),
|
||||
KEY index_product_category (product_category),
|
||||
KEY index_inference_category (inference_category),
|
||||
KEY index_maybe_model_type_and_maybe_model_token (maybe_model_type, maybe_model_token),
|
||||
KEY index_maybe_model_type (maybe_model_type),
|
||||
KEY index_maybe_external_third_party_id (maybe_external_third_party_id),
|
||||
KEY fk_maybe_model_token (maybe_model_token),
|
||||
KEY fk_maybe_input_source_token (maybe_input_source_token),
|
||||
KEY fk_maybe_input_source_token_and_type (maybe_input_source_token, maybe_input_source_token_type),
|
||||
|
||||
@@ -16,23 +16,23 @@ path = "src/main.rs"
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
enums = { path = "../../../_types/enums" }
|
||||
enums = { path = "../../../schema/public/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
filesys = { path = "../../../lib/files/filesys" }
|
||||
hashing = { path = "../../../lib/files/hashing" }
|
||||
idempotency = { path = "../../../lib/idempotency" }
|
||||
mimetypes = { path = "../../../lib/files/mimetypes" }
|
||||
mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
mysql_queries = { path = "../../../schema/database/mysql_queries" }
|
||||
storyteller_root = { path = "../../../lib/storyteller_root" }
|
||||
tokens = { path = "../../../_types/tokens" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
tokens = { path = "../../../schema/public/tokens" }
|
||||
|
||||
# External
|
||||
log = "0.4.14"
|
||||
|
||||
@@ -16,20 +16,20 @@ path = "src/main.rs"
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
enums = { path = "../../../_types/enums" }
|
||||
enums = { path = "../../../schema/public/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
filesys = { path = "../../../lib/files/filesys" }
|
||||
mimetypes = { path = "../../../lib/files/mimetypes" }
|
||||
mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
tokens = { path = "../../../_types/tokens" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
mysql_queries = { path = "../../../schema/database/mysql_queries" }
|
||||
tokens = { path = "../../../schema/public/tokens" }
|
||||
|
||||
# External
|
||||
clap = "4.4.8"
|
||||
|
||||
@@ -16,14 +16,13 @@ path = "src/main.rs"
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
errors = { path = "../../../lib/errors" }
|
||||
web_scrapers = { path = "../../../lib/web_scrapers" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
|
||||
# External
|
||||
clap = "4.4.8"
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
|
||||
@@ -17,15 +17,15 @@ normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
datetimes = { path = "../../../lib/data/datetimes" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
enums = { path = "../../../_types/enums" }
|
||||
enums = { path = "../../../schema/public/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
tokens = { path = "../../../_types/tokens" }
|
||||
mysql_queries = { path = "../../../schema/database/mysql_queries" }
|
||||
tokens = { path = "../../../schema/public/tokens" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.86"
|
||||
|
||||
@@ -16,20 +16,16 @@ path = "src/main.rs"
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.86"
|
||||
# TODO/NB: Commenting out clap4 as it prevents windows builds
|
||||
# NB: clap4 = { package = "clap", version = "4.0" }
|
||||
dotenv = "0.15.0"
|
||||
log = "0.4.14"
|
||||
|
||||
# Serialization
|
||||
serde = "1.0.125"
|
||||
serde_derive = "1.0.125"
|
||||
|
||||
@@ -16,17 +16,17 @@ path = "src/main.rs"
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
enums = { path = "../../../_types/enums" }
|
||||
enums = { path = "../../../schema/public/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
mysql_queries = { path = "../../../schema/database/mysql_queries" }
|
||||
storyteller_root = { path = "../../../lib/storyteller_root" }
|
||||
tokens = { path = "../../../_types/tokens" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
tokens = { path = "../../../schema/public/tokens" }
|
||||
|
||||
# External
|
||||
clap = "4.4.8"
|
||||
|
||||
@@ -16,20 +16,20 @@ path = "src/main.rs"
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
elasticsearch_schema = { path = "../../../lib/elasticsearch_schema" }
|
||||
enums = { path = "../../../_types/enums" }
|
||||
elasticsearch_schema = { path = "../../../schema/database/elasticsearch_schema" }
|
||||
enums = { path = "../../../schema/public/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
mysql_queries = { path = "../../../schema/database/mysql_queries" }
|
||||
primitives = { path = "../../../lib/data/primitives" }
|
||||
storyteller_root = { path = "../../../lib/storyteller_root" }
|
||||
tokens = { path = "../../../_types/tokens" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
tokens = { path = "../../../schema/public/tokens" }
|
||||
|
||||
# External
|
||||
clap = "4.4.8"
|
||||
|
||||
@@ -16,14 +16,14 @@ path = "src/main.rs"
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile yytimes)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
|
||||
# Workspace hack (faster compile yytimes)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
|
||||
# External
|
||||
log = "0.4.20"
|
||||
once_cell = "1.18.0"
|
||||
|
||||
@@ -16,13 +16,13 @@ path = "src/main.rs"
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
errors = { path = "../../../lib/errors" }
|
||||
logging = { path = "../../../lib/logging" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
|
||||
# External
|
||||
clap = { version = "4.4.8", features = ["derive"] }
|
||||
walkdir = "2.4.0"
|
||||
|
||||
@@ -16,21 +16,21 @@ path = "src/main.rs"
|
||||
normal = ["workspace-build-acceleration"]
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
bucket_paths = { path = "../../../lib/bucket_paths" }
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
enums = { path = "../../../_types/enums" }
|
||||
enums = { path = "../../../schema/public/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
filesys = { path = "../../../lib/files/filesys" }
|
||||
hashing = { path = "../../../lib/files/hashing" }
|
||||
mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
mysql_queries = { path = "../../../schema/database/mysql_queries" }
|
||||
storyteller_root = { path = "../../../lib/storyteller_root" }
|
||||
tokens = { path = "../../../_types/tokens" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../../workspace-build-acceleration" }
|
||||
tokens = { path = "../../../schema/public/tokens" }
|
||||
|
||||
# External
|
||||
clap = "4.4.8"
|
||||
|
||||
@@ -23,25 +23,25 @@ tauri-build = { version = "2.1.1", features = [] }
|
||||
workspace-build-acceleration = { workspace = true }
|
||||
|
||||
# Internal
|
||||
crockford = { workspace = true }
|
||||
artcraft_api_defs = { workspace = true }
|
||||
build_metadata = { workspace = true }
|
||||
errors = { workspace = true }
|
||||
fal_client = { workspace = true }
|
||||
filesys = { workspace = true }
|
||||
memory_caching = { workspace = true }
|
||||
idempotency = { workspace = true }
|
||||
memory_store = { workspace = true }
|
||||
mimetypes = { workspace = true }
|
||||
openai_sora_client = { workspace = true }
|
||||
primitives = { workspace = true }
|
||||
storyteller_client = { workspace = true }
|
||||
subprocess_common = { workspace = true }
|
||||
tokens = { workspace = true }
|
||||
web_base64 = { workspace = true }
|
||||
|
||||
# External
|
||||
anyhow = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
bytes = "1.10"
|
||||
bytes = "1.10.1"
|
||||
chrono = { workspace = true }
|
||||
const_format = "0.2.34"
|
||||
directories = "6.0.0"
|
||||
dirs = "6.0.0"
|
||||
futures = "0.3"
|
||||
@@ -54,12 +54,11 @@ reqwest = { workspace = true, features = ["multipart", "stream"], default-featur
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-log = "2.3.1"
|
||||
tauri-plugin-upload = "2.2.1"
|
||||
tempdir = "0.3.7"
|
||||
tempfile = "3.18.0"
|
||||
tempdir = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { version = "1.42", features = ["rt", "rt-multi-thread", "fs"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
url = { workspace = true }
|
||||
@@ -73,9 +72,7 @@ discord-rich-presence = { git = "https://github.com/vionya/discord-rich-presence
|
||||
tauri-plugin-dialog = "2"
|
||||
|
||||
[dev-dependencies]
|
||||
libloading = "0.8.6"
|
||||
serial_test = "3.2.0"
|
||||
assertor = "0.0.3"
|
||||
|
||||
[profile.dev]
|
||||
incremental = true # Compile your binary in smaller steps.
|
||||
|
||||
@@ -10,19 +10,15 @@
|
||||
"log:default",
|
||||
"upload:allow-download",
|
||||
"upload:default",
|
||||
"http:allow-fetch",
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [
|
||||
{
|
||||
"url": "https://*.storyteller.ai"
|
||||
"url": "https://*"
|
||||
},
|
||||
{
|
||||
"url": "https://*.fakeyou.com"
|
||||
}
|
||||
],
|
||||
"deny": [
|
||||
{
|
||||
"url": "https://private.tauri.app"
|
||||
"url": "http://localhost:12345"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ArtcraftError {
|
||||
AnyhowError(anyhow::Error),
|
||||
DecodeError(base64::DecodeError),
|
||||
IoError(std::io::Error),
|
||||
StorytellerError(StorytellerError),
|
||||
}
|
||||
|
||||
impl Error for ArtcraftError {}
|
||||
|
||||
impl Display for ArtcraftError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::AnyhowError(e) => write!(f, "AnyhowError: {:?}", e),
|
||||
Self::DecodeError(e) => write!(f, "DecodeError: {:?}", e),
|
||||
Self::IoError(e) => write!(f, "IoError: {:?}", e),
|
||||
Self::StorytellerError(e) => write!(f, "StorytellerError: {:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for ArtcraftError {
|
||||
fn from(value: anyhow::Error) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<base64::DecodeError> for ArtcraftError {
|
||||
fn from(value: base64::DecodeError) -> Self {
|
||||
Self::DecodeError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ArtcraftError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorytellerError> for ArtcraftError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
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::object::handle_object_artcraft::handle_object_artcraft;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
use log::{debug, error, info, warn};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use reqwest::Url;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::read_to_string;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::ops::Add;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueTextToImageRequest {
|
||||
/// Text prompt for the image generation. Required.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<EnqueueTextToImageModel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueTextToImageModel {
|
||||
#[serde(rename = "flux_1_dev")]
|
||||
Flux1Dev,
|
||||
#[serde(rename = "flux_1_schnell")]
|
||||
Flux1Schnell,
|
||||
#[deprecated(note="use `flux_pro_11_ultra` instead")]
|
||||
#[serde(rename = "flux_pro_ultra")]
|
||||
FluxProUltra,
|
||||
#[serde(rename = "flux_pro_11")]
|
||||
FluxPro11,
|
||||
#[serde(rename = "flux_pro_11_ultra")]
|
||||
FluxPro11Ultra,
|
||||
#[serde(rename = "gpt_image_1")]
|
||||
GptImage1,
|
||||
#[serde(rename = "recraft_3")]
|
||||
Recraft3,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueTextToImageSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueTextToImageSuccessResponse {}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueTextToImageErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// No Fal API key available
|
||||
NeedsFalApiKey,
|
||||
/// Fal had an API error
|
||||
FalError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_text_to_image_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueTextToImageSuccessResponse, EnqueueTextToImageErrorType, ()> {
|
||||
|
||||
info!("enqueue_text_to_image called");
|
||||
|
||||
let result = handle_request(
|
||||
&app,
|
||||
request,
|
||||
&app_data_root,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
&fal_task_queue,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueTextToImageErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match err {
|
||||
InternalImageError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueTextToImageErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
InternalImageError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueTextToImageErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(()) => {
|
||||
Ok(EnqueueTextToImageSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<(), InternalImageError> {
|
||||
|
||||
match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(EnqueueTextToImageModel::GptImage1) => {
|
||||
handle_image_sora(&app, request, sora_creds_manager, sora_task_queue).await?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
// Fall-through
|
||||
}
|
||||
};
|
||||
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
handle_image_fal(&app, request, fal_creds_manager, fal_task_queue).await?;
|
||||
} else {
|
||||
handle_image_artcraft(request, &app, app_env_configs, app_data_root, storyteller_creds_manager).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, 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;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::GenerateFlux1DevTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::GenerateFlux1SchnellTextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::GenerateFluxPro11TextToImageRequest;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::GenerateHunyuan20ImageTo3dRequest;
|
||||
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::GenerateKling16ProImageToVideoRequest;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info, warn};
|
||||
use storyteller_client::generate::image::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
|
||||
use storyteller_client::generate::image::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
|
||||
use storyteller_client::generate::object::generate_hunyuan_3d_2_0_image_to_3d::generate_hunyuan3d_2_0_image_to_3d;
|
||||
use storyteller_client::generate::video::generate_kling_16_pro_image_to_video::generate_kling_16_pro_image_to_video;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
pub async fn handle_image_artcraft(
|
||||
request: EnqueueTextToImageRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalImageError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
error!("No Artcraft credentials are set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::GenerateImage);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalImageError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
info!("Calling Artcraft text to image...");
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let mut selected_model = None;
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(EnqueueTextToImageModel::GptImage1) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: artcraft is handling sora images")));
|
||||
}
|
||||
Some(EnqueueTextToImageModel::Recraft3) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented in Artcraft")));
|
||||
}
|
||||
Some(EnqueueTextToImageModel::Flux1Dev) => {
|
||||
info!("enqueue Flux 1 Dev");
|
||||
selected_model = Some(GenerationModel::Flux1Dev);
|
||||
let request = GenerateFlux1DevTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_1_dev_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux 1 Dev text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux 1 Dev text to image generation: {:?}", err);
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(EnqueueTextToImageModel::Flux1Schnell) => {
|
||||
info!("enqueue Flux 1 Schnell");
|
||||
selected_model = Some(GenerationModel::Flux1Schnell);
|
||||
let request = GenerateFlux1SchnellTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_1_schnell_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux 1 Schnell text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux 1 Schnell text to image generation: {:?}", err);
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(EnqueueTextToImageModel::FluxPro11) => {
|
||||
info!("enqueue Flux Pro 1.1");
|
||||
selected_model = Some(GenerationModel::FluxPro11);
|
||||
let request = GenerateFluxPro11TextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_pro_11_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux Pro 1.1 text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux Pro 1.1 text to image generation: {:?}", err);
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(
|
||||
EnqueueTextToImageModel::FluxProUltra |
|
||||
EnqueueTextToImageModel::FluxPro11Ultra
|
||||
) => {
|
||||
info!("enqueue Flux Pro 1.1 Ultra");
|
||||
selected_model = Some(GenerationModel::FluxPro11Ultra);
|
||||
let request = GenerateFluxPro11UltraTextToImageRequest {
|
||||
uuid_idempotency_token,
|
||||
prompt: request.prompt,
|
||||
aspect_ratio: None,
|
||||
num_images: None,
|
||||
};
|
||||
let result = generate_flux_pro_11_ultra_text_to_image(
|
||||
&app_env_configs.storyteller_host,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Flux Pro 1.1 Ultra text to image generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Flux Pro 1.1 Ultra text to image generation: {:?}", err);
|
||||
return Err(InternalImageError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_image_fal(
|
||||
app: &AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalImageError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
error!("No FAL API key is set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::GenerateImage);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalImageError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
let api_key = fal_creds_manager.get_key_required()
|
||||
.map_err(|err| {
|
||||
error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
InternalImageError::NeedsFalApiKey
|
||||
})?;
|
||||
|
||||
let prompt = request.prompt.as_deref().unwrap_or("");
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(InternalImageError::NoModelSpecified);
|
||||
}
|
||||
Some(EnqueueTextToImageModel::GptImage1) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("wrong logic: fal is handling sora images")));
|
||||
}
|
||||
Some(
|
||||
EnqueueTextToImageModel::Flux1Dev |
|
||||
EnqueueTextToImageModel::Flux1Schnell |
|
||||
EnqueueTextToImageModel::FluxPro11
|
||||
) => {
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("not yet implemented: {:?}", request.model)));
|
||||
}
|
||||
Some(
|
||||
EnqueueTextToImageModel::FluxProUltra |
|
||||
EnqueueTextToImageModel::FluxPro11Ultra
|
||||
) => {
|
||||
info!("enqueue Flux Pro 1.1 Ultra text-to-image with prompt: {}", prompt);
|
||||
enqueue_flux_pro_11_ultra_text_to_image(FluxPro11UltraTextToImageArgs {
|
||||
prompt,
|
||||
api_key: &api_key,
|
||||
}).await
|
||||
}
|
||||
Some(EnqueueTextToImageModel::Recraft3) => {
|
||||
info!("enqueue Recraft v3 text-to-image with prompt: {}", prompt);
|
||||
enqueue_recraft3_text_to_image(Recraft3TextToImageArgs {
|
||||
prompt,
|
||||
api_key: &api_key,
|
||||
}).await
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued text to image");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(InternalImageError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue text to image: {:?}", err);
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalImageError::FalError(err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::image::internal_image_error::InternalImageError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::creds::sora_credential_set::SoraCredentialSet;
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const SORA_SIMPLE_IMAGE_GEN_TIMEOUT : Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
pub async fn handle_image_sora(
|
||||
app: &AppHandle,
|
||||
request: EnqueueTextToImageRequest,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<(), InternalImageError> {
|
||||
|
||||
let mut creds = sora_creds_manager
|
||||
.get_credentials_required()
|
||||
.map_err(|err| {
|
||||
error!("EnqueueTextToImage Sora creds required: {:?}", err);
|
||||
InternalImageError::NeedsFalApiKey
|
||||
})?;
|
||||
|
||||
let credential_updated = maybe_upgrade_or_renew_session(&mut creds)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upgrade or renew session: {:?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
if credential_updated {
|
||||
info!("Storing updated credentials");
|
||||
sora_creds_manager.set_credentials(&creds)?;
|
||||
}
|
||||
|
||||
let prompt = request.prompt.as_deref().unwrap_or("");
|
||||
|
||||
let result =
|
||||
simple_image_gen_with_session_auto_renew(SimpleImageGenAutoRenewRequest {
|
||||
prompt: prompt.to_string(),
|
||||
num_images: NumImages::One,
|
||||
image_size: ImageSize::Square,
|
||||
credentials: &creds,
|
||||
request_timeout: Some(SORA_SIMPLE_IMAGE_GEN_TIMEOUT),
|
||||
}).await;
|
||||
|
||||
let (response, maybe_new_creds) =
|
||||
match result {
|
||||
Ok((response, maybe_new_creds)) => (response, maybe_new_creds),
|
||||
Err(err) => {
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalImageError::SoraError(err));
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(new_creds) = maybe_new_creds {
|
||||
info!("Storing updated credentials.");
|
||||
sora_creds_manager.set_credentials(&new_creds)?;
|
||||
}
|
||||
|
||||
info!("New Sora Task ID: {:?} ", response.task_id);
|
||||
|
||||
sora_task_queue.insert(&response.task_id)?;
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalImageError {
|
||||
NoModelSpecified,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
FalError(FalErrorPlus),
|
||||
SoraError(SoraError),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InternalImageError {
|
||||
fn from(value: AnyhowError) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FalErrorPlus> for InternalImageError {
|
||||
fn from(value: FalErrorPlus) -> Self {
|
||||
Self::FalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SoraError> for InternalImageError {
|
||||
fn from(value: SoraError) -> Self {
|
||||
Self::SoraError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorytellerError> for InternalImageError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for InternalImageError {
|
||||
fn from(value: DecodeError) -> Self {
|
||||
Self::DecodeError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for InternalImageError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InternalImageError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod enqueue_text_to_image_command;
|
||||
pub mod handle_image_artcraft;
|
||||
pub mod handle_image_fal;
|
||||
pub mod handle_image_sora;
|
||||
pub mod internal_image_error;
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod image;
|
||||
pub mod object;
|
||||
pub mod video;
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
use crate::core::commands::enqueue::object::handle_object_artcraft::handle_object_artcraft;
|
||||
use crate::core::commands::enqueue::object::handle_object_fal::handle_object_fal;
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
use log::{debug, error, info, warn};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use reqwest::Url;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::read_to_string;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::ops::Add;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageTo3dObjectRequest {
|
||||
/// Image media file; the image to remove the background from.
|
||||
/// TODO: In the future we may support base64 images, URLs, or file paths here.
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<EnqueueImageTo3dObjectModel>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageTo3dObjectModel {
|
||||
#[deprecated(note="Use `hunyuan_3d_2_0` instead")]
|
||||
#[serde(rename = "hunyuan_3d_2")]
|
||||
Hunyuan3d2,
|
||||
#[serde(rename = "hunyuan_3d_2_0")]
|
||||
Hunyuan3d2_0,
|
||||
#[serde(rename = "hunyuan_3d_2_1")]
|
||||
Hunyuan3d2_1,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueImageTo3dObjectSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueImageTo3dObjectSuccessResponse {}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageTo3dObjectErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// No Fal API key available
|
||||
NeedsFalApiKey,
|
||||
/// Fal had an API error
|
||||
FalError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_image_to_3d_object_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueImageTo3dObjectSuccessResponse, EnqueueImageTo3dObjectErrorType, ()> {
|
||||
|
||||
info!("enqueue_image_to_3d_object_command called");
|
||||
|
||||
let result = handle_request(
|
||||
&app,
|
||||
request,
|
||||
&app_data_root,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueImageTo3dObjectErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match err {
|
||||
InternalObjectError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
InternalObjectError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
InternalObjectError::NeedsStorytellerCredentials => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageTo3dObjectErrorType::NeedsStorytellerCredentials;
|
||||
error_message = "You need to be logged into Artcraft.";
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(()) => {
|
||||
Ok(EnqueueImageTo3dObjectSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
handle_object_fal(&app, app_data_root, request, fal_creds_manager, fal_task_queue).await?;
|
||||
} else {
|
||||
handle_object_artcraft(request, &app, app_data_root, storyteller_creds_manager).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::GenerateHunyuan20ImageTo3dRequest;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info, warn};
|
||||
use storyteller_client::generate::object::generate_hunyuan_3d_2_0_image_to_3d::generate_hunyuan3d_2_0_image_to_3d;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, State};
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::GenerateHunyuan21ImageTo3dRequest;
|
||||
use storyteller_client::generate::object::generate_hunyuan_3d_2_1_image_to_3d::generate_hunyuan3d_2_1_image_to_3d;
|
||||
|
||||
pub async fn handle_object_artcraft(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
error!("No Artcraft credentials are set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::ImageTo3d);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalObjectError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalObjectError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
|
||||
info!("Calling Artcraft image to 3d ...");
|
||||
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2 |
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0
|
||||
) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 2.0");
|
||||
let request = GenerateHunyuan20ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
};
|
||||
let result = generate_hunyuan3d_2_0_image_to_3d(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Hunyuan 3D 2.0");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Hunyuan 3D 2.0: {:?}", err);
|
||||
return Err(InternalObjectError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(EnqueueImageTo3dObjectModel::Hunyuan3d2_1) => {
|
||||
info!("enqueue Artcraft Hunyuan 3D 2.1");
|
||||
let request = GenerateHunyuan21ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
};
|
||||
let result = generate_hunyuan3d_2_1_image_to_3d(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Hunyuan 3D 2.1");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Hunyuan 3D 2.1: {:?}", err);
|
||||
return Err(InternalObjectError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued 3D generation");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
//if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
// error!("Failed to enqueue task: {:?}", err);
|
||||
// return Err(InternalObjectError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
//}
|
||||
|
||||
// match result {
|
||||
// Ok(enqueued) => {
|
||||
// }
|
||||
// Err(err) => {
|
||||
// error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
//
|
||||
// let event = GenerationEnqueueFailureEvent {
|
||||
// action: GenerationAction::ImageTo3d,
|
||||
// service: GenerationServiceProvider::Fal,
|
||||
// model: None,
|
||||
// reason: None,
|
||||
// };
|
||||
//
|
||||
// if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
// }
|
||||
//
|
||||
// return Err(InternalObjectError::FalError(err));
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::object::internal_object_error::InternalObjectError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_object_fal(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalObjectError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
error!("No FAL API key is set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::ImageTo3d);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalObjectError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalObjectError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
temp_download = download_media_file_to_temp_dir(&app_data_root, &media_token).await?;
|
||||
|
||||
} else {
|
||||
return Err(InternalObjectError::AnyhowError(anyhow!("No image media token provided")));
|
||||
}
|
||||
|
||||
info!("Calling FAL image to 3d ...");
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(InternalObjectError::NoModelSpecified);
|
||||
}
|
||||
Some(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0 |
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2
|
||||
) => {
|
||||
info!("enqueue Hunyuan 3D 2.0");
|
||||
enqueue_hunyuan2_image_to_3d(Hunyuan2Args {
|
||||
image_path: filename,
|
||||
api_key: &api_key,
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
return Err(InternalObjectError::AnyhowError(anyhow!("Wrong model specified: {:?}", request.model)));
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued text to image");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(InternalObjectError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalObjectError::FalError(err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalObjectError {
|
||||
NoModelSpecified,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InternalObjectError {
|
||||
fn from(value: AnyhowError) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FalErrorPlus> for InternalObjectError {
|
||||
fn from(value: FalErrorPlus) -> Self {
|
||||
Self::FalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorytellerError> for InternalObjectError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for InternalObjectError {
|
||||
fn from(value: DecodeError) -> Self {
|
||||
Self::DecodeError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for InternalObjectError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InternalObjectError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod enqueue_image_to_3d_object_command;
|
||||
pub mod handle_object_artcraft;
|
||||
pub mod handle_object_fal;
|
||||
pub mod internal_object_error;
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
use crate::core::commands::enqueue::object::handle_object_artcraft::handle_object_artcraft;
|
||||
use crate::core::commands::enqueue::object::handle_object_fal::handle_object_fal;
|
||||
use crate::core::commands::enqueue::video::handle_video_artcraft::handle_video_artcraft;
|
||||
use crate::core::commands::enqueue::video::handle_video_fal::handle_video_fal;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::model::video_models::VideoModel;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
use log::{debug, error, info, warn};
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::creds::credential_migration::CredentialMigrationRef;
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use openai_sora_client::sora_error::SoraError;
|
||||
use reqwest::Url;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::read_to_string;
|
||||
use std::io::{Cursor, Read, Write};
|
||||
use std::ops::Add;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueImageToVideoRequest {
|
||||
/// Image media file; the image to remove the background from.
|
||||
/// TODO: In the future we may support base64 images, URLs, or file paths here.
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<VideoModel>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueImageToVideoSuccessResponse {
|
||||
}
|
||||
|
||||
impl SerializeMarker for EnqueueImageToVideoSuccessResponse {}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EnqueueImageToVideoErrorType {
|
||||
/// Caller didn't specify a model
|
||||
ModelNotSpecified,
|
||||
/// Generic server error
|
||||
ServerError,
|
||||
/// No Fal API key available
|
||||
NeedsFalApiKey,
|
||||
/// Fal had an API error
|
||||
FalError,
|
||||
/// Needs to be logged into Artcraft
|
||||
NeedsStorytellerCredentials,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_image_to_video_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueImageToVideoSuccessResponse, EnqueueImageToVideoErrorType, ()> {
|
||||
|
||||
info!("enqueue_image_to_video_command called");
|
||||
|
||||
let result = handle_request(
|
||||
&app,
|
||||
request,
|
||||
&app_data_root,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = EnqueueImageToVideoErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match err {
|
||||
InternalVideoError::NoModelSpecified => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = EnqueueImageToVideoErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for image generation";
|
||||
}
|
||||
InternalVideoError::NeedsFalApiKey => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageToVideoErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
},
|
||||
InternalVideoError::NeedsStorytellerCredentials => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = EnqueueImageToVideoErrorType::NeedsStorytellerCredentials;
|
||||
error_message = "You need to be logged into Artcraft.";
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
Ok(()) => {
|
||||
Ok(EnqueueImageToVideoSuccessResponse {}.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn handle_request(
|
||||
app: &AppHandle,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
handle_video_fal(&app, app_data_root, request, fal_creds_manager, fal_task_queue).await?;
|
||||
} else {
|
||||
handle_video_artcraft(request, &app, app_data_root, storyteller_creds_manager).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::model::video_models::VideoModel;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::GenerateHunyuan20ImageTo3dRequest;
|
||||
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::{GenerateKling16ProAspectRatio, GenerateKling16ProImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_kling_2_1_master_image_to_video::{GenerateKling21MasterAspectRatio, GenerateKling21MasterImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_kling_2_1_pro_image_to_video::{GenerateKling21ProAspectRatio, GenerateKling21ProImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteImageToVideoRequest;
|
||||
use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::{GenerateVeo2AspectRatio, GenerateVeo2ImageToVideoRequest};
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::Kling16ProAspectRatio;
|
||||
use fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::Veo2AspectRatio;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info, warn};
|
||||
use storyteller_client::generate::object::generate_hunyuan_3d_2_0_image_to_3d::generate_hunyuan3d_2_0_image_to_3d;
|
||||
use storyteller_client::generate::video::generate_kling_16_pro_image_to_video::generate_kling_16_pro_image_to_video;
|
||||
use storyteller_client::generate::video::generate_kling_21_master_image_to_video::generate_kling_21_master_image_to_video;
|
||||
use storyteller_client::generate::video::generate_kling_21_pro_image_to_video::generate_kling_21_pro_image_to_video;
|
||||
use storyteller_client::generate::video::generate_seedance_1_0_lite_image_to_video::generate_seedance_1_0_lite_image_to_video;
|
||||
use storyteller_client::generate::video::generate_veo_2_image_to_video::generate_veo_2_image_to_video;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
pub async fn handle_video_artcraft(
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
|
||||
let creds = match storyteller_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
error!("No Artcraft credentials are set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_artcraft_credentials(GenerationAction::GenerateVideo);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalVideoError::NeedsStorytellerCredentials);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalVideoError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
|
||||
info!("Calling Artcraft image to video...");
|
||||
|
||||
|
||||
let uuid_idempotency_token = generate_random_uuid();
|
||||
|
||||
let mut selected_model = None;
|
||||
|
||||
let job_token = match request.model {
|
||||
None => {
|
||||
return Err(InternalVideoError::NoModelSpecified);
|
||||
}
|
||||
Some(VideoModel::Kling16Pro) => {
|
||||
info!("enqueue Kling 1.6 Pro with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Kling1_6);
|
||||
let request = GenerateKling16ProImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateKling16ProAspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_16_pro_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Kling 1.6 video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Kling 1.6 video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Kling21Master) => {
|
||||
info!("enqueue Kling 2.1 Master with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Kling21Master);
|
||||
let request = GenerateKling21MasterImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateKling21MasterAspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_21_master_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Kling 2.1 Master video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Kling 2.1 Master video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Kling21Pro) => {
|
||||
|
||||
info!("enqueue Kling 2.1 Pro with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Kling21Pro);
|
||||
let request = GenerateKling21ProImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateKling21ProAspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_21_pro_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Kling 2.1 Pro video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Kling 2.1 Pro video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Seedance10Lite) => {
|
||||
info!("enqueue Seedance 1.0 Lite with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Seedance10Lite);
|
||||
let request = GenerateSeedance10LiteImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
prompt: None,
|
||||
resolution: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_seedance_1_0_lite_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Seedance 1.0 Lite video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Seedance 1.0 Lite video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(VideoModel::Veo2) => {
|
||||
info!("enqueue Veo 2 with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Veo2);
|
||||
let request = GenerateVeo2ImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
aspect_ratio: Some(GenerateVeo2AspectRatio::WideSixteenNine),
|
||||
prompt: None,
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_veo_2_image_to_video(
|
||||
&ApiHost::Storyteller,
|
||||
Some(&creds),
|
||||
request,
|
||||
).await;
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Artcraft Veo 2 video generation");
|
||||
enqueued.inference_job_token
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Artcraft Veo 2 video generation: {:?}", err);
|
||||
return Err(InternalVideoError::StorytellerError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
info!("Successfully enqueued video generation");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
model: selected_model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
//if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
// error!("Failed to enqueue task: {:?}", err);
|
||||
// return Err(InternalVideoError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
//}
|
||||
|
||||
// match result {
|
||||
// Ok(enqueued) => {
|
||||
// }
|
||||
// Err(err) => {
|
||||
// error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
//
|
||||
// let event = GenerationEnqueueFailureEvent {
|
||||
// action: GenerationAction::ImageTo3d,
|
||||
// service: GenerationServiceProvider::Fal,
|
||||
// model: None,
|
||||
// reason: None,
|
||||
// };
|
||||
//
|
||||
// if let Err(err) = event.send(app) {
|
||||
// error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
// }
|
||||
//
|
||||
// return Err(InternalVideoError::FalError(err));
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::{EnqueueTextToImageModel, EnqueueTextToImageRequest};
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::video::internal_video_error::InternalVideoError;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::model::video_models::VideoModel;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::image_gen::enqueue_recraft3_text_to_image::{enqueue_recraft3_text_to_image, Recraft3TextToImageArgs};
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::{enqueue_kling_16_pro_image_to_video, Kling16ProArgs, Kling16ProAspectRatio, Kling16ProDuration};
|
||||
use log::{error, info, warn};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_video_fal(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<(), InternalVideoError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
error!("No FAL API key is set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::GenerateVideo);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalVideoError::NeedsFalApiKey);
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalVideoError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
temp_download = download_media_file_to_temp_dir(&app_data_root, &media_token).await?;
|
||||
|
||||
} else {
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("No image media token provided")));
|
||||
}
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let mut selected_model = None;
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(InternalVideoError::NoModelSpecified);
|
||||
}
|
||||
Some(VideoModel::Kling16Pro) => {
|
||||
info!("enqueue Kling 1.6 image to video with Kling API");
|
||||
selected_model = Some(GenerationModel::Kling1_6);
|
||||
enqueue_kling_16_pro_image_to_video(Kling16ProArgs {
|
||||
image_path: filename,
|
||||
api_key: &api_key,
|
||||
duration: Kling16ProDuration::Default,
|
||||
aspect_ratio: Kling16ProAspectRatio::WideSixteenNine,
|
||||
prompt: "",
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("Unsupported model: {:?}", request.model)));
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Kling 1.6 image to video");
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: selected_model,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(InternalVideoError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue image to video: {:?}", err);
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: selected_model,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(InternalVideoError::FalError(err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalVideoError {
|
||||
NoModelSpecified,
|
||||
NeedsFalApiKey,
|
||||
NeedsSoraCredentials,
|
||||
NeedsStorytellerCredentials,
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InternalVideoError {
|
||||
fn from(value: AnyhowError) -> Self {
|
||||
Self::AnyhowError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FalErrorPlus> for InternalVideoError {
|
||||
fn from(value: FalErrorPlus) -> Self {
|
||||
Self::FalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorytellerError> for InternalVideoError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for InternalVideoError {
|
||||
fn from(value: DecodeError) -> Self {
|
||||
Self::DecodeError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for InternalVideoError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InternalVideoError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod enqueue_image_to_video_command;
|
||||
pub mod handle_video_artcraft;
|
||||
pub mod handle_video_fal;
|
||||
pub mod internal_video_error;
|
||||
@@ -0,0 +1,40 @@
|
||||
use crate::core::commands::response::shorthand::InfallibleResponse;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::os_platform::OsPlatform;
|
||||
use crate::services::fal::commands::fal_background_removal_command::FalBackgroundRemovalSuccessResponse;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use chrono::{DateTime, Utc};
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::ImageReader;
|
||||
use log::info;
|
||||
use serde_derive::Serialize;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BuildInfoResponse {
|
||||
pub build_timestamp: DateTime<Utc>,
|
||||
pub git_commit_id: Option<String>,
|
||||
pub git_commit_short_id: Option<String>,
|
||||
pub git_commit_timestamp: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl SerializeMarker for BuildInfoResponse {}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_build_info_command() -> InfallibleResponse<BuildInfoResponse> {
|
||||
info!("get_build_info_command called...");
|
||||
|
||||
let build_timestamp = build_metadata::build_timestamp();
|
||||
let git_commit_id = build_metadata::git_commit_id();
|
||||
let git_commit_short_id = build_metadata::git_commit_short_id();
|
||||
let git_commit_timestamp = build_metadata::git_commit_timestamp();
|
||||
|
||||
BuildInfoResponse {
|
||||
build_timestamp,
|
||||
git_commit_id: git_commit_id.map(|s| s.to_string()),
|
||||
git_commit_short_id: git_commit_short_id.map(|s| s.to_string()),
|
||||
git_commit_timestamp,
|
||||
}.into()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod app_preferences;
|
||||
pub mod command_response_wrapper;
|
||||
pub mod enqueue;
|
||||
pub mod flip_image;
|
||||
pub mod get_build_info_command;
|
||||
pub mod platform_info_command;
|
||||
pub mod response;
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::core::commands::response::shorthand::InfallibleResponse;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::os_platform::OsPlatform;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
@@ -13,6 +15,8 @@ pub struct PlatformInfoResponse {
|
||||
pub webview_runtime: WebviewRuntime,
|
||||
}
|
||||
|
||||
impl SerializeMarker for PlatformInfoResponse {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DetectedOs {
|
||||
@@ -36,7 +40,7 @@ pub enum WebviewRuntime {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn platform_info_command() -> Result<PlatformInfoResponse, String> {
|
||||
pub fn platform_info_command() -> InfallibleResponse<PlatformInfoResponse> {
|
||||
info!("platform_info_command called...");
|
||||
|
||||
let os_platform = match OsPlatform::maybe_get() {
|
||||
@@ -55,9 +59,9 @@ pub fn platform_info_command() -> Result<PlatformInfoResponse, String> {
|
||||
DetectedOs::Unknown => WebviewRuntime::Unknown,
|
||||
};
|
||||
|
||||
Ok(PlatformInfoResponse {
|
||||
PlatformInfoResponse {
|
||||
os_platform,
|
||||
webview_runtime,
|
||||
})
|
||||
}.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
use serde::Serialize;
|
||||
|
||||
/// Statuses for errorful commands.
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CommandErrorStatus {
|
||||
BadRequest, // 400
|
||||
Unauthorized, // 401
|
||||
TooManyRequests, // 429
|
||||
ServerError, // 500
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct CommandErrorResponseWrapper<ErrType: Serialize, ErrPayload: Serialize> {
|
||||
/// Both "success" and "error" types have a `status` field for the frontend.
|
||||
/// We constrain the value types for successes and failures.
|
||||
pub status: CommandErrorStatus,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error_message: Option<String>,
|
||||
|
||||
/// Represents a "type" of error, preferably an enum without payloads.
|
||||
/// Preferably this serializes to a string.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error_type: Option<ErrType>,
|
||||
|
||||
/// Represents a full error details object or payload.
|
||||
/// This could be a structure, message, or whatever.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error_details: Option<ErrPayload>,
|
||||
}
|
||||
|
||||
impl From<&str> for CommandErrorResponseWrapper<(), ()> {
|
||||
fn from(value: &str) -> Self {
|
||||
CommandErrorResponseWrapper {
|
||||
status: CommandErrorStatus::BadRequest, // NB: Default to 500 error.
|
||||
error_message: Some(value.to_string()),
|
||||
error_type: None,
|
||||
error_details: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod failure_response_wrapper;
|
||||
pub mod shorthand;
|
||||
pub mod success_response_wrapper;
|
||||
@@ -0,0 +1,32 @@
|
||||
use crate::core::commands::response::failure_response_wrapper::CommandErrorResponseWrapper;
|
||||
use crate::core::commands::response::success_response_wrapper::CommandSuccessResponseWrapper;
|
||||
|
||||
// TODO(needs command response refactor):
|
||||
// - get_app_preferences_command
|
||||
// - update_app_preferences_command
|
||||
// - flip_image
|
||||
// - check_sora_session_command
|
||||
// - open_sora_login_command
|
||||
// - sora_image_generation_command
|
||||
|
||||
// TODO(already updated, need to update frontend):
|
||||
// - platform_info_command
|
||||
|
||||
// TODO(simplify error types):
|
||||
// - enqueue_text_to_image_command
|
||||
// - fal_background_removal_command
|
||||
// - fal_hunyuan_image_to_3d_command
|
||||
// - fal_kling_image_to_video_command
|
||||
// - get_fal_api_key_command
|
||||
// - set_fal_api_key_command
|
||||
// - sora_image_remix_command
|
||||
|
||||
/// Easy to use infallible response.
|
||||
pub type InfallibleResponse<SuccessPayload> = CommandSuccessResponseWrapper<SuccessPayload>;
|
||||
|
||||
/// Easy to use Result<T, E> type.
|
||||
pub type Response<SuccessPayload, ErrType, ErrPayload> =
|
||||
Result<CommandSuccessResponseWrapper<SuccessPayload>, CommandErrorResponseWrapper<ErrType, ErrPayload>>;
|
||||
|
||||
/// No inner payloads for this type. Just strings as messages.
|
||||
pub type SimpleResponse = Response<(), (), ()>;
|
||||
+12
-58
@@ -1,13 +1,4 @@
|
||||
use serde::Serialize;
|
||||
use std::error::Error;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Easy to use Result<T, E> type.
|
||||
pub type CommandResult<SuccessPayload, ErrType, ErrPayload> =
|
||||
Result<CommandSuccessResponseWrapper<SuccessPayload>, CommandErrorResponseWrapper<ErrType, ErrPayload>>;
|
||||
|
||||
/// No inner payloads for this type. Just strings as messages.
|
||||
pub type SimpleCommandResult = CommandResult<(), (), ()>;
|
||||
|
||||
/// Statuses for successful commands.
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
@@ -16,49 +7,30 @@ pub enum CommandSuccessStatus {
|
||||
Success, // 200
|
||||
}
|
||||
|
||||
/// Statuses for errorful commands.
|
||||
#[derive(Serialize, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CommandErrorStatus {
|
||||
BadRequest, // 400
|
||||
Unauthorized, // 401
|
||||
TooManyRequests, // 429
|
||||
ServerError, // 500
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct CommandSuccessResponseWrapper<T: Serialize> {
|
||||
/// Both "success" and "error" types have a `status` field for the frontend.
|
||||
/// We constrain the value types for successes and failures.
|
||||
pub status: CommandSuccessStatus,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub success_message: Option<String>,
|
||||
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub payload: Option<T>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct CommandErrorResponseWrapper<ErrType: Serialize, ErrPayload: Serialize> {
|
||||
/// Both "success" and "error" types have a `status` field for the frontend.
|
||||
pub status: CommandErrorStatus,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error_message: Option<String>,
|
||||
|
||||
/// Represents a "type" of error, preferably an enum without payloads.
|
||||
/// Preferably this serializes to a string.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error_type: Option<ErrType>,
|
||||
|
||||
/// Represents a full error details object or payload.
|
||||
/// This could be a structure, message, or whatever.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error_details: Option<ErrPayload>,
|
||||
}
|
||||
|
||||
/// NB: This is a hack because of `()` (a.k.a. the "unit" type) and `Serialize` trait coherence and overlap.
|
||||
/// We cannot currently implement a trait for `()` and generic `Serialize` because `()` itself implements
|
||||
/// `Serialize`. Rust's `"specialization"`, which would let us account for this, is an unstable, nightly-only
|
||||
/// feature. So instead we manually mark all types other than `()` with `SerializeMarker` as a hack to
|
||||
/// distinguish the two.
|
||||
pub trait SerializeMarker : Serialize {}
|
||||
|
||||
impl CommandSuccessResponseWrapper<()> {
|
||||
pub fn empty_success() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
pub fn empty() -> Self {
|
||||
CommandSuccessResponseWrapper {
|
||||
status: CommandSuccessStatus::Success,
|
||||
@@ -68,13 +40,6 @@ impl CommandSuccessResponseWrapper<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// NB: This is a hack because of `()` (a.k.a. the "unit" type) and `Serialize` trait coherence and overlap.
|
||||
/// We cannot currently implement a trait for `()` and generic `Serialize` because `()` itself implements
|
||||
/// `Serialize`. Rust's `"specialization"`, which would let us account for this, is an unstable, nightly-only
|
||||
/// feature. So instead we manually mark all types other than `()` with `SerializeMarker` as a hack to
|
||||
/// distinguish the two.
|
||||
pub trait SerializeMarker : Serialize {}
|
||||
|
||||
// NB: () is Serialize! So we can't broadly implement just `T: Serialize`.
|
||||
impl From<()> for CommandSuccessResponseWrapper<()> {
|
||||
fn from(_: ()) -> Self {
|
||||
@@ -108,20 +73,9 @@ impl From<&str> for CommandSuccessResponseWrapper<String> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for CommandErrorResponseWrapper<(), ()> {
|
||||
fn from(value: &str) -> Self {
|
||||
CommandErrorResponseWrapper {
|
||||
status: CommandErrorStatus::BadRequest, // NB: Default to 500 error.
|
||||
error_message: Some(value.to_string()),
|
||||
error_type: None,
|
||||
error_details: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_empty_tuple_serialize() {
|
||||
let response = super::CommandSuccessResponseWrapper::<()>::from(());
|
||||
@@ -0,0 +1,51 @@
|
||||
use crate::core::events::sendable_event_error::SendableEventError;
|
||||
use log::info;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
// Tagged enums here are waaay too "Rustic"
|
||||
// #[derive(Clone, Serialize)]
|
||||
// #[serde(rename_all = "camelCase", rename_all_fields = "camelCase", tag = "event", content = "data")]
|
||||
// pub enum BasicEvent<T: Serialize> {
|
||||
// Success(T),
|
||||
// Failure(T),
|
||||
// }
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BasicEventStatus {
|
||||
Success,
|
||||
Failure,
|
||||
}
|
||||
|
||||
/// Wrap the event with "status" and "data" fields.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
struct BasicEventWrapper<T: Serialize + Debug> {
|
||||
status: BasicEventStatus,
|
||||
data: T,
|
||||
}
|
||||
|
||||
pub trait BasicSendableEvent : Clone + Debug + Serialize {
|
||||
/// This is the name of the event that the frontend subscribes to.
|
||||
const FRONTEND_EVENT_NAME: &'static str;
|
||||
|
||||
/// This is the status of the event: Success, Failure, etc.
|
||||
const EVENT_STATUS: BasicEventStatus;
|
||||
|
||||
/// Default implementation of send().
|
||||
/// This serializes and sends the event to the frontend.
|
||||
fn send(&self, app: &AppHandle) -> Result<(), SendableEventError> {
|
||||
let wrapped = BasicEventWrapper {
|
||||
status: Self::EVENT_STATUS,
|
||||
data: self.clone(),
|
||||
};
|
||||
info!("Sending event to frontend: {} ; payload = {:?}", Self::FRONTEND_EVENT_NAME, wrapped);
|
||||
let result = app.emit(Self::FRONTEND_EVENT_NAME, wrapped);
|
||||
if let Err(err) = result {
|
||||
return Err(SendableEventError::from(err));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerationServiceProvider {
|
||||
Artcraft,
|
||||
Fal,
|
||||
Sora,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerationModel {
|
||||
#[serde(rename = "flux_1_dev")]
|
||||
Flux1Dev,
|
||||
#[serde(rename = "flux_1_schnell")]
|
||||
Flux1Schnell,
|
||||
#[serde(rename = "flux_pro_1.1")]
|
||||
FluxPro11,
|
||||
#[serde(rename = "flux_pro_1.1_ultra")]
|
||||
FluxPro11Ultra,
|
||||
#[serde(rename = "kling_1.6")]
|
||||
Kling1_6,
|
||||
#[serde(rename = "kling_2.0")]
|
||||
Kling2_0,
|
||||
#[serde(rename = "kling_2.1_master")]
|
||||
Kling21Master,
|
||||
#[serde(rename = "kling_2.1_pro")]
|
||||
Kling21Pro,
|
||||
Sora,
|
||||
#[serde(rename = "seedance_1.0_lite")]
|
||||
Seedance10Lite,
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerationAction {
|
||||
GenerateImage,
|
||||
GenerateVideo,
|
||||
RemoveBackground,
|
||||
#[serde(rename = "image_to_3d")]
|
||||
ImageTo3d,
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct GenerationCompleteEvent {
|
||||
pub action: Option<GenerationAction>,
|
||||
pub service: GenerationServiceProvider,
|
||||
pub model: Option<GenerationModel>,
|
||||
}
|
||||
|
||||
impl BasicSendableEvent for GenerationCompleteEvent {
|
||||
const FRONTEND_EVENT_NAME: &'static str = "generation-complete-event";
|
||||
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success;
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct GenerationEnqueueFailureEvent {
|
||||
pub action: GenerationAction,
|
||||
pub service: GenerationServiceProvider,
|
||||
pub model: Option<GenerationModel>,
|
||||
|
||||
/// User-facing reason for the failure, if available.
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
impl BasicSendableEvent for GenerationEnqueueFailureEvent {
|
||||
const FRONTEND_EVENT_NAME: &'static str = "generation-enqueue-failure-event";
|
||||
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Failure;
|
||||
}
|
||||
|
||||
impl GenerationEnqueueFailureEvent {
|
||||
pub fn no_artcraft_credentials(action: GenerationAction) -> Self {
|
||||
Self {
|
||||
action,
|
||||
service: GenerationServiceProvider::Artcraft,
|
||||
model: None,
|
||||
reason: Some("Please log into Artcraft.".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_fal_api_key(action: GenerationAction) -> Self {
|
||||
Self {
|
||||
action,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
reason: Some("No FAL API key is set. Configure this in your settings.".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct GenerationEnqueueSuccessEvent {
|
||||
pub action: GenerationAction,
|
||||
pub service: GenerationServiceProvider,
|
||||
pub model: Option<GenerationModel>,
|
||||
}
|
||||
|
||||
impl BasicSendableEvent for GenerationEnqueueSuccessEvent {
|
||||
const FRONTEND_EVENT_NAME: &'static str = "generation-enqueue-success-event";
|
||||
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct GenerationFailedEvent {
|
||||
pub action: GenerationAction,
|
||||
pub service: GenerationServiceProvider,
|
||||
pub model: Option<GenerationModel>,
|
||||
|
||||
/// User-facing reason for the failure, if available.
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
impl BasicSendableEvent for GenerationFailedEvent {
|
||||
const FRONTEND_EVENT_NAME: &'static str = "generation-failed-event";
|
||||
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Failure;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod common;
|
||||
pub mod generation_enqueue_failure_event;
|
||||
pub mod generation_enqueue_success_event;
|
||||
pub mod generation_complete_event;
|
||||
pub mod generation_failed_event;
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod notification_event;
|
||||
pub mod basic_sendable_event_trait;
|
||||
pub mod generation_events;
|
||||
pub mod sendable_event_error;
|
||||
pub mod sendable_event_trait;
|
||||
@@ -1,45 +0,0 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NotificationModelType {
|
||||
ImageSegmentation,
|
||||
TextEncoder,
|
||||
TextTokenizer,
|
||||
Unet,
|
||||
Vae,
|
||||
Json,
|
||||
ClipEncoder,
|
||||
FluxModel,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NotificationEvent<'a> {
|
||||
ModelDownloadStarted {
|
||||
model_name: &'a str,
|
||||
model_type: NotificationModelType,
|
||||
},
|
||||
ModelDownloadProgress {
|
||||
model_name: &'a str,
|
||||
model_type: NotificationModelType,
|
||||
currently_downloaded_bytes: usize,
|
||||
total_file_bytes: usize,
|
||||
},
|
||||
ModelDownloadComplete {
|
||||
model_name: &'a str,
|
||||
model_type: NotificationModelType,
|
||||
},
|
||||
ModelLoading {
|
||||
model_name: String,
|
||||
model_type: NotificationModelType,
|
||||
},
|
||||
ModelInference {
|
||||
model_name: String,
|
||||
model_type: NotificationModelType,
|
||||
},
|
||||
ModelInferenceComplete {
|
||||
model_name: String,
|
||||
model_type: NotificationModelType,
|
||||
},
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
use log::info;
|
||||
use crate::core::events::sendable_event_error::SendableEventError;
|
||||
use log::info;
|
||||
use serde::Serialize;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
/// Implement this on a Serializable object to be able to send it as an event to the frontend.
|
||||
#[deprecated(note = "Use BasicSendableEvent instead as it wraps the event with status and data fields.")]
|
||||
pub trait SendableEvent : Serialize {
|
||||
/// This is the name of the event that the frontend subscribes to.
|
||||
const FRONTEND_EVENT_NAME: &'static str;
|
||||
|
||||
/// Default implementation of send().
|
||||
/// This serializes and sends the event to the frontend.
|
||||
#[deprecated(note = "Use BasicSendableEvent instead as it wraps the event with status and data fields.")]
|
||||
fn send(&self, app: &AppHandle) -> Result<(), SendableEventError> {
|
||||
info!("Sending event to frontend: {}", Self::FRONTEND_EVENT_NAME);
|
||||
let result = app.emit(Self::FRONTEND_EVENT_NAME, self);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod artcraft_error;
|
||||
pub mod commands;
|
||||
pub mod events;
|
||||
pub mod model;
|
||||
pub mod state;
|
||||
pub mod threads;
|
||||
pub mod utils;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod image_models;
|
||||
pub mod video_models;
|
||||
@@ -0,0 +1,22 @@
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum VideoModel {
|
||||
#[serde(rename = "kling_1.6_pro")]
|
||||
Kling16Pro,
|
||||
|
||||
#[serde(rename = "kling_2.1_pro")]
|
||||
Kling21Pro,
|
||||
|
||||
#[serde(rename = "kling_2.1_master")]
|
||||
Kling21Master,
|
||||
|
||||
#[serde(rename = "seedance_1.0_lite")]
|
||||
Seedance10Lite,
|
||||
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use log::info;
|
||||
use errors::AnyhowResult;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use crate::core::state::app_env_configs::app_env_configs_serializeable::{AppEnvConfigsSerializable, StorytellerApiHost};
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppEnvConfigs {
|
||||
pub storyteller_host: ApiHost,
|
||||
}
|
||||
|
||||
impl AppEnvConfigs {
|
||||
|
||||
pub fn load_from_filesystem(root: &AppDataRoot) -> AnyhowResult<Self> {
|
||||
println!("Loading app environmental configs from filesystem...");
|
||||
let input = AppEnvConfigsSerializable::load_from_filesystem(root)?;
|
||||
|
||||
let storyteller = input.as_ref()
|
||||
.map(|i| i.storyteller_host)
|
||||
.flatten();
|
||||
|
||||
let storyteller_api = match storyteller {
|
||||
Some(StorytellerApiHost::Localhost) => ApiHost::Localhost {
|
||||
port: input.as_ref()
|
||||
.and_then(|i| i.storyteller_port)
|
||||
.unwrap_or(12345)
|
||||
},
|
||||
Some(StorytellerApiHost::Production) => ApiHost::Storyteller,
|
||||
_ => ApiHost::Storyteller,
|
||||
};
|
||||
|
||||
println!("Using storyteller API host: {:?}", storyteller_api);
|
||||
|
||||
Ok(Self {
|
||||
storyteller_host: storyteller_api,
|
||||
})
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use errors::AnyhowResult;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
|
||||
const CURRENT_VERSION: &str = "1";
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StorytellerApiHost {
|
||||
Production,
|
||||
Localhost,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct AppEnvConfigsSerializable {
|
||||
/// Versioning string.
|
||||
pub version: Option<String>,
|
||||
pub storyteller_port: Option<u32>,
|
||||
pub storyteller_host: Option<StorytellerApiHost>,
|
||||
}
|
||||
|
||||
impl AppEnvConfigsSerializable {
|
||||
pub fn load_from_filesystem(app_data_root: &AppDataRoot) -> AnyhowResult<Option<Self>> {
|
||||
let filename = app_data_root.settings_dir().get_app_env_configs_path();
|
||||
if !filename.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let contents = std::fs::read_to_string(filename)?;
|
||||
let data: Self = serde_json::from_str(&contents)?;
|
||||
Ok(Some(data))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod app_env_configs;
|
||||
pub mod app_env_configs_serializeable;
|
||||
@@ -33,6 +33,7 @@ pub struct AppDataRoot {
|
||||
impl AppDataRoot {
|
||||
pub fn create_default() -> anyhow::Result<Self> {
|
||||
let directory = get_default_data_dir()?;
|
||||
println!("App data directory: {:?}", directory);
|
||||
Self::create_existing(directory)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,11 @@ impl AppSettingsDir {
|
||||
pub fn get_app_preferences_path(&self) -> PathBuf {
|
||||
self.path.join("app_preferences.json")
|
||||
}
|
||||
|
||||
|
||||
pub fn get_app_env_configs_path(&self) -> PathBuf {
|
||||
self.path.join("env_configs.json")
|
||||
}
|
||||
|
||||
pub fn write_app_preferences(&self, prefs: &AppPreferences) -> AnyhowResult<()> {
|
||||
let serializable = prefs.to_serializable();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod app_env_configs;
|
||||
pub mod app_preferences;
|
||||
pub mod app_startup_time;
|
||||
pub mod data_dir;
|
||||
|
||||
@@ -23,6 +23,15 @@ pub async fn main_window_thread(
|
||||
let window_pos_slot: CloneSlot<MainWindowPosition> = CloneSlot::empty();
|
||||
let app_startup_time = AppStartupTime::new();
|
||||
|
||||
// Emit debugging information to the logs (do not remove this!)
|
||||
info!("git commit id: {:?}", build_metadata::git_commit_id()
|
||||
.unwrap_or_else(|| "unknown"));
|
||||
info!("git commit timestamp: {:?}", build_metadata::git_commit_timestamp()
|
||||
.map(|t| t.to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string()));
|
||||
info!("build timestamp: {:?}", build_metadata::build_timestamp()
|
||||
.to_string());
|
||||
|
||||
loop {
|
||||
for (window_name, window) in app.windows() {
|
||||
if window_name == MAIN_WINDOW_NAME {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use errors::AnyhowResult;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tempfile::NamedTempFile;
|
||||
@@ -9,7 +9,7 @@ use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
// TODO: Better more concrete error handling
|
||||
|
||||
pub async fn download_media_file_to_temp_dir(app_data_root: &AppDataRoot, token: &MediaFileToken) -> AnyhowResult<NamedTempFile> {
|
||||
pub async fn download_media_file_to_temp_dir(app_data_root: &AppDataRoot, token: &MediaFileToken) -> Result<NamedTempFile, ArtcraftError> {
|
||||
let response = get_media_file(&ApiHost::Storyteller, token).await?;
|
||||
|
||||
let media_file_url = &response.media_file.media_links.cdn_url;
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use errors::AnyhowResult;
|
||||
use mimetypes::mimetype_info::mimetype_info::MimetypeInfo;
|
||||
use std::io::Write;
|
||||
use tempfile::NamedTempFile;
|
||||
use web_base64::web_base64_decode::web_base64_decode;
|
||||
|
||||
// TODO: Retain error types (io/decode)
|
||||
|
||||
pub async fn save_base64_image_to_temp_dir(app_data_root: &AppDataRoot, base64_image: String) -> AnyhowResult<NamedTempFile> {
|
||||
let bytes = BASE64_STANDARD.decode(base64_image)?;
|
||||
pub async fn save_base64_image_to_temp_dir(app_data_root: &AppDataRoot, base64_image: String) -> Result<NamedTempFile, ArtcraftError> {
|
||||
let bytes = web_base64_decode(&base64_image)?;
|
||||
|
||||
let extension = MimetypeInfo::get_for_bytes(&bytes)
|
||||
.map(|info| info.file_extension())
|
||||
|
||||
@@ -5,8 +5,13 @@ use tauri::Manager;
|
||||
|
||||
use crate::core::commands::app_preferences::get_app_preferences_command::get_app_preferences_command;
|
||||
use crate::core::commands::app_preferences::update_app_preference_command::update_app_preferences_command;
|
||||
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::enqueue_text_to_image_command;
|
||||
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::enqueue_image_to_3d_object_command;
|
||||
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::enqueue_image_to_video_command;
|
||||
use crate::core::commands::flip_image::flip_image;
|
||||
use crate::core::commands::get_build_info_command::get_build_info_command;
|
||||
use crate::core::commands::platform_info_command::platform_info_command;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::load_app_preferences_or_default;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
@@ -67,6 +72,9 @@ pub fn run() {
|
||||
|
||||
let fal_task_queue = FalTaskQueue::new();
|
||||
let fal_task_queue_2 = fal_task_queue.clone();
|
||||
|
||||
let app_env_configs = AppEnvConfigs::load_from_filesystem(&app_data_root)
|
||||
.expect("AppEnvConfigs should be loaded from disk");
|
||||
|
||||
println!("Initializing backend runtime...");
|
||||
|
||||
@@ -151,6 +159,7 @@ pub fn run() {
|
||||
Ok(())
|
||||
})
|
||||
.manage(app_data_root)
|
||||
.manage(app_env_configs)
|
||||
.manage(app_preferences)
|
||||
.manage(fal_creds_manager)
|
||||
.manage(fal_task_queue)
|
||||
@@ -159,11 +168,15 @@ pub fn run() {
|
||||
.manage(storyteller_creds_manager_3)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
check_sora_session_command,
|
||||
enqueue_image_to_3d_object_command,
|
||||
enqueue_image_to_video_command,
|
||||
enqueue_text_to_image_command,
|
||||
fal_background_removal_command,
|
||||
fal_hunyuan_image_to_3d_command,
|
||||
fal_kling_image_to_video_command,
|
||||
flip_image,
|
||||
get_app_preferences_command,
|
||||
get_build_info_command,
|
||||
get_fal_api_key_command,
|
||||
open_sora_login_command,
|
||||
platform_info_command,
|
||||
|
||||
+61
-27
@@ -1,4 +1,11 @@
|
||||
use crate::core::commands::command_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus, CommandResult, SerializeMarker};
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
@@ -8,8 +15,6 @@ use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::sora::events::sora_image_enqueue_failure_event::SoraImageEnqueueFailureEvent;
|
||||
use crate::services::sora::events::sora_image_enqueue_success_event::SoraImageEnqueueSuccessEvent;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
@@ -19,8 +24,8 @@ use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use fal_client::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::remove_background_rembg::remove_background_rembg;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::queue::remove_background_rembg::remove_background_rembg;
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
@@ -45,6 +50,7 @@ use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
@@ -85,11 +91,12 @@ pub enum BackgroundRemovalErrorType {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fal_background_removal_command(
|
||||
app: AppHandle,
|
||||
request: FalBackgroundRemovalRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
) -> CommandResult<FalBackgroundRemovalSuccessResponse, BackgroundRemovalErrorType, ()> {
|
||||
) -> Response<FalBackgroundRemovalSuccessResponse, BackgroundRemovalErrorType, ()> {
|
||||
|
||||
info!("fal_background_removal_command called; image media token: {:?}", request.image_media_token);
|
||||
|
||||
@@ -99,6 +106,13 @@ pub async fn fal_background_removal_command(
|
||||
|
||||
if !has_credentials {
|
||||
warn!("No API key found");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::RemoveBackground);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(CommandErrorResponseWrapper {
|
||||
status: CommandErrorStatus::Unauthorized,
|
||||
error_message: Some("You need to set a FAL api key".to_string()),
|
||||
@@ -107,6 +121,16 @@ pub async fn fal_background_removal_command(
|
||||
});
|
||||
}
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::RemoveBackground,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
let result = remove_background(
|
||||
request,
|
||||
&app_data_root,
|
||||
@@ -118,20 +142,21 @@ pub async fn fal_background_removal_command(
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
//let event = SoraImageEnqueueFailureEvent {};
|
||||
//let result = event.send(&app);
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to emit event: {:?}", err);
|
||||
//}
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::RemoveBackground,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = BackgroundRemovalErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
//match (err) {
|
||||
// _ => {},
|
||||
//}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
@@ -140,12 +165,6 @@ pub async fn fal_background_removal_command(
|
||||
})
|
||||
}
|
||||
Ok(result) => {
|
||||
//let event = SoraImageEnqueueSuccessEvent {};
|
||||
//let result = event.send(&app);
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to emit event: {:?}", err);
|
||||
//}
|
||||
|
||||
Ok(FalBackgroundRemovalSuccessResponse {
|
||||
media_token: result.0.media_file.token,
|
||||
cdn_url: result.0.media_file.media_links.cdn_url,
|
||||
@@ -159,9 +178,10 @@ pub async fn fal_background_removal_command(
|
||||
enum InnerError {
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerApiError(ApiError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InnerError {
|
||||
@@ -176,9 +196,9 @@ impl From<FalErrorPlus> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InnerError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
impl From<StorytellerError> for InnerError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +214,17 @@ impl From<std::io::Error> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InnerError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_background(
|
||||
request: FalBackgroundRemovalRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
@@ -207,9 +238,11 @@ pub async fn remove_background(
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
info!("From media token: {:?}", &media_token);
|
||||
temp_download = download_media_file_to_temp_dir(&app_data_root, &media_token).await?;
|
||||
|
||||
} else if let Some(base64_bytes) = request.base64_image {
|
||||
info!("From base64 bytes...");
|
||||
temp_download = save_base64_image_to_temp_dir(&app_data_root, base64_bytes).await?;
|
||||
} else {
|
||||
return Err(InnerError::AnyhowError(anyhow!("No image media token or base64 image provided")));
|
||||
@@ -220,6 +253,8 @@ pub async fn remove_background(
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let result = remove_background_rembg(filename, &api_key).await?;
|
||||
|
||||
info!("Unpacking FAL result...");
|
||||
|
||||
let extension_with_dot = get_url_file_extension(&result.image_url)
|
||||
.map(|ext| format!(".{}", ext))
|
||||
@@ -254,4 +289,3 @@ pub async fn remove_background(
|
||||
|
||||
Ok((response, base64_bytes))
|
||||
}
|
||||
|
||||
|
||||
+51
-29
@@ -1,4 +1,10 @@
|
||||
use crate::core::commands::command_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus, CommandResult, SerializeMarker};
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
@@ -9,8 +15,6 @@ use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::events::sora_image_enqueue_failure_event::SoraImageEnqueueFailureEvent;
|
||||
use crate::services::sora::events::sora_image_enqueue_success_event::SoraImageEnqueueSuccessEvent;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
@@ -20,10 +24,8 @@ use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use fal_client::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
|
||||
use fal_client::requests::remove_background_rembg::remove_background_rembg;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
@@ -48,6 +50,7 @@ use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
@@ -76,12 +79,13 @@ pub enum HunyuanErrorType {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fal_hunyuan_image_to_3d_command(
|
||||
app: AppHandle,
|
||||
request: FalHunyuanImageTo3dRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
) -> CommandResult<(), HunyuanErrorType, ()> {
|
||||
) -> Response<(), HunyuanErrorType, ()> {
|
||||
|
||||
info!("fal_hunyuan_image_to_3d_command called; image media token: {:?}", request.image_media_token);
|
||||
|
||||
@@ -110,20 +114,21 @@ pub async fn fal_hunyuan_image_to_3d_command(
|
||||
if let Err(err) = result {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
//let event = SoraImageEnqueueFailureEvent {};
|
||||
//let result = event.send(&app);
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to emit event: {:?}", 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.
|
||||
}
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = HunyuanErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
//match (err) {
|
||||
// _ => {},
|
||||
//}
|
||||
|
||||
return Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
@@ -131,13 +136,17 @@ pub async fn fal_hunyuan_image_to_3d_command(
|
||||
error_details: None,
|
||||
})
|
||||
}
|
||||
|
||||
//let event = SoraImageEnqueueSuccessEvent {};
|
||||
//let result = event.send(&app);
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to emit event: {:?}", err);
|
||||
//}
|
||||
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
@@ -145,9 +154,10 @@ pub async fn fal_hunyuan_image_to_3d_command(
|
||||
enum InnerError {
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerApiError(ApiError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InnerError {
|
||||
@@ -162,9 +172,9 @@ impl From<FalErrorPlus> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InnerError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
impl From<StorytellerError> for InnerError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +190,19 @@ impl From<std::io::Error> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InnerError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn image_to_3d(
|
||||
request: FalHunyuanImageTo3dRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
@@ -189,7 +212,6 @@ pub async fn image_to_3d(
|
||||
) -> Result<(), InnerError> {
|
||||
|
||||
let api_key = fal_creds_manager.get_key_required()?;
|
||||
let creds = storyteller_creds_manager.get_credentials_required()?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
|
||||
+58
-28
@@ -1,4 +1,10 @@
|
||||
use crate::core::commands::command_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus, CommandResult, SerializeMarker};
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
@@ -9,8 +15,6 @@ use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_to_tempfile;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::events::sora_image_enqueue_failure_event::SoraImageEnqueueFailureEvent;
|
||||
use crate::services::sora::events::sora_image_enqueue_success_event::SoraImageEnqueueSuccessEvent;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
@@ -20,9 +24,8 @@ use anyhow::anyhow;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::{DecodeError, Engine};
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use fal_client::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::requests::enqueue_kling_16_image_to_video::{enqueue_kling_16_image_to_video, Kling16Args, Kling16Duration};
|
||||
use fal_client::requests::remove_background_rembg::remove_background_rembg;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
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 filesys::file_read_bytes::file_read_bytes;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::{DynamicImage, EncodableLayout, ImageReader};
|
||||
@@ -47,6 +50,7 @@ use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
@@ -57,10 +61,15 @@ use tokens::tokens::media_files::MediaFileToken;
|
||||
#[derive(Deserialize)]
|
||||
pub struct FalKlingImageToVideoRequest {
|
||||
/// Image media file; the image to remove the background from.
|
||||
/// This must be supplied **OR** the `base64_image`.
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Base64-encoded image
|
||||
/// This must be supplied **OR** the `image_media_token`.
|
||||
pub base64_image: Option<String>,
|
||||
|
||||
/// Optional: Text prompt.
|
||||
pub prompt: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
@@ -75,12 +84,13 @@ pub enum KlingImageToVideoErrorType {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn fal_kling_image_to_video_command(
|
||||
app: AppHandle,
|
||||
request: FalKlingImageToVideoRequest,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
) -> CommandResult<(), KlingImageToVideoErrorType, ()> {
|
||||
) -> Response<(), KlingImageToVideoErrorType, ()> {
|
||||
|
||||
info!("fal_kling_image_to_video_command called; image media token: {:?}", request.image_media_token);
|
||||
|
||||
@@ -109,20 +119,21 @@ pub async fn fal_kling_image_to_video_command(
|
||||
if let Err(err) = result {
|
||||
error!("error: {:?}", err);
|
||||
|
||||
//let event = SoraImageEnqueueFailureEvent {};
|
||||
//let result = event.send(&app);
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to emit event: {:?}", err);
|
||||
//}
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = KlingImageToVideoErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
//match (err) {
|
||||
// _ => {},
|
||||
//}
|
||||
|
||||
return Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
@@ -131,12 +142,16 @@ pub async fn fal_kling_image_to_video_command(
|
||||
})
|
||||
}
|
||||
|
||||
//let event = SoraImageEnqueueSuccessEvent {};
|
||||
//let result = event.send(&app);
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to emit event: {:?}", err);
|
||||
//}
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
@@ -144,9 +159,10 @@ pub async fn fal_kling_image_to_video_command(
|
||||
enum InnerError {
|
||||
FalError(FalErrorPlus),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerApiError(ApiError),
|
||||
StorytellerError(StorytellerError),
|
||||
DecodeError(DecodeError),
|
||||
IoError(std::io::Error),
|
||||
ArtcraftError(ArtcraftError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InnerError {
|
||||
@@ -161,9 +177,9 @@ impl From<FalErrorPlus> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InnerError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
impl From<StorytellerError> for InnerError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +195,18 @@ impl From<std::io::Error> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtcraftError> for InnerError {
|
||||
fn from(value: ArtcraftError) -> Self {
|
||||
match value {
|
||||
ArtcraftError::AnyhowError(e) => Self::AnyhowError(e),
|
||||
ArtcraftError::DecodeError(e) => Self::DecodeError(e),
|
||||
ArtcraftError::IoError(e) => Self::IoError(e),
|
||||
ArtcraftError::StorytellerError(e) => Self::StorytellerError(e),
|
||||
_ => Self::ArtcraftError(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn image_to_video(
|
||||
request: FalKlingImageToVideoRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
@@ -205,10 +233,12 @@ pub async fn image_to_video(
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let enqueued = enqueue_kling_16_image_to_video(Kling16Args {
|
||||
let enqueued = enqueue_kling_16_pro_image_to_video(Kling16ProArgs {
|
||||
image_path: filename,
|
||||
api_key: &api_key,
|
||||
duration: Kling16Duration::Default,
|
||||
duration: Kling16ProDuration::Default,
|
||||
aspect_ratio: Kling16ProAspectRatio::WideSixteenNine,
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
}).await?;
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::core::commands::command_response_wrapper::{CommandResult, SerializeMarker};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::core::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
@@ -22,7 +23,7 @@ impl SerializeMarker for GetFalApiKeyResponse {}
|
||||
#[tauri::command]
|
||||
pub async fn get_fal_api_key_command(
|
||||
creds_manager: State<'_, FalCredentialManager>,
|
||||
) -> CommandResult<GetFalApiKeyResponse, (), ()> {
|
||||
) -> Response<GetFalApiKeyResponse, (), ()> {
|
||||
|
||||
let key = get_key(&creds_manager)
|
||||
.await
|
||||
|
||||
@@ -2,4 +2,6 @@ pub mod fal_background_removal_command;
|
||||
pub mod fal_hunyuan_image_to_3d_command;
|
||||
pub mod fal_kling_image_to_video_command;
|
||||
pub mod get_fal_api_key_command;
|
||||
pub mod set_fal_api_key_command;
|
||||
pub mod set_fal_api_key_command;
|
||||
// pub mod get_open_ai_api_key_command;
|
||||
// pub mod set_open_ai_api_key_command;
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::core::commands::command_response_wrapper::CommandResult;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::core::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
@@ -21,7 +21,7 @@ pub struct SetFalApiKeyRequest{
|
||||
pub async fn set_fal_api_key_command(
|
||||
request: SetFalApiKeyRequest,
|
||||
creds_manager: State<'_, FalCredentialManager>,
|
||||
) -> CommandResult<(), (), ()> {
|
||||
) -> Response<(), (), ()> {
|
||||
info!("update_app_preferences_command called");
|
||||
|
||||
set_key(request, &creds_manager)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
use crate::core::events::generation_events::common::GenerationAction;
|
||||
use fal_client::model::fal_endpoint::FalEndpoint;
|
||||
|
||||
/// Adapter of generation types/categories for FAL
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FalGenerationType {
|
||||
TextToImage,
|
||||
ImageToImage,
|
||||
TextToVideo,
|
||||
ImageToVideo,
|
||||
ImageTo3d,
|
||||
ImageBackgroundRemoval,
|
||||
}
|
||||
|
||||
impl FalGenerationType {
|
||||
pub fn from_fal_endpoint(endpoint: &FalEndpoint) -> Option<Self> {
|
||||
match endpoint {
|
||||
FalEndpoint::FluxProUltraTextToImage => Some(Self::TextToImage),
|
||||
FalEndpoint::Hunyuan3d2Base => Some(Self::ImageTo3d),
|
||||
FalEndpoint::Kling16ImageToVideo => Some(Self::ImageToVideo),
|
||||
FalEndpoint::Minimax01ImageToVideo => Some(Self::ImageToVideo),
|
||||
FalEndpoint::RecraftV3TextToImage => Some(Self::TextToImage),
|
||||
FalEndpoint::Other(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_event_generation_action(&self) -> GenerationAction {
|
||||
match self {
|
||||
Self::TextToImage => GenerationAction::GenerateImage,
|
||||
Self::ImageToImage => GenerationAction::GenerateImage,
|
||||
Self::TextToVideo => GenerationAction::GenerateVideo,
|
||||
Self::ImageToVideo => GenerationAction::GenerateVideo,
|
||||
Self::ImageTo3d => GenerationAction::ImageTo3d,
|
||||
Self::ImageBackgroundRemoval => GenerationAction::RemoveBackground,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::services::fal::state::fal_generation_type::FalGenerationType;
|
||||
use chrono::{DateTime, Utc};
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::model::enqueued_request::EnqueuedRequest;
|
||||
@@ -10,12 +11,14 @@ use std::sync::{Arc, RwLock};
|
||||
pub struct FalTaskDetails {
|
||||
pub enqueue_time: DateTime<Utc>,
|
||||
pub enqueued_request: EnqueuedRequest,
|
||||
pub generation_type: Option<FalGenerationType>,
|
||||
}
|
||||
|
||||
impl FalTaskDetails {
|
||||
pub fn for_request(request: EnqueuedRequest) -> Self {
|
||||
Self {
|
||||
enqueue_time: Utc::now(),
|
||||
generation_type: FalGenerationType::from_fal_endpoint(&request.fal_endpoint),
|
||||
enqueued_request: request,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod fal_credential_manager;
|
||||
pub mod fal_task_queue;
|
||||
pub mod read_fal_api_key_from_disk;
|
||||
pub mod read_fal_api_key_from_disk;
|
||||
mod fal_generation_type;
|
||||
@@ -1,16 +1,16 @@
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
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_complete_event::GenerationCompleteEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::download_url_to_temp_dir::download_url_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::events::sora_image_generation_complete_event::SoraImageGenerationCompleteEvent;
|
||||
use crate::services::sora::events::sora_image_generation_failed_event::SoraImageGenerationFailedEvent;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::export::queue::Status;
|
||||
use fal_client::fal_error_plus::FalErrorPlus;
|
||||
use fal_client::model::fal_request_id::FalRequestId;
|
||||
use fal_client::utils::queue_status_checker::QueueStatusChecker;
|
||||
use log::{error, info, warn};
|
||||
@@ -146,10 +146,17 @@ async fn polling_loop(
|
||||
Ok(success) => {
|
||||
info!("Uploaded to API backend: {:?}", success.media_file_token);
|
||||
|
||||
//let event = SoraImageGenerationCompleteEvent {
|
||||
// media_file_token: success.media_file_token,
|
||||
//};
|
||||
//event.send(&app_handle)?;
|
||||
let event = GenerationCompleteEvent {
|
||||
//media_file_token: result.media_file_token,
|
||||
action: task.generation_type
|
||||
.map(|typ| typ.to_event_generation_action()),
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app_handle) {
|
||||
error!("Failed to send GenerationCompleteEvent: {:?}", err); // Fail open
|
||||
}
|
||||
|
||||
succeeded_tasks.push(task_id);
|
||||
}
|
||||
@@ -158,7 +165,6 @@ async fn polling_loop(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -228,7 +234,7 @@ async fn polling_loop(
|
||||
|
||||
fal_task_queue.remove_list(&succeeded_task_ids)?;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(6_000)).await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1_000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use crate::core::commands::command_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus, CommandResult};
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::sora::events::sora_image_enqueue_failure_event::SoraImageEnqueueFailureEvent;
|
||||
use crate::services::sora::events::sora_image_enqueue_success_event::SoraImageEnqueueSuccessEvent;
|
||||
use crate::services::sora::state::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::services::sora::state::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
@@ -36,13 +39,16 @@ use storyteller_client::error::api_error::ApiError::InternalServerError;
|
||||
use storyteller_client::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter, Manager, State};
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
const SORA_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
const SORA_IMAGE_REMIX_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
#[derive(Deserialize)]
|
||||
const DEFAULT_ASPECT_RATIO : SoraAspectRatio = SoraAspectRatio::Square;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SoraImageRemixCommand {
|
||||
/// Image media file; the engine or canvas snapshot (screenshot).
|
||||
pub snapshot_media_token: MediaFileToken,
|
||||
@@ -57,6 +63,17 @@ pub struct SoraImageRemixCommand {
|
||||
pub maybe_additional_images: Option<Vec<MediaFileToken>>,
|
||||
|
||||
pub maybe_number_of_samples: Option<u32>,
|
||||
|
||||
/// Aspect ratio.
|
||||
pub aspect_ratio: Option<SoraAspectRatio>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SoraAspectRatio {
|
||||
Square,
|
||||
Wide,
|
||||
Tall,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
@@ -74,7 +91,6 @@ pub enum SoraImageRemixErrorType {
|
||||
SoraIsHavingProblems,
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sora_image_remix_command(
|
||||
app: AppHandle,
|
||||
@@ -82,10 +98,10 @@ pub async fn sora_image_remix_command(
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> CommandResult<(), SoraImageRemixErrorType, ()> {
|
||||
) -> Response<(), SoraImageRemixErrorType, ()> {
|
||||
|
||||
info!("image_generation_command called; scene media token: {:?}, additional images: {:?}",
|
||||
request.snapshot_media_token, request.maybe_additional_images);
|
||||
info!("image_generation_command called; scene media token: {:?}, additional images: {:?}, full request: {:?}",
|
||||
&request.snapshot_media_token, &request.maybe_additional_images, &request);
|
||||
|
||||
// TODO(bt,2025-04-24): Better error messages to caller
|
||||
|
||||
@@ -95,9 +111,23 @@ pub async fn sora_image_remix_command(
|
||||
|
||||
if !has_credentials {
|
||||
warn!("No apparently completed credentials found");
|
||||
|
||||
let error_message = "You need to log into Sora to continue. See the settings menu.";
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
reason: Some(error_message.to_string()),
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(CommandErrorResponseWrapper {
|
||||
status: CommandErrorStatus::Unauthorized,
|
||||
error_message: Some("You need to log into Sora to continue. See the settings menu.".to_string()),
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_type: Some(SoraImageRemixErrorType::SoraLoginRequired),
|
||||
error_details: None,
|
||||
});
|
||||
@@ -107,38 +137,42 @@ pub async fn sora_image_remix_command(
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
error!("error: {:?}", err);
|
||||
error!("Sora image remix error: {:?}", err);
|
||||
|
||||
let event = SoraImageEnqueueFailureEvent {};
|
||||
let result = event.send(&app);
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to emit event: {:?}", err);
|
||||
}
|
||||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = SoraImageRemixErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
let mut error_message = "An error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
|
||||
match (err) {
|
||||
InnerError::SoraError(SoraError::TooManyConcurrentTasks) => {
|
||||
error_type = SoraImageRemixErrorType::TooManyConcurrentTasks;
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_message = "You already have work in progress. Please wait for it to finish.";
|
||||
error_message = "You have too many Sora image generation tasks running. Please wait a moment.";
|
||||
},
|
||||
InnerError::SoraError(SoraError::SoraUsernameNotYetCreated) => {
|
||||
error_type = SoraImageRemixErrorType::SoraUsernameNotYetCreated;
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_message = "You need to create a username on Sora.com to continue.";
|
||||
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 wait a moment and then retry.";
|
||||
error_message = "Sora is having problems. Please try again later.";
|
||||
}
|
||||
_ => {},
|
||||
}
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
reason: Some(error_message.to_string()),
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
@@ -147,11 +181,14 @@ pub async fn sora_image_remix_command(
|
||||
})
|
||||
}
|
||||
Ok(_) => {
|
||||
let event = SoraImageEnqueueSuccessEvent {};
|
||||
let result = event.send(&app);
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to emit event: {:?}", err);
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
Ok(().into())
|
||||
@@ -163,7 +200,7 @@ pub async fn sora_image_remix_command(
|
||||
enum InnerError {
|
||||
SoraError(SoraError),
|
||||
AnyhowError(AnyhowError),
|
||||
StorytellerApiError(ApiError),
|
||||
StorytellerError(StorytellerError),
|
||||
}
|
||||
|
||||
impl From<AnyhowError> for InnerError {
|
||||
@@ -178,9 +215,9 @@ impl From<SoraError> for InnerError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for InnerError {
|
||||
fn from(value: ApiError) -> Self {
|
||||
Self::StorytellerApiError(value)
|
||||
impl From<StorytellerError> for InnerError {
|
||||
fn from(value: StorytellerError) -> Self {
|
||||
Self::StorytellerError(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +277,14 @@ pub async fn generate_image(
|
||||
sora_media_tokens.push(response.id);
|
||||
}
|
||||
|
||||
let aspect_ratio = request.aspect_ratio.unwrap_or(DEFAULT_ASPECT_RATIO);
|
||||
|
||||
let aspect_ratio = match aspect_ratio {
|
||||
SoraAspectRatio::Square => ImageSize::Square,
|
||||
SoraAspectRatio::Wide => ImageSize::Wide,
|
||||
SoraAspectRatio::Tall => ImageSize::Tall,
|
||||
};
|
||||
|
||||
info!("Calling image generation...");
|
||||
|
||||
// TODO(bt,2025-04-21): Download media tokens.
|
||||
@@ -250,7 +295,7 @@ pub async fn generate_image(
|
||||
image_remix_with_session_auto_renew(ImageRemixAutoRenewRequest {
|
||||
prompt: request.prompt.to_string(),
|
||||
num_images: NumImages::One,
|
||||
image_size: ImageSize::Square,
|
||||
image_size: aspect_ratio,
|
||||
sora_media_tokens: sora_media_tokens.clone(),
|
||||
credentials: &creds,
|
||||
request_timeout: Some(SORA_IMAGE_REMIX_TIMEOUT),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::core::commands::app_preferences::get_app_preferences_command::AppPreferencesPayload;
|
||||
use crate::core::commands::command_response_wrapper::{CommandResult, CommandSuccessResponseWrapper, SimpleCommandResult};
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::core::commands::response::shorthand::SimpleResponse;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::windows::sora_login_window::open_sora_login_window::open_sora_login_window;
|
||||
use errors::{AnyhowError, AnyhowResult};
|
||||
use log::{error, info};
|
||||
@@ -17,7 +17,7 @@ use tauri::{AppHandle, State};
|
||||
#[tauri::command]
|
||||
pub async fn sora_logout_command(
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
) -> SimpleCommandResult {
|
||||
) -> SimpleResponse {
|
||||
info!("sora_logout_command called");
|
||||
|
||||
if let Err(err) = sora_creds_manager.purge_credentials_from_disk() {
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
pub mod sora_image_enqueue_failure_event;
|
||||
pub mod sora_image_enqueue_success_event;
|
||||
pub mod sora_image_generation_complete_event;
|
||||
pub mod sora_image_generation_failed_event;
|
||||
pub mod sora_login_success_event;
|
||||
@@ -1,11 +0,0 @@
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SoraImageEnqueueFailureEvent {
|
||||
// TODO: Reason.
|
||||
}
|
||||
|
||||
impl SendableEvent for SoraImageEnqueueFailureEvent {
|
||||
const FRONTEND_EVENT_NAME: &'static str = "sora-image-enqueue-failure";
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SoraImageEnqueueSuccessEvent {
|
||||
// TODO: Token, etc.
|
||||
}
|
||||
|
||||
impl SendableEvent for SoraImageEnqueueSuccessEvent {
|
||||
const FRONTEND_EVENT_NAME: &'static str = "sora-image-enqueue-success";
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
use serde_derive::Serialize;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SoraImageGenerationCompleteEvent {
|
||||
pub media_file_token: MediaFileToken,
|
||||
}
|
||||
|
||||
impl SendableEvent for SoraImageGenerationCompleteEvent {
|
||||
const FRONTEND_EVENT_NAME: &'static str = "sora-image-generation-complete";
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
use serde_derive::Serialize;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SoraImageGenerationFailedEvent {
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
impl SendableEvent for SoraImageGenerationFailedEvent {
|
||||
const FRONTEND_EVENT_NAME: &'static str = "sora-image-generation-failed";
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use openai_sora_client::requests::bearer::generate::generate_bearer_with_cookie;
|
||||
use openai_sora_client::requests::bearer::generate_bearer_with_cookie::generate_bearer_with_cookie;
|
||||
|
||||
/// Loading the JWT bearer token for the first time.
|
||||
pub async fn initialize_sora_jwt_bearer_token(app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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_complete_event::GenerationCompleteEvent;
|
||||
use crate::core::events::generation_events::generation_failed_event::GenerationFailedEvent;
|
||||
use crate::core::events::sendable_event_trait::SendableEvent;
|
||||
use crate::services::sora::events::sora_image_generation_complete_event::SoraImageGenerationCompleteEvent;
|
||||
use crate::services::sora::events::sora_image_generation_failed_event::SoraImageGenerationFailedEvent;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
@@ -95,10 +97,19 @@ async fn polling_loop(
|
||||
|
||||
for task in failed_tasks.iter() {
|
||||
if sora_task_queue.contains_key(&task.id)? {
|
||||
let event = SoraImageGenerationFailedEvent {
|
||||
prompt: task.prompt.clone(),
|
||||
let event = GenerationFailedEvent {
|
||||
action: GenerationAction::GenerateImage,
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
reason: None,
|
||||
// prompt: task.prompt
|
||||
// .as_deref()
|
||||
// .map(|s| s.to_string())
|
||||
// .unwrap_or_else(||"".to_string()),
|
||||
};
|
||||
event.send(&app_handle)?;
|
||||
if let Err(err) = event.send(&app_handle) {
|
||||
error!("Failed to send GenerationFailedEvent: {:?}", err); // Fail open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,12 +137,17 @@ async fn polling_loop(
|
||||
let result = upload_image_media_file_from_file(&api_host, Some(&creds), download_path).await?;
|
||||
|
||||
info!("Uploaded to API backend: {:?}", result.media_file_token);
|
||||
|
||||
let event = SoraImageGenerationCompleteEvent {
|
||||
media_file_token: result.media_file_token,
|
||||
|
||||
let event = GenerationCompleteEvent {
|
||||
//media_file_token: result.media_file_token,
|
||||
action: Some(GenerationAction::GenerateImage),
|
||||
service: GenerationServiceProvider::Sora,
|
||||
model: None,
|
||||
};
|
||||
|
||||
event.send(&app_handle)?;
|
||||
|
||||
if let Err(err) = event.send(&app_handle) {
|
||||
error!("Failed to send GenerationCompleteEvent: {:?}", err); // Fail open
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"build": {
|
||||
"frontendDist": "../../../frontend/apps/editor3d/dist",
|
||||
"beforeDevCommand": "echo 'no dev'",
|
||||
"devUrl": "http://localhost:5173",
|
||||
"beforeBuildCommand": "npx nx run editor3d:build"
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@
|
||||
"width": 2400,
|
||||
"height": 1300,
|
||||
"resizable": true,
|
||||
"fullscreen": false
|
||||
"fullscreen": false,
|
||||
"dragDropEnabled": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
@@ -33,7 +34,7 @@
|
||||
"https://www.googletagmanager.com",
|
||||
"ipc://localhost",
|
||||
"tauri://localhost"
|
||||
],
|
||||
],
|
||||
"connect-src": [
|
||||
"'self'",
|
||||
"'unsafe-eval'",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"default":{"identifier":"default","description":"enables the default permissions","local":true,"windows":["main"],"permissions":["core:default","log:default"]}}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user