mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-30 15:25:57 -04:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98b0503d97 | ||
|
|
5d3c168b5b | ||
|
|
e2a4e426dd | ||
|
|
fbad200dee | ||
|
|
9e0a5558a8 | ||
|
|
6c0b32aa44 | ||
|
|
6f9b637da0 | ||
|
|
40dda6e5cd | ||
|
|
06fca6b54f |
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET task_status = \"cancelled_by_user\"\n WHERE task_status NOT IN (\n \"complete_success\",\n \"complete_failure\",\n \"dead\",\n \"cancelled_by_user\",\n \"cancelled_by_provider\",\n \"cancelled_by_us\"\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "30272128d83543d9fbfca930b7cdd4af30adfb4d90f2de044463a111c6340495"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET is_dismissed_by_user = 1\n WHERE is_dismissed_by_user != 1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f83018ec5493982659a56576296914f937bb4e75db3a522eb4bda486d5caa402"
|
||||
}
|
||||
+5
-2
@@ -1,6 +1,6 @@
|
||||
use crate::api::api_types::run_object_id::RunObjectId;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::api_types::upload_mime_type::UploadMimeType;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE};
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
use crate::credentials::world_labs_cookies::WorldLabsCookies;
|
||||
@@ -25,7 +25,10 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACH
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
const BASE_URL : &str = "https://api.worldlabs.ai/api/v1/objects";
|
||||
|
||||
// Note: WorldLabs is phasing out the old URL scheme:
|
||||
// const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
|
||||
fn get_url(upload_id: &UploadObjectId) -> String {
|
||||
format!("{}/{}:upload", BASE_URL, upload_id.0)
|
||||
|
||||
@@ -24,7 +24,10 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACH
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
const URL : &str = "https://api.worldlabs.ai/api/v1/objects";
|
||||
|
||||
// Note: WorldLabs is phasing out the old URL scheme:
|
||||
// const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
|
||||
pub struct CreateRunObjectArgs<'a> {
|
||||
pub cookies: &'a WorldLabsCookies,
|
||||
|
||||
+5
-2
@@ -1,6 +1,6 @@
|
||||
use crate::api::api_types::run_object_id::RunObjectId;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::api_types::upload_mime_type::UploadMimeType;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE};
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
use crate::credentials::world_labs_cookies::WorldLabsCookies;
|
||||
@@ -25,7 +25,10 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACH
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
const URL : &str = "https://api.worldlabs.ai/api/v1/objects";
|
||||
|
||||
// Note: WorldLabs is phasing out the old URL scheme:
|
||||
// const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
|
||||
pub struct CreateUploadObjectArgs<'a> {
|
||||
pub cookies: &'a WorldLabsCookies,
|
||||
|
||||
+6
-3
@@ -1,6 +1,6 @@
|
||||
use crate::api::api_types::run_object_id::RunObjectId;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::api_types::upload_mime_type::UploadMimeType;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE};
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
use crate::credentials::world_labs_cookies::WorldLabsCookies;
|
||||
@@ -25,7 +25,10 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACH
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
const BASE_URL : &str = "https://api.worldlabs.ai/api/v1/objects";
|
||||
|
||||
// Note: WorldLabs is phasing out the old URL scheme:
|
||||
// const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
|
||||
fn get_url(upload_id: &UploadObjectId) -> String {
|
||||
format!("{}/{}:complete", BASE_URL, upload_id.0)
|
||||
@@ -131,12 +134,12 @@ struct RawResponse {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use log::LevelFilter;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::requests::objects::finalize_object_image_upload::{finalize_object_image_upload, get_url, FinalizeObjectImageUploadArgs};
|
||||
use crate::test_utils::get_test_bearer_token::get_test_bearer_token;
|
||||
use crate::test_utils::get_test_cookies::get_typed_test_cookies;
|
||||
use crate::test_utils::setup_test_logging::setup_test_logging;
|
||||
use log::LevelFilter;
|
||||
|
||||
#[test]
|
||||
fn test_get_url() {
|
||||
|
||||
+6
-3
@@ -1,9 +1,9 @@
|
||||
use crate::api::api_types::image_input_object_id::ImageInputObjectId;
|
||||
use crate::api::api_types::meta_world_object_id::MetaWorldObjectId;
|
||||
use crate::api::api_types::pano_object_id::PanoObjectId;
|
||||
use crate::api::api_types::run_object_id::RunObjectId;
|
||||
use crate::api::api_types::upload_mime_type::UploadMimeType;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::api_types::meta_world_object_id::MetaWorldObjectId;
|
||||
use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE};
|
||||
use crate::api::utils::upload_id_to_image_url::upload_id_to_image_url;
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
@@ -31,7 +31,10 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACH
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
const BASE_URL : &str = "https://api.worldlabs.ai/api/v1/objects";
|
||||
|
||||
// Note: WorldLabs is phasing out the old URL scheme:
|
||||
// const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
|
||||
fn get_url(run_id: &RunObjectId) -> String {
|
||||
format!("{}/{}", BASE_URL, run_id.0)
|
||||
@@ -398,9 +401,9 @@ struct RawResponse {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::api_types::image_input_object_id::ImageInputObjectId;
|
||||
use crate::api::api_types::meta_world_object_id::MetaWorldObjectId;
|
||||
use crate::api::api_types::pano_object_id::PanoObjectId;
|
||||
use crate::api::api_types::run_object_id::RunObjectId;
|
||||
use crate::api::api_types::meta_world_object_id::MetaWorldObjectId;
|
||||
use crate::api::requests::objects::update_run_object_with_upload::{RawRequest, UpdateRunObjectWithUploadPayloadArgs};
|
||||
|
||||
#[test]
|
||||
|
||||
+3
-1
@@ -31,8 +31,10 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACH
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
use crate::api::api_types::world_id::WorldObjectId;
|
||||
const BASE_URL : &str = "https://api.worldlabs.ai/api/v1/objects";
|
||||
|
||||
const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
// Note: WorldLabs is phasing out the old URL scheme:
|
||||
// const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
|
||||
fn get_url(run_id: &RunObjectId) -> String {
|
||||
format!("{}/{}", BASE_URL, run_id.0)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::api::api_types::run_object_id::RunObjectId;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::api_types::upload_mime_type::UploadMimeType;
|
||||
use crate::api::api_types::upload_object_id::UploadObjectId;
|
||||
use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE};
|
||||
use crate::api::utils::upload_id_to_image_url::upload_id_to_image_url;
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
@@ -24,7 +24,10 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACH
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/recaption2";
|
||||
const URL : &str = "https://api.worldlabs.ai/api/v1/recaption2";
|
||||
|
||||
// Note: WorldLabs is phasing out the old URL scheme:
|
||||
// const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/recaption2";
|
||||
|
||||
pub struct RecaptionImageArgs<'a> {
|
||||
pub cookies: &'a WorldLabsCookies,
|
||||
|
||||
@@ -27,7 +27,10 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACH
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/worlds";
|
||||
const URL : &str = "https://api.worldlabs.ai/api/v1/worlds";
|
||||
|
||||
// Note: WorldLabs is phasing out the old URL scheme:
|
||||
//const URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/worlds";
|
||||
|
||||
pub struct CreateWorldArgs<'a> {
|
||||
pub cookies: &'a WorldLabsCookies,
|
||||
|
||||
@@ -18,7 +18,7 @@ use http_headers::values::pragma::PRAGMA_NO_CACHE;
|
||||
use http_headers::values::priority::PRIORITY_4;
|
||||
use http_headers::values::sec::{SEC_FETCH_DEST_EMPTY, SEC_FETCH_MODE_CORS, SEC_FETCH_SITE_CROSS_SITE};
|
||||
use http_headers::values::te::TE_TRAILERS;
|
||||
use log::{debug, error};
|
||||
use log::{debug, error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
@@ -26,7 +26,10 @@ use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACH
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/worlds";
|
||||
const BASE_URL : &str = "https://api.worldlabs.ai/api/v1/worlds";
|
||||
|
||||
// Note: WorldLabs is phasing out the old URL scheme:
|
||||
// const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/worlds";
|
||||
|
||||
fn get_url(world_id: &WorldObjectId) -> String {
|
||||
format!("{}/{}", BASE_URL, world_id.0)
|
||||
@@ -55,7 +58,7 @@ pub async fn poll_world_status(args: PollWorldStatusArgs<'_>) -> Result<PollWorl
|
||||
|
||||
let url = get_url(args.world_id);
|
||||
|
||||
debug!("Requesting URL: {}", url);
|
||||
info!("Polling World: {}", url);
|
||||
|
||||
let mut request_builder = client.get(url)
|
||||
.header(ACCEPT, ACCEPT_ALL)
|
||||
@@ -169,7 +172,30 @@ mod tests {
|
||||
let cookies = get_typed_test_cookies().unwrap();
|
||||
let bearer_token = get_test_bearer_token().unwrap();
|
||||
|
||||
let world_id = WorldObjectId("d60cfa21-5506-43de-9a9c-0707fc17a5ec".to_string());
|
||||
let world_id = WorldObjectId("403c379b-b7d2-4449-8f1e-ebdb56142604".to_string());
|
||||
|
||||
let response = poll_world_status(PollWorldStatusArgs {
|
||||
cookies: &cookies,
|
||||
bearer_token: &bearer_token,
|
||||
world_id: &world_id,
|
||||
request_timeout: None,
|
||||
}).await.unwrap();
|
||||
|
||||
println!("Is complete: {}", response.is_complete);
|
||||
println!("Spz Url: {}", response.spz_splat_url.unwrap_or_else(|| "incomplete".to_string()));
|
||||
|
||||
assert_eq!(1, 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Client side tests only
|
||||
async fn test_request_2() {
|
||||
setup_test_logging(LevelFilter::Debug);
|
||||
|
||||
let cookies = get_typed_test_cookies().unwrap();
|
||||
let bearer_token = get_test_bearer_token().unwrap();
|
||||
|
||||
let world_id = WorldObjectId("e739b11e-6039-49c7-b73d-b79e1b28e295".to_string());
|
||||
|
||||
let response = poll_world_status(PollWorldStatusArgs {
|
||||
cookies: &cookies,
|
||||
|
||||
+11
-16
@@ -277,12 +277,10 @@ pub async fn upload_image_and_create_world_with_retry(args: UploadImageAndCreate
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
use crate::credentials::world_labs_cookies::WorldLabsCookies;
|
||||
use crate::credentials::worldlabs_refresh_token::WorldLabsRefreshToken;
|
||||
use crate::recipes::upload_image_and_create_world_with_retry::{upload_image_and_create_world_with_retry, FileBytesOrPath, UploadImageAndCreateWorldWithRetryArgs};
|
||||
use crate::test_utils::get_test_bearer_token::get_test_bearer_token;
|
||||
use crate::test_utils::get_test_cookies::get_typed_test_cookies;
|
||||
use crate::test_utils::get_test_refresh_token::get_typed_test_refresh_token;
|
||||
use crate::test_utils::setup_test_logging::setup_test_logging;
|
||||
use filesys::file_read_bytes::file_read_bytes;
|
||||
use log::LevelFilter;
|
||||
@@ -294,10 +292,10 @@ mod tests {
|
||||
|
||||
let cookies = get_typed_test_cookies().unwrap();
|
||||
let bearer_token = get_test_bearer_token().unwrap();
|
||||
let refresh_token = WorldLabsRefreshToken::new("AMf-vBy91tuAOoeCwbXV3zNcFAM79ayEKb_cRmTBhVLAIRB1kDzswDgenVnxHfgbmUP95YTlJkpSwJUK3eTZv0iV1An-rZUVII8tOoyLNpV-O1dhytsMXFjR5Mc_hRxUhwuRphUZT6Zub9T1Twd8OL88twFnN-qlwq12W_eRZnEC_IExX0-j6vhfRbVXY0Bf9F1jddeaN7FMgagVzlIt7c7hbboR3XRlU61oBhuEw90ynHWPhDrnTVBtT9xI_TrNFr-v6mMh3M1DGFN9iaHVl74fEwbV6VanUGua55uxdVgia-SyOkYboKRGanWof2g69vZNvREdlMiNFYfWDtsHgcd_DaS6oemS5CVoSbK8sRREUAXHRCmwO5OyZehacnKstyy0TnNwZuvtn_KMzpk3ZmLM-Q-yERlJvpd9rHU-d-uV2R-yZIWgT3rshE9WyK9i4p731RgXfEC6".to_string());
|
||||
let refresh_token = get_typed_test_refresh_token().unwrap();
|
||||
|
||||
//let file_path = "/home/bt/Pictures/locations/island.jpg";
|
||||
let file_path = "/Users/bt/Pictures/Midjourney/dusk_house.jpeg";
|
||||
let file_path = "/Users/bt/Pictures/Midjourney/jeep.jpeg";
|
||||
let file_bytes = file_read_bytes(file_path).unwrap();
|
||||
|
||||
println!("File bytes len: {}", file_bytes.len());
|
||||
@@ -310,8 +308,12 @@ mod tests {
|
||||
file: FileBytesOrPath::Bytes(file_bytes),
|
||||
}).await.unwrap();
|
||||
|
||||
println!("Upload URL: {}", results.image_upload_url);
|
||||
println!("Object ID: {}", results.run_id.0);
|
||||
println!("World ID: {}", results.world_id.0);
|
||||
println!("Upload URL: {}", results.image_upload_url);
|
||||
println!("Has new access tokens: {}", results.maybe_new_access_tokens.is_some());
|
||||
|
||||
assert_eq!(1, 2);
|
||||
}
|
||||
|
||||
|
||||
@@ -320,16 +322,9 @@ mod tests {
|
||||
async fn test_requests_2() {
|
||||
setup_test_logging(LevelFilter::Debug);
|
||||
|
||||
// bt@brand.io
|
||||
let cookies = "";
|
||||
let bearer_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk4OGQ1YTM3OWI3OGJkZjFlNTBhNDA5MTEzZjJiMGM3NWU0NTJlNDciLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiQnJhbmRvbiBUaG9tYXMiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL2EvQUNnOG9jS0lucl8yWGJ2Z1VERUxNOEpCTGdfa2tKbVpxWl9WcTlzZkI2bVR4VmQzc1NJRk4tST1zOTYtYyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS93bHQtdHJhaW5pbmctZ3NjIiwiYXVkIjoid2x0LXRyYWluaW5nLWdzYyIsImF1dGhfdGltZSI6MTc2MDE3Nzk3OCwidXNlcl9pZCI6IjA1c0dhaHRySWdVMjVMaXR5NjhoZjRpMmV4SjIiLCJzdWIiOiIwNXNHYWh0cklnVTI1TGl0eTY4aGY0aTJleEoyIiwiaWF0IjoxNzY2MTI2ODg4LCJleHAiOjE3NjYxMzA0ODgsImVtYWlsIjoiYnRAYnJhbmQuaW8iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjEwNjEwODI2NzgzNTYyOTEwMTc1OSJdLCJlbWFpbCI6WyJidEBicmFuZC5pbyJdfSwic2lnbl9pbl9wcm92aWRlciI6Imdvb2dsZS5jb20ifX0.YLXZHg0rzMzyhhyJsO3DSMhU8GjnxtQtg1GiDwaxZjuybmabqOcR_b4C2lE2XwESg_dDYnIkmdas9wsHshy2bGPCAAtrinQpxTHWhIDxy0bsF8yW0BCOszwVseiTkXd6vveaGYH06EvH-nM4Q5AB_oKbsgRyMEbXhepvX7SlrmeaXxqldCLRalDj6gcEIY_TSwMmjnZ6-sFMPe92YjLY1ITPRx7S3YyWNwBG6mVpgoHcHudNAJ4TFxborZlhC1zpF0NDgtIhN5ITg0I1P0A-JTaBTJHukQwCPKhifFRA1kfcxzbAoZyPiPw2DdgXpDDnNzXUYgnaOOVyGs96Tx8hOQ";
|
||||
let refresh_token = "AMf-vByUgA0J9S93vULECe-sD50cbR85AFVYF60gKAmLFF1MBYwXlKRJCsv7z3oSpLtP0ApyOU5fAl52qHWM2U0yKgEu5gQ5UCUDt5SEG4UBRHWvHEmLLcu-Zl04Fq4ljocSEO3qJzuX1wyshmptZxlDPRkjdLYgyn-Kp03woI2yRPKLuQF5hQtABHabsNUh9Zhs129sgxuUlOuw4zsFI4L8UdE3bEuF3k6Mic7KlE3440YwyQ8Qtmk9zHtXIx2ob56N-1WTGSqwMUuQ1mX4OOvLve9bw2zDW3UzchomTWkh732886RPrP-DNRkPbO3FejUEMxuYQFWYX3EEeIEdfNGXFNGPL1dNniSXlSaKadpX699Ishjviv-x9o-pPQa3zoAj3NzWLyd8mn5rDBh9qAO_iDETaeo01Sjhb4mGYfAWZ-g4S6nW778";
|
||||
|
||||
//let cookies = get_typed_test_cookies().unwrap();
|
||||
|
||||
let bearer_token = WorldLabsBearerToken::new(bearer_token.to_string());
|
||||
let refresh_token = WorldLabsRefreshToken::new(refresh_token.to_string());
|
||||
let cookies = WorldLabsCookies::new(cookies.to_string());
|
||||
let cookies = get_typed_test_cookies().unwrap();
|
||||
let bearer_token = get_test_bearer_token().unwrap();
|
||||
let refresh_token = get_typed_test_refresh_token().unwrap();
|
||||
|
||||
//let file_path = "/home/bt/Pictures/locations/island.jpg";
|
||||
let file_path = "/Users/bt/Pictures/Midjourney/train_ghibli.jpeg";
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::fs::read_to_string;
|
||||
|
||||
#[cfg(test)]
|
||||
pub (crate) fn get_test_bearer_token_raw() -> AnyhowResult<String> {
|
||||
let cookies = read_to_string("/Users/bt/Artcraft/credentials/world_labs_bearer.txt")?;
|
||||
let cookies = read_to_string("/Users/bt/Artcraft/credentials/worldlabs_bearer.txt")?;
|
||||
//let cookies = read_to_string("/home/bt/Artcraft/credentials/world_labs_bearer.txt")?;
|
||||
let cookies = cookies.trim().to_string();
|
||||
Ok(cookies)
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::fs::read_to_string;
|
||||
|
||||
#[cfg(test)]
|
||||
pub (crate) fn get_test_cookies() -> AnyhowResult<String> {
|
||||
//let cookies = read_to_string("/home/bt/Artcraft/credentials/world_labs_cookies.txt")?;
|
||||
let cookies = read_to_string("/Users/bt/Artcraft/credentials/world_labs_cookies.txt")?;
|
||||
//let cookies = read_to_string("/home/bt/Artcraft/credentials/worldlabs_cookies.txt")?;
|
||||
let cookies = read_to_string("/Users/bt/Artcraft/credentials/worldlabs_cookies.txt")?;
|
||||
let cookies = cookies.trim().to_string();
|
||||
Ok(cookies)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::credentials::worldlabs_refresh_token::WorldLabsRefreshToken;
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[cfg(test)]
|
||||
pub (crate) fn get_test_refresh_token() -> AnyhowResult<String> {
|
||||
//let cookies = read_to_string("/home/bt/Artcraft/credentials/worldlabs_cookies.txt")?;
|
||||
let token = read_to_string("/Users/bt/Artcraft/credentials/worldlabs_refresh.txt")?;
|
||||
let token = token.trim().to_string();
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub (crate) fn get_typed_test_refresh_token() -> AnyhowResult<WorldLabsRefreshToken> {
|
||||
let token = get_test_refresh_token()?;
|
||||
Ok(WorldLabsRefreshToken::new(token))
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod get_test_cookies;
|
||||
pub mod get_test_bearer_token;
|
||||
pub mod setup_test_logging;
|
||||
pub mod setup_test_logging;
|
||||
pub mod get_test_refresh_token;
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod get_task_queue_command;
|
||||
pub mod mark_task_as_dismissed_command;
|
||||
pub mod mark_task_as_dismissed_command;
|
||||
pub mod tasks_nuke_all_command;
|
||||
@@ -0,0 +1,61 @@
|
||||
use crate::core::commands::response::shorthand::ResponseOrErrorMessage;
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use chrono::{DateTime, Utc};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_model_type::TaskModelType;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sqlite_tasks::queries::dismiss_all_tasks::dismiss_all_tasks;
|
||||
use sqlite_tasks::queries::list_tasks_for_frontend::list_tasks_for_frontend;
|
||||
use sqlite_tasks::queries::mark_task_as_dismissed::mark_task_as_dismissed;
|
||||
use sqlite_tasks::queries::nuke_all_tasks::nuke_all_tasks;
|
||||
use storyteller_client::endpoints::media_files::delete_media_file::delete_media_file;
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::sqlite::tasks::TaskId;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct TasksNukeAllResponse {
|
||||
success: bool,
|
||||
}
|
||||
|
||||
impl SerializeMarker for TasksNukeAllResponse {}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn tasks_nuke_all_command(
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
) -> ResponseOrErrorMessage<TasksNukeAllResponse> {
|
||||
|
||||
info!("tasks_nuke_all_command called");
|
||||
|
||||
let result = handle_request(
|
||||
&task_database,
|
||||
).await;
|
||||
|
||||
if let Err(err) = &result {
|
||||
error!("tasks_nuke_all_command failed: {:?}", err);
|
||||
return Err("tasks_nuke_all_command failed".into())
|
||||
}
|
||||
|
||||
Ok(TasksNukeAllResponse{
|
||||
success: true,
|
||||
}.into())
|
||||
}
|
||||
|
||||
pub async fn handle_request(
|
||||
task_database: &TaskDatabase,
|
||||
) -> AnyhowResult<()> {
|
||||
let _result = nuke_all_tasks(task_database.get_connection()).await?;
|
||||
let _result = dismiss_all_tasks(task_database.get_connection()).await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -69,4 +69,12 @@ impl AppCredentialsDir {
|
||||
pub fn get_worldlabs_cookies_path(&self) -> PathBuf {
|
||||
self.path.join("worldlabs_cookies.txt")
|
||||
}
|
||||
|
||||
pub fn get_worldlabs_bearer_path(&self) -> PathBuf {
|
||||
self.path.join("worldlabs_bearer.txt")
|
||||
}
|
||||
|
||||
pub fn get_worldlabs_refresh_path(&self) -> PathBuf {
|
||||
self.path.join("worldlabs_refresh.txt")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::core::commands::providers::get_provider_order_command::get_provider_o
|
||||
use crate::core::commands::providers::set_provider_order_command::set_provider_order_command;
|
||||
use crate::core::commands::task_queue::get_task_queue_command::get_task_queue_command;
|
||||
use crate::core::commands::task_queue::mark_task_as_dismissed_command::mark_task_as_dismissed_command;
|
||||
use crate::core::commands::task_queue::tasks_nuke_all_command::tasks_nuke_all_command;
|
||||
use crate::core::lifecycle::startup::handle_tauri_startup::handle_tauri_startup;
|
||||
use crate::core::lifecycle::startup::setup_main_window::setup_main_window;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
@@ -221,6 +222,7 @@ pub fn run() {
|
||||
storyteller_open_customer_portal_cancel_plan_command,
|
||||
storyteller_open_customer_portal_manage_plan_command,
|
||||
storyteller_open_customer_portal_switch_plan_command,
|
||||
tasks_nuke_all_command,
|
||||
storyteller_open_customer_portal_update_payment_method_command,
|
||||
storyteller_open_subscription_purchase_command,
|
||||
storyteller_purge_credentials_command,
|
||||
|
||||
+25
-6
@@ -192,8 +192,12 @@ impl WorldlabsCredentialManager {
|
||||
.map(|cookies| cookies.to_serializable()),
|
||||
user_id: None, // TODO
|
||||
user_email: None, // TODO
|
||||
bearer_token: creds.world_labs_bearer_token.map(|token| token.to_raw_string()),
|
||||
refresh_token: creds.world_labs_refresh_token.map(|token| token.to_raw_string()),
|
||||
bearer_token: creds.world_labs_bearer_token
|
||||
.as_ref()
|
||||
.map(|token| token.to_raw_string()),
|
||||
refresh_token: creds.world_labs_refresh_token
|
||||
.as_ref()
|
||||
.map(|token| token.to_raw_string()),
|
||||
};
|
||||
|
||||
let path = self.app_data_root.credentials_dir().get_worldlabs_state_path();
|
||||
@@ -202,10 +206,25 @@ impl WorldlabsCredentialManager {
|
||||
std::fs::write(path, serialized)?;
|
||||
|
||||
// TODO: This is just for building the client and testing.
|
||||
if let Some(cookies) = creds.browser_cookies.as_ref() {
|
||||
let cookies_header = cookies.to_cookie_string();
|
||||
let path = self.app_data_root.credentials_dir().get_worldlabs_cookies_path();
|
||||
std::fs::write(path, cookies_header)?;
|
||||
{
|
||||
if let Some(cookies) = creds.browser_cookies.as_ref() {
|
||||
let cookies_header = cookies.to_cookie_string();
|
||||
let path = self.app_data_root.credentials_dir().get_worldlabs_cookies_path();
|
||||
std::fs::write(path, cookies_header)?;
|
||||
}
|
||||
|
||||
// TODO: This is just for building the client and testing.
|
||||
if let Some(bearer) = creds.world_labs_bearer_token.as_ref() {
|
||||
let value = bearer.to_raw_string();
|
||||
let path = self.app_data_root.credentials_dir().get_worldlabs_bearer_path();
|
||||
std::fs::write(path, value)?;
|
||||
}
|
||||
|
||||
if let Some(refresh) = creds.world_labs_refresh_token.as_ref() {
|
||||
let value = refresh.to_raw_string();
|
||||
let path = self.app_data_root.credentials_dir().get_worldlabs_refresh_path();
|
||||
std::fs::write(path, value)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"build": {
|
||||
"frontendDist": "../../../frontend/apps/artcraft/dist",
|
||||
"beforeDevCommand": "echo 'this is static development; no live reload'",
|
||||
"beforeBuildCommand": "echo 'this is static development; no live reload'",
|
||||
"devUrl": null
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"windows": [
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use crate::connection::TaskDbConnection;
|
||||
use crate::error::SqliteTasksError;
|
||||
|
||||
/// Mark everything as dismissed.
|
||||
pub async fn dismiss_all_tasks(
|
||||
db: &TaskDbConnection,
|
||||
) -> Result<bool, SqliteTasksError> {
|
||||
|
||||
let query = sqlx::query!(r#"
|
||||
UPDATE tasks
|
||||
SET is_dismissed_by_user = 1
|
||||
WHERE is_dismissed_by_user != 1
|
||||
"#);
|
||||
|
||||
let res = query.execute(db.get_pool()).await?;
|
||||
let rows_updated = res.rows_affected() > 0;
|
||||
|
||||
Ok(rows_updated)
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
pub mod create_task;
|
||||
pub mod dismiss_all_tasks;
|
||||
pub mod get_task_by_provider_and_provider_job_id;
|
||||
pub mod list_tasks_by_provider_and_status;
|
||||
pub mod list_tasks_by_provider_and_tokens;
|
||||
pub mod list_tasks_for_frontend;
|
||||
pub mod mark_task_as_dismissed;
|
||||
pub mod nuke_all_tasks;
|
||||
pub mod task;
|
||||
pub mod update_successful_task_status_with_metadata;
|
||||
pub mod update_successful_task_status_with_metadata_by_provider;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use crate::connection::TaskDbConnection;
|
||||
use crate::error::SqliteTasksError;
|
||||
|
||||
/// Abandon all in-progress tasks.
|
||||
/// Marked as user cancellation.
|
||||
pub async fn nuke_all_tasks(
|
||||
db: &TaskDbConnection,
|
||||
) -> Result<bool, SqliteTasksError> {
|
||||
|
||||
// TODO(bt,2025-07-15): We can't set a LIMIT without a certain compiler flag for SQLite ?
|
||||
let query = sqlx::query!(r#"
|
||||
UPDATE tasks
|
||||
SET task_status = "cancelled_by_user"
|
||||
WHERE task_status NOT IN (
|
||||
"complete_success",
|
||||
"complete_failure",
|
||||
"dead",
|
||||
"cancelled_by_user",
|
||||
"cancelled_by_provider",
|
||||
"cancelled_by_us"
|
||||
)
|
||||
"#);
|
||||
|
||||
let res = query.execute(db.get_pool()).await?;
|
||||
let rows_updated = res.rows_affected() > 0;
|
||||
|
||||
Ok(rows_updated)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from "@storyteller/ui-gallery-modal";
|
||||
import type { GalleryItem } from "@storyteller/ui-gallery-modal";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { GetTaskQueue, MarkTaskAsDismissed } from "@storyteller/tauri-api";
|
||||
import { GetTaskQueue, MarkTaskAsDismissed, TasksNukeAll } from "@storyteller/tauri-api";
|
||||
import type { TaskQueueItem } from "@storyteller/tauri-api";
|
||||
import { listen, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import {
|
||||
@@ -528,12 +528,8 @@ export const TaskQueue = () => {
|
||||
};
|
||||
|
||||
const dismissAll = async () => {
|
||||
const allIds = [
|
||||
...completed.map((t) => t.id),
|
||||
...inProgress.map((t) => t.id),
|
||||
];
|
||||
try {
|
||||
await Promise.all(allIds.map((id) => MarkTaskAsDismissed(id)));
|
||||
await TasksNukeAll();
|
||||
} catch (_) {
|
||||
// ignore
|
||||
} finally {
|
||||
|
||||
@@ -18,9 +18,9 @@ const billingTabs = [
|
||||
|
||||
const pricingConfig = {
|
||||
header: {
|
||||
title: "Purchase a subscription",
|
||||
title: "Subscribe to ArtCraft",
|
||||
subtitle:
|
||||
"Upgrade to gain access to Pro features and generate more, faster.",
|
||||
"Support ArtCraft development and get generation credits without having to add 3rd party services.",
|
||||
},
|
||||
yearlyDiscount: 20,
|
||||
};
|
||||
|
||||
@@ -41,6 +41,7 @@ import { ImageModel } from "@storyteller/model-list";
|
||||
import { ImagePromptRow } from "./ImagePromptRow";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { GenerationProvider } from "@storyteller/api-enums";
|
||||
import { GenerationCountPicker } from "./common/GenerationCountPicker";
|
||||
|
||||
export type AspectRatio = "wide" | "tall" | "square";
|
||||
|
||||
@@ -133,40 +134,13 @@ export const PromptBox2D = ({
|
||||
},
|
||||
]);
|
||||
|
||||
const [generationCountList, setGenerationCountList] = useState<PopoverItem[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
// Update generation count options from selected model capabilities
|
||||
useEffect(() => {
|
||||
const caps = getCapabilitiesForModel(selectedImageModel);
|
||||
const items: PopoverItem[] = Array.from(
|
||||
{ length: caps.maxGenerationCount },
|
||||
(_, i) => i + 1,
|
||||
).map((count) => ({
|
||||
label: String(count),
|
||||
selected: count === generationCount,
|
||||
}));
|
||||
setGenerationCountList(items);
|
||||
|
||||
// Clamp selection to allowed range
|
||||
if (generationCount < 1 || generationCount > caps.maxGenerationCount) {
|
||||
setGenerationCount(
|
||||
Math.min(Math.max(1, generationCount), caps.maxGenerationCount),
|
||||
);
|
||||
if (selectedImageModel?.isValidGenerationCount(generationCount)) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedImageModel]);
|
||||
|
||||
// Keep UI selection in sync when store value changes
|
||||
useEffect(() => {
|
||||
setGenerationCountList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === String(generationCount),
|
||||
})),
|
||||
);
|
||||
}, [generationCount]);
|
||||
const defaultGenerations = selectedImageModel?.defaultGenerationCount || 4;
|
||||
setGenerationCount(defaultGenerations);
|
||||
}, [selectedImageModel, generationCount]);
|
||||
|
||||
useEffect(() => {
|
||||
setResolutionList((prev) =>
|
||||
@@ -208,17 +182,6 @@ export const PromptBox2D = ({
|
||||
|
||||
// Image prompt row replaces legacy upload/gallery UI
|
||||
|
||||
const handleGenerationCountSelect = (selectedItem: PopoverItem) => {
|
||||
const count = parseInt(selectedItem.label, 10);
|
||||
setGenerationCount(count);
|
||||
setGenerationCountList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -612,23 +575,13 @@ export const PromptBox2D = ({
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
content="Number of generations"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={generationCountList}
|
||||
onSelect={handleGenerationCountSelect}
|
||||
mode="toggle"
|
||||
panelTitle="No. of images"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faCopy} className="h-4 w-4" />
|
||||
}
|
||||
buttonClassName="h-9"
|
||||
/>
|
||||
</Tooltip>
|
||||
<GenerationCountPicker
|
||||
currentModel={selectedImageModel}
|
||||
currentCount={generationCount}
|
||||
handleCountChange={(count) => {
|
||||
setGenerationCount(count);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className="flex items-center border-none bg-primary px-3 text-sm text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
icon={!isEnqueueing ? faSparkles : undefined}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { ImagePromptRow, type UploadImageFn } from "./ImagePromptRow";
|
||||
import { RefImage, usePromptEditStore } from "./promptStore";
|
||||
import { GenerationProvider } from "@storyteller/api-enums";
|
||||
import { AspectRatioPicker } from "./common/AspectRatioPicker";
|
||||
import { GenerationCountPicker } from "./common/GenerationCountPicker";
|
||||
|
||||
export interface PromptBoxEditProps {
|
||||
onModeChange?: (mode: string) => void;
|
||||
@@ -82,9 +83,6 @@ export const PromptBoxEdit = ({
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [useSystemPrompt, setUseSystemPrompt] = useState(true);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [generationCount, setGenerationCount] = useState<number>(
|
||||
typeof generationCountProp === "number" ? generationCountProp : 1,
|
||||
);
|
||||
const [internalEnqueueing, setInternalEnqueueing] = useState(false);
|
||||
const referenceImages = usePromptEditStore((s) => s.referenceImages);
|
||||
const setReferenceImages = usePromptEditStore((s) => s.setReferenceImages);
|
||||
@@ -95,10 +93,6 @@ export const PromptBoxEdit = ({
|
||||
|
||||
const [showImagePrompts, setShowImagePrompts] = useState(false);
|
||||
|
||||
const [generationCountList, setGenerationCountList] = useState<PopoverItem[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const [aspectRatioList, setAspectRatioList] = useState<PopoverItem[]>([
|
||||
{
|
||||
label: "Auto",
|
||||
@@ -142,6 +136,8 @@ export const PromptBoxEdit = ({
|
||||
},
|
||||
]);
|
||||
|
||||
const generationCount = generationCountProp || 1;
|
||||
|
||||
useEffect(() => {
|
||||
setAspectRatioList((prev) =>
|
||||
prev.map((item) => ({
|
||||
@@ -193,42 +189,12 @@ export const PromptBoxEdit = ({
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof generationCountProp === "number") {
|
||||
setGenerationCount(generationCountProp);
|
||||
if (selectedImageModel?.isValidGenerationCount(generationCount)) {
|
||||
return;
|
||||
}
|
||||
}, [generationCountProp]);
|
||||
|
||||
useEffect(() => {
|
||||
const caps = getCapabilitiesForModel(selectedImageModel);
|
||||
const items: PopoverItem[] = Array.from(
|
||||
{ length: caps.maxGenerationCount },
|
||||
(_, i) => i + 1,
|
||||
).map((count) => ({
|
||||
label: String(count),
|
||||
selected: count === generationCount,
|
||||
icon: <FontAwesomeIcon icon={faCopy} className="h-4 w-4" />,
|
||||
}));
|
||||
setGenerationCountList(items);
|
||||
|
||||
if (generationCount < 1 || generationCount > caps.maxGenerationCount) {
|
||||
const clamped = Math.min(
|
||||
Math.max(1, generationCount),
|
||||
caps.maxGenerationCount,
|
||||
);
|
||||
setGenerationCount(clamped);
|
||||
onGenerationCountChange?.(clamped);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedImageModel]);
|
||||
|
||||
useEffect(() => {
|
||||
setGenerationCountList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === String(generationCount),
|
||||
})),
|
||||
);
|
||||
}, [generationCount]);
|
||||
const defaultGenerations = selectedImageModel?.defaultGenerationCount || 4;
|
||||
onGenerationCountChange?.(defaultGenerations);
|
||||
}, [selectedImageModel, generationCount]);
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
e.preventDefault();
|
||||
@@ -532,24 +498,13 @@ export const PromptBoxEdit = ({
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
content="Number of generations"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={generationCountList}
|
||||
onSelect={handleGenerationCountSelect}
|
||||
mode="toggle"
|
||||
panelTitle="No. of images"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faCopy} className="h-4 w-4" />
|
||||
}
|
||||
panelClassName="min-w-28"
|
||||
buttonClassName="h-9"
|
||||
/>
|
||||
</Tooltip>
|
||||
<GenerationCountPicker
|
||||
currentModel={selectedImageModel}
|
||||
currentCount={generationCount}
|
||||
handleCountChange={(count) => {
|
||||
onGenerationCountChange?.(count);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className="flex items-center border-none bg-primary px-3 text-sm text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
icon={
|
||||
|
||||
@@ -34,6 +34,7 @@ import { twMerge } from "tailwind-merge";
|
||||
import { ImagePromptRow } from "./ImagePromptRow";
|
||||
import { GenerationProvider } from "@storyteller/api-enums";
|
||||
import { AspectRatioPicker } from "./common/AspectRatioPicker";
|
||||
import { GenerationCountPicker } from "./common/GenerationCountPicker";
|
||||
|
||||
interface PromptBoxImageProps {
|
||||
useJobContext: () => JobContextType;
|
||||
@@ -150,9 +151,6 @@ export const PromptBoxImage = ({
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
const [generationCountList, setGenerationCountList] = useState<PopoverItem[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@@ -176,33 +174,12 @@ export const PromptBoxImage = ({
|
||||
}, [imageMediaId, url]);
|
||||
|
||||
useEffect(() => {
|
||||
const caps = getCapabilitiesForModel(selectedModel);
|
||||
const defaultCount = Math.min(
|
||||
Math.max(1, caps.defaultGenerationCount ?? 1),
|
||||
caps.maxGenerationCount,
|
||||
);
|
||||
|
||||
setGenerationCount(defaultCount);
|
||||
|
||||
const items: PopoverItem[] = Array.from(
|
||||
{ length: caps.maxGenerationCount },
|
||||
(_, i) => i + 1,
|
||||
).map((count) => ({
|
||||
label: String(count),
|
||||
selected: count === defaultCount,
|
||||
icon: <FontAwesomeIcon icon={faCopy} className="h-4 w-4" />,
|
||||
}));
|
||||
setGenerationCountList(items);
|
||||
}, [selectedModel]);
|
||||
|
||||
useEffect(() => {
|
||||
setGenerationCountList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === String(generationCount),
|
||||
})),
|
||||
);
|
||||
}, [generationCount]);
|
||||
if (selectedModel?.isValidGenerationCount(generationCount)) {
|
||||
return;
|
||||
}
|
||||
const defaultGenerations = selectedModel?.defaultGenerationCount || 4;
|
||||
setGenerationCount(defaultGenerations);
|
||||
}, [selectedModel, generationCount]);
|
||||
|
||||
useEffect(() => {
|
||||
setAspectRatioList((prev) =>
|
||||
@@ -236,17 +213,6 @@ export const PromptBoxImage = ({
|
||||
setResolution(selectedItem.label.toLowerCase() as any);
|
||||
};
|
||||
|
||||
const handleGenerationCountSelect = (selectedItem: PopoverItem) => {
|
||||
const count = parseInt(selectedItem.label, 10);
|
||||
setGenerationCount(count);
|
||||
setGenerationCountList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
e.preventDefault();
|
||||
const pastedText = e.clipboardData.getData("text");
|
||||
@@ -544,24 +510,13 @@ export const PromptBoxImage = ({
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip
|
||||
content="Number of generations"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={generationCountList}
|
||||
onSelect={handleGenerationCountSelect}
|
||||
mode="toggle"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faCopy} className="h-4 w-4" />
|
||||
}
|
||||
panelClassName="min-w-28"
|
||||
panelTitle="No. of images"
|
||||
buttonClassName="h-9"
|
||||
/>
|
||||
</Tooltip>
|
||||
<GenerationCountPicker
|
||||
currentModel={selectedModel}
|
||||
currentCount={generationCount}
|
||||
handleCountChange={(count) => {
|
||||
setGenerationCount(count);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className="flex items-center border-none bg-primary px-3 text-sm text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
icon={!isEnqueueing ? faSparkles : undefined}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { faMagicWandSparkles, faRectangle, faRectangleVertical, faRectangleWide, faSquare, IconDefinition } from "@fortawesome/pro-regular-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { PopoverItem, PopoverMenu } from "libs/components/popover/src/lib/popover"
|
||||
import Tooltip from "libs/components/tooltip/src/lib/tooltip"
|
||||
import { ImageModel } from "libs/model-list/src/lib/classes/ImageModel"
|
||||
import { CommonAspectRatio } from "libs/model-list/src/lib/classes/properties/CommonAspectRatio"
|
||||
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover"
|
||||
import { Tooltip } from "@storyteller/ui-tooltip"
|
||||
import { ImageModel } from "@storyteller/model-list"
|
||||
import { CommonAspectRatio } from "@storyteller/model-list"
|
||||
|
||||
|
||||
interface AspectRatioPickerProps {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { faCopy } from "@fortawesome/pro-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { PopoverMenu, PopoverItem } from "@storyteller/ui-popover";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { ImageModel } from "@storyteller/model-list";
|
||||
|
||||
const DEFAULT_GENERATION_COUNT : number = 4;
|
||||
|
||||
interface GenerationCountPickerProps {
|
||||
currentModel?: ImageModel,
|
||||
currentCount: number,
|
||||
handleCountChange: (count: number) => void,
|
||||
}
|
||||
|
||||
export const GenerationCountPicker = ({
|
||||
currentModel,
|
||||
currentCount,
|
||||
handleCountChange
|
||||
}: GenerationCountPickerProps) => {
|
||||
|
||||
const maxGenerationCount = currentModel?.maxGenerationCount || DEFAULT_GENERATION_COUNT;
|
||||
const hasPredefinedOptions = !!currentModel?.predefinedGenerationCounts;
|
||||
|
||||
let generationCountOptions: PopoverItem[];
|
||||
|
||||
// Count pickers either have a "[1,2, ... max]" set of options, or a pre-defined list of options.
|
||||
if (hasPredefinedOptions) {
|
||||
generationCountOptions = buildPredefinedCountOptions(currentModel?.predefinedGenerationCounts || [], currentCount);
|
||||
} else {
|
||||
generationCountOptions = buildSequentialCountOptions(maxGenerationCount, currentCount);
|
||||
}
|
||||
|
||||
const onSelect = (item: PopoverItem) => {
|
||||
let count = parseInt(item.label, 10);
|
||||
if (isNaN(count)) {
|
||||
return;
|
||||
}
|
||||
if (count < 1 || count > maxGenerationCount) {
|
||||
// Clamp to valid range
|
||||
count = Math.min(Math.max(1, count), maxGenerationCount);
|
||||
}
|
||||
handleCountChange(count);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
content="Number of generations"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={generationCountOptions}
|
||||
onSelect={onSelect}
|
||||
mode="toggle"
|
||||
panelTitle="No. of images"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faCopy} className="h-4 w-4" />
|
||||
}
|
||||
buttonClassName="h-9"
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const buildSequentialCountOptions = (maxCount: number, currentCount: number): PopoverItem[] => {
|
||||
const options = [];
|
||||
for (let i = 0; i < maxCount; i++) {
|
||||
const count = i + 1;
|
||||
options.push({
|
||||
label: String(count),
|
||||
selected: count === currentCount,
|
||||
});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
const buildPredefinedCountOptions = (options: number[], currentCount: number): PopoverItem[] => {
|
||||
const result: PopoverItem[] = [];
|
||||
options.forEach(option => {
|
||||
result.push({
|
||||
label: String(option),
|
||||
selected: option === currentCount,
|
||||
});
|
||||
});
|
||||
return result;
|
||||
};
|
||||
@@ -8,6 +8,9 @@
|
||||
{
|
||||
"path": "../camera-settings-modal"
|
||||
},
|
||||
{
|
||||
"path": "../../api-enums"
|
||||
},
|
||||
{
|
||||
"path": "../../model-list"
|
||||
},
|
||||
@@ -17,32 +20,32 @@
|
||||
{
|
||||
"path": "../../tauri-api"
|
||||
},
|
||||
{
|
||||
"path": "../gallery-modal"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-utils"
|
||||
},
|
||||
{
|
||||
"path": "../modal"
|
||||
},
|
||||
{
|
||||
"path": "../button"
|
||||
},
|
||||
{
|
||||
"path": "../tooltip"
|
||||
},
|
||||
{
|
||||
"path": "../popover"
|
||||
},
|
||||
{
|
||||
"path": "../../api"
|
||||
"path": "../toaster"
|
||||
},
|
||||
{
|
||||
"path": "../../common"
|
||||
},
|
||||
{
|
||||
"path": "../toaster"
|
||||
"path": "../../api"
|
||||
},
|
||||
{
|
||||
"path": "../gallery-modal"
|
||||
},
|
||||
{
|
||||
"path": "../tooltip"
|
||||
},
|
||||
{
|
||||
"path": "../button"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
{
|
||||
"path": "../camera-settings-modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../api-enums/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../model-list/tsconfig.lib.json"
|
||||
},
|
||||
@@ -58,32 +61,32 @@
|
||||
{
|
||||
"path": "../../tauri-api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../gallery-modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../tauri-utils/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../button/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../tooltip/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../popover/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../api/tsconfig.lib.json"
|
||||
"path": "../toaster/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../common/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../toaster/tsconfig.lib.json"
|
||||
"path": "../../api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../gallery-modal/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../tooltip/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../button/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+7
-1
@@ -10,7 +10,13 @@ export function ArtcraftBillingBlock({
|
||||
<div className="w-full">
|
||||
<PricingContent
|
||||
title="Video Generation is Resource Intensive"
|
||||
subtitle="Creating high-quality videos requires significant computing power. To generate more, you'll need to subscribe for monthly credits. Your support also helps us keep building and improving ArtCraft!"
|
||||
subtitle="
|
||||
Creating high-quality videos requires significant computing power.
|
||||
To generate more, you can subscribe to ArtCraft or buy ArtCraft credits.
|
||||
You can also add 3rd party providers and use your existing services.
|
||||
Your support helps us keep building and improving ArtCraft!
|
||||
We're building the image and video tool that you can own forever.
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
+4
@@ -24,8 +24,12 @@ function getServiceProviderName(provider: GenerationProvider) : string {
|
||||
return "Sora";
|
||||
case GenerationProvider.Fal:
|
||||
return "Fal";
|
||||
case GenerationProvider.Grok:
|
||||
return "Grok";
|
||||
case GenerationProvider.Midjourney:
|
||||
return "Midjourney";
|
||||
case GenerationProvider.WorldLabs:
|
||||
return "World Labs";
|
||||
case GenerationProvider.Artcraft:
|
||||
default:
|
||||
return "Artcraft";
|
||||
|
||||
@@ -23,9 +23,11 @@ export function ProviderBillingModal({
|
||||
let block;
|
||||
|
||||
switch (provider) {
|
||||
case GenerationProvider.Fal:
|
||||
case GenerationProvider.Grok:
|
||||
case GenerationProvider.Midjourney:
|
||||
case GenerationProvider.Sora:
|
||||
case GenerationProvider.Fal:
|
||||
case GenerationProvider.WorldLabs:
|
||||
// NB: We're just going to ask users to set up billing on the provider's website.
|
||||
block = <GenericProviderBillingBlock provider={provider} />;
|
||||
break;
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
{
|
||||
"path": "../../common"
|
||||
},
|
||||
{
|
||||
"path": "../pricing-modal"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
|
||||
@@ -51,6 +51,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../common/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../pricing-modal/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -51,9 +51,19 @@ export const BillingSettingsPane = (args: BillingSettingsPaneProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4 text-base-fg">
|
||||
<Label>Support ArtCraft</Label>
|
||||
<p className="text-sm opacity-70">
|
||||
You do not have to purchase anything from us to use ArtCraft, but
|
||||
you can support ArtCraft development by subscribing or buying credits.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div className="space-y-4 text-base-fg">
|
||||
<div className="space-y-2">
|
||||
<Label>Current Plan</Label>
|
||||
<Label>Current ArtCraft Plan</Label>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-xl font-semibold ">
|
||||
<FontAwesomeIcon
|
||||
@@ -102,7 +112,7 @@ export const BillingSettingsPane = (args: BillingSettingsPaneProps) => {
|
||||
|
||||
<div className="flex flex-col">
|
||||
<Label htmlFor="credits" className="flex items-center gap-2">
|
||||
Your credit balance
|
||||
Your ArtCraft credit balance
|
||||
</Label>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -17,6 +17,9 @@ export class ImageModel extends Model {
|
||||
// Default number of images that can be generated at once
|
||||
readonly defaultGenerationCount: number;
|
||||
|
||||
// If set, use this instead of `maxGenerationCount`.
|
||||
readonly predefinedGenerationCounts?: number[];
|
||||
|
||||
// Signals image editing models that focus on editing a single image.
|
||||
readonly canEditImages: boolean;
|
||||
|
||||
@@ -63,6 +66,7 @@ export class ImageModel extends Model {
|
||||
selectorBadges: string[];
|
||||
maxGenerationCount: number;
|
||||
defaultGenerationCount: number;
|
||||
predefinedGenerationCounts?: number[];
|
||||
canEditImages?: boolean;
|
||||
usesInpaintingMask?: boolean;
|
||||
editingIsInpainting?: boolean;
|
||||
@@ -91,6 +95,7 @@ export class ImageModel extends Model {
|
||||
super(args);
|
||||
this.maxGenerationCount = args.maxGenerationCount;
|
||||
this.defaultGenerationCount = args.defaultGenerationCount;
|
||||
this.predefinedGenerationCounts = args.predefinedGenerationCounts;
|
||||
this.canEditImages = args.canEditImages ?? false;
|
||||
this.usesInpaintingMask = args.usesInpaintingMask ?? false;
|
||||
this.editingIsInpainting = args.editingIsInpainting ?? false;
|
||||
@@ -125,4 +130,13 @@ export class ImageModel extends Model {
|
||||
&& this.aspectRatios
|
||||
&& this.aspectRatios.length > 0;
|
||||
}
|
||||
|
||||
// Return whether the count of generations is valid for this model
|
||||
isValidGenerationCount(count: number): boolean {
|
||||
if (this.predefinedGenerationCounts) {
|
||||
return this.predefinedGenerationCounts.includes(count);
|
||||
} else {
|
||||
return count >= 1 && count <= this.maxGenerationCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,10 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
selectorName: "Grok",
|
||||
selectorDescription: "Fast af",
|
||||
selectorBadges: ["10 sec."],
|
||||
defaultGenerationCount: 4,
|
||||
// NB: Grok only supports 6 images at a time
|
||||
defaultGenerationCount: 6,
|
||||
maxGenerationCount: 6,
|
||||
predefinedGenerationCounts: [6],
|
||||
canUseImagePrompt: false,
|
||||
maxImagePromptCount: 6,
|
||||
progressBarTime: 10000,
|
||||
@@ -44,8 +46,10 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
selectorName: "Midjourney",
|
||||
selectorDescription: "Stunning style and quality",
|
||||
selectorBadges: ["45 sec."],
|
||||
// NB: Midjourney only supports 4 images at a time
|
||||
maxGenerationCount: 4,
|
||||
defaultGenerationCount: 4,
|
||||
predefinedGenerationCounts: [4],
|
||||
canUseImagePrompt: false,
|
||||
maxImagePromptCount: 6,
|
||||
progressBarTime: 45000,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { SoundEffect } from "./SoundEffect";
|
||||
|
||||
export class SoundRegistry {
|
||||
static #instance: SoundRegistry | undefined;
|
||||
|
||||
#sounds: Map<string, SoundEffect>;
|
||||
|
||||
private constructor() {
|
||||
@@ -10,10 +8,30 @@ export class SoundRegistry {
|
||||
}
|
||||
|
||||
public static getInstance() {
|
||||
if (SoundRegistry.#instance === undefined) {
|
||||
SoundRegistry.#instance = new SoundRegistry();
|
||||
// NB(bt,2026-01-03): Our `nx` build setup is deeply flawed and in need
|
||||
// of repair: Production builds will compile libraries to multiple
|
||||
// independent symbolic spaces, making singleton pattern unreliable.
|
||||
// Effectively, the singletons cannot be used across bundle boundaries.
|
||||
// We're going to be gross and bind everything to the global namespace.
|
||||
//
|
||||
// To be clear, this does not work:
|
||||
//
|
||||
// class SoundRegistry {
|
||||
// static #instance: SoundRegistry | undefined;
|
||||
//
|
||||
// [...]
|
||||
//
|
||||
// static getInstance() {
|
||||
// if (SoundRegistry.#instance === undefined) {
|
||||
// SoundRegistry.#instance = new SoundRegistry();
|
||||
// }
|
||||
// return SoundRegistry.#instance;
|
||||
// }
|
||||
|
||||
if ((window as any).artcraft_sound_registry === undefined) {
|
||||
(window as any).artcraft_sound_registry = new SoundRegistry();
|
||||
}
|
||||
return SoundRegistry.#instance;
|
||||
return (window as any).artcraft_sound_registry;
|
||||
}
|
||||
|
||||
public hasSound(key: string) : boolean {
|
||||
|
||||
@@ -3,13 +3,14 @@ export * from "./lib/artcraft/ArtcraftGetSubscription.js";
|
||||
export * from "./lib/artcraft/media_files/MediaFileDelete.js";
|
||||
export * from "./lib/artcraft/task_queue/GetTaskQueue.js";
|
||||
export * from "./lib/artcraft/task_queue/MarkTaskAsDismissed.js";
|
||||
export * from "./lib/artcraft/task_queue/TasksNukeAll.js";
|
||||
export * from "./lib/common/CommandStatus.js";
|
||||
export * from "./lib/enqueue/EnqueueEditImage.js";
|
||||
export * from "./lib/enqueue/EnqueueImageBgRemovalCommand.js";
|
||||
export * from "./lib/enqueue/EnqueueImageInpaint.js";
|
||||
export * from "./lib/enqueue/EnqueueImageTo3dObject.js";
|
||||
export * from "./lib/enqueue/EnqueueImageToGaussian.js";
|
||||
export * from "./lib/enqueue/EnqueueImageTo3dWorld.js";
|
||||
export * from "./lib/enqueue/EnqueueImageToGaussian.js";
|
||||
export * from "./lib/enqueue/EnqueueImageToVideo.js";
|
||||
export * from "./lib/enqueue/EnqueueTextToImage.js";
|
||||
export * from "./lib/events/BasicEventWrapper.js";
|
||||
@@ -36,4 +37,4 @@ export * from "./lib/sora/useSoraLoginListener.js";
|
||||
export * from "./lib/sora/waitForSoraLogin.js";
|
||||
export * from "./lib/system/GetAppInfo.js";
|
||||
export * from "./lib/util/LoadWithoutCors.js";
|
||||
export * from "./lib/worldlabs/WorldLabsGetCredentialInfo.js";
|
||||
export * from "./lib/worldlabs/WorldLabsGetCredentialInfo.js";
|
||||
@@ -0,0 +1,18 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { CommandResult } from "../../common/CommandStatus";
|
||||
|
||||
interface TasksNukeAllSuccess extends CommandResult {
|
||||
payload: void;
|
||||
}
|
||||
|
||||
export const TasksNukeAll = async () : Promise<TasksNukeAllSuccess> => {
|
||||
try {
|
||||
let result = await invoke("tasks_nuke_all_command") as TasksNukeAllSuccess;
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary just for testing -
|
||||
(window as any).test_tasks_nuke_all = TasksNukeAll;
|
||||
@@ -19,7 +19,7 @@ sqlite_db_file="/tmp/tasks.sqlite"
|
||||
# it decoheres the and corrupts terminal output, and it's slow. This configuration
|
||||
# allows us to start without it. Simply launch `nx` and the dev server as a separate
|
||||
# process and leave tauri out of the loop
|
||||
config_path="${rust_crate_path}/tauri.artcraft_3d.no_dev.conf.json"
|
||||
config_path="${rust_crate_path}/tauri-dev-hot-reload.conf.json"
|
||||
|
||||
export TAURI_FRONTEND_PATH="${frontend_path}"
|
||||
export TAURI_APP_PATH="${rust_crate_path}"
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
# This works on Linux and MacOS to launch the Rust dev server
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
echo "Running Artcraft Rust in Dev Mode..."
|
||||
echo ""
|
||||
|
||||
echo "You'll need to launch the frontend dev server as a second script!"
|
||||
echo ""
|
||||
|
||||
root_dir=$(pwd)
|
||||
frontend_path="${root_dir}/frontend"
|
||||
rust_crate_path="${root_dir}/crates/desktop/artcraft"
|
||||
|
||||
sqlite_db_file="/tmp/tasks.sqlite"
|
||||
|
||||
# The tauri dev server integration is super annoying: It eats ctrl-c interrupts,
|
||||
# it decoheres the and corrupts terminal output, and it's slow. This configuration
|
||||
# allows us to start without it. Simply launch `nx` and the dev server as a separate
|
||||
# process and leave tauri out of the loop
|
||||
config_path="${rust_crate_path}/tauri-dev-static.conf.json"
|
||||
|
||||
export TAURI_FRONTEND_PATH="${frontend_path}"
|
||||
export TAURI_APP_PATH="${rust_crate_path}"
|
||||
|
||||
echo "Create database file..."
|
||||
touch "${sqlite_db_file}"
|
||||
|
||||
sqlx_migrate() {
|
||||
echo "SQLx migrate database file..."
|
||||
sqlx migrate run \
|
||||
--database-url "sqlite:${sqlite_db_file}" \
|
||||
--source "${root_dir}/_sql/artcraft_migrations"
|
||||
|
||||
export DATABASE_URL="sqlite:${sqlite_db_file}"
|
||||
}
|
||||
|
||||
sqlx_offline() {
|
||||
echo "SQLx offline..."
|
||||
export SQLX_OFFLINE=true
|
||||
}
|
||||
|
||||
# NB: Switch this for actively developing queries.
|
||||
#sqlx_migrate
|
||||
sqlx_offline
|
||||
|
||||
# Issue with Linux performance
|
||||
# https://github.com/tauri-apps/tauri/issues/13498
|
||||
export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
||||
export WEBKIT_DISABLE_COMPOSITING_MODE=1
|
||||
|
||||
echo "Build and run..."
|
||||
RUSTFLAGS="-Awarnings" cargo tauri dev \
|
||||
--no-dev-server \
|
||||
--no-dev-server-wait \
|
||||
--no-watch \
|
||||
--config "${config_path}"
|
||||
Reference in New Issue
Block a user