Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
162ef43a30 |
@@ -48,13 +48,13 @@ jobs:
|
||||
# You can remove the one that doesn't apply to your app to speed up the workflow a bit.
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: cd ./crates/desktop/artcraft && yarn install # change this to npm, pnpm or bun depending on which one you use.
|
||||
run: cd ./crates/desktop/tauri-artcraft && yarn install # change this to npm, pnpm or bun depending on which one you use.
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
projectPath: ./crates/desktop/artcraft
|
||||
projectPath: ./crates/desktop/tauri-artcraft
|
||||
tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
|
||||
releaseName: 'App v__VERSION__'
|
||||
releaseBody: 'See the assets to download this version and install.'
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<codeStyleSettings language="Rust">
|
||||
<option name="RIGHT_MARGIN" value="180" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="false" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE generic_inference_jobs\nSET\n status = \"complete_success\",\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 = ?,\n success_inference_execution_millis = ?,\n retry_at = NULL,\n successfully_completed_at = NOW()\nWHERE token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "2bd7a5b886c9624ae48533ddac409cfca0668f30ab1f7016c412876b5904168b"
|
||||
}
|
||||
@@ -802,54 +802,6 @@ dependencies = [
|
||||
"num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "artcraft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assertor",
|
||||
"base64 0.22.1",
|
||||
"bytes 1.10.0",
|
||||
"chrono",
|
||||
"const_format",
|
||||
"crockford",
|
||||
"directories 6.0.0",
|
||||
"dirs 6.0.0",
|
||||
"discord-rich-presence",
|
||||
"errors",
|
||||
"filesys",
|
||||
"futures 0.3.29",
|
||||
"image",
|
||||
"indexmap 2.9.0",
|
||||
"libloading 0.8.6",
|
||||
"log",
|
||||
"memory_caching",
|
||||
"memory_store",
|
||||
"once_cell",
|
||||
"openai_sora_client",
|
||||
"primitives",
|
||||
"rand 0.9.0",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"serial_test",
|
||||
"storyteller_client",
|
||||
"subprocess_common",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-http",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-upload",
|
||||
"tempdir",
|
||||
"tempfile",
|
||||
"tokens",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "0.9.3"
|
||||
@@ -888,14 +840,11 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5"
|
||||
dependencies = [
|
||||
"brotli 3.5.0",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"zstd 0.13.1",
|
||||
"zstd-safe 7.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1402,11 +1351,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
|
||||
checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037"
|
||||
dependencies = [
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1972,7 +1921,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"fnv",
|
||||
"uuid 1.8.0",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2928,12 +2877,6 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
|
||||
|
||||
[[package]]
|
||||
name = "datetimes"
|
||||
version = "0.0.1"
|
||||
@@ -3315,36 +3258,12 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "discord-rich-presence"
|
||||
version = "0.2.5"
|
||||
source = "git+https://github.com/vionya/discord-rich-presence#9c6cabe04a70cfcc822affd1bf8be51321f8a821"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"thiserror 2.0.12",
|
||||
"uuid 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.4"
|
||||
@@ -5406,7 +5325,6 @@ dependencies = [
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"rustls 0.23.13",
|
||||
"rustls-native-certs",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
@@ -5629,7 +5547,7 @@ dependencies = [
|
||||
name = "idempotency"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"uuid 1.8.0",
|
||||
"uuid",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
@@ -5735,10 +5653,8 @@ dependencies = [
|
||||
name = "images"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"errors",
|
||||
"image",
|
||||
"mimetypes",
|
||||
"testing",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
@@ -6852,15 +6768,6 @@ dependencies = [
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memory_store"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"errors",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metal"
|
||||
version = "0.27.0"
|
||||
@@ -6961,9 +6868,7 @@ dependencies = [
|
||||
name = "mimetypes"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"infer 0.19.0",
|
||||
"testing",
|
||||
"infer 0.16.0",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
@@ -7193,10 +7098,10 @@ dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
"keyboard-types",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
@@ -7615,9 +7520,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.1"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551"
|
||||
checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
"objc2-exception-helper",
|
||||
@@ -7625,77 +7530,75 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
|
||||
checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2 0.6.1",
|
||||
"block2 0.6.0",
|
||||
"libc",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-quartz-core 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-quartz-core 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d"
|
||||
checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d"
|
||||
checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"dispatch2",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-graphics"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
|
||||
checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"dispatch2",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-image"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e"
|
||||
checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56"
|
||||
dependencies = [
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7727,25 +7630,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
|
||||
checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2 0.6.1",
|
||||
"block2 0.6.0",
|
||||
"libc",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
|
||||
checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
@@ -7776,13 +7679,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
|
||||
checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7792,23 +7695,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "777a571be14a42a3990d4ebedaeb8b54cd17377ec21b92e8200ac03797b3bee1"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-web-kit"
|
||||
version = "0.3.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad"
|
||||
checksum = "b717127e4014b0f9f3e8bba3d3f2acec81f1bde01f656823036e823ed2c94dce"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"block2 0.6.1",
|
||||
"objc2 0.6.1",
|
||||
"block2 0.6.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7890,7 +7793,7 @@ dependencies = [
|
||||
"toml 0.8.20",
|
||||
"ureq",
|
||||
"url",
|
||||
"uuid 1.8.0",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -9449,17 +9352,6 @@ dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "read-progress-stream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6435842fc2fea44b528719eb8c32203bbc1bb2f5b619fbe0c0a3d8350fd8d2a8"
|
||||
dependencies = [
|
||||
"bytes 1.10.0",
|
||||
"futures 0.3.29",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "realfft"
|
||||
version = "3.4.0"
|
||||
@@ -9728,7 +9620,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.13",
|
||||
"rustls-native-certs",
|
||||
"rustls-pemfile 2.1.2",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
@@ -9739,7 +9630,6 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls",
|
||||
"tokio-socks",
|
||||
"tokio-util",
|
||||
"tower 0.5.2",
|
||||
"tower-service",
|
||||
@@ -9810,7 +9700,7 @@ dependencies = [
|
||||
"rkyv_derive",
|
||||
"seahash",
|
||||
"tinyvec",
|
||||
"uuid 1.8.0",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10067,19 +9957,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile 2.1.2",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
@@ -10224,7 +10101,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"uuid 1.8.0",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11314,7 +11191,6 @@ dependencies = [
|
||||
"hostname 0.4.0",
|
||||
"http_server_common",
|
||||
"idempotency",
|
||||
"images",
|
||||
"infer 0.16.0",
|
||||
"language-tags",
|
||||
"lexical-sort",
|
||||
@@ -11372,7 +11248,7 @@ dependencies = [
|
||||
"user_traits_component",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
"uuid 1.8.0",
|
||||
"uuid",
|
||||
"videos",
|
||||
"workspace-build-acceleration",
|
||||
"zip 0.6.6",
|
||||
@@ -11385,11 +11261,9 @@ dependencies = [
|
||||
"anyhow",
|
||||
"backoff",
|
||||
"chrono",
|
||||
"cookie 0.18.1",
|
||||
"enums",
|
||||
"enums_public",
|
||||
"errors",
|
||||
"idempotency",
|
||||
"log",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
@@ -11398,7 +11272,6 @@ dependencies = [
|
||||
"tokens",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid 1.8.0",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
@@ -12056,9 +11929,9 @@ dependencies = [
|
||||
"ndk 0.9.0",
|
||||
"ndk-context",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.2",
|
||||
"raw-window-handle",
|
||||
@@ -12128,9 +12001,9 @@ dependencies = [
|
||||
"log",
|
||||
"mime",
|
||||
"muda",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
@@ -12156,6 +12029,49 @@ dependencies = [
|
||||
"windows 0.60.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-artcraft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assertor",
|
||||
"base64 0.22.1",
|
||||
"bytes 1.10.0",
|
||||
"chrono",
|
||||
"const_format",
|
||||
"crockford",
|
||||
"directories 6.0.0",
|
||||
"dirs 6.0.0",
|
||||
"errors",
|
||||
"filesys",
|
||||
"futures 0.3.29",
|
||||
"image",
|
||||
"libloading 0.8.6",
|
||||
"log",
|
||||
"memory_caching",
|
||||
"once_cell",
|
||||
"openai_sora_client",
|
||||
"primitives",
|
||||
"rand 0.9.0",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"serial_test",
|
||||
"storyteller_client",
|
||||
"subprocess_common",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-log",
|
||||
"tempdir",
|
||||
"tempfile",
|
||||
"tokens",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "2.1.1"
|
||||
@@ -12201,7 +12117,7 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"url",
|
||||
"uuid 1.8.0",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -12221,9 +12137,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin"
|
||||
version = "2.2.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37a5ebe6a610d1b78a94650896e6f7c9796323f408800cef436e0fa0539de601"
|
||||
checksum = "a4190775d6ff73fe66d9af44c012739a2659720efd9c0e1e56a918678038699d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"glob",
|
||||
@@ -12236,52 +12152,6 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88371e340ad2f07409a3b68294abe73f20bc9c1bc1b631a31dc37a3d0161f682"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"percent-encoding",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.8.20",
|
||||
"url",
|
||||
"uuid 1.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-http"
|
||||
version = "2.4.3"
|
||||
dependencies = [
|
||||
"bytes 1.10.0",
|
||||
"cookie_store",
|
||||
"data-url",
|
||||
"http 1.1.0",
|
||||
"regex",
|
||||
"reqwest 0.12.15",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"urlpattern",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-log"
|
||||
version = "2.3.1"
|
||||
@@ -12292,8 +12162,8 @@ dependencies = [
|
||||
"byte-unit",
|
||||
"fern",
|
||||
"log",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@@ -12304,25 +12174,6 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-upload"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e90ac6d3a783d4406caeae8c75aa05e96346474765517fddfd1dc313ff91aa89"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"read-progress-stream",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-realtime"
|
||||
version = "0.1.0"
|
||||
@@ -12394,9 +12245,9 @@ dependencies = [
|
||||
"http 1.1.0",
|
||||
"jni",
|
||||
"log",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
@@ -12413,9 +12264,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "2.4.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2900399c239a471bcff7f15c4399eb1a8c4fe511ba2853e07c996d771a5e0a4"
|
||||
checksum = "1f037e66c7638cc0a2213f61566932b9a06882b8346486579c90e4b019bac447"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"brotli 7.0.0",
|
||||
@@ -12445,7 +12296,7 @@ dependencies = [
|
||||
"toml 0.8.20",
|
||||
"url",
|
||||
"urlpattern",
|
||||
"uuid 1.8.0",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -12898,18 +12749,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
"thiserror 1.0.63",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.16"
|
||||
@@ -13229,11 +13068,11 @@ dependencies = [
|
||||
"dirs 6.0.0",
|
||||
"libappindicator",
|
||||
"muda",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
@@ -13701,15 +13540,6 @@ dependencies = [
|
||||
"zip 1.1.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.8.0"
|
||||
@@ -14229,10 +14059,10 @@ version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
|
||||
dependencies = [
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
"raw-window-handle",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-version",
|
||||
@@ -14871,7 +14701,7 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
"ureq",
|
||||
"url",
|
||||
"uuid 1.8.0",
|
||||
"uuid",
|
||||
"void",
|
||||
"winapi 0.3.9",
|
||||
"windows-sys 0.48.0",
|
||||
@@ -14904,7 +14734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b19b78efae8b853c6c817e8752fc1dbf9cab8a8ffe9c30f399bd750ccf0f0730"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.1",
|
||||
"block2 0.6.0",
|
||||
"cookie 0.18.1",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
@@ -14918,10 +14748,10 @@ dependencies = [
|
||||
"kuchikiki",
|
||||
"libc",
|
||||
"ndk 0.9.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2 0.6.0",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
"once_cell",
|
||||
|
||||
@@ -24,7 +24,7 @@ members = [
|
||||
"crates/cli/production/migration-tool",
|
||||
|
||||
# Desktop apps
|
||||
"crates/desktop/artcraft",
|
||||
"crates/desktop/tauri-artcraft",
|
||||
"crates/desktop/tauri-realtime",
|
||||
|
||||
# Shared libraries
|
||||
@@ -39,7 +39,6 @@ members = [
|
||||
"crates/lib/bootstrap",
|
||||
"crates/lib/bucket_paths",
|
||||
"crates/lib/caching/memory_caching",
|
||||
"crates/lib/caching/memory_store",
|
||||
"crates/lib/caching/redis_caching",
|
||||
"crates/lib/concurrency",
|
||||
"crates/lib/cookies",
|
||||
@@ -118,7 +117,6 @@ members = [
|
||||
"crates/vendor/candle/candle-transformers",
|
||||
"crates/vendor/candle/tensor-tools",
|
||||
"crates/vendor/limitation",
|
||||
"crates/vendor/tauri-plugin-http",
|
||||
# NB(bt,2023-12-12): Newrelic keeps breaking with upgrades. Kill for now.
|
||||
#"crates/vendor/newrelic-telemetry-sdk-rust",
|
||||
|
||||
@@ -149,10 +147,6 @@ publish = false
|
||||
# - tauri-runtime-wry
|
||||
# - tauri-utils
|
||||
#
|
||||
# Broken on Windows:
|
||||
# - core-foundation
|
||||
# - core-foundation-sys
|
||||
#
|
||||
workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
|
||||
|
||||
# External libs
|
||||
@@ -163,7 +157,6 @@ base64 = "0.22.1"
|
||||
chrono = { version = "0.4.40", features = ["serde"] }
|
||||
cookie = "0.18.1"
|
||||
hex-literal = "1.0.0"
|
||||
indexmap = "2.9.0"
|
||||
log = "0.4.27"
|
||||
once_cell = "1.21.3"
|
||||
rand = "0.9.0"
|
||||
@@ -174,7 +167,6 @@ serde_derive = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
serde_yaml = "0.9.1"
|
||||
sha3 = "0.10.8"
|
||||
tauri = { version = "2.4.1", features = ["unstable", "devtools"] } # NB: "devtools" prevents App Store distribution
|
||||
thiserror = "2.0.12"
|
||||
toml = "0.8.20"
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
@@ -185,11 +177,7 @@ enums = { path = "crates/_types/enums" }
|
||||
enums_public = { path = "crates/_types/enums_public" }
|
||||
errors = { path = "crates/lib/errors" }
|
||||
filesys = { path = "crates/lib/files/filesys" }
|
||||
idempotency = { path = "crates/lib/idempotency" }
|
||||
images = { path = "crates/lib/files/images" }
|
||||
memory_caching = { path = "crates/lib/caching/memory_caching" }
|
||||
memory_store = { path = "crates/lib/caching/memory_store" }
|
||||
mimetypes = { path = "crates/lib/files/mimetypes" }
|
||||
openai_sora_client = { path = "crates/lib/api_clients/openai_sora_client" }
|
||||
primitives = { path = "crates/lib/data/primitives" }
|
||||
shared_service_components = { path = "crates/lib/shared_service_components" }
|
||||
|
||||
@@ -28,12 +28,11 @@ pub enum MediaFileOriginCategory {
|
||||
DeviceApi,
|
||||
|
||||
/// From Storyteller Studio Engine
|
||||
#[deprecated(note="This db field should only denote file provenance, not the product.")]
|
||||
#[serde(rename = "studio")]
|
||||
StorytellerStudio,
|
||||
|
||||
/// From Storyteller Studio Engine
|
||||
#[deprecated(note = "DO NOT USE. Originally deprecated in favor of `StorytellerStudio`.")]
|
||||
#[deprecated(note = "Use StorytellerStudio instead")]
|
||||
StoryEngine,
|
||||
}
|
||||
|
||||
|
||||
@@ -39,10 +39,6 @@ pub enum MediaFileOriginProductCategory {
|
||||
#[serde(rename = "vst")]
|
||||
VideoStyleTransfer,
|
||||
|
||||
/// Image Studio
|
||||
#[serde(rename = "image_studio")]
|
||||
ImageStudio,
|
||||
|
||||
/// Storyteller Studio
|
||||
#[serde(rename = "studio")]
|
||||
StorytellerStudio,
|
||||
@@ -91,7 +87,6 @@ impl MediaFileOriginProductCategory {
|
||||
Self::FaceFusion => "face_fusion",
|
||||
Self::FaceMirror => "face_mirror",
|
||||
Self::VideoStyleTransfer => "vst",
|
||||
Self::ImageStudio => "image_studio",
|
||||
Self::StorytellerStudio => "studio",
|
||||
Self::TextToSpeech => "tts",
|
||||
Self::VoiceConversion => "voice_conversion",
|
||||
@@ -110,7 +105,6 @@ impl MediaFileOriginProductCategory {
|
||||
"face_fusion" => Ok(Self::FaceFusion),
|
||||
"face_mirror" => Ok(Self::FaceMirror),
|
||||
"vst" => Ok(Self::VideoStyleTransfer),
|
||||
"image_studio" => Ok(Self::ImageStudio),
|
||||
"studio" => Ok(Self::StorytellerStudio),
|
||||
"tts" => Ok(Self::TextToSpeech),
|
||||
"voice_conversion" => Ok(Self::VoiceConversion),
|
||||
@@ -132,7 +126,6 @@ impl MediaFileOriginProductCategory {
|
||||
Self::FaceFusion,
|
||||
Self::FaceMirror,
|
||||
Self::VideoStyleTransfer,
|
||||
Self::ImageStudio,
|
||||
Self::StorytellerStudio,
|
||||
Self::TextToSpeech,
|
||||
Self::VoiceConversion,
|
||||
@@ -160,7 +153,6 @@ mod tests {
|
||||
assert_serialization(MediaFileOriginProductCategory::FaceFusion, "face_fusion");
|
||||
assert_serialization(MediaFileOriginProductCategory::FaceMirror, "face_mirror");
|
||||
assert_serialization(MediaFileOriginProductCategory::VideoStyleTransfer, "vst");
|
||||
assert_serialization(MediaFileOriginProductCategory::ImageStudio, "image_studio");
|
||||
assert_serialization(MediaFileOriginProductCategory::StorytellerStudio, "studio");
|
||||
assert_serialization(MediaFileOriginProductCategory::TextToSpeech, "tts");
|
||||
assert_serialization(MediaFileOriginProductCategory::VoiceConversion, "voice_conversion");
|
||||
@@ -178,7 +170,6 @@ mod tests {
|
||||
assert_eq!(MediaFileOriginProductCategory::FaceFusion.to_str(), "face_fusion");
|
||||
assert_eq!(MediaFileOriginProductCategory::FaceMirror.to_str(), "face_mirror");
|
||||
assert_eq!(MediaFileOriginProductCategory::VideoStyleTransfer.to_str(), "vst");
|
||||
assert_eq!(MediaFileOriginProductCategory::ImageStudio.to_str(), "image_studio");
|
||||
assert_eq!(MediaFileOriginProductCategory::StorytellerStudio.to_str(), "studio");
|
||||
assert_eq!(MediaFileOriginProductCategory::TextToSpeech.to_str(), "tts");
|
||||
assert_eq!(MediaFileOriginProductCategory::VoiceConversion.to_str(), "voice_conversion");
|
||||
@@ -196,7 +187,6 @@ mod tests {
|
||||
assert_eq!(MediaFileOriginProductCategory::from_str("face_fusion").unwrap(), MediaFileOriginProductCategory::FaceFusion);
|
||||
assert_eq!(MediaFileOriginProductCategory::from_str("face_mirror").unwrap(), MediaFileOriginProductCategory::FaceMirror);
|
||||
assert_eq!(MediaFileOriginProductCategory::from_str("vst").unwrap(), MediaFileOriginProductCategory::VideoStyleTransfer);
|
||||
assert_eq!(MediaFileOriginProductCategory::from_str("image_studio").unwrap(), MediaFileOriginProductCategory::ImageStudio);
|
||||
assert_eq!(MediaFileOriginProductCategory::from_str("studio").unwrap(), MediaFileOriginProductCategory::StorytellerStudio);
|
||||
assert_eq!(MediaFileOriginProductCategory::from_str("tts").unwrap(), MediaFileOriginProductCategory::TextToSpeech);
|
||||
assert_eq!(MediaFileOriginProductCategory::from_str("voice_conversion").unwrap(), MediaFileOriginProductCategory::VoiceConversion);
|
||||
@@ -210,13 +200,12 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = MediaFileOriginProductCategory::all_variants();
|
||||
assert_eq!(variants.len(), 14);
|
||||
assert_eq!(variants.len(), 13);
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::Unknown));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::FaceAnimator));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::FaceFusion));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::FaceMirror));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::VideoStyleTransfer));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::ImageStudio));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::StorytellerStudio));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::TextToSpeech));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::VoiceConversion));
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"log:default",
|
||||
"upload:allow-download",
|
||||
"upload:default",
|
||||
{
|
||||
"identifier": "http:default",
|
||||
"allow": [
|
||||
{
|
||||
"url": "https://*.storyteller.ai"
|
||||
},
|
||||
{
|
||||
"url": "https://*.fakeyou.com"
|
||||
}
|
||||
],
|
||||
"deny": [
|
||||
{
|
||||
"url": "https://private.tauri.app"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 1023 B |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
@@ -1,46 +0,0 @@
|
||||
use crate::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use serde_derive::Serialize;
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GetAppPreferencesResponse {
|
||||
pub preferences: AppPreferencesPayload,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AppPreferencesPayload {
|
||||
/// The downloads directory to use when a user downloads a file.
|
||||
pub preferred_download_directory: PreferredDownloadDirectory,
|
||||
/// Play sounds on events.
|
||||
pub play_sounds: bool,
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_app_preferences_command(
|
||||
app_prefs: State<'_, AppPreferencesManager>,
|
||||
) -> Result<GetAppPreferencesResponse, String> {
|
||||
info!("get_app_preferences_command called");
|
||||
|
||||
let result = get_prefs(&app_prefs)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error getting app preferences: {:?}", err);
|
||||
format!("Error getting app preferences: {:?}", err)
|
||||
})?;
|
||||
|
||||
Ok(GetAppPreferencesResponse {
|
||||
preferences: result,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_prefs(app_prefs: &AppPreferencesManager) -> AnyhowResult<AppPreferencesPayload> {
|
||||
let prefs = app_prefs.get_clone()?;
|
||||
Ok(AppPreferencesPayload {
|
||||
preferred_download_directory: prefs.preferred_download_directory,
|
||||
play_sounds: prefs.play_sounds,
|
||||
})
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod get_app_preferences_command;
|
||||
pub mod update_app_preference_command;
|
||||
@@ -1,70 +0,0 @@
|
||||
use crate::state::app_preferences::app_preferences_manager::AppPreferencesManager;
|
||||
use crate::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
/// For now, we'll only update a single preference at a time.
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateAppPreferencesRequest {
|
||||
pub preference: PreferenceName,
|
||||
/// We'll decode this with respect to the preference value.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PreferenceName {
|
||||
PreferredDownloadDirectory,
|
||||
PlaySounds,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UpdateAppPreferencesResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_app_preferences_command(
|
||||
request: UpdateAppPreferencesRequest,
|
||||
app_prefs: State<'_, AppPreferencesManager>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
) -> Result<UpdateAppPreferencesResponse, String> {
|
||||
info!("update_app_preferences_command called");
|
||||
|
||||
update_prefs(request, &app_prefs, &app_data_root)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error getting app preferences: {:?}", err);
|
||||
format!("Error getting app preferences: {:?}", err)
|
||||
})?;
|
||||
|
||||
Ok(UpdateAppPreferencesResponse {
|
||||
success: true,
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_prefs(
|
||||
request: UpdateAppPreferencesRequest,
|
||||
app_prefs: &AppPreferencesManager,
|
||||
app_data_root: &AppDataRoot,
|
||||
) -> AnyhowResult<()> {
|
||||
let mut prefs = app_prefs.get_clone()?;
|
||||
|
||||
match request.preference {
|
||||
PreferenceName::PreferredDownloadDirectory => {
|
||||
prefs.preferred_download_directory = request.value.parse::<PreferredDownloadDirectory>()?;
|
||||
}
|
||||
PreferenceName::PlaySounds => {
|
||||
prefs.play_sounds = request.value.parse::<bool>()?;
|
||||
}
|
||||
}
|
||||
|
||||
app_prefs.set_clone(&prefs)?;
|
||||
app_data_root.settings_dir().write_app_preferences(&prefs)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod app_preferences;
|
||||
pub mod flip_image;
|
||||
pub mod platform_info_command;
|
||||
pub mod sora;
|
||||
@@ -1,63 +0,0 @@
|
||||
use crate::state::os_platform::OsPlatform;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||
use image::ImageReader;
|
||||
use log::info;
|
||||
use serde_derive::Serialize;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct PlatformInfoResponse {
|
||||
pub os_platform: DetectedOs,
|
||||
pub webview_runtime: WebviewRuntime,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DetectedOs {
|
||||
Windows,
|
||||
#[serde(rename = "macos")]
|
||||
MacOS,
|
||||
Linux,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WebviewRuntime {
|
||||
#[serde(rename = "webkit_safari")]
|
||||
WebkitSafari,
|
||||
#[serde(rename = "webkit_gtk")]
|
||||
WebkitGtk,
|
||||
#[serde(rename = "edge_webview_2")]
|
||||
EdgeWebview2,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn platform_info_command() -> Result<PlatformInfoResponse, String> {
|
||||
info!("platform_info_command called...");
|
||||
|
||||
let os_platform = match OsPlatform::maybe_get() {
|
||||
Some(OsPlatform::Linux) => DetectedOs::Linux,
|
||||
Some(OsPlatform::MacOs) => DetectedOs::MacOS,
|
||||
Some(OsPlatform::Windows) => DetectedOs::Windows,
|
||||
None => DetectedOs::Unknown,
|
||||
};
|
||||
|
||||
// These are the webviews that Tauri uses on each OS:
|
||||
// https://tauri.app/reference/webview-versions/
|
||||
let webview_runtime = match os_platform {
|
||||
DetectedOs::Windows => WebviewRuntime::EdgeWebview2,
|
||||
DetectedOs::MacOS => WebviewRuntime::WebkitSafari,
|
||||
DetectedOs::Linux => WebviewRuntime::WebkitGtk,
|
||||
DetectedOs::Unknown => WebviewRuntime::Unknown,
|
||||
};
|
||||
|
||||
Ok(PlatformInfoResponse {
|
||||
os_platform,
|
||||
webview_runtime,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
pub mod commands;
|
||||
pub mod events;
|
||||
pub mod state;
|
||||
pub mod threads;
|
||||
pub mod utils;
|
||||
|
||||
use tauri::Manager;
|
||||
|
||||
use crate::commands::flip_image::flip_image;
|
||||
use crate::commands::platform_info_command::platform_info_command;
|
||||
use crate::commands::sora::open_sora_login_command::open_sora_login_command;
|
||||
use crate::commands::sora::sora_image_generation_command::sora_image_generation_command;
|
||||
use crate::commands::sora::sora_image_remix_command::sora_image_remix_command;
|
||||
use crate::commands::app_preferences::get_app_preferences_command::get_app_preferences_command;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::main_window_size::MainWindowSize;
|
||||
use crate::state::sora::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::state::sora::sora_task_queue::SoraTaskQueue;
|
||||
use crate::state::storyteller::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::threads::discord_presence_thread::discord_presence_thread;
|
||||
use crate::threads::main_window_thread::main_window_thread::main_window_thread;
|
||||
use crate::threads::sora_session_login_thread::sora_session_login_thread;
|
||||
use crate::threads::sora_task_polling_thread::sora_task_polling_thread;
|
||||
use crate::utils::webview_unsafe::webview_unsafe_for_app;
|
||||
|
||||
use crate::state::app_preferences::app_preferences_manager::load_app_preferences_or_default;
|
||||
use tauri_plugin_http;
|
||||
use tauri_plugin_log::Target;
|
||||
use tauri_plugin_log::TargetKind;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
// NB: Tauri wants to install the logger itself, so we can't rely on the logger crate
|
||||
// until the tauri runtime begins.
|
||||
println!("Loading config...");
|
||||
let app_data_root = AppDataRoot::create_default().expect("data directory should be created");
|
||||
let app_data_root_2 = app_data_root.clone();
|
||||
|
||||
println!("Loading app preferences...");
|
||||
let app_preferences = load_app_preferences_or_default(&app_data_root);
|
||||
|
||||
println!("Attempting to read existing artcraft credentials...");
|
||||
let storyteller_creds_manager = StorytellerCredentialManager::initialize_from_disk_infallible(&app_data_root);
|
||||
let storyteller_creds_manager_2 = storyteller_creds_manager.clone();
|
||||
|
||||
println!("Attempting to read existing credentials...");
|
||||
let sora_creds_manager = SoraCredentialManager::initialize_from_disk_infallible(&app_data_root);
|
||||
let sora_creds_manager_2 = sora_creds_manager.clone();
|
||||
|
||||
// Other state
|
||||
let sora_task_queue = SoraTaskQueue::new();
|
||||
let sora_task_queue_2 = sora_task_queue.clone();
|
||||
|
||||
println!("Initializing backend runtime...");
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_upload::init())
|
||||
.plugin(tauri_plugin_log::Builder::new()
|
||||
.level(log::LevelFilter::Info)
|
||||
.targets(vec![
|
||||
Target::new(TargetKind::Stdout),
|
||||
Target::new(TargetKind::LogDir {
|
||||
file_name: Some(app_data_root.log_file_name_str().to_string())
|
||||
}),
|
||||
])
|
||||
.build())
|
||||
.setup(move |app| {
|
||||
// TODO(bt): This is broken on windows
|
||||
// log_environment_details();
|
||||
|
||||
//if cfg!(debug_assertions) {
|
||||
// app.handle().plugin(
|
||||
// tauri_plugin_log::Builder::default()
|
||||
// .level(log::LevelFilter::Info)
|
||||
// .build(),
|
||||
// )?;
|
||||
//}
|
||||
let app = app.handle().clone();
|
||||
|
||||
let result = webview_unsafe_for_app(&app);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Error setting webview unsafe: {:?}", err);
|
||||
}
|
||||
|
||||
let app_2 = app.clone();
|
||||
let app_3 = app.clone();
|
||||
let app_4 = app.clone();
|
||||
let app_data_root_3 = app_data_root_2.clone();
|
||||
let app_data_root_4 = app_data_root_2.clone();
|
||||
let sora_creds_manager_3 = sora_creds_manager_2.clone();
|
||||
let storyteller_creds_manager_3 = storyteller_creds_manager.clone();
|
||||
|
||||
match MainWindowSize::from_filesystem_configs(&app_data_root_3) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(size)) => {
|
||||
println!("Resizing window to: {:?}", size);
|
||||
let result = size.apply_to_main_window(&app);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Could not set window size: {:?}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read window size from disk: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
tauri::async_runtime::spawn(sora_session_login_thread(app_2, app_data_root_2, sora_creds_manager_2));
|
||||
tauri::async_runtime::spawn(main_window_thread(app_3, app_data_root_3, storyteller_creds_manager_2));
|
||||
tauri::async_runtime::spawn(sora_task_polling_thread(app_4, app_data_root_4, sora_creds_manager_3, storyteller_creds_manager_3, sora_task_queue_2));
|
||||
tauri::async_runtime::spawn(discord_presence_thread());
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.manage(app_data_root)
|
||||
.manage(app_preferences)
|
||||
.manage(sora_creds_manager)
|
||||
.manage(sora_task_queue)
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
flip_image,
|
||||
get_app_preferences_command,
|
||||
open_sora_login_command,
|
||||
platform_info_command,
|
||||
sora_image_generation_command,
|
||||
sora_image_remix_command,
|
||||
])
|
||||
.run(tauri::generate_context!("tauri.conf.json"))
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
use crate::state::app_preferences::app_preferences_serializable::AppPreferencesSerializable;
|
||||
use crate::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppPreferences {
|
||||
/// The downloads directory to use when a user downloads a file.
|
||||
pub preferred_download_directory: PreferredDownloadDirectory,
|
||||
|
||||
/// Play sounds on events.
|
||||
pub play_sounds: bool,
|
||||
}
|
||||
|
||||
impl Default for AppPreferences {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
preferred_download_directory: PreferredDownloadDirectory::SystemDefault,
|
||||
play_sounds: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppPreferences {
|
||||
pub fn load_from_file_or_default(data_root: &AppDataRoot) -> Self {
|
||||
let filename = data_root.settings_dir().get_app_preferences_path();
|
||||
if !filename.exists() {
|
||||
return Self::default();
|
||||
}
|
||||
|
||||
match AppPreferencesSerializable::load_from_file(data_root) {
|
||||
Ok(Some(serializable)) => {
|
||||
serializable.to_preferences()
|
||||
}
|
||||
Ok(None) => {
|
||||
Self::default()
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error loading app preferences: {}", err);
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_serializable(&self) -> AppPreferencesSerializable {
|
||||
AppPreferencesSerializable {
|
||||
preferred_download_directory: Some(self.preferred_download_directory.clone()),
|
||||
play_sounds: Some(self.play_sounds),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
use crate::state::app_preferences::app_preferences::AppPreferences;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use memory_store::clone_cell::CloneCell;
|
||||
|
||||
pub type AppPreferencesManager = CloneCell<AppPreferences>;
|
||||
|
||||
pub fn load_app_preferences_or_default(data_root: &AppDataRoot) -> AppPreferencesManager {
|
||||
let prefs = AppPreferences::load_from_file_or_default(data_root);
|
||||
AppPreferencesManager::with_owned(prefs)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
use crate::state::app_preferences::app_preferences::AppPreferences;
|
||||
use crate::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AppPreferencesSerializable {
|
||||
/// The downloads directory to use when a user downloads a file.
|
||||
pub preferred_download_directory: Option<PreferredDownloadDirectory>,
|
||||
|
||||
/// Play sounds on events.
|
||||
pub play_sounds: Option<bool>,
|
||||
}
|
||||
|
||||
impl AppPreferencesSerializable {
|
||||
pub fn load_from_file(app_data_root: &AppDataRoot) -> AnyhowResult<Option<Self>> {
|
||||
let filename = app_data_root.settings_dir().get_app_preferences_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))
|
||||
}
|
||||
|
||||
pub fn save_to_file(&self, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
let filename = app_data_root.settings_dir().get_app_preferences_path();
|
||||
let json = serde_json::to_string(self)?;
|
||||
std::fs::write(filename, json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_preferences(&self) -> AppPreferences {
|
||||
let mut preferences = AppPreferences::default();
|
||||
|
||||
if let Some(preferred_download_directory) = &self.preferred_download_directory {
|
||||
preferences.preferred_download_directory = preferred_download_directory.clone();
|
||||
}
|
||||
|
||||
if let Some(play_sounds) = self.play_sounds {
|
||||
preferences.play_sounds = play_sounds;
|
||||
}
|
||||
|
||||
preferences
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod app_preferences;
|
||||
pub mod app_preferences_manager;
|
||||
pub mod app_preferences_serializable;
|
||||
pub mod preferred_download_directory;
|
||||
@@ -1,100 +0,0 @@
|
||||
use errors::AnyhowError;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
const SYSTEM_DEFAULT_SENTINEL_VALUE: &str = "system_default";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PreferredDownloadDirectory {
|
||||
/// The system-default downloads directory, which varies by OS.
|
||||
/// NB: Serializes as `"system_default"` in JSON.
|
||||
SystemDefault,
|
||||
|
||||
/// A user-defined path
|
||||
/// NB: Serializes as `{"custom": "/path/to/dir"}` in JSON.
|
||||
Custom(PathBuf),
|
||||
}
|
||||
|
||||
impl FromStr for PreferredDownloadDirectory {
|
||||
type Err = AnyhowError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s == SYSTEM_DEFAULT_SENTINEL_VALUE || s == "\"system_default\"" {
|
||||
Ok(PreferredDownloadDirectory::SystemDefault)
|
||||
} else {
|
||||
// TODO: If it gets serialized as JSON, we may need to remove the `{"custom": ...` wrapping layer.
|
||||
let path = PathBuf::from(s);
|
||||
Ok(PreferredDownloadDirectory::Custom(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PreferredDownloadDirectory {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
PreferredDownloadDirectory::SystemDefault => write!(f, "{}", SYSTEM_DEFAULT_SENTINEL_VALUE),
|
||||
PreferredDownloadDirectory::Custom(path) => write!(f, "{}", path.to_string_lossy()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
mod json {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn to_json_system_default() {
|
||||
let val = PreferredDownloadDirectory::SystemDefault;
|
||||
let val = serde_json::to_string(&val).unwrap();
|
||||
assert_eq!(&val, "\"system_default\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_json_custom() {
|
||||
let val = PreferredDownloadDirectory::Custom("/tmp".into());
|
||||
let val = serde_json::to_string(&val).unwrap();
|
||||
assert_eq!(&val, "{\"custom\":\"/tmp\"}");
|
||||
}
|
||||
}
|
||||
|
||||
mod string {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn to_string_system_default() {
|
||||
let val = PreferredDownloadDirectory::SystemDefault;
|
||||
let val = val.to_string();
|
||||
assert_eq!(&val, "system_default");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string_custom() {
|
||||
let val = PreferredDownloadDirectory::Custom("/tmp/foo".into());
|
||||
let val = val.to_string();
|
||||
assert_eq!(&val, "/tmp/foo");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_string_system_default() {
|
||||
let val = "system_default";
|
||||
let val = PreferredDownloadDirectory::from_str(val).unwrap();
|
||||
assert_eq!(val, PreferredDownloadDirectory::SystemDefault);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_string_custom() {
|
||||
let val = "/tmp/foo";
|
||||
let val = PreferredDownloadDirectory::from_str(val).unwrap();
|
||||
assert_eq!(val, PreferredDownloadDirectory::Custom(PathBuf::from("/tmp/foo")));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
|
||||
pub struct AppStartupTime {
|
||||
startup_time: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl AppStartupTime {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
startup_time: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_startup_time(&self) -> DateTime<Utc> {
|
||||
self.startup_time
|
||||
}
|
||||
|
||||
pub fn time_delta_since(&self) -> TimeDelta {
|
||||
let now = Utc::now();
|
||||
now.signed_duration_since(self.startup_time)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use crate::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use chrono::{DateTime, Local};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppAssetsDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DataSubdir for AppAssetsDir {
|
||||
const DIRECTORY_NAME: &'static str = "assets";
|
||||
|
||||
fn new_from<P: AsRef<Path>>(dir: P) -> Self {
|
||||
Self {
|
||||
path: dir.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl AppAssetsDir {
|
||||
/// Return the current date asset directory
|
||||
/// If it doesn't exist, create it.
|
||||
pub fn make_or_get_current_date_dir(&self) -> anyhow::Result<PathBuf> {
|
||||
let format: DateTime<Local> = Local::now();
|
||||
let date_directory_name = format.format("%Y-%d-%m").to_string();
|
||||
let full_path = self.path.join(date_directory_name);
|
||||
if !full_path.exists() {
|
||||
std::fs::create_dir(&full_path)?;
|
||||
}
|
||||
Ok(full_path)
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
use crate::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppCredentialsDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DataSubdir for AppCredentialsDir {
|
||||
const DIRECTORY_NAME: &'static str = "credentials";
|
||||
|
||||
fn new_from<P: AsRef<Path>> (dir: P) -> Self {
|
||||
Self {
|
||||
path: dir.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl AppCredentialsDir {
|
||||
pub fn get_sora_cookie_file_path(&self) -> PathBuf {
|
||||
self.path.join("sora_cookies.txt")
|
||||
}
|
||||
|
||||
pub fn get_sora_bearer_token_file_path(&self) -> PathBuf {
|
||||
self.path.join("sora_bearer_token.txt")
|
||||
}
|
||||
|
||||
pub fn get_sora_sentinel_file_path(&self) -> PathBuf {
|
||||
self.path.join("sora_sentinel.txt")
|
||||
}
|
||||
|
||||
pub fn get_storyteller_avt_cookie_file_path(&self) -> PathBuf {
|
||||
self.path.join("artcraft_avt.txt")
|
||||
}
|
||||
|
||||
pub fn get_storyteller_session_cookie_file_path(&self) -> PathBuf {
|
||||
self.path.join("artcraft_session.txt")
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
use crate::state::data_dir::app_assets_dir::AppAssetsDir;
|
||||
use crate::state::data_dir::app_credentials_dir::AppCredentialsDir;
|
||||
use crate::state::data_dir::app_downloads_dir::AppDownloadsDir;
|
||||
use crate::state::data_dir::app_settings_dir::AppSettingsDir;
|
||||
use crate::state::data_dir::app_weights_dir::AppWeightsDir;
|
||||
use crate::state::data_dir::temporary_dir::TemporaryDir;
|
||||
use crate::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::state::expanduser::expanduser;
|
||||
use crate::state::os_platform::OsPlatform;
|
||||
use anyhow::anyhow;
|
||||
use directories::UserDirs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const DEFAULT_DATA_DIR : &str = "Artcraft";
|
||||
|
||||
/// Note: Tauri appends ".log" to the end of the filename.
|
||||
const LOG_FILE_NAME : &str = "application_debug";
|
||||
|
||||
/// The path to the application data directory, which includes "asset" and "weights" data.
|
||||
#[derive(Clone)]
|
||||
pub struct AppDataRoot {
|
||||
path: PathBuf,
|
||||
assets_dir: AppAssetsDir,
|
||||
credentials_dir: AppCredentialsDir,
|
||||
downloads_dir: AppDownloadsDir,
|
||||
settings_dir: AppSettingsDir,
|
||||
weights_dir: AppWeightsDir,
|
||||
temp_dir: TemporaryDir,
|
||||
log_file_name: PathBuf,
|
||||
log_file_name_string: String,
|
||||
}
|
||||
|
||||
impl AppDataRoot {
|
||||
pub fn create_default() -> anyhow::Result<Self> {
|
||||
let directory = get_default_data_dir()?;
|
||||
Self::create_existing(directory)
|
||||
}
|
||||
|
||||
pub fn create_existing<P: AsRef<Path>>(dir: P) -> anyhow::Result<Self> {
|
||||
let mut dir = dir.as_ref().to_path_buf();
|
||||
|
||||
match OsPlatform::get() {
|
||||
OsPlatform::Linux | OsPlatform::MacOs => {
|
||||
if let Some(d) = dir.as_os_str().to_str() {
|
||||
dir = expanduser(d)?;
|
||||
}
|
||||
},
|
||||
OsPlatform::Windows => {}
|
||||
}
|
||||
|
||||
if !dir.is_dir() {
|
||||
println!("Creating directory {:?}", dir);
|
||||
std::fs::create_dir_all(&dir)?;
|
||||
}
|
||||
|
||||
match dir.canonicalize() {
|
||||
Ok(d) => dir = d,
|
||||
Err(err) => {
|
||||
println!("Error canonicalizing {:?}: {}", dir, err);
|
||||
}
|
||||
}
|
||||
|
||||
let assets_dir = AppAssetsDir::get_or_create_in_root_dir(&dir)?;
|
||||
let credentials_dir = AppCredentialsDir::get_or_create_in_root_dir(&dir)?;
|
||||
let downloads_dir = AppDownloadsDir::get_or_create_in_root_dir(&dir)?;
|
||||
let settings_dir = AppSettingsDir::get_or_create_in_root_dir(&dir)?;
|
||||
let temp_dir = TemporaryDir::get_or_create_in_root_dir(&dir)?;
|
||||
let weights_dir = AppWeightsDir::get_or_create_in_root_dir(&dir)?;
|
||||
let log_file_name = dir.join(LOG_FILE_NAME);
|
||||
let log_file_name_string = log_file_name
|
||||
.to_str()
|
||||
.ok_or(anyhow!("couldn't convert log path to str"))?
|
||||
.to_string();
|
||||
|
||||
Ok(Self {
|
||||
path: dir,
|
||||
assets_dir,
|
||||
credentials_dir,
|
||||
downloads_dir,
|
||||
settings_dir,
|
||||
weights_dir,
|
||||
temp_dir,
|
||||
log_file_name,
|
||||
log_file_name_string,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn assets_dir(&self) -> &AppAssetsDir {
|
||||
&self.assets_dir
|
||||
}
|
||||
|
||||
pub fn credentials_dir(&self) -> &AppCredentialsDir {
|
||||
&self.credentials_dir
|
||||
}
|
||||
|
||||
pub fn downloads_dir(&self) -> &AppDownloadsDir {
|
||||
&self.downloads_dir
|
||||
}
|
||||
|
||||
pub fn settings_dir(&self) -> &AppSettingsDir {
|
||||
&self.settings_dir
|
||||
}
|
||||
|
||||
pub fn weights_dir(&self) -> &AppWeightsDir {
|
||||
&self.weights_dir
|
||||
}
|
||||
|
||||
pub fn temp_dir(&self) -> &TemporaryDir {
|
||||
&self.temp_dir
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn log_file_name(&self) -> &Path {
|
||||
&self.log_file_name
|
||||
}
|
||||
|
||||
pub fn log_file_name_str(&self) -> &str {
|
||||
&self.log_file_name_string
|
||||
}
|
||||
|
||||
pub fn get_sora_cookie_file_path(&self) -> PathBuf {
|
||||
self.credentials_dir().get_sora_cookie_file_path()
|
||||
}
|
||||
|
||||
pub fn get_sora_bearer_token_file_path(&self) -> PathBuf {
|
||||
self.credentials_dir().get_sora_bearer_token_file_path()
|
||||
}
|
||||
|
||||
pub fn get_sora_sentinel_file_path(&self) -> PathBuf {
|
||||
self.credentials_dir().get_sora_sentinel_file_path()
|
||||
}
|
||||
|
||||
pub fn get_window_size_config_file(&self) -> PathBuf {
|
||||
self.path.join("window_size.json")
|
||||
}
|
||||
}
|
||||
|
||||
// eg. /home/bob/artcraft, /Users/bob/artcraft, or C:\Users\Alice\artcraft
|
||||
fn get_default_data_dir() -> anyhow::Result<PathBuf> {
|
||||
Ok(UserDirs::new()
|
||||
.ok_or_else(|| anyhow!("could not determine user home directory"))?
|
||||
.home_dir()
|
||||
.join(DEFAULT_DATA_DIR))
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
use crate::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppDownloadsDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DataSubdir for AppDownloadsDir {
|
||||
const DIRECTORY_NAME: &'static str = "downloads";
|
||||
|
||||
fn new_from<P: AsRef<Path>>(dir: P) -> Self {
|
||||
Self {
|
||||
path: dir.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
use crate::state::app_preferences::app_preferences::AppPreferences;
|
||||
use crate::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// For now, let's just keep a collection of loose json files in a settings dir.
|
||||
/// Once we know the structure and schema, we can standardize on a single json file.
|
||||
#[derive(Clone)]
|
||||
pub struct AppSettingsDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DataSubdir for AppSettingsDir {
|
||||
const DIRECTORY_NAME: &'static str = "settings";
|
||||
|
||||
fn new_from<P: AsRef<Path>> (dir: P) -> Self {
|
||||
Self {
|
||||
path: dir.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl AppSettingsDir {
|
||||
pub fn get_app_preferences_path(&self) -> PathBuf {
|
||||
self.path.join("app_preferences.json")
|
||||
}
|
||||
|
||||
pub fn write_app_preferences(&self, prefs: &AppPreferences) -> AnyhowResult<()> {
|
||||
let serializable = prefs.to_serializable();
|
||||
let serializable = serde_json::to_string(&serializable)?;
|
||||
|
||||
let filename = self.get_app_preferences_path();
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(filename)?;
|
||||
|
||||
file.write_all(serializable.as_bytes())?;
|
||||
file.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
use crate::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppWeightsDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DataSubdir for AppWeightsDir {
|
||||
const DIRECTORY_NAME: &'static str = "weights";
|
||||
|
||||
fn new_from<P: AsRef<Path>>(dir: P) -> Self {
|
||||
Self {
|
||||
path: dir.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
pub mod app_assets_dir;
|
||||
pub mod app_credentials_dir;
|
||||
pub mod app_data_root;
|
||||
pub mod app_downloads_dir;
|
||||
pub mod app_settings_dir;
|
||||
pub mod app_weights_dir;
|
||||
pub mod temporary_dir;
|
||||
pub mod trait_data_subdir;
|
||||
@@ -1,38 +0,0 @@
|
||||
use crate::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tempdir::TempDir;
|
||||
use tempfile::{Builder, NamedTempFile};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TemporaryDir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl DataSubdir for TemporaryDir {
|
||||
const DIRECTORY_NAME: &'static str = "temp";
|
||||
|
||||
fn new_from<P: AsRef<Path>>(dir: P) -> Self {
|
||||
Self {
|
||||
path: dir.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
||||
fn path(&self) -> &Path {
|
||||
&self.path
|
||||
}
|
||||
}
|
||||
|
||||
impl TemporaryDir {
|
||||
pub fn with_prefix(&self, prefix: &str) -> anyhow::Result<TempDir> {
|
||||
let tempdir = TempDir::new_in(&self.path, prefix)?;
|
||||
Ok(tempdir)
|
||||
}
|
||||
|
||||
pub fn new_named_temp_file(&self) -> anyhow::Result<NamedTempFile> {
|
||||
let tempfile = Builder::new()
|
||||
.prefix("temp_")
|
||||
.suffix(".bin")
|
||||
.tempfile_in(&self.path)?;
|
||||
Ok(tempfile)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
use std::path::Path;
|
||||
|
||||
pub trait DataSubdir : Sized {
|
||||
const DIRECTORY_NAME: &'static str;
|
||||
|
||||
fn new_from<P: AsRef<Path>>(dir: P) -> Self;
|
||||
|
||||
fn path(&self) -> &Path;
|
||||
|
||||
fn get_or_create_in_root_dir<P: AsRef<Path>>(root_dir: P) -> anyhow::Result<Self> {
|
||||
Self::get_or_create_dir(root_dir.as_ref().join(Self::DIRECTORY_NAME))
|
||||
}
|
||||
|
||||
fn get_or_create_dir<P: AsRef<Path>>(dir: P) -> anyhow::Result<Self> {
|
||||
let mut dir = dir.as_ref().to_path_buf();
|
||||
|
||||
match dir.canonicalize() {
|
||||
Ok(d) => dir = d,
|
||||
Err(err) => {
|
||||
println!("Error canonicalizing {:?}: {}", dir, err);
|
||||
}
|
||||
}
|
||||
|
||||
if !dir.exists() {
|
||||
println!("Creating directory {:?}", dir);
|
||||
std::fs::create_dir(&dir)?;
|
||||
}
|
||||
|
||||
Ok(Self::new_from(dir))
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use tauri::{AppHandle, Manager, PhysicalSize, Window};
|
||||
|
||||
const MAIN_WINDOW_NAME: &str = "main";
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct MainWindowSize {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl MainWindowSize {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
|
||||
pub fn from_main_window(app: &AppHandle) -> AnyhowResult<Self> {
|
||||
let windows = app.windows();
|
||||
windows.get(MAIN_WINDOW_NAME)
|
||||
.map(|window| Self::from_window(window))
|
||||
.unwrap_or_else(|| Err(anyhow::anyhow!("Main window not found")))
|
||||
}
|
||||
|
||||
pub fn from_window(window: &Window) -> AnyhowResult<Self> {
|
||||
let size = window.inner_size()?;
|
||||
Ok(Self {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_filesystem_configs(app_data_root: &AppDataRoot) -> AnyhowResult<Option<Self>> {
|
||||
let filename = app_data_root.get_window_size_config_file();
|
||||
if !filename.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
let contents = std::fs::read_to_string(filename)?;
|
||||
let size: MainWindowSize = serde_json::from_str(&contents)?;
|
||||
Ok(Some(size))
|
||||
}
|
||||
|
||||
pub fn apply_to_main_window(&self, app: &AppHandle) -> AnyhowResult<()> {
|
||||
let windows = app.windows();
|
||||
let window = windows.get(MAIN_WINDOW_NAME)
|
||||
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
|
||||
window.set_size(PhysicalSize::new(self.width, self.height))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn persist_to_filesystem(&self, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
let filename = app_data_root.get_window_size_config_file();
|
||||
let json = serde_json::to_string(self)?;
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(filename)?;
|
||||
file.write_all(json.as_bytes())?;
|
||||
file.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_physical_size(&self) -> PhysicalSize<u32> {
|
||||
PhysicalSize::new(self.width, self.height)
|
||||
}
|
||||
|
||||
pub fn matches_physical_size(&self, size: &PhysicalSize<u32>) -> bool {
|
||||
self.width == size.width && self.height == size.height
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
pub mod app_preferences;
|
||||
pub mod app_startup_time;
|
||||
pub mod data_dir;
|
||||
pub mod expanduser;
|
||||
pub mod main_window_size;
|
||||
pub mod os_platform;
|
||||
pub mod sora;
|
||||
pub mod storyteller;
|
||||
@@ -1,24 +0,0 @@
|
||||
|
||||
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
|
||||
pub enum OsPlatform {
|
||||
Linux,
|
||||
MacOs,
|
||||
Windows,
|
||||
}
|
||||
|
||||
impl OsPlatform {
|
||||
pub fn maybe_get() -> Option<Self> {
|
||||
// Totally scientific.
|
||||
// https://doc.rust-lang.org/std/env/consts/constant.OS.html
|
||||
match std::env::consts::OS {
|
||||
"linux" => Some(Self::Linux),
|
||||
"windows" => Some(Self::Windows),
|
||||
"macos" | "apple" => Some(Self::MacOs),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get() -> Self {
|
||||
Self::maybe_get().unwrap_or(Self::Linux)
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
use chrono::{DateTime, Utc};
|
||||
use errors::AnyhowResult;
|
||||
use indexmap::IndexMap;
|
||||
use openai_sora_client::requests::image_gen::image_gen_status::TaskId;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TaskDetails {
|
||||
pub enqueue_time: DateTime<Utc>
|
||||
}
|
||||
|
||||
impl TaskDetails {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
enqueue_time: Utc::now()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TaskIdAndDetails {
|
||||
pub task_id: TaskId,
|
||||
pub details: TaskDetails,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SoraTaskQueue {
|
||||
// Insertion-order map of task IDs to task details
|
||||
queue: Arc<RwLock<IndexMap<TaskId, TaskDetails>>>
|
||||
}
|
||||
|
||||
impl SoraTaskQueue {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
queue: Arc::new(RwLock::new(IndexMap::new()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if queue is empty
|
||||
/// This is an O(1) operation.
|
||||
pub fn is_empty(&self) -> AnyhowResult<bool> {
|
||||
match self.queue.read() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err))
|
||||
}
|
||||
Ok(lock) => Ok(lock.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get length of set
|
||||
/// This is an O(1) operation.
|
||||
pub fn len(&self) -> AnyhowResult<usize> {
|
||||
match self.queue.read() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err))
|
||||
}
|
||||
Ok(lock) => Ok(lock.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if the key is in the queue
|
||||
/// This is an O(1) operation.
|
||||
pub fn contains_key(&self, task_id: &TaskId) -> AnyhowResult<bool> {
|
||||
match self.queue.read() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err))
|
||||
}
|
||||
Ok(lock) => Ok(lock.contains_key(task_id))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the first enqueued task
|
||||
/// This is an O(1) operation.
|
||||
pub fn first(&self) -> AnyhowResult<Option<TaskIdAndDetails>> {
|
||||
match self.queue.read() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err))
|
||||
}
|
||||
Ok(lock) => match lock.first() {
|
||||
None => Ok(None),
|
||||
Some((task_id, task_details)) => Ok(Some(TaskIdAndDetails {
|
||||
task_id: task_id.clone(),
|
||||
details: task_details.clone(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the last enqueued task
|
||||
/// This is an O(1) operation.
|
||||
pub fn last(&self) -> AnyhowResult<Option<TaskIdAndDetails>> {
|
||||
match self.queue.read() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err))
|
||||
}
|
||||
Ok(lock) => match lock.last() {
|
||||
None => Ok(None),
|
||||
Some((task_id, task_details)) => Ok(Some(TaskIdAndDetails {
|
||||
task_id: task_id.clone(),
|
||||
details: task_details.clone(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a new task. If a task already exists, we update its value.
|
||||
/// This is an O(1) operation.
|
||||
pub fn insert(&self, task_id: &TaskId) -> AnyhowResult<Option<TaskDetails>> {
|
||||
match self.queue.write() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err))
|
||||
}
|
||||
Ok(mut lock) => {
|
||||
// NB: Probably fine to compute TaskDetails here for now. We may eventually expose this if we include additional info.
|
||||
let task_details = TaskDetails::new();
|
||||
Ok(lock.insert(task_id.clone(), task_details))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a task by ID.
|
||||
/// This is an O(n) operation.
|
||||
pub fn remove(&self, task_id: &TaskId) -> AnyhowResult<Option<TaskDetails>> {
|
||||
match self.queue.write() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err))
|
||||
}
|
||||
Ok(mut lock) => {
|
||||
Ok(lock.shift_remove(task_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a list of tasks by ID.
|
||||
/// This is an O(n) operation.
|
||||
pub fn remove_list(&self, task_ids: &[&TaskId]) -> AnyhowResult<()> {
|
||||
match self.queue.write() {
|
||||
Err(err) => {
|
||||
Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err))
|
||||
}
|
||||
Ok(mut lock) => {
|
||||
// TODO: Cleaner way to do this?
|
||||
let mut set : HashSet<&TaskId, _> = HashSet::with_capacity(task_ids.len());
|
||||
set.extend(task_ids);
|
||||
lock.retain(|k, v| !set.contains(k));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use errors::AnyhowResult;
|
||||
use openai_sora_client::requests::image_gen::image_gen_status::TaskId;
|
||||
use crate::state::sora::sora_task_queue::SoraTaskQueue;
|
||||
|
||||
#[test]
|
||||
fn remove_list() -> AnyhowResult<()> {
|
||||
let task_queue = SoraTaskQueue::new();
|
||||
|
||||
assert!(task_queue.is_empty()?);
|
||||
assert_eq!(task_queue.len()?, 0);
|
||||
|
||||
task_queue.insert(&TaskId("a".to_string()))?;
|
||||
task_queue.insert(&TaskId("b".to_string()))?;
|
||||
task_queue.insert(&TaskId("c".to_string()))?;
|
||||
task_queue.insert(&TaskId("d".to_string()))?;
|
||||
task_queue.insert(&TaskId("e".to_string()))?;
|
||||
task_queue.insert(&TaskId("f".to_string()))?;
|
||||
|
||||
assert!(!task_queue.is_empty()?);
|
||||
assert_eq!(task_queue.len()?, 6);
|
||||
|
||||
task_queue.insert(&TaskId("a".to_string()))?;
|
||||
task_queue.insert(&TaskId("b".to_string()))?;
|
||||
task_queue.insert(&TaskId("c".to_string()))?;
|
||||
task_queue.insert(&TaskId("d".to_string()))?;
|
||||
task_queue.insert(&TaskId("e".to_string()))?;
|
||||
task_queue.insert(&TaskId("f".to_string()))?;
|
||||
|
||||
assert!(!task_queue.is_empty()?);
|
||||
assert_eq!(task_queue.len()?, 6);
|
||||
|
||||
task_queue.remove_list(&[
|
||||
&TaskId("a".to_string()),
|
||||
&TaskId("c".to_string()),
|
||||
&TaskId("e".to_string()),
|
||||
])?;
|
||||
|
||||
assert!(!task_queue.is_empty()?);
|
||||
assert_eq!(task_queue.len()?, 3);
|
||||
|
||||
assert!(task_queue.contains_key(&TaskId("b".to_string()))?);
|
||||
assert!(task_queue.contains_key(&TaskId("d".to_string()))?);
|
||||
assert!(task_queue.contains_key(&TaskId("f".to_string()))?);
|
||||
|
||||
assert!(!task_queue.contains_key(&TaskId("a".to_string()))?);
|
||||
assert!(!task_queue.contains_key(&TaskId("c".to_string()))?);
|
||||
assert!(!task_queue.contains_key(&TaskId("e".to_string()))?);
|
||||
|
||||
task_queue.remove_list(&[
|
||||
&TaskId("foo".to_string()),
|
||||
&TaskId("bar".to_string()),
|
||||
&TaskId("baz".to_string()),
|
||||
])?;
|
||||
|
||||
assert!(!task_queue.is_empty()?);
|
||||
assert_eq!(task_queue.len()?, 3);
|
||||
|
||||
assert!(task_queue.contains_key(&TaskId("b".to_string()))?);
|
||||
assert!(task_queue.contains_key(&TaskId("d".to_string()))?);
|
||||
assert!(task_queue.contains_key(&TaskId("f".to_string()))?);
|
||||
|
||||
task_queue.remove_list(&[
|
||||
&TaskId("b".to_string()),
|
||||
&TaskId("d".to_string()),
|
||||
&TaskId("f".to_string()),
|
||||
])?;
|
||||
|
||||
assert!(task_queue.is_empty()?);
|
||||
assert_eq!(task_queue.len()?, 0);
|
||||
|
||||
assert!(!task_queue.contains_key(&TaskId("b".to_string()))?);
|
||||
assert!(!task_queue.contains_key(&TaskId("d".to_string()))?);
|
||||
assert!(!task_queue.contains_key(&TaskId("f".to_string()))?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod read_storyteller_credentials_from_disk;
|
||||
pub mod storyteller_credential_holder;
|
||||
pub mod storyteller_credential_manager;
|
||||
@@ -1,39 +0,0 @@
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use storyteller_client::credentials::storyteller_avt_cookie::StorytellerAvtCookie;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::credentials::storyteller_session_cookie::StorytellerSessionCookie;
|
||||
|
||||
pub fn read_storyteller_credentials_from_disk(app_data_root: &AppDataRoot) -> AnyhowResult<StorytellerCredentialSet> {
|
||||
let creds_dir= app_data_root.credentials_dir();
|
||||
|
||||
let mut avt = None;
|
||||
|
||||
{
|
||||
let avt_file = creds_dir.get_storyteller_avt_cookie_file_path();
|
||||
if avt_file.exists() {
|
||||
let contents = read_to_string(&avt_file)?
|
||||
.trim()
|
||||
.to_string();
|
||||
avt = Some(StorytellerAvtCookie::new(contents));
|
||||
}
|
||||
}
|
||||
|
||||
let mut session = None;
|
||||
|
||||
{
|
||||
let session_file = creds_dir.get_storyteller_session_cookie_file_path();
|
||||
if session_file.exists() {
|
||||
let contents = read_to_string(&session_file)?
|
||||
.trim()
|
||||
.to_string();
|
||||
session = Some(StorytellerSessionCookie::new(contents));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(StorytellerCredentialSet::initialize(
|
||||
avt,
|
||||
session,
|
||||
))
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
|
||||
/// Hold credentials for the application.
|
||||
#[derive(Clone)]
|
||||
pub struct StorytellerCredentialHolder {
|
||||
credentials: Arc<RwLock<Option<StorytellerCredentialSet>>>,
|
||||
}
|
||||
|
||||
impl StorytellerCredentialHolder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
credentials: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_credentials(&self, credentials: &StorytellerCredentialSet) -> AnyhowResult<()> {
|
||||
match self.credentials.write() {
|
||||
Err(err) => Err(anyhow!("Failed to acquire write lock: {:?}", err)),
|
||||
Ok(mut creds) => {
|
||||
*creds = Some(credentials.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_credentials(&self) -> AnyhowResult<()> {
|
||||
match self.credentials.write() {
|
||||
Err(err) => Err(anyhow!("Failed to acquire write lock: {:?}", err)),
|
||||
Ok(mut creds) => {
|
||||
*creds = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_credentials(&self) -> AnyhowResult<Option<StorytellerCredentialSet>> {
|
||||
match self.credentials.read() {
|
||||
Err(err) => Err(anyhow!("Failed to acquire read lock: {:?}", err)),
|
||||
Ok(creds) => {
|
||||
Ok(creds.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_credentials_required(&self) -> AnyhowResult<StorytellerCredentialSet> {
|
||||
match self.credentials.read() {
|
||||
Err(err) => Err(anyhow!("Failed to acquire read lock: {:?}", err)),
|
||||
Ok(creds) => match &*creds {
|
||||
None => Err(anyhow!("Credentials not set")),
|
||||
Some(creds) => Ok(creds.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::sora::read_sora_credentials_from_disk::read_sora_credentials_from_disk;
|
||||
use crate::state::sora::sora_credential_holder::SoraCredentialHolder;
|
||||
use crate::state::storyteller::read_storyteller_credentials_from_disk::read_storyteller_credentials_from_disk;
|
||||
use crate::state::storyteller::storyteller_credential_holder::StorytellerCredentialHolder;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info, warn};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use storyteller_client::credentials::storyteller_avt_cookie::StorytellerAvtCookie;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::credentials::storyteller_session_cookie::StorytellerSessionCookie;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StorytellerCredentialManager {
|
||||
holder: StorytellerCredentialHolder,
|
||||
app_data_root: AppDataRoot,
|
||||
}
|
||||
|
||||
impl StorytellerCredentialManager {
|
||||
|
||||
pub fn initialize_from_disk_infallible(app_data_root: &AppDataRoot) -> Self {
|
||||
let holder = StorytellerCredentialHolder::new();
|
||||
|
||||
match read_storyteller_credentials_from_disk(app_data_root) {
|
||||
Err(err) => warn!("Failed to read credentials from disk: {:?}", err),
|
||||
Ok(creds) => {
|
||||
holder.set_credentials(&creds).expect("Failed to set credentials");
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
holder,
|
||||
app_data_root: app_data_root.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_credentials(&self, creds: &StorytellerCredentialSet) -> AnyhowResult<()> {
|
||||
self.holder.set_credentials(creds)
|
||||
}
|
||||
|
||||
pub fn clear_credentials(&self) -> AnyhowResult<()> {
|
||||
self.holder.clear_credentials()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_credentials(&self) -> AnyhowResult<Option<StorytellerCredentialSet>> {
|
||||
self.holder.get_credentials()
|
||||
}
|
||||
|
||||
pub fn get_credentials_required(&self) -> AnyhowResult<StorytellerCredentialSet> {
|
||||
self.holder.get_credentials_required()
|
||||
}
|
||||
|
||||
pub fn reset_from_disk(&self) -> AnyhowResult<StorytellerCredentialSet> {
|
||||
let creds = read_storyteller_credentials_from_disk(&self.app_data_root)?;
|
||||
self.holder.set_credentials(&creds)?;
|
||||
Ok(creds)
|
||||
}
|
||||
|
||||
pub fn persist_all_to_disk(&self) -> AnyhowResult<()> {
|
||||
let credentials = self.holder.get_credentials_required()?;
|
||||
|
||||
info!("Persisting avt cookie to disk...");
|
||||
if let Some(avt) = credentials.avt.as_ref() {
|
||||
persist_avt_to_disk(&avt, &self.app_data_root)?;
|
||||
}
|
||||
|
||||
info!("Persisting session cookie to disk...");
|
||||
if let Some(session) = credentials.session.as_ref() {
|
||||
persist_session_to_disk(&session, &self.app_data_root)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn persist_session_to_disk(session: &StorytellerSessionCookie, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
let creds_dir = app_data_root.credentials_dir();
|
||||
let filename = creds_dir.get_storyteller_session_cookie_file_path();
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(filename)?;
|
||||
|
||||
file.write_all(session.as_bytes())?;
|
||||
file.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn persist_avt_to_disk(avt: &StorytellerAvtCookie, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
let creds_dir = app_data_root.credentials_dir();
|
||||
let filename = creds_dir.get_storyteller_avt_cookie_file_path();
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(filename)?;
|
||||
|
||||
file.write_all(avt.as_bytes())?;
|
||||
file.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
use anyhow::anyhow;
|
||||
use discord_rich_presence::{DiscordIpc, DiscordIpcClient};
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
|
||||
/// Our Discord App ID. Not sure if this is a secret.
|
||||
const DISCORD_APP_ID : &str = "1366596912593113138";
|
||||
|
||||
pub async fn discord_presence_thread() -> ! {
|
||||
loop {
|
||||
let client = DiscordIpcClient::new(DISCORD_APP_ID);
|
||||
let _err = discord_main_loop(client).await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10_000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn discord_main_loop(mut client: DiscordIpcClient) -> AnyhowResult<()> {
|
||||
info!("Connecting Discord IPC client");
|
||||
|
||||
client.connect()
|
||||
.map_err(|err| anyhow!("Error connecting to Discord IPC: {:?}", err))?;
|
||||
|
||||
loop {
|
||||
info!("Notifying discord...");
|
||||
|
||||
let assets = discord_rich_presence::activity::Assets::new()
|
||||
.large_image("https://storyteller.ai/android-chrome-512x512.png")
|
||||
.large_text("https://GetArtCraft.com");
|
||||
//.small_image("https://storyteller.ai/android-chrome-192x192.png")
|
||||
//.small_text("small text");
|
||||
|
||||
client.set_activity(discord_rich_presence::activity::Activity::new()
|
||||
//.state("Anyone can make arts!")
|
||||
.state("GetArtCraft.com")
|
||||
.details("Making Arts with ArtCraft")
|
||||
.activity_type(discord_rich_presence::activity::ActivityType::Playing)
|
||||
.assets(assets))
|
||||
.map_err(|err| anyhow!("Error setting Discord activity: {:?}", err))?;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(30_000)).await;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::main_window_size::MainWindowSize;
|
||||
use crate::state::storyteller::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::threads::main_window_thread::persist_storyteller_cookies_task::persist_storyteller_cookies_task;
|
||||
use crate::threads::main_window_thread::persist_window_resize_task::persist_window_resize_task;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use memory_store::clone_slot::CloneSlot;
|
||||
use tauri::{AppHandle, Manager, Webview, Window};
|
||||
use crate::state::app_startup_time::AppStartupTime;
|
||||
|
||||
const MAIN_WINDOW_NAME : &str = "main";
|
||||
|
||||
pub async fn main_window_thread(
|
||||
app: AppHandle,
|
||||
app_data_root: AppDataRoot,
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
) -> ! {
|
||||
// TODO: Move these into some kind of dependency injection framework
|
||||
let window_size_slot: CloneSlot<MainWindowSize> = CloneSlot::empty();
|
||||
let app_startup_time = AppStartupTime::new();
|
||||
|
||||
loop {
|
||||
for (window_name, window) in app.windows() {
|
||||
if window_name == MAIN_WINDOW_NAME {
|
||||
let result = handle_main_window(
|
||||
&app,
|
||||
&window,
|
||||
&app_data_root,
|
||||
&storyteller_creds_manager,
|
||||
&window_size_slot,
|
||||
&app_startup_time,
|
||||
).await;
|
||||
if let Err(err) = result {
|
||||
error!("Error handling main window: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1_000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_main_window(
|
||||
app: &AppHandle,
|
||||
window: &Window,
|
||||
app_data_root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
window_size_slot: &CloneSlot<MainWindowSize>,
|
||||
app_startup_time: &AppStartupTime,
|
||||
) -> AnyhowResult<()> {
|
||||
loop {
|
||||
log_errors(persist_window_resize_task(window, app_data_root, window_size_slot).await);
|
||||
log_errors(persist_storyteller_cookies_task(app, storyteller_creds_manager, app_startup_time).await);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1_000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_errors<T>(result: AnyhowResult<T>) {
|
||||
if let Err(err) = result {
|
||||
error!("Error persisting window size: {:?}", err);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod main_window_thread;
|
||||
pub mod persist_storyteller_cookies_task;
|
||||
pub mod persist_window_resize_task;
|
||||
@@ -1,114 +0,0 @@
|
||||
use crate::state::app_startup_time::AppStartupTime;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::storyteller::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::threads::sora_session_login_thread::LOGIN_WINDOW_NAME;
|
||||
use anyhow::anyhow;
|
||||
use chrono::TimeDelta;
|
||||
use errors::AnyhowResult;
|
||||
use log::{debug, error, info};
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::Url;
|
||||
use std::fs;
|
||||
use std::sync::LockResult;
|
||||
use storyteller_client::credentials::storyteller_avt_cookie::StorytellerAvtCookie;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::credentials::storyteller_session_cookie::StorytellerSessionCookie;
|
||||
use tauri::{AppHandle, Manager, Webview, Window};
|
||||
use tauri_plugin_http::reqwest_cookie_store::CookieStore;
|
||||
use tauri_plugin_http::Http;
|
||||
|
||||
const MAIN_WEBVIEW_NAME: &str = "main";
|
||||
|
||||
const STORYTELLER_ROOT_COOKIE_URL_STR: &str = "https://api.storyteller.ai/";
|
||||
|
||||
static STORYTELLER_ROOT_COOKIE_URL: Lazy<Url> = Lazy::new(|| {
|
||||
Url::parse(STORYTELLER_ROOT_COOKIE_URL_STR).expect("URL should parse")
|
||||
});
|
||||
|
||||
const AVT_COOKIE_NAME : &str = "visitor";
|
||||
const SESSION_COOKIE_NAME : &str = "session";
|
||||
|
||||
/// There's some kind of race condition that we need to wait for before inspecting cookies.
|
||||
const RACE_CONDITION_WAIT_TIME: TimeDelta = TimeDelta::milliseconds(5000);
|
||||
|
||||
pub async fn persist_storyteller_cookies_task(
|
||||
app: &AppHandle,
|
||||
storyteller_credential_manager: &StorytellerCredentialManager,
|
||||
app_startup_time: &AppStartupTime,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
//if app_startup_time.time_delta_since() < RACE_CONDITION_WAIT_TIME {
|
||||
// // NB: There's an issue when the "main window thread" inquires about the
|
||||
// // webview cookies shortly after app startup. When it attempts to dump the
|
||||
// // cookies within a few milliseconds of app launch, it deadlocks and the
|
||||
// // app never launches correctly. The entire webview goes blank and the app
|
||||
// // freezes. This hack seems to fix the problem.
|
||||
// return Ok(())
|
||||
//}
|
||||
|
||||
let maybe_http = app.try_state::<Http>();
|
||||
|
||||
let http = match maybe_http {
|
||||
None => {
|
||||
error!("No HTTP plugin found");
|
||||
return Err(anyhow!("No HTTP plugin found"));
|
||||
}
|
||||
Some(http) => http,
|
||||
};
|
||||
|
||||
match http.cookies_jar.store.lock() {
|
||||
Err(err) => {
|
||||
error!("Failed to lock cookie jar: {:?}", err);
|
||||
return Err(anyhow!("Failed to lock cookie jar"));
|
||||
}
|
||||
Ok(cookie_jar) => {
|
||||
sync_tauri_credentials(&cookie_jar, storyteller_credential_manager)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync_tauri_credentials(
|
||||
cookie_store: &CookieStore,
|
||||
storyteller_credential_manager: &StorytellerCredentialManager,
|
||||
) -> AnyhowResult<()> {
|
||||
let current_http_plugin_credentials = get_credentials_from_cookie_store(cookie_store)?;
|
||||
|
||||
let mut replace_credentials = true;
|
||||
|
||||
let maybe_old_credentials = storyteller_credential_manager.get_credentials()?;
|
||||
|
||||
if let Some(old_credentials) = maybe_old_credentials {
|
||||
if old_credentials.equals(¤t_http_plugin_credentials) {
|
||||
replace_credentials = false;
|
||||
}
|
||||
}
|
||||
|
||||
if replace_credentials {
|
||||
info!("Syncing ArtCraft credentials ...");
|
||||
storyteller_credential_manager.set_credentials(¤t_http_plugin_credentials)?;
|
||||
// NB: tauri-plugin-http stores the credentials on disk, so we can defer to that for now.
|
||||
//storyteller_credential_manager.persist_all_to_disk()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_credentials_from_cookie_store(cookie_jar: &CookieStore) -> AnyhowResult<StorytellerCredentialSet> {
|
||||
let mut avt_cookie = None;
|
||||
let mut session_cookie = None;
|
||||
|
||||
for cookie in cookie_jar.iter_unexpired() {
|
||||
if let Some(avt) = StorytellerAvtCookie::maybe_from_cookie(&cookie) {
|
||||
avt_cookie = Some(avt);
|
||||
} else if let Some(session) = StorytellerSessionCookie::maybe_from_cookie(&cookie) {
|
||||
session_cookie = Some(session);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(StorytellerCredentialSet::initialize(
|
||||
avt_cookie,
|
||||
session_cookie,
|
||||
))
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::main_window_size::MainWindowSize;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use memory_store::clone_slot::CloneSlot;
|
||||
use tauri::Window;
|
||||
|
||||
pub async fn persist_window_resize_task(
|
||||
window: &Window,
|
||||
app_data_root: &AppDataRoot,
|
||||
window_size_slot: &CloneSlot<MainWindowSize>,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
let current_window_size = MainWindowSize::from_window(window)?;
|
||||
let current_physical_size = window.inner_size()?;
|
||||
|
||||
let mut save_size_to_disk = true;
|
||||
|
||||
if let Ok(Some(old_size)) = window_size_slot.get_clone() {
|
||||
if old_size.matches_physical_size(¤t_physical_size) {
|
||||
save_size_to_disk = false;
|
||||
}
|
||||
}
|
||||
|
||||
if save_size_to_disk {
|
||||
info!("Saving window size configs to disk...");
|
||||
current_window_size.persist_to_filesystem(app_data_root)?;
|
||||
window_size_slot.set_clone(¤t_window_size)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod discord_presence_thread;
|
||||
pub mod main_window_thread;
|
||||
pub mod sora_session_login_thread;
|
||||
pub mod sora_task_polling_thread;
|
||||
@@ -1,169 +0,0 @@
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::state::sora::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::state::sora::sora_task_queue::SoraTaskQueue;
|
||||
use crate::state::storyteller::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::list_sora_task_status_with_session_auto_renew::{list_sora_task_status_with_session_auto_renew, StatusRequestArgs};
|
||||
use openai_sora_client::requests::image_gen::image_gen_status::{Generation, TaskId, TaskStatus};
|
||||
use reqwest::Url;
|
||||
use serde_derive::Serialize;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use storyteller_client::media_files::upload_image_media_file_from_file::upload_image_media_file_from_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tempdir::TempDir;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SoraImageGenerationComplete {
|
||||
pub media_file_token: MediaFileToken,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct SoraImageGenerationFailed {
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
pub async fn sora_task_polling_thread(
|
||||
app_handle: AppHandle,
|
||||
app_data_root: AppDataRoot,
|
||||
sora_creds_manager: SoraCredentialManager,
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
sora_task_queue: SoraTaskQueue,
|
||||
) -> ! {
|
||||
loop {
|
||||
let res = polling_loop(
|
||||
&app_handle,
|
||||
&sora_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&sora_task_queue,
|
||||
&app_data_root,
|
||||
).await;
|
||||
if let Err(err) = res {
|
||||
error!("An error occurred: {:?}", err);
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(30_000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn polling_loop(
|
||||
app_handle: &AppHandle,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
app_data_root: &AppDataRoot,
|
||||
) -> AnyhowResult<()> {
|
||||
loop {
|
||||
if sora_task_queue.is_empty()? {
|
||||
// No need to poll if we don't have pending tasks.
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1_000)).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
info!("Task queue has {} pending tasks.", sora_task_queue.len()?);
|
||||
|
||||
let creds = sora_creds_manager.get_credentials_required()?;
|
||||
|
||||
let (response, maybe_new_creds) = list_sora_task_status_with_session_auto_renew(StatusRequestArgs {
|
||||
limit: None,
|
||||
// TODO: How can we use the task id to poll better? Our existing code doesn't seem to illuminate this.
|
||||
before: None,
|
||||
credentials: creds,
|
||||
}).await?;
|
||||
|
||||
if let Some(new_creds) = maybe_new_creds {
|
||||
info!("Saving new credentials.");
|
||||
sora_creds_manager.set_credentials(&new_creds)?;
|
||||
}
|
||||
|
||||
let mut succeeded_tasks = Vec::new();
|
||||
let mut failed_tasks = Vec::new();
|
||||
|
||||
// TODO: The cursoring logic likely needs to improve.
|
||||
for task in response.task_responses {
|
||||
let status = TaskStatus::from_str(&task.status);
|
||||
|
||||
match status {
|
||||
TaskStatus::Succeeded => {
|
||||
succeeded_tasks.push(task);
|
||||
}
|
||||
TaskStatus::Failed => {
|
||||
failed_tasks.push(task);
|
||||
}
|
||||
TaskStatus::Queued => {}
|
||||
TaskStatus::Running => {}
|
||||
TaskStatus::Unknown(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
for task in failed_tasks.iter() {
|
||||
if sora_task_queue.contains_key(&task.id)? {
|
||||
app_handle.emit("sora-image-generation-failed", SoraImageGenerationFailed {
|
||||
prompt: task.prompt.clone(),
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
let failed_task_ids: Vec<&TaskId> = failed_tasks
|
||||
.iter()
|
||||
.map(|task| &task.id)
|
||||
.collect();
|
||||
|
||||
sora_task_queue.remove_list(&failed_task_ids)?;
|
||||
|
||||
let creds = storyteller_creds_manager.get_credentials_required()?;
|
||||
let api_host = ApiHost::Storyteller;
|
||||
|
||||
for task in succeeded_tasks.iter() {
|
||||
if !sora_task_queue.contains_key(&task.id)? {
|
||||
continue;
|
||||
}
|
||||
info!("Task succeeded: {:?}", task.id);
|
||||
|
||||
for (i, generation) in task.generations.iter().enumerate() {
|
||||
info!("Downloading generated file...");
|
||||
let download_path = download_generation(generation, &app_data_root).await?;
|
||||
|
||||
info!("Uploading to backend...");
|
||||
let result = upload_image_media_file_from_file(&api_host, Some(&creds), download_path).await?;
|
||||
|
||||
info!("Uploaded to API backend: {:?}", result.media_file_token);
|
||||
|
||||
app_handle.emit("sora-image-generation-complete", SoraImageGenerationComplete {
|
||||
media_file_token: result.media_file_token
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
let succeeded_task_ids : Vec<&TaskId> = succeeded_tasks
|
||||
.iter()
|
||||
.map(|task| &task.id)
|
||||
.collect();
|
||||
|
||||
sora_task_queue.remove_list(&succeeded_task_ids)?;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(2_000)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn download_generation(generation: &Generation, app_data_root: &AppDataRoot) -> AnyhowResult<PathBuf> {
|
||||
let url = Url::parse(&generation.url)?;
|
||||
|
||||
let response = reqwest::get(&generation.url).await?;
|
||||
let image_bytes = response.bytes().await?;
|
||||
|
||||
let ext = url.path().split(".").last().unwrap_or("png");
|
||||
|
||||
let tempdir = app_data_root.temp_dir().path();
|
||||
let download_filename = format!("{}.{}", generation.id, ext);
|
||||
let download_path = tempdir.join(download_filename);
|
||||
|
||||
let mut file = File::create(&download_path)?;
|
||||
file.write_all(&image_bytes)?;
|
||||
|
||||
Ok(download_path)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub mod sora_webview_cookies;
|
||||
@@ -1,38 +0,0 @@
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
const MAIN_WINDOW_NAME: &str = "main";
|
||||
|
||||
pub fn webview_unsafe_for_app(app: &AppHandle) -> AnyhowResult<()> {
|
||||
let windows = app.windows();
|
||||
let maybe_window = windows.get(MAIN_WINDOW_NAME);
|
||||
|
||||
/*if let Some(window) = maybe_window {
|
||||
let webview = window.get_webview(MAIN_WINDOW_NAME);
|
||||
if let Some(webview) = webview {
|
||||
webview_unsafe(&webview);
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Main window not found"));
|
||||
}*/
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*pub fn webview_unsafe(webview: &Webview) {
|
||||
let r = webview.with_webview(|webview| {
|
||||
webview.controller();
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe {
|
||||
let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();
|
||||
let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();
|
||||
let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();
|
||||
|
||||
//view.setPageZoom(40.);
|
||||
//controller.removeAllUserScripts();
|
||||
let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(1.0, 0.2, 0.4, 1.);
|
||||
window.setBackgroundColor(Some(&bg_color));
|
||||
}
|
||||
|
||||
});
|
||||
}*/
|
||||
@@ -1,5 +0,0 @@
|
||||
# For some reason, we can't pass json files to the tauri dev command
|
||||
[build]
|
||||
frontendDist = "../../../frontend/apps/editor2d/dist"
|
||||
beforeDevCommand = "echo 'no dev'"
|
||||
devUrl = "http://localhost:5741"
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"frontendDist": "../../../frontend/apps/editor3d/dist",
|
||||
"beforeDevCommand": "echo 'no dev'",
|
||||
"devUrl": "http://localhost:5173",
|
||||
"beforeBuildCommand": "nx run editor3d:build"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
# For some reason, we can't pass json files to the tauri dev command
|
||||
[build]
|
||||
frontendDist = "../../../frontend/apps/editor3d/dist"
|
||||
#beforeDevCommand = "nx run editor3d:dev"
|
||||
beforeDevCommand = "echo 'no dev'"
|
||||
devUrl = "http://localhost:5173"
|
||||