Compare commits

...
Author SHA1 Message Date
bflatastic 2b176d023e updated upload flow - auto open asset modal after upload 2025-05-07 16:04:30 +07:00
bflatastic eef929c065 improved upload 2025-05-07 14:23:45 +07:00
bflatastic 91ea263b0c tab selector to have 3d first, and logic to hide tooltip when asset modal open 2025-05-07 12:42:04 +07:00
Brandon Thomas 196e1c4615 fix broken package (#585) 2025-05-06 17:54:34 -04:00
bflatastic 8f321f06d0 thumnail to always load in img tag even if there's error 2025-05-06 22:30:27 +07:00
bflatastic 0b338f3886 login logic fixed 2025-05-06 14:26:49 +07:00
BFlat de76fc6c36 login modal on 3D (#584)
- login modal is a shared component that
- finished but needs checking session if openai/sora logged in or not
- replaced video for 3d (it had a black screen at the end)
2025-05-06 13:15:05 +07:00
bflatastic 9fb0ca2dab fixed logo being squished 2025-05-06 10:36:09 +07:00
Brandon Thomas 42965dfcfa modal settings menu (#583) 2025-05-05 23:06:32 -04:00
ArEnScandTanish Manku 8c7b358534 Feature/2d integration (#581)
Co-authored-by: Tanish Manku <-h>
2025-05-05 16:00:12 -04:00
ArEnScandMichael Chung d8851abf24 Removing lower panel and timeline files too much stuff, builds and wo… (#580)
…rks fine.

Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-05 15:06:28 -04:00
bflatastic 2c8254e50f updated artcraft site to show carter's thunderbolts video 2025-05-05 22:33:49 +07:00
bflatastic 15348f7b0e draw attention to add 3d object with bouncing tooltip 2025-05-05 22:08:16 +07:00
Brandon Thomas 6071dda4f9 Settings system backend (#579) 2025-05-04 16:02:54 -04:00
Brandon Thomas 1f6416856a rename tauri-artcraft to artcraft because it impacts exe name 2025-05-04 00:37:59 -04:00
Brandon Thomas 1370ab63d9 sora cloudflare 502s 2025-05-04 00:28:30 -04:00
Brandon Thomas 1e0f31ed8d move installation of tauri events (temporary) 2025-05-03 23:47:16 -04:00
Brandon Thomas fd4f284af6 set up packages to play sounds on events (more desktop feel) 2025-05-03 22:20:22 -04:00
Brandon Thomas 51a114a699 storyteller-web tauri cors 2025-05-02 23:27:12 -04:00
Brandon Thomas 75621ea85a emit task success / failure 2025-05-02 22:18:31 -04:00
Brandon Thomas e003e94e54 fix more cors issues 2025-05-02 20:49:17 -04:00
Brandon Thomas 0d1bd6b014 build script more friendly for windows 2025-05-02 19:29:14 -04:00
Brandon Thomas 9861263b08 remove objc/mac libs from tauri app 2025-05-02 19:05:23 -04:00
Brandon Thomas 9fa73c6e06 fix tauri conf - no more require-corp CORS 2025-05-02 19:02:31 -04:00
Brandon Thomas caada00cd9 remove cors require-corp from frontend 2025-05-02 18:59:41 -04:00
ArEnScandMichael Chung df8d999ed7 Feature/download click listen (#578)
Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-02 18:06:17 -04:00
Brandon Thomas 3f94e48f12 fix headers on mac 2025-05-02 17:31:37 -04:00
Brandon Thomas 68e57bd601 don't require objc - this won't work for cors anyway 2025-05-02 17:29:02 -04:00
Brandon Thomas e65011cd51 fix cors in dev and production mac builds 2025-05-02 12:22:33 -04:00
ArEnScandMichael Chung b2f989b0b7 Add icons for all platforms for the desktop app artcraft (#577)
Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-02 10:19:38 -04:00
Brandon Thomas 233d61d7a1 production build script 2025-05-02 09:21:50 -04:00
Brandon Thomas ab412c6f10 WIP unsafe hack to modify webkit 2025-05-02 07:41:36 -04:00
bflatastic 9d6b31200e top right buttons styling fixes and tooltip (profile dropdown/activity/settings) 2025-05-02 12:12:18 +07:00
bflatastic a1aacad7dd removed marquee error (unused) 2025-05-02 11:57:57 +07:00
Brandon Thomas 6436de9f44 unsafe CSP - will this help load images? 2025-05-01 23:31:36 -04:00
Brandon Thomas 116782e6ed enqueue sora gens from production app works 2025-05-01 23:31:36 -04:00
Michael Chung 431f2efcf0 Fix login and signup back button links 2025-05-01 23:30:05 -04:00
ArEnScandMichael Chung 7c64da88da Navigation Fixes (#576)
Added Navigation

---------

Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-01 22:52:46 -04:00
ArEnScandMichael Chung 93eb78da91 When the desktop app is running, the profile dropdown should not be w… (#575)
…hite.

Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-05-01 22:32:55 -04:00
Brandon Thomas f96e79ff49 use "@storyteller/api" for snapshots and prompting 2025-05-01 22:20:21 -04:00
Brandon Thomas 39e6f4511f use new media files client for importing objects in 3D 2025-05-01 21:41:31 -04:00
Brandon Thomas 4c0d062d76 production build + production origin host fix 2025-05-01 20:57:59 -04:00
315 changed files with 4065 additions and 3944 deletions
+2 -2
View File
@@ -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/tauri-artcraft && yarn install # change this to npm, pnpm or bun depending on which one you use.
run: cd ./crates/desktop/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/tauri-artcraft
projectPath: ./crates/desktop/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.'
Generated
+737 -163
View File
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -24,8 +24,7 @@ members = [
"crates/cli/production/migration-tool",
# Desktop apps
"crates/desktop/tauri-artcraft",
"crates/desktop/tauri-realtime",
"crates/desktop/artcraft",
# Shared libraries
"crates/lib/actix_cors_configs",
@@ -174,7 +173,7 @@ 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"] }
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"] }
@@ -1,5 +1,5 @@
[package]
name = "tauri-artcraft"
name = "artcraft"
version = "0.1.0"
description = "Artcraft"
authors = ["you"]
@@ -12,7 +12,7 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "tauri_artcraft_app_lib"
name = "artcraft_app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
@@ -55,6 +55,7 @@ serde_json = { workspace = true }
serde_yaml = { workspace = true }
tauri = { workspace = true }
tauri-plugin-log = "2.3.1"
tauri-plugin-upload = "2.2.1"
tempdir = "0.3.7"
tempfile = "3.18.0"
tokio = { version = "1.42", features = ["rt", "rt-multi-thread", "fs"] }
@@ -66,6 +67,7 @@ tauri-plugin-http = { path = "../../vendor/tauri-plugin-http" }
# NB: The version of the `discord-rich-presence` crate (0.2.5) last published has non-Send/Sync error
# types and can't do threading. We need to wait for a new version to unpin from Git.
discord-rich-presence = { git = "https://github.com/vionya/discord-rich-presence" }
tauri-plugin-dialog = "2"
[dev-dependencies]
libloading = "0.8.6"
@@ -8,13 +8,24 @@
"permissions": [
"core:default",
"log:default",
"upload:allow-download",
"upload:default",
{
"identifier": "http:default",
"allow": [
{ "url": "https://*.storyteller.ai" },
{ "url": "https://*.fakeyou.com" }
{
"url": "https://*.storyteller.ai"
},
{
"url": "https://*.fakeyou.com"
}
],
"deny": [{ "url": "https://private.tauri.app" }]
}
"deny": [
{
"url": "https://private.tauri.app"
}
]
},
"dialog:default"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

@@ -0,0 +1,59 @@
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,
/// Key pointing to file; defined in the frontend code.
pub generation_success_sound: Option<String>,
/// Key pointing to file; defined in the frontend code.
pub generation_failure_sound: Option<String>,
/// Key pointing to file; defined in the frontend code.
pub generation_enqueue_sound: Option<String>,
}
#[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,
generation_success_sound: prefs.generation_success_sound,
generation_failure_sound: prefs.generation_failure_sound,
generation_enqueue_sound: prefs.generation_enqueue_sound,
})
}
@@ -0,0 +1,2 @@
pub mod get_app_preferences_command;
pub mod update_app_preference_command;
@@ -0,0 +1,116 @@
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 anyhow::anyhow;
use errors::AnyhowResult;
use log::{error, info};
use openai_sora_client::sora_error::SoraError::AnyhowError;
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: Option<ValueType>,
}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
pub enum ValueType {
Bool(bool),
String(String),
DownloadDirectory(PreferredDownloadDirectory),
}
#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PreferenceName {
PreferredDownloadDirectory,
PlaySounds,
GenerationSuccessSound,
GenerationFailureSound,
GenerationEnqueueSound,
}
#[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()?;
info!("Value is: {:?}", request.value);
match request.preference {
PreferenceName::PreferredDownloadDirectory => {
match request.value {
Some(ValueType::DownloadDirectory(dir)) =>
prefs.preferred_download_directory = dir,
_ =>
return Err(anyhow!("Invalid value: {:?}", request.value)),
}
}
PreferenceName::PlaySounds => {
match request.value {
Some(ValueType::Bool(val)) =>
prefs.play_sounds = val,
_ =>
return Err(anyhow!("Invalid value: {:?}", request.value)),
}
}
PreferenceName::GenerationSuccessSound => {
prefs.generation_success_sound = request.value
.map(|val| string_value(&val))
.transpose()?;
}
PreferenceName::GenerationFailureSound => {
prefs.generation_failure_sound = request.value
.map(|val| string_value(&val))
.transpose()?;
}
PreferenceName::GenerationEnqueueSound => {
prefs.generation_enqueue_sound = request.value
.map(|val| string_value(&val))
.transpose()?;
}
}
app_prefs.set_clone(&prefs)?;
app_data_root.settings_dir().write_app_preferences(&prefs)?;
Ok(())
}
fn string_value(value: &ValueType) -> AnyhowResult<String> {
match value {
ValueType::String(val) => Ok(val.to_string()),
_ => Err(anyhow!("Invalid value type: {:?}", value)),
}
}
@@ -1,3 +1,4 @@
pub mod app_preferences;
pub mod flip_image;
pub mod platform_info_command;
pub mod sora;
@@ -21,13 +21,23 @@ use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
use openai_sora_client::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
use openai_sora_client::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
use openai_sora_client::requests::upload::upload_media_from_file::sora_media_upload_from_file;
use serde_derive::Deserialize;
use serde_derive::{Deserialize, Serialize};
use std::fs::read_to_string;
use std::io::Cursor;
use storyteller_client::media_files::get_media_file::get_media_file;
use storyteller_client::utils::api_host::ApiHost;
use tauri::{AppHandle, Manager, State};
use tauri::{AppHandle, Emitter, Manager, State};
use tokens::tokens::media_files::MediaFileToken;
use crate::threads::sora_task_polling_thread::SoraImageGenerationComplete;
#[derive(Debug, Clone, Serialize)]
pub struct SoraImageEnqueueFailure {
// TODO: Reason.
}
#[derive(Debug, Clone, Serialize)]
pub struct SoraImageEnqueueSuccess {
}
#[derive(Deserialize)]
pub struct SoraImageRemixCommand {
@@ -48,22 +58,35 @@ pub struct SoraImageRemixCommand {
#[tauri::command]
pub async fn sora_image_remix_command(
_app: AppHandle,
app: AppHandle,
request: SoraImageRemixCommand,
app_data_root: State<'_, AppDataRoot>,
sora_creds_manager: State<'_, SoraCredentialManager>,
sora_task_queue: State<'_, SoraTaskQueue>,
) -> Result<String, String> {
info!("image_generation_command called; processing image...");
info!("image_generation_command called; scene media token: {:?}, additional images: {:?}",
request.snapshot_media_token, request.maybe_additional_images);
// TODO(bt,2025-04-24): Better error messages to caller
generate_image(request, &app_data_root, &sora_creds_manager, &sora_task_queue)
.await
.map_err(|err| {
error!("error: {:?}", err);
"there was an error".to_string()
})?;
let result = generate_image(request, &app_data_root, &sora_creds_manager, &sora_task_queue).await;
if let Err(err) = result {
error!("error: {:?}", err);
let result = app.emit("sora-image-enqueue-failure", SoraImageEnqueueFailure{});
if let Err(err) = result {
error!("Failed to emit event: {:?}", err);
}
return Err("there was an error".to_string());
}
let result = app.emit("sora-image-enqueue-success", SoraImageEnqueueSuccess{});
if let Err(err) = result {
error!("Failed to emit event: {:?}", err);
}
Ok("success".to_string())
}
@@ -7,11 +7,13 @@ pub mod utils;
use tauri::Manager;
use crate::commands::flip_image::flip_image;
use crate::commands::app_preferences::get_app_preferences_command::get_app_preferences_command;
use crate::commands::app_preferences::update_app_preference_command::update_app_preferences_command;
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::state::app_config::AppConfig;
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;
@@ -20,8 +22,11 @@ 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_dialog;
use tauri_plugin_log::Target;
use tauri_plugin_log::TargetKind;
@@ -29,14 +34,13 @@ use tauri_plugin_log::TargetKind;
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 model config...");
let config = AppConfig::init()
.expect("config should load");
let app_data_root = config.app_data_root.clone();
let app_data_root_2 = config.app_data_root.clone();
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();
@@ -52,7 +56,9 @@ pub fn run() {
println!("Initializing backend runtime...");
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_upload::init())
.plugin(tauri_plugin_log::Builder::new()
.level(log::LevelFilter::Info)
.targets(vec![
@@ -74,9 +80,15 @@ pub fn run() {
// )?;
//}
let app = app.handle().clone();
let result = webview_unsafe_for_app(&app);
if let Err(err) = result {
eprintln!("Error setting webview unsafe: {:?}", err);
}
let 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();
@@ -98,17 +110,19 @@ pub fn run() {
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_data_root_4, sora_creds_manager_3, storyteller_creds_manager_3, sora_task_queue_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(config)
.manage(app_preferences)
.manage(sora_creds_manager)
.manage(sora_task_queue)
.invoke_handler(tauri::generate_handler![
flip_image,
get_app_preferences_command,
update_app_preferences_command,
open_sora_login_command,
platform_info_command,
sora_image_generation_command,
@@ -2,5 +2,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tauri_artcraft_app_lib::run();
artcraft_app_lib::run();
}
@@ -0,0 +1,60 @@
use crate::state::app_preferences::app_preferences_serializable::AppPreferencesSerializable;
use crate::state::app_preferences::preferred_download_directory::{PreferredDownloadDirectory, SystemDownloadDirectory};
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,
/// Key pointing to file; defined in the frontend code.
pub generation_success_sound: Option<String>,
/// Key pointing to file; defined in the frontend code.
pub generation_failure_sound: Option<String>,
/// Key pointing to file; defined in the frontend code.
pub generation_enqueue_sound: Option<String>,
}
impl Default for AppPreferences {
fn default() -> Self {
Self {
preferred_download_directory: PreferredDownloadDirectory::System(SystemDownloadDirectory::Downloads),
play_sounds: true,
// NB: These are defined in the frontend.
generation_success_sound: Some("flower".to_string()),
generation_failure_sound: Some("crumble".to_string()),
generation_enqueue_sound: Some("done".to_string()),
}
}
}
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::from_preferences(self)
}
}
@@ -0,0 +1,10 @@
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)
}
@@ -0,0 +1,71 @@
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};
/// Vector clock versioning string rather than semver.
const CURRENT_VERSION: &str = "1";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppPreferencesSerializable {
/// Versioning string.
pub version: String,
/// 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>,
/// Key pointing to file; defined in the frontend code.
pub generation_success_sound: Option<String>,
/// Key pointing to file; defined in the frontend code.
pub generation_failure_sound: Option<String>,
/// Key pointing to file; defined in the frontend code.
pub generation_enqueue_sound: Option<String>,
}
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 from_preferences(preferences: &AppPreferences) -> Self {
Self {
version: CURRENT_VERSION.to_string(),
preferred_download_directory: Some(preferences.preferred_download_directory.clone()),
play_sounds: Some(preferences.play_sounds),
generation_success_sound: preferences.generation_success_sound.clone(),
generation_failure_sound: preferences.generation_failure_sound.clone(),
generation_enqueue_sound: preferences.generation_enqueue_sound.clone(),
}
}
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.generation_success_sound = self.generation_success_sound.clone();
preferences.generation_failure_sound = self.generation_failure_sound.clone();
preferences.generation_enqueue_sound = self.generation_enqueue_sound.clone();
preferences
}
}
@@ -0,0 +1,4 @@
pub mod app_preferences;
pub mod app_preferences_manager;
pub mod app_preferences_serializable;
pub mod preferred_download_directory;
@@ -0,0 +1,82 @@
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";
/// NB: The reason these are not flat is so that they serialize/deserialize nicely to/from JSON.
#[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.
System(SystemDownloadDirectory),
/// A user-defined path
/// NB: Serializes as `{"custom": "/path/to/dir"}` in JSON.
Custom(PathBuf),
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum SystemDownloadDirectory {
Downloads,
Documents,
}
impl FromStr for SystemDownloadDirectory {
type Err = AnyhowError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match (s) {
"downloads" => Ok(SystemDownloadDirectory::Downloads),
"documents" => Ok(SystemDownloadDirectory::Documents),
_ => Err(AnyhowError::msg(format!("Invalid system download directory: {}", s))),
}
}
}
impl Display for PreferredDownloadDirectory {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PreferredDownloadDirectory::System(system) => system.fmt(f),
PreferredDownloadDirectory::Custom(path) => write!(f, "{}", path.to_string_lossy()),
}
}
}
impl Display for SystemDownloadDirectory {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SystemDownloadDirectory::Downloads => write!(f, "downloads"),
SystemDownloadDirectory::Documents => write!(f, "documents"),
}
}
}
#[cfg(test)]
mod tests {
use crate::state::app_preferences::preferred_download_directory::PreferredDownloadDirectory;
use crate::state::app_preferences::preferred_download_directory::SystemDownloadDirectory;
use std::str::FromStr;
mod json {
use super::*;
#[test]
fn to_json_system_default() {
let val = PreferredDownloadDirectory::System(SystemDownloadDirectory::Documents);
let val = serde_json::to_string(&val).unwrap();
assert_eq!(&val, "{\"system\":\"documents\"}");
}
#[test]
fn to_json_custom() {
let val = PreferredDownloadDirectory::Custom("/tmp".into());
let val = serde_json::to_string(&val).unwrap();
assert_eq!(&val, "{\"custom\":\"/tmp\"}");
}
}
}
@@ -1,6 +1,7 @@
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;
@@ -12,7 +13,6 @@ 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";
@@ -23,6 +23,7 @@ pub struct AppDataRoot {
assets_dir: AppAssetsDir,
credentials_dir: AppCredentialsDir,
downloads_dir: AppDownloadsDir,
settings_dir: AppSettingsDir,
weights_dir: AppWeightsDir,
temp_dir: TemporaryDir,
log_file_name: PathBuf,
@@ -60,10 +61,11 @@ impl AppDataRoot {
}
let assets_dir = AppAssetsDir::get_or_create_in_root_dir(&dir)?;
let downloads_dir = AppDownloadsDir::get_or_create_in_root_dir(&dir)?;
let credentials_dir = AppCredentialsDir::get_or_create_in_root_dir(&dir)?;
let weights_dir = AppWeightsDir::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()
@@ -75,6 +77,7 @@ impl AppDataRoot {
assets_dir,
credentials_dir,
downloads_dir,
settings_dir,
weights_dir,
temp_dir,
log_file_name,
@@ -94,6 +97,10 @@ impl AppDataRoot {
&self.downloads_dir
}
pub fn settings_dir(&self) -> &AppSettingsDir {
&self.settings_dir
}
pub fn weights_dir(&self) -> &AppWeightsDir {
&self.weights_dir
}
@@ -0,0 +1,56 @@
use crate::state::app_preferences::app_preferences::AppPreferences;
use crate::state::data_dir::trait_data_subdir::DataSubdir;
use errors::AnyhowResult;
use serde::Serialize;
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 formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
let mut buf = Vec::new();
let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
serializable.serialize(&mut ser)?;
let filename = self.get_app_preferences_path();
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(filename)?;
file.write_all(&buf)?;
file.flush()?;
Ok(())
}
}
@@ -2,6 +2,7 @@ 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,9 +1,8 @@
pub mod app_config;
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;
pub mod yaml_config;
pub mod storyteller;

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