mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 09:22:41 -04:00
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b96d8f5df | ||
|
|
29ceb13492 | ||
|
|
44889e735b | ||
|
|
16c14a49b4 | ||
|
|
5e7f8ca8d1 | ||
|
|
2a95f9eda3 | ||
|
|
b320ae3746 | ||
|
|
b6337c30d5 | ||
|
|
08f6f569bf | ||
|
|
1abc74d735 | ||
|
|
fc6503ecd1 | ||
|
|
ad4fc71802 | ||
|
|
cef7b24b48 | ||
|
|
9475b7ef20 | ||
|
|
ce3ed46903 | ||
|
|
4ad4270801 |
@@ -1,7 +1,18 @@
|
||||
use crate::core::lifecycle::startup::bootstrap_task_database::bootstrap_task_database;
|
||||
use crate::core::lifecycle::startup::spawn_storytoller_task_polling_thread::spawn_storyteller_task_polling_thread;
|
||||
use crate::core::lifecycle::startup::tasks::bootstrap_task_database::bootstrap_task_database;
|
||||
use crate::core::lifecycle::startup::tasks::load_provider_priority_state::load_provider_priority_state;
|
||||
use crate::core::lifecycle::startup::tasks::set_app_log_level::set_app_log_level;
|
||||
use crate::core::lifecycle::startup::tasks::initially_size_and_position_windows::initially_size_and_position_windows;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_discord_presence_thread::spawn_discord_presence_thread;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_fal_task_polling_thread::spawn_fal_task_polling_thread;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_main_window_thread::spawn_main_window_thread;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_sora_task_polling_thread::spawn_sora_task_polling_thread;
|
||||
use crate::core::lifecycle::startup::tasks::spawn_storytoller_task_polling_thread::spawn_storyteller_task_polling_thread;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
@@ -11,8 +22,30 @@ pub async fn handle_tauri_startup(
|
||||
root: AppDataRoot,
|
||||
app_env_configs: AppEnvConfigs,
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
sora_credential_manager: SoraCredentialManager,
|
||||
sora_task_queue: SoraTaskQueue,
|
||||
fal_credential_manager: FalCredentialManager,
|
||||
fal_task_queue: FalTaskQueue,
|
||||
) -> AnyhowResult<()> {
|
||||
let task_database = bootstrap_task_database(&app, &root).await?;
|
||||
|
||||
set_app_log_level(
|
||||
&app,
|
||||
&root,
|
||||
)?;
|
||||
|
||||
let task_database =
|
||||
bootstrap_task_database(&app, &root).await?;
|
||||
|
||||
load_provider_priority_state(
|
||||
&app,
|
||||
&root,
|
||||
)?;
|
||||
|
||||
spawn_main_window_thread(
|
||||
&app,
|
||||
&root,
|
||||
&storyteller_creds_manager,
|
||||
)?;
|
||||
|
||||
spawn_storyteller_task_polling_thread(
|
||||
&app,
|
||||
@@ -21,5 +54,27 @@ pub async fn handle_tauri_startup(
|
||||
&storyteller_creds_manager,
|
||||
)?;
|
||||
|
||||
spawn_sora_task_polling_thread(
|
||||
&app,
|
||||
&root,
|
||||
&app_env_configs,
|
||||
&sora_credential_manager,
|
||||
&storyteller_creds_manager,
|
||||
&sora_task_queue,
|
||||
)?;
|
||||
|
||||
spawn_fal_task_polling_thread(
|
||||
&app,
|
||||
&root,
|
||||
&app_env_configs,
|
||||
&fal_credential_manager,
|
||||
&fal_task_queue,
|
||||
&storyteller_creds_manager,
|
||||
)?;
|
||||
|
||||
spawn_discord_presence_thread()?;
|
||||
|
||||
initially_size_and_position_windows(&app, &root);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
pub mod bootstrap_task_database;
|
||||
pub mod handle_tauri_startup;
|
||||
mod spawn_storytoller_task_polling_thread;
|
||||
mod tasks;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::window::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::window::main_window_size::MainWindowSize;
|
||||
use crate::core::utils::window::position_main_window::position_main_window;
|
||||
use crate::core::utils::window::resize_main_window::resize_main_window;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub fn initially_size_and_position_windows(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
) {
|
||||
|
||||
println!("Sizing and positioning window...");
|
||||
|
||||
match MainWindowSize::from_filesystem_configs(&root) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(size)) => {
|
||||
println!("Resizing window to: {:?}", size);
|
||||
let result = resize_main_window(app, &size);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Could not set window size: {:?}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read window size from disk: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match MainWindowPosition::from_filesystem_configs(&root) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(pos)) => {
|
||||
println!("Moving window to: {:?}", pos);
|
||||
let result = position_main_window(app, &pos);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Could not set window position: {:?}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read window position from disk: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub fn load_provider_priority_state(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
) -> AnyhowResult<()> {
|
||||
let provider_priority = match ProviderPriorityStore::from_filesystem_configs(root) {
|
||||
Ok(Some(priority)) => {
|
||||
println!("Loaded provider priority from disk: {:?}", priority.get_priority());
|
||||
priority
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("No provider priority found on disk, using default.");
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read provider priority from disk: {:?}", err);
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
};
|
||||
|
||||
let success = app.manage(provider_priority);
|
||||
|
||||
if !success {
|
||||
eprintln!("Failed to manage provider priority state.");
|
||||
return Err(anyhow!("Failed to manage provider priority state."));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub (super) mod bootstrap_task_database;
|
||||
pub (super) mod set_app_log_level;
|
||||
pub (super) mod initially_size_and_position_windows;
|
||||
pub (super) mod spawn_discord_presence_thread;
|
||||
pub (super) mod spawn_fal_task_polling_thread;
|
||||
pub (super) mod spawn_main_window_thread;
|
||||
pub (super) mod spawn_sora_task_polling_thread;
|
||||
pub (super) mod spawn_storytoller_task_polling_thread;
|
||||
pub (super) mod load_provider_priority_state;
|
||||
@@ -0,0 +1,73 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use errors::AnyhowResult;
|
||||
use log::LevelFilter;
|
||||
use std::env;
|
||||
use std::env::VarError;
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_log::{Target, TargetKind};
|
||||
|
||||
const RUST_LOG: &str = "RUST_LOG";
|
||||
|
||||
pub fn set_app_log_level(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
//print_env_var(RUST_LOG);
|
||||
|
||||
// NB: The semantics of this do not match `RUST_LOG` exactly as it's not set per-package.
|
||||
let maybe_level = env::var(RUST_LOG)
|
||||
.map(|value| value.trim().to_ascii_lowercase());
|
||||
|
||||
let mut log_level = LevelFilter::Info;
|
||||
|
||||
match maybe_level {
|
||||
Ok(level) => {
|
||||
println!("The `RUST_LOG` environment variable was set to: `{}`.", level);
|
||||
|
||||
if level.contains("debug") {
|
||||
log_level = LevelFilter::Debug;
|
||||
} else if level.contains("info") {
|
||||
log_level = LevelFilter::Info;
|
||||
} else if level.contains("warn") {
|
||||
log_level = LevelFilter::Warn;
|
||||
} else if level.contains("error") {
|
||||
log_level = LevelFilter::Error;
|
||||
} else if level.contains("off") {
|
||||
log_level = LevelFilter::Off;
|
||||
}
|
||||
}
|
||||
Err(VarError::NotPresent) => {
|
||||
// NB: This demonstrably tests stderr output in the default case.
|
||||
eprintln!("No `RUST_LOG` environment variable found.");
|
||||
}
|
||||
Err(VarError::NotUnicode(_)) => {
|
||||
// NB: This demonstrably tests stderr output in the default case.
|
||||
eprintln!("Could not parse `RUST_LOG` environment variable; not unicode.");
|
||||
}
|
||||
}
|
||||
|
||||
println!("Setting app log level to: {:?}", log_level);
|
||||
|
||||
app.plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log_level)
|
||||
.targets(vec![
|
||||
Target::new(TargetKind::Stdout),
|
||||
Target::new(TargetKind::LogDir {
|
||||
file_name: Some(root.log_file_name_str().to_string())
|
||||
}),
|
||||
])
|
||||
.build(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn print_env_var(var_name: &str) {
|
||||
// match env::var(var_name) {
|
||||
// Ok(value) => println!("Environment variable `{}` = {}", var_name, value),
|
||||
// Err(VarError::NotPresent) => println!("Environment variable `{}` is not set.", var_name),
|
||||
// Err(VarError::NotUnicode(_)) => println!("Environment variable `{}` is not a valid Unicode string", var_name),
|
||||
// }
|
||||
// }
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
use crate::core::threads::discord_presence_thread::discord_presence_thread;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
pub fn spawn_discord_presence_thread(
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
tauri::async_runtime::spawn(discord_presence_thread());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::fal::threads::fal_task_polling_thread::fal_task_polling_thread;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub fn spawn_fal_task_polling_thread(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_credential_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
tauri::async_runtime::spawn(fal_task_polling_thread(
|
||||
app.clone(),
|
||||
app_env_configs.clone(),
|
||||
root.clone(),
|
||||
fal_credential_manager.clone(),
|
||||
fal_task_queue.clone(),
|
||||
storyteller_creds_manager.clone(),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::threads::main_window_thread::main_window_thread::main_window_thread;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub fn spawn_main_window_thread(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
tauri::async_runtime::spawn(main_window_thread(
|
||||
app.clone(),
|
||||
root.clone(),
|
||||
storyteller_creds_manager.clone(),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::sora::threads::sora_task_polling_thread::sora_task_polling_thread;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub fn spawn_sora_task_polling_thread(
|
||||
app: &AppHandle,
|
||||
root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
sora_credential_manager: &SoraCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
tauri::async_runtime::spawn(sora_task_polling_thread(
|
||||
app.clone(),
|
||||
app_env_configs.clone(),
|
||||
root.clone(),
|
||||
sora_credential_manager.clone(),
|
||||
storyteller_creds_manager.clone(),
|
||||
sora_task_queue.clone(),
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,8 +3,7 @@ pub mod app_preferences;
|
||||
pub mod app_startup_time;
|
||||
pub mod data_dir;
|
||||
pub mod expanduser;
|
||||
pub mod main_window_position;
|
||||
pub mod main_window_size;
|
||||
pub mod os_platform;
|
||||
pub mod provider_priority;
|
||||
pub mod task_database;
|
||||
pub mod task_database;
|
||||
pub mod window;
|
||||
-8
@@ -42,14 +42,6 @@ impl MainWindowPosition {
|
||||
Ok(Some(pos))
|
||||
}
|
||||
|
||||
pub fn apply_to_main_window(&self, app: &AppHandle) -> AnyhowResult<()> {
|
||||
let windows = app.windows();
|
||||
let window = windows.get(MAIN_WINDOW_NAME)
|
||||
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
|
||||
window.set_position(PhysicalPosition::new(self.x, self.y))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn persist_to_filesystem(&self, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
let filename = app_data_root.get_window_position_config_file();
|
||||
let json = serde_json::to_string(self)?;
|
||||
+19
-11
@@ -1,12 +1,13 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::best_window_size_heuristic::best_window_size_heuristic;
|
||||
use crate::core::windows::main_window::constants::MAIN_WINDOW_NAME;
|
||||
use crate::core::utils::window::get_window_size_heuristic::get_window_size_heuristic;
|
||||
use crate::core::windows::main_window::constants::{MAIN_WINDOW_MIN_HEIGHT, MAIN_WINDOW_MIN_WIDTH, MAIN_WINDOW_NAME};
|
||||
use errors::AnyhowResult;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use tauri::{AppHandle, Manager, PhysicalSize, Window};
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct MainWindowSize {
|
||||
pub width: u32,
|
||||
@@ -26,7 +27,7 @@ impl MainWindowSize {
|
||||
}
|
||||
|
||||
pub fn from_window(window: &Window) -> AnyhowResult<Self> {
|
||||
let size = best_window_size_heuristic(window)?;
|
||||
let size = get_window_size_heuristic(window)?;
|
||||
Ok(Self {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
@@ -43,14 +44,6 @@ impl MainWindowSize {
|
||||
Ok(Some(size))
|
||||
}
|
||||
|
||||
pub fn apply_to_main_window(&self, app: &AppHandle) -> AnyhowResult<()> {
|
||||
let windows = app.windows();
|
||||
let window = windows.get(MAIN_WINDOW_NAME)
|
||||
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
|
||||
window.set_size(PhysicalSize::new(self.width, self.height))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn persist_to_filesystem(&self, app_data_root: &AppDataRoot) -> AnyhowResult<()> {
|
||||
let filename = app_data_root.get_window_size_config_file();
|
||||
let json = serde_json::to_string(self)?;
|
||||
@@ -64,6 +57,21 @@ impl MainWindowSize {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Window size is not wide enough to persist or apply.
|
||||
pub fn is_not_wide_enough(&self) -> bool {
|
||||
self.width < MAIN_WINDOW_MIN_WIDTH
|
||||
}
|
||||
|
||||
/// Window size is not tall enough to persist or apply.
|
||||
pub fn is_not_tall_enough(&self) -> bool {
|
||||
self.height < MAIN_WINDOW_MIN_HEIGHT
|
||||
}
|
||||
|
||||
/// Window size is not big enough (tall or wide) to persist or apply.
|
||||
pub fn is_not_big_enough(&self) -> bool {
|
||||
self.is_not_wide_enough() || self.is_not_tall_enough()
|
||||
}
|
||||
|
||||
pub fn to_physical_size(&self) -> PhysicalSize<u32> {
|
||||
PhysicalSize::new(self.width, self.height)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod main_window_position;
|
||||
pub mod main_window_size;
|
||||
@@ -1,10 +0,0 @@
|
||||
pub mod frontend_subscriber_id;
|
||||
pub mod frontend_subscriber_payload;
|
||||
pub mod task_provider;
|
||||
pub mod task;
|
||||
pub mod task_id;
|
||||
pub mod task_pool;
|
||||
pub mod task_type;
|
||||
mod task_provider_job_id;
|
||||
mod task_state;
|
||||
pub mod task_database;
|
||||
@@ -1,42 +0,0 @@
|
||||
use multi_index_map::MultiIndexMap;
|
||||
use crate::core::tasks::task_id::TaskId;
|
||||
use crate::core::tasks::task_provider::TaskProvider;
|
||||
use crate::core::tasks::task_provider_job_id::TaskProviderJobId;
|
||||
use crate::core::tasks::task_state::TaskState;
|
||||
use crate::core::tasks::task_type::TaskType;
|
||||
|
||||
/// The provider's native identifier for the task.
|
||||
pub type ProviderTaskId = String;
|
||||
|
||||
#[derive(MultiIndexMap, Debug, Clone)]
|
||||
#[multi_index_derive(Debug)]
|
||||
#[multi_index_hash(rustc_hash::FxBuildHasher)]
|
||||
pub struct Task {
|
||||
#[multi_index(hashed_unique)]
|
||||
pub id: TaskId,
|
||||
|
||||
#[multi_index(hashed_non_unique)]
|
||||
pub task_state: TaskState,
|
||||
|
||||
pub task_type: Option<TaskType>,
|
||||
|
||||
#[multi_index(hashed_non_unique)]
|
||||
pub provider: Option<TaskProvider>,
|
||||
|
||||
/// Identifier used by the provider to track the job.
|
||||
#[multi_index(hashed_unique)]
|
||||
pub provider_job_id: Option<TaskProviderJobId>,
|
||||
|
||||
/// If the frontend is subscribing, it might have an identifier
|
||||
pub frontend_subscriber_id: Option<String>,
|
||||
|
||||
/// If the frontend is subscribing, it might have a payload.
|
||||
/// This is opaque, but could be for instance base64-encoded JSON.
|
||||
pub frontend_subscriber_payload: Option<String>,
|
||||
|
||||
// TODO: Submitted and completion datetimes
|
||||
|
||||
// TODO: Progress
|
||||
|
||||
// TODO: Completion media file, completion batch token
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
pub struct TaskDatabase {
|
||||
pub pool: Pool<Sqlite>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub async fn new(app_handle: &AppHandle) -> Result<Self> {
|
||||
let app_dir = app_handle
|
||||
.path_resolver()
|
||||
.app_data_dir()
|
||||
.expect("failed to get app dir");
|
||||
|
||||
// Ensure the app directory exists
|
||||
fs::create_dir_all(&app_dir)?;
|
||||
|
||||
let db_path = app_dir.join("grid_search.db");
|
||||
|
||||
// Set the DATABASE_URL environment variable to point to this SQLite file
|
||||
env::set_var("DATABASE_URL", format!("sqlite://{}", db_path.display()));
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
|
||||
/// Our identifier for tasks.
|
||||
/// These are ephemeral (for now), but may be peristed later on.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TaskId(String);
|
||||
|
||||
impl TaskId {
|
||||
pub fn generate() -> Self {
|
||||
let task_id = format!("tauri_task_{}", generate_random_uuid());
|
||||
Self(task_id)
|
||||
}
|
||||
|
||||
pub fn from_str(task_id: &str) -> Self {
|
||||
Self(task_id.to_string())
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
/// Hand-rolling this instead of SQLite for now.
|
||||
/// Not sure if SQLite will create crazy build issues.
|
||||
/// Can replace with SQLite later, perhaps.
|
||||
pub struct TaskPool {
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum TaskProvider {
|
||||
Artcraft,
|
||||
Fal,
|
||||
Sora,
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct TaskProviderJobId(String);
|
||||
|
||||
impl TaskProviderJobId {
|
||||
pub fn from_str(job_id: &str) -> Self {
|
||||
Self(job_id.to_string())
|
||||
}
|
||||
|
||||
pub fn from_artcraft_job_id(job_id: InferenceJobToken) -> Self {
|
||||
Self(job_id.to_string())
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum TaskState {
|
||||
Submitted,
|
||||
InProgress,
|
||||
|
||||
// Terminal states
|
||||
|
||||
Complete,
|
||||
Failed,
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum TaskType {
|
||||
BackgroundRemoval,
|
||||
ImageGeneration,
|
||||
VideoGeneration,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::core::state::app_startup_time::AppStartupTime;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::main_window_size::MainWindowSize;
|
||||
use crate::core::state::window::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::window::main_window_size::MainWindowSize;
|
||||
use crate::core::threads::main_window_thread::persist_storyteller_cookies_task::persist_storyteller_cookies_task;
|
||||
use crate::core::threads::main_window_thread::persist_window_position_task::persist_window_position_task;
|
||||
use crate::core::threads::main_window_thread::persist_window_resize_task::persist_window_resize_task;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::window::main_window_position::MainWindowPosition;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use memory_store::clone_slot::CloneSlot;
|
||||
|
||||
+7
-3
@@ -1,6 +1,6 @@
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_size::MainWindowSize;
|
||||
use crate::core::utils::best_window_size_heuristic::best_window_size_heuristic;
|
||||
use crate::core::state::window::main_window_size::MainWindowSize;
|
||||
use crate::core::utils::window::get_window_size_heuristic::get_window_size_heuristic;
|
||||
use errors::AnyhowResult;
|
||||
use log::info;
|
||||
use memory_store::clone_slot::CloneSlot;
|
||||
@@ -13,7 +13,7 @@ pub async fn persist_window_resize_task(
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
let current_window_size = MainWindowSize::from_window(window)?;
|
||||
let current_physical_size = best_window_size_heuristic(window)?;
|
||||
let current_physical_size = get_window_size_heuristic(window)?;
|
||||
|
||||
let mut save_size_to_disk = true;
|
||||
|
||||
@@ -22,6 +22,10 @@ pub async fn persist_window_resize_task(
|
||||
save_size_to_disk = false;
|
||||
}
|
||||
}
|
||||
|
||||
if current_window_size.is_not_big_enough() {
|
||||
save_size_to_disk = false;
|
||||
}
|
||||
|
||||
if save_size_to_disk {
|
||||
info!("Saving window size configs to disk...");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod best_window_size_heuristic;
|
||||
pub mod clear_all_webview_cookies;
|
||||
pub mod download_media_file_to_temp_dir;
|
||||
pub mod download_url_to_temp_dir;
|
||||
@@ -10,4 +9,4 @@ pub mod object_store;
|
||||
pub mod save_base64_image_to_temp_dir;
|
||||
pub mod simple_http_download;
|
||||
pub mod simple_http_download_to_tempfile;
|
||||
pub mod webview_unsafe;
|
||||
pub mod window;
|
||||
@@ -1,38 +0,0 @@
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
const MAIN_WINDOW_NAME: &str = "main";
|
||||
|
||||
pub fn webview_unsafe_for_app(app: &AppHandle) -> AnyhowResult<()> {
|
||||
let windows = app.windows();
|
||||
let maybe_window = windows.get(MAIN_WINDOW_NAME);
|
||||
|
||||
/*if let Some(window) = maybe_window {
|
||||
let webview = window.get_webview(MAIN_WINDOW_NAME);
|
||||
if let Some(webview) = webview {
|
||||
webview_unsafe(&webview);
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Main window not found"));
|
||||
}*/
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*pub fn webview_unsafe(webview: &Webview) {
|
||||
let r = webview.with_webview(|webview| {
|
||||
webview.controller();
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe {
|
||||
let view: &objc2_web_kit::WKWebView = &*webview.inner().cast();
|
||||
let controller: &objc2_web_kit::WKUserContentController = &*webview.controller().cast();
|
||||
let window: &objc2_app_kit::NSWindow = &*webview.ns_window().cast();
|
||||
|
||||
//view.setPageZoom(40.);
|
||||
//controller.removeAllUserScripts();
|
||||
let bg_color = objc2_app_kit::NSColor::colorWithDeviceRed_green_blue_alpha(1.0, 0.2, 0.4, 1.);
|
||||
window.setBackgroundColor(Some(&bg_color));
|
||||
}
|
||||
|
||||
});
|
||||
}*/
|
||||
+4
-4
@@ -4,18 +4,18 @@ use tauri::{PhysicalSize, Window};
|
||||
// A typical height difference when dev tools are open:
|
||||
// Window inner size = PhysicalSize { width: 3704, height: 732 }
|
||||
// Window outer size = PhysicalSize { width: 3704, height: 1476 }
|
||||
const HEIGHT_DIFFERENCE_THRESHOLD: u32 = 300;
|
||||
const DEV_TOOLS_HEIGHT_DIFFERENCE_THRESHOLD: u32 = 300;
|
||||
|
||||
/// Get the best window size
|
||||
pub fn best_window_size_heuristic(window: &Window) -> AnyhowResult<PhysicalSize<u32>> {
|
||||
pub fn get_window_size_heuristic(window: &Window) -> AnyhowResult<PhysicalSize<u32>> {
|
||||
// NB: Ordinarily we'd use inner size, as the outer size includes the window decorations.
|
||||
// However, dev tools contract the inner size, and in the event they're open, we should
|
||||
// prefer the outer size.
|
||||
let inner = window.inner_size()?;
|
||||
let outer = window.outer_size()?;
|
||||
|
||||
|
||||
let height_delta = outer.height.saturating_sub(inner.height);
|
||||
if height_delta > HEIGHT_DIFFERENCE_THRESHOLD {
|
||||
if height_delta > DEV_TOOLS_HEIGHT_DIFFERENCE_THRESHOLD {
|
||||
// Dev tools are open, use the outer size
|
||||
Ok(outer)
|
||||
} else {
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod get_window_size_heuristic;
|
||||
pub mod position_main_window;
|
||||
pub mod resize_main_window;
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::core::state::window::main_window_position::MainWindowPosition;
|
||||
use crate::core::windows::main_window::constants::MAIN_WINDOW_NAME;
|
||||
use errors::AnyhowResult;
|
||||
use tauri::{AppHandle, Manager, PhysicalPosition};
|
||||
|
||||
/// Try to position the main window at startup.
|
||||
/// If it doesn't meet certain placement heuristics, abandon manual placement and let the OS
|
||||
/// decide where to place the app.
|
||||
pub fn position_main_window(
|
||||
app: &AppHandle,
|
||||
position: &MainWindowPosition,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
// NB: This non-negative position heuristic is good for MacBook (single screen).
|
||||
// Not sure how this fares on Windows, Linux, or multiscreen setups.
|
||||
// On Mac, this prevents the windows from being positioned off to the top-left,
|
||||
// as (0,0) is the top left directly under the top menu bar.
|
||||
if position.x < 0 {
|
||||
// TODO(bt,2025-07-16): just in the case of x/width, allow slight placement off
|
||||
// screen as long as the app is wide enough.
|
||||
return Err(anyhow::anyhow!("X position must be non-negative"));
|
||||
} else if position.y < 0 {
|
||||
return Err(anyhow::anyhow!("Y position must be non-negative"));
|
||||
}
|
||||
|
||||
let windows = app.windows();
|
||||
|
||||
let window = windows.get(MAIN_WINDOW_NAME)
|
||||
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
|
||||
|
||||
if let Ok(Some(monitor)) = window.current_monitor() {
|
||||
// Reports the size of the monitor, e.g. PhysicalSize { width: 4112, height: 2658 }
|
||||
// This is not impacted by `scale_factor`, even if that's a multiple like "2".
|
||||
// i.e., positioning the app at `monitor.size().width/2` and `monitor.size().height/2`
|
||||
// will place the **top left corner of the app** in roughly the center of the monitor.
|
||||
let monitor_size = monitor.size();
|
||||
|
||||
println!("Current monitor - size: {:?}", monitor_size);
|
||||
println!("Current monitor - scale factor: {:?}", monitor.scale_factor());
|
||||
println!("Current monitor - position: {:?}", monitor.position());
|
||||
|
||||
let width_padding = monitor_size.width.div_ceil(6);
|
||||
let height_padding = monitor_size.height.div_ceil(6);
|
||||
|
||||
let max_width = monitor_size.width.saturating_sub(width_padding);
|
||||
let max_height = monitor_size.height.saturating_sub(height_padding);
|
||||
|
||||
let max_width = i32::try_from(max_width)?;
|
||||
let max_height = i32::try_from(max_height)?;
|
||||
|
||||
// Prevent the window from being positioned off the screen to the bottom, right, and bottom right.
|
||||
// This works on MacBook (single screen), but should be tested on Windows, Linux, and multiscreen setups.
|
||||
if position.x > max_width {
|
||||
return Err(anyhow::anyhow!("X position must be less than or equal to {} (monitor width {})",
|
||||
max_width, monitor_size.width));
|
||||
} else if position.y > max_height {
|
||||
return Err(anyhow::anyhow!("Y position must be less than or equal to {} (monitor height {})",
|
||||
max_height, monitor_size.height));
|
||||
}
|
||||
}
|
||||
|
||||
window.set_position(PhysicalPosition::new(position.x, position.y))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use crate::core::state::window::main_window_size::MainWindowSize;
|
||||
use crate::core::windows::main_window::constants::{MAIN_WINDOW_MIN_HEIGHT, MAIN_WINDOW_MIN_WIDTH, MAIN_WINDOW_NAME};
|
||||
use tauri::{AppHandle, Manager, PhysicalSize};
|
||||
|
||||
/// Try to resize the main window at startup.
|
||||
/// If it doesn't meet certain sizing heuristics, abandon manual resize and let the OS
|
||||
/// decide how to size the app.
|
||||
pub fn resize_main_window(
|
||||
app: &AppHandle,
|
||||
size: &MainWindowSize,
|
||||
) -> errors::AnyhowResult<()> {
|
||||
|
||||
let mut resize_width = size.width;
|
||||
let mut resize_height = size.height;
|
||||
let mut resize_was_corrected = false;
|
||||
|
||||
if resize_width < MAIN_WINDOW_MIN_WIDTH {
|
||||
resize_width = MAIN_WINDOW_MIN_WIDTH;
|
||||
resize_was_corrected = true;
|
||||
}
|
||||
if resize_height < MAIN_WINDOW_MIN_HEIGHT {
|
||||
resize_height = MAIN_WINDOW_MIN_HEIGHT;
|
||||
resize_was_corrected = true;
|
||||
}
|
||||
|
||||
let windows = app.windows();
|
||||
let window = windows.get(MAIN_WINDOW_NAME)
|
||||
.ok_or_else(|| anyhow::anyhow!("Main window not found"))?;
|
||||
|
||||
if let Ok(Some(monitor)) = window.current_monitor() {
|
||||
let scale_factor = monitor.scale_factor();
|
||||
|
||||
if scale_factor > 1.0 {
|
||||
|
||||
let min_width = MAIN_WINDOW_MIN_WIDTH as f64 * scale_factor;
|
||||
let min_height = MAIN_WINDOW_MIN_HEIGHT as f64 * scale_factor;
|
||||
|
||||
// Is this safe? https://users.rust-lang.org/t/convert-an-f64-to-an-i32/50991/4
|
||||
let min_width = min_width as u32;
|
||||
let min_height = min_height as u32;
|
||||
|
||||
if resize_width < min_width {
|
||||
resize_width = min_width;
|
||||
resize_was_corrected = true;
|
||||
}
|
||||
if resize_height < min_height {
|
||||
resize_height = min_height;
|
||||
resize_was_corrected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if resize_was_corrected {
|
||||
println!("Resizing window to: {}x{} (corrected from {}x{})",
|
||||
resize_width, resize_height, size.width, size.height);
|
||||
} else {
|
||||
println!("Resizing window to: {}x{} (no correction needed)", resize_width, resize_height);
|
||||
}
|
||||
|
||||
window.set_size(PhysicalSize::new(resize_width, resize_height))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,3 +2,11 @@
|
||||
/// The name of the main application window.
|
||||
/// This is the Artcraft application UX itself.
|
||||
pub const MAIN_WINDOW_NAME : &str = "main";
|
||||
|
||||
// TODO: handle pixel density, eg. retina displays
|
||||
/// This is the minimum width to resize the main window in an automated fashion
|
||||
pub const MAIN_WINDOW_MIN_WIDTH : u32 = 800;
|
||||
|
||||
// TODO: handle pixel density, eg. retina displays
|
||||
/// This is the minimum height to resize the main window in an automated fashion
|
||||
pub const MAIN_WINDOW_MIN_HEIGHT : u32 = 600;
|
||||
|
||||
@@ -19,12 +19,9 @@ use crate::core::lifecycle::startup::handle_tauri_startup::handle_tauri_startup;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::app_preferences::app_preferences_manager::load_app_preferences_or_default;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::main_window_position::MainWindowPosition;
|
||||
use crate::core::state::main_window_size::MainWindowSize;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::threads::discord_presence_thread::discord_presence_thread;
|
||||
use crate::core::threads::main_window_thread::main_window_thread::main_window_thread;
|
||||
use crate::core::utils::webview_unsafe::webview_unsafe_for_app;
|
||||
use crate::services::fal::commands::fal_background_removal_command::fal_background_removal_command;
|
||||
use crate::services::fal::commands::fal_hunyuan_image_to_3d_command::fal_hunyuan_image_to_3d_command;
|
||||
use crate::services::fal::commands::fal_kling_image_to_video_command::fal_kling_image_to_video_command;
|
||||
@@ -87,36 +84,12 @@ pub fn run() {
|
||||
|
||||
let app_env_configs_2 = app_env_configs.clone();
|
||||
|
||||
let provider_priority = match ProviderPriorityStore::from_filesystem_configs(&app_data_root) {
|
||||
Ok(Some(priority)) => {
|
||||
println!("Loaded provider priority from disk: {:?}", priority.get_priority());
|
||||
priority
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("No provider priority found on disk, using default.");
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read provider priority from disk: {:?}", err);
|
||||
ProviderPriorityStore::default()
|
||||
}
|
||||
};
|
||||
|
||||
println!("Initializing backend runtime...");
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_upload::init())
|
||||
.plugin(tauri_plugin_log::Builder::new()
|
||||
.level(log::LevelFilter::Info)
|
||||
.targets(vec![
|
||||
Target::new(TargetKind::Stdout),
|
||||
Target::new(TargetKind::LogDir {
|
||||
file_name: Some(app_data_root.log_file_name_str().to_string())
|
||||
}),
|
||||
])
|
||||
.build())
|
||||
.setup(move |app| {
|
||||
// TODO(bt): This is broken on windows
|
||||
// log_environment_details();
|
||||
@@ -133,66 +106,28 @@ pub fn run() {
|
||||
let root = app_data_root_2.clone();
|
||||
let env_config = app_env_configs_2.clone();
|
||||
let storyteller_creds = storyteller_creds_manager_2.clone();
|
||||
let sora_creds = sora_creds_manager_2.clone();
|
||||
let sora_tasks = sora_task_queue_2.clone();
|
||||
let fal_creds = fal_creds_manager_2.clone();
|
||||
let fal_tasks = fal_task_queue_2.clone();
|
||||
|
||||
tauri::async_runtime::block_on(async move {
|
||||
if let Err(err) = handle_tauri_startup(handle, root, env_config, storyteller_creds).await {
|
||||
let result = handle_tauri_startup(
|
||||
handle,
|
||||
root,
|
||||
env_config,
|
||||
storyteller_creds,
|
||||
sora_creds,
|
||||
sora_tasks,
|
||||
fal_creds,
|
||||
fal_tasks,
|
||||
).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to handle Tauri startup: {:?}", err);
|
||||
panic!("Failed to handle Tauri startup: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
let result = webview_unsafe_for_app(&app);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Error setting webview unsafe: {:?}", err);
|
||||
}
|
||||
|
||||
// TODO(bt): Clean this up. We can just clone at the callsite. Also clean initialization
|
||||
let app_2 = app.clone();
|
||||
let app_3 = app.clone();
|
||||
let app_4 = app.clone();
|
||||
let app_5 = app.clone();
|
||||
let app_env_configs_3 = app_env_configs_2.clone();
|
||||
let app_data_root_3 = app_data_root_2.clone();
|
||||
let app_data_root_4 = app_data_root_2.clone();
|
||||
let app_data_root_5 = app_data_root_2.clone();
|
||||
let sora_creds_manager_3 = sora_creds_manager_2.clone();
|
||||
let storyteller_creds_manager_3 = storyteller_creds_manager.clone();
|
||||
let storyteller_creds_manager_4 = storyteller_creds_manager.clone();
|
||||
let fal_creds_manager_3 = fal_creds_manager_2.clone();
|
||||
let fal_task_queue_3 = fal_task_queue_2.clone();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
match MainWindowPosition::from_filesystem_configs(&app_data_root_3) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(pos)) => {
|
||||
println!("Moving window to: {:?}", pos);
|
||||
let result = pos.apply_to_main_window(&app);
|
||||
if let Err(err) = result {
|
||||
eprintln!("Could not set window position: {:?}", err);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Failed to read window position from disk: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
tauri::async_runtime::spawn(main_window_thread(app_3, app_data_root_3, storyteller_creds_manager_2));
|
||||
tauri::async_runtime::spawn(sora_task_polling_thread(app_4, app_env_configs_2, app_data_root_4, sora_creds_manager_3, storyteller_creds_manager_3, sora_task_queue_2));
|
||||
tauri::async_runtime::spawn(discord_presence_thread());
|
||||
tauri::async_runtime::spawn(fal_task_polling_thread(app_5, app_env_configs_3, app_data_root_5, fal_creds_manager_3, fal_task_queue_3, storyteller_creds_manager_4));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
@@ -201,7 +136,6 @@ pub fn run() {
|
||||
.manage(app_preferences)
|
||||
.manage(fal_creds_manager)
|
||||
.manage(fal_task_queue)
|
||||
.manage(provider_priority)
|
||||
.manage(sora_creds_manager)
|
||||
.manage(sora_task_queue)
|
||||
.manage(storyteller_creds_manager_3)
|
||||
|
||||
Reference in New Issue
Block a user