Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4bbafeae6 | ||
|
|
e39a7f0360 | ||
|
|
5ea90154b1 | ||
|
|
f627da6d8f | ||
|
|
682f11c79e | ||
|
|
0bdbb5214b | ||
|
|
94f2d2ec68 | ||
|
|
384b841528 | ||
|
|
62f97317ff | ||
|
|
8210a9f063 | ||
|
|
b4d603098d | ||
|
|
ffd86f896c | ||
|
|
30b77afb23 | ||
|
|
0d879af3a4 | ||
|
|
4ac71a18c5 | ||
|
|
81e1fce6a0 | ||
|
|
855ddbdf2d | ||
|
|
0a7d3028c9 | ||
|
|
c5aebaffb9 | ||
|
|
931304887f | ||
|
|
3dcec8bc85 | ||
|
|
af30eea6f7 | ||
|
|
df7db6b35d | ||
|
|
d228a43baf | ||
|
|
ebcc52dd1a | ||
|
|
726206fd0c | ||
|
|
ac896fd93a | ||
|
|
85a4ff5c55 | ||
|
|
e3f15b0d93 | ||
|
|
e31a2c0a91 | ||
|
|
ea85abdc62 | ||
|
|
4f3c93635f | ||
|
|
82b9b90ff9 | ||
|
|
ad5ea26a81 | ||
|
|
6121d118f0 | ||
|
|
28ca7be96c | ||
|
|
a2a7f7d274 | ||
|
|
e1255a55a0 | ||
|
|
9f629ee5e2 | ||
|
|
6a82fcb6e0 | ||
|
|
35eac53b9d | ||
|
|
c84c36d774 | ||
|
|
3269b62e14 | ||
|
|
cd49fe0015 | ||
|
|
326fa8c4a4 | ||
|
|
51bdd4eff8 | ||
|
|
a64ca7d4ac | ||
|
|
ec3ac242b0 | ||
|
|
fbe7fa3c77 | ||
|
|
418d824c35 | ||
|
|
2e6823834f | ||
|
|
7ef20bc885 | ||
|
|
b031a68000 | ||
|
|
e4b648b55d | ||
|
|
d78ccbca25 | ||
|
|
bae6bc6863 | ||
|
|
faa43a7361 | ||
|
|
f8b3b708c0 | ||
|
|
4c40379470 | ||
|
|
0c32a89828 | ||
|
|
d7365ea045 | ||
|
|
3386247858 | ||
|
|
7ea9c96d77 | ||
|
|
059f4b4e38 | ||
|
|
3c36e041ca | ||
|
|
f027fcb29a | ||
|
|
6f2ed9e61e | ||
|
|
2b40b2f8d6 | ||
|
|
9996b41bc2 | ||
|
|
b9a5768679 | ||
|
|
614a5edc55 | ||
|
|
01468020bd | ||
|
|
289ef6f352 |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/*~": true,
|
||||
"frontend_old/**": true
|
||||
}
|
||||
}
|
||||
@@ -802,47 +802,6 @@ dependencies = [
|
||||
"num",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "artcraft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assertor",
|
||||
"base64 0.22.1",
|
||||
"bytes 1.10.0",
|
||||
"chrono",
|
||||
"const_format",
|
||||
"crockford",
|
||||
"directories 6.0.0",
|
||||
"dirs 6.0.0",
|
||||
"errors",
|
||||
"filesys",
|
||||
"futures 0.3.29",
|
||||
"image",
|
||||
"libloading 0.8.6",
|
||||
"log",
|
||||
"memory_caching",
|
||||
"once_cell",
|
||||
"openai_sora_client",
|
||||
"primitives",
|
||||
"rand 0.9.0",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"serial_test",
|
||||
"subprocess_common",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-log",
|
||||
"tempdir",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ascii"
|
||||
version = "0.9.3"
|
||||
@@ -5035,6 +4994,12 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71"
|
||||
|
||||
[[package]]
|
||||
name = "hf-hub"
|
||||
version = "0.3.2"
|
||||
@@ -6130,6 +6095,15 @@ dependencies = [
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
@@ -7849,14 +7823,20 @@ name = "openai_sora_client"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"crockford",
|
||||
"errors",
|
||||
"filesys",
|
||||
"hex-literal",
|
||||
"idempotency",
|
||||
"log",
|
||||
"rand 0.9.0",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"subprocess_common",
|
||||
"testing",
|
||||
"thiserror 2.0.11",
|
||||
@@ -10605,6 +10585,16 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha3"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@@ -11995,6 +11985,47 @@ dependencies = [
|
||||
"windows 0.60.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-artcraft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assertor",
|
||||
"base64 0.22.1",
|
||||
"bytes 1.10.0",
|
||||
"chrono",
|
||||
"const_format",
|
||||
"crockford",
|
||||
"directories 6.0.0",
|
||||
"dirs 6.0.0",
|
||||
"errors",
|
||||
"filesys",
|
||||
"futures 0.3.29",
|
||||
"image",
|
||||
"libloading 0.8.6",
|
||||
"log",
|
||||
"memory_caching",
|
||||
"once_cell",
|
||||
"openai_sora_client",
|
||||
"primitives",
|
||||
"rand 0.9.0",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"serial_test",
|
||||
"subprocess_common",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-log",
|
||||
"tempdir",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "2.1.1"
|
||||
|
||||
@@ -24,7 +24,7 @@ members = [
|
||||
"crates/cli/production/migration-tool",
|
||||
|
||||
# Desktop apps
|
||||
"crates/desktop/tauri-4o",
|
||||
"crates/desktop/tauri-artcraft",
|
||||
"crates/desktop/tauri-realtime",
|
||||
|
||||
# Shared libraries
|
||||
@@ -152,7 +152,9 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
|
||||
# Centrally pinned versions so that we can uniformly upgrade/downgrade versions across the workspace
|
||||
# If a crate needs to use an older/newer version, the dependency should be managed in that crate's own Cargo.toml
|
||||
anyhow = "1.0.97"
|
||||
base64 = "0.22.1"
|
||||
chrono = { version = "0.4.40", features = ["serde"] }
|
||||
hex-literal = "1.0.0"
|
||||
log = "0.4.27"
|
||||
once_cell = "1.21.3"
|
||||
rand = "0.9.0"
|
||||
@@ -162,10 +164,10 @@ serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_derive = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
serde_yaml = "0.9.1"
|
||||
sha3 = "0.10.8"
|
||||
thiserror = "2"
|
||||
toml = "0.7.4"
|
||||
url = "2.5.4"
|
||||
|
||||
# Internal libs
|
||||
crockford = { path = "crates/lib/data/crockford" }
|
||||
errors = { path = "crates/lib/errors" }
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP INDEX `index_maybe_creator_anonymous_visitor_token`
|
||||
ON `generic_inference_jobs`;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD INDEX index_maybe_creator_anonymous_visitor_token (maybe_creator_anonymous_visitor_token),
|
||||
ALGORITHM=INPLACE, LOCK=NONE;
|
||||
@@ -297,6 +297,7 @@ CREATE TABLE generic_inference_jobs (
|
||||
KEY fk_maybe_input_source_token_and_type (maybe_input_source_token, maybe_input_source_token_type),
|
||||
KEY fk_on_success_result_entity_token (on_success_result_entity_token),
|
||||
KEY fk_maybe_creator_user_token (maybe_creator_user_token),
|
||||
KEY index_maybe_creator_anonymous_visitor_token (maybe_creator_anonymous_visitor_token),
|
||||
KEY index_creator_ip_address (creator_ip_address),
|
||||
KEY index_priority_level (priority_level),
|
||||
KEY index_is_debug_request (is_debug_request),
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
#[test]
|
||||
fn test_dynamic_library_loading() {
|
||||
let name = libloading::library_filename("cublas");
|
||||
|
||||
assert_eq!(name, "libcublas.so");
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "artcraft"
|
||||
name = "tauri-artcraft"
|
||||
version = "0.1.0"
|
||||
description = "Artcraft"
|
||||
authors = ["you"]
|
||||
@@ -12,7 +12,7 @@ publish = false
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
name = "tauri_artcraft_app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
@@ -2,5 +2,5 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
tauri_artcraft_app_lib::run();
|
||||
}
|
||||
@@ -10,7 +10,7 @@ pub fn encode_rgb_image_base64_png(
|
||||
|
||||
image.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Png)?;
|
||||
|
||||
let bytes = BASE64_STANDARD.encode(bytes);
|
||||
let bytes = (&BASE64_STANDARD).encode(bytes);
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# For some reason, we can't pass json files to the tauri dev command
|
||||
[build]
|
||||
frontendDist = "../../../frontend/apps/editor2d/dist"
|
||||
beforeDevCommand = "nx run editor2d:dev"
|
||||
devUrl = "http://localhost:5741"
|
||||
@@ -0,0 +1,5 @@
|
||||
# For some reason, we can't pass json files to the tauri dev command
|
||||
[build]
|
||||
frontendDist = "../../../frontend/apps/editor3d/dist"
|
||||
beforeDevCommand = "nx run editor3d:dev"
|
||||
devUrl = "http://localhost:5173"
|
||||
@@ -2,12 +2,12 @@
|
||||
"$schema": "https://schema.tauri.app/config/2.0.0-rc",
|
||||
"productName": "ArtCraft",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.unique.value.thing.why",
|
||||
"identifier": "ai.artcraft.app",
|
||||
"build": {
|
||||
"frontendDist": "../../../frontend/realtime/dist",
|
||||
"devUrl": "http://localhost:5174",
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeBuildCommand": "npm run build2"
|
||||
"frontendDist": "../../../frontend/apps/editor2d/dist",
|
||||
"devUrl": "http://localhost:5741",
|
||||
"beforeDevCommand": "nx run editor2d:dev",
|
||||
"beforeBuildCommand": "nx run editor2d:build"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
@@ -20,6 +20,7 @@ workspace-build-acceleration = { workspace = true }
|
||||
crockford = { path = "../../data/crockford" }
|
||||
errors = { path = "../../errors" }
|
||||
subprocess_common = { path = "../../subprocess_common" }
|
||||
idempotency = { path = "../../idempotency" }
|
||||
|
||||
# External
|
||||
anyhow = { workspace = true }
|
||||
@@ -28,9 +29,14 @@ reqwest = { workspace = true, features = ["multipart"] }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha3 = { workspace = true }
|
||||
hex-literal = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
toml = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
chrono = { workspace = true}
|
||||
rand = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
testing = { path = "../../testing" }
|
||||
|
||||
@@ -15,6 +15,9 @@ pub enum SoraError {
|
||||
#[error("Sora sentinel block: {0}")]
|
||||
SentinelBlock(String),
|
||||
|
||||
#[error("Sora too many concurrent tasks: {0}")]
|
||||
TooManyConcurrentTasks(String),
|
||||
|
||||
#[error("Sora API error: {0}")]
|
||||
GenericError(String),
|
||||
|
||||
@@ -115,10 +118,10 @@ pub enum RawSoraError {
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SoraErrorCode {
|
||||
pub enum SoraErrorCode {
|
||||
SentinelBlock,
|
||||
TokenExpired,
|
||||
|
||||
TooManyConcurrentTasks,
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
@@ -179,6 +182,9 @@ pub (crate) async fn image_gen_http_request(sora_request: RawSoraImageGenRequest
|
||||
SoraErrorCode::SentinelBlock => {
|
||||
return Err(SoraError::SentinelBlock(error_response.error.message));
|
||||
}
|
||||
SoraErrorCode::TooManyConcurrentTasks => {
|
||||
return Err(SoraError::TooManyConcurrentTasks(error_response.error.message));
|
||||
}
|
||||
SoraErrorCode::Unknown(_) => {
|
||||
return Err(SoraError::GenericError(error_response.error.message));
|
||||
}
|
||||
|
||||
@@ -83,16 +83,16 @@ pub struct Generation {
|
||||
pub created_at: String,
|
||||
pub deleted_at: Option<String>,
|
||||
pub url: String,
|
||||
pub seed: i64,
|
||||
pub can_download: bool,
|
||||
pub download_status: String,
|
||||
pub is_favorite: bool,
|
||||
pub is_liked: bool,
|
||||
pub is_public: bool,
|
||||
pub is_archived: bool,
|
||||
pub is_featured: bool,
|
||||
pub has_feedback: bool,
|
||||
pub like_count: i32,
|
||||
pub seed: Option<i64>,
|
||||
pub can_download: Option<bool>,
|
||||
pub download_status: Option<String>,
|
||||
pub is_favorite: Option<bool>,
|
||||
pub is_liked: Option<bool>,
|
||||
pub is_public: Option<bool>,
|
||||
pub is_archived: Option<bool>,
|
||||
pub is_featured: Option<bool>,
|
||||
pub has_feedback: Option<bool>,
|
||||
pub like_count: Option<i32>,
|
||||
pub cloudflare_metadata: Option<serde_json::Value>,
|
||||
pub cf_thumbnail_url: Option<String>,
|
||||
pub encodings: Encodings,
|
||||
|
||||
@@ -2,4 +2,5 @@ pub mod credentials;
|
||||
pub mod image_gen;
|
||||
pub mod job;
|
||||
pub mod upload;
|
||||
pub mod sentinel_refresh;
|
||||
pub mod sentinel_refresh;
|
||||
mod sora_error;
|
||||
@@ -0,0 +1,671 @@
|
||||
// cores = [8, 16, 24, 32]
|
||||
// cached_scripts = ["https://cdn.oaistatic.com/_next/static/cXh69klOLzS0Gy2joLDRS/_ssgManifest.js?dpl=453ebaec0d44c2decab71692e1bfe39be35a24b3"]
|
||||
|
||||
// cached_dpl = ["prod-f501fe933b3edf57aea882da888e1a544df99840"]
|
||||
|
||||
// navigator_key = [
|
||||
// "registerProtocolHandler−function registerProtocolHandler() { [native code] }",
|
||||
// "storage−[object StorageManager]",
|
||||
// "locks−[object LockManager]",
|
||||
// "appCodeName−Mozilla",
|
||||
// "permissions−[object Permissions]",
|
||||
// "share−function share() { [native code] }",
|
||||
// "webdriver−false",
|
||||
// "managed−[object NavigatorManagedData]",
|
||||
// "canShare−function canShare() { [native code] }",
|
||||
// "vendor−Google Inc.",
|
||||
// "vendor−Google Inc.",
|
||||
// "mediaDevices−[object MediaDevices]",
|
||||
// "vibrate−function vibrate() { [native code] }",
|
||||
// "storageBuckets−[object StorageBucketManager]",
|
||||
// "mediaCapabilities−[object MediaCapabilities]",
|
||||
// "getGamepads−function getGamepads() { [native code] }",
|
||||
// "bluetooth−[object Bluetooth]",
|
||||
// "share−function share() { [native code] }",
|
||||
// "cookieEnabled−true",
|
||||
// "virtualKeyboard−[object VirtualKeyboard]",
|
||||
// "product−Gecko",
|
||||
// "mediaDevices−[object MediaDevices]",
|
||||
// "canShare−function canShare() { [native code] }",
|
||||
// "getGamepads−function getGamepads() { [native code] }",
|
||||
// "product−Gecko",
|
||||
// "xr−[object XRSystem]",
|
||||
// "clipboard−[object Clipboard]",
|
||||
// "storageBuckets−[object StorageBucketManager]",
|
||||
// "unregisterProtocolHandler−function unregisterProtocolHandler() { [native code] }",
|
||||
// "productSub−20030107",
|
||||
// "login−[object NavigatorLogin]",
|
||||
// "vendorSub−",
|
||||
// "login−[object NavigatorLogin]",
|
||||
// "getInstalledRelatedApps−function getInstalledRelatedApps() { [native code] }",
|
||||
// "mediaDevices−[object MediaDevices]",
|
||||
// "locks−[object LockManager]",
|
||||
// "webkitGetUserMedia−function webkitGetUserMedia() { [native code] }",
|
||||
// "vendor−Google Inc.",
|
||||
// "xr−[object XRSystem]",
|
||||
// "mediaDevices−[object MediaDevices]",
|
||||
// "virtualKeyboard−[object VirtualKeyboard]",
|
||||
// "virtualKeyboard−[object VirtualKeyboard]",
|
||||
// "appName−Netscape",
|
||||
// "storageBuckets−[object StorageBucketManager]",
|
||||
// "presentation−[object Presentation]",
|
||||
// "onLine−true",
|
||||
// "mimeTypes−[object MimeTypeArray]",
|
||||
// "credentials−[object CredentialsContainer]",
|
||||
// "presentation−[object Presentation]",
|
||||
// "getGamepads−function getGamepads() { [native code] }",
|
||||
// "vendorSub−",
|
||||
// "virtualKeyboard−[object VirtualKeyboard]",
|
||||
// "serviceWorker−[object ServiceWorkerContainer]",
|
||||
// "xr−[object XRSystem]",
|
||||
// "product−Gecko",
|
||||
// "keyboard−[object Keyboard]",
|
||||
// "gpu−[object GPU]",
|
||||
// "getInstalledRelatedApps−function getInstalledRelatedApps() { [native code] }",
|
||||
// "webkitPersistentStorage−[object DeprecatedStorageQuota]",
|
||||
// "doNotTrack",
|
||||
// "clearAppBadge−function clearAppBadge() { [native code] }",
|
||||
// "presentation−[object Presentation]",
|
||||
// "serial−[object Serial]",
|
||||
// "locks−[object LockManager]",
|
||||
// "requestMIDIAccess−function requestMIDIAccess() { [native code] }",
|
||||
// "locks−[object LockManager]",
|
||||
// "requestMediaKeySystemAccess−function requestMediaKeySystemAccess() { [native code] }",
|
||||
// "vendor−Google Inc.",
|
||||
// "pdfViewerEnabled−true",
|
||||
// "language−zh-CN",
|
||||
// "setAppBadge−function setAppBadge() { [native code] }",
|
||||
// "geolocation−[object Geolocation]",
|
||||
// "userAgentData−[object NavigatorUAData]",
|
||||
// "mediaCapabilities−[object MediaCapabilities]",
|
||||
// "requestMIDIAccess−function requestMIDIAccess() { [native code] }",
|
||||
// "getUserMedia−function getUserMedia() { [native code] }",
|
||||
// "mediaDevices−[object MediaDevices]",
|
||||
// "webkitPersistentStorage−[object DeprecatedStorageQuota]",
|
||||
// "sendBeacon−function sendBeacon() { [native code] }",
|
||||
// "hardwareConcurrency−32",
|
||||
// "credentials−[object CredentialsContainer]",
|
||||
// "storage−[object StorageManager]",
|
||||
// "cookieEnabled−true",
|
||||
// "pdfViewerEnabled−true",
|
||||
// "windowControlsOverlay−[object WindowControlsOverlay]",
|
||||
// "scheduling−[object Scheduling]",
|
||||
// "pdfViewerEnabled−true",
|
||||
// "hardwareConcurrency−32",
|
||||
// "xr−[object XRSystem]",
|
||||
// "webdriver−false",
|
||||
// "getInstalledRelatedApps−function getInstalledRelatedApps() { [native code] }",
|
||||
// "getInstalledRelatedApps−function getInstalledRelatedApps() { [native code] }",
|
||||
// "bluetooth−[object Bluetooth]"
|
||||
// ]
|
||||
|
||||
// document_key = ['_reactListeningo743lnnpvdg', 'location']
|
||||
|
||||
// window_key = [
|
||||
// "0",
|
||||
// "window",
|
||||
// "self",
|
||||
// "document",
|
||||
// "name",
|
||||
// "location",
|
||||
// "customElements",
|
||||
// "history",
|
||||
// "navigation",
|
||||
// "locationbar",
|
||||
// "menubar",
|
||||
// "personalbar",
|
||||
// "scrollbars",
|
||||
// "statusbar",
|
||||
// "toolbar",
|
||||
// "status",
|
||||
// "closed",
|
||||
// "frames",
|
||||
// "length",
|
||||
// "top",
|
||||
// "opener",
|
||||
// "parent",
|
||||
// "frameElement",
|
||||
// "navigator",
|
||||
// "origin",
|
||||
// "external",
|
||||
// "screen",
|
||||
// "innerWidth",
|
||||
// "innerHeight",
|
||||
// "scrollX",
|
||||
// "pageXOffset",
|
||||
// "scrollY",
|
||||
// "pageYOffset",
|
||||
// "visualViewport",
|
||||
// "screenX",
|
||||
// "screenY",
|
||||
// "outerWidth",
|
||||
// "outerHeight",
|
||||
// "devicePixelRatio",
|
||||
// "clientInformation",
|
||||
// "screenLeft",
|
||||
// "screenTop",
|
||||
// "styleMedia",
|
||||
// "onsearch",
|
||||
// "isSecureContext",
|
||||
// "trustedTypes",
|
||||
// "performance",
|
||||
// "onappinstalled",
|
||||
// "onbeforeinstallprompt",
|
||||
// "crypto",
|
||||
// "indexedDB",
|
||||
// "sessionStorage",
|
||||
// "localStorage",
|
||||
// "onbeforexrselect",
|
||||
// "onabort",
|
||||
// "onbeforeinput",
|
||||
// "onbeforematch",
|
||||
// "onbeforetoggle",
|
||||
// "onblur",
|
||||
// "oncancel",
|
||||
// "oncanplay",
|
||||
// "oncanplaythrough",
|
||||
// "onchange",
|
||||
// "onclick",
|
||||
// "onclose",
|
||||
// "oncontentvisibilityautostatechange",
|
||||
// "oncontextlost",
|
||||
// "oncontextmenu",
|
||||
// "oncontextrestored",
|
||||
// "oncuechange",
|
||||
// "ondblclick",
|
||||
// "ondrag",
|
||||
// "ondragend",
|
||||
// "ondragenter",
|
||||
// "ondragleave",
|
||||
// "ondragover",
|
||||
// "ondragstart",
|
||||
// "ondrop",
|
||||
// "ondurationchange",
|
||||
// "onemptied",
|
||||
// "onended",
|
||||
// "onerror",
|
||||
// "onfocus",
|
||||
// "onformdata",
|
||||
// "oninput",
|
||||
// "oninvalid",
|
||||
// "onkeydown",
|
||||
// "onkeypress",
|
||||
// "onkeyup",
|
||||
// "onload",
|
||||
// "onloadeddata",
|
||||
// "onloadedmetadata",
|
||||
// "onloadstart",
|
||||
// "onmousedown",
|
||||
// "onmouseenter",
|
||||
// "onmouseleave",
|
||||
// "onmousemove",
|
||||
// "onmouseout",
|
||||
// "onmouseover",
|
||||
// "onmouseup",
|
||||
// "onmousewheel",
|
||||
// "onpause",
|
||||
// "onplay",
|
||||
// "onplaying",
|
||||
// "onprogress",
|
||||
// "onratechange",
|
||||
// "onreset",
|
||||
// "onresize",
|
||||
// "onscroll",
|
||||
// "onsecuritypolicyviolation",
|
||||
// "onseeked",
|
||||
// "onseeking",
|
||||
// "onselect",
|
||||
// "onslotchange",
|
||||
// "onstalled",
|
||||
// "onsubmit",
|
||||
// "onsuspend",
|
||||
// "ontimeupdate",
|
||||
// "ontoggle",
|
||||
// "onvolumechange",
|
||||
// "onwaiting",
|
||||
// "onwebkitanimationend",
|
||||
// "onwebkitanimationiteration",
|
||||
// "onwebkitanimationstart",
|
||||
// "onwebkittransitionend",
|
||||
// "onwheel",
|
||||
// "onauxclick",
|
||||
// "ongotpointercapture",
|
||||
// "onlostpointercapture",
|
||||
// "onpointerdown",
|
||||
// "onpointermove",
|
||||
// "onpointerrawupdate",
|
||||
// "onpointerup",
|
||||
// "onpointercancel",
|
||||
// "onpointerover",
|
||||
// "onpointerout",
|
||||
// "onpointerenter",
|
||||
// "onpointerleave",
|
||||
// "onselectstart",
|
||||
// "onselectionchange",
|
||||
// "onanimationend",
|
||||
// "onanimationiteration",
|
||||
// "onanimationstart",
|
||||
// "ontransitionrun",
|
||||
// "ontransitionstart",
|
||||
// "ontransitionend",
|
||||
// "ontransitioncancel",
|
||||
// "onafterprint",
|
||||
// "onbeforeprint",
|
||||
// "onbeforeunload",
|
||||
// "onhashchange",
|
||||
// "onlanguagechange",
|
||||
// "onmessage",
|
||||
// "onmessageerror",
|
||||
// "onoffline",
|
||||
// "ononline",
|
||||
// "onpagehide",
|
||||
// "onpageshow",
|
||||
// "onpopstate",
|
||||
// "onrejectionhandled",
|
||||
// "onstorage",
|
||||
// "onunhandledrejection",
|
||||
// "onunload",
|
||||
// "crossOriginIsolated",
|
||||
// "scheduler",
|
||||
// "alert",
|
||||
// "atob",
|
||||
// "blur",
|
||||
// "btoa",
|
||||
// "cancelAnimationFrame",
|
||||
// "cancelIdleCallback",
|
||||
// "captureEvents",
|
||||
// "clearInterval",
|
||||
// "clearTimeout",
|
||||
// "close",
|
||||
// "confirm",
|
||||
// "createImageBitmap",
|
||||
// "fetch",
|
||||
// "find",
|
||||
// "focus",
|
||||
// "getComputedStyle",
|
||||
// "getSelection",
|
||||
// "matchMedia",
|
||||
// "moveBy",
|
||||
// "moveTo",
|
||||
// "open",
|
||||
// "postMessage",
|
||||
// "print",
|
||||
// "prompt",
|
||||
// "queueMicrotask",
|
||||
// "releaseEvents",
|
||||
// "reportError",
|
||||
// "requestAnimationFrame",
|
||||
// "requestIdleCallback",
|
||||
// "resizeBy",
|
||||
// "resizeTo",
|
||||
// "scroll",
|
||||
// "scrollBy",
|
||||
// "scrollTo",
|
||||
// "setInterval",
|
||||
// "setTimeout",
|
||||
// "stop",
|
||||
// "structuredClone",
|
||||
// "webkitCancelAnimationFrame",
|
||||
// "webkitRequestAnimationFrame",
|
||||
// "chrome",
|
||||
// "caches",
|
||||
// "cookieStore",
|
||||
// "ondevicemotion",
|
||||
// "ondeviceorientation",
|
||||
// "ondeviceorientationabsolute",
|
||||
// "launchQueue",
|
||||
// "documentPictureInPicture",
|
||||
// "getScreenDetails",
|
||||
// "queryLocalFonts",
|
||||
// "showDirectoryPicker",
|
||||
// "showOpenFilePicker",
|
||||
// "showSaveFilePicker",
|
||||
// "originAgentCluster",
|
||||
// "onpageswap",
|
||||
// "onpagereveal",
|
||||
// "credentialless",
|
||||
// "speechSynthesis",
|
||||
// "onscrollend",
|
||||
// "webkitRequestFileSystem",
|
||||
// "webkitResolveLocalFileSystemURL",
|
||||
// "sendMsgToSolverCS",
|
||||
// "webpackChunk_N_E",
|
||||
// "__next_set_public_path__",
|
||||
// "next",
|
||||
// "__NEXT_DATA__",
|
||||
// "__SSG_MANIFEST_CB",
|
||||
// "__NEXT_P",
|
||||
// "_N_E",
|
||||
// "regeneratorRuntime",
|
||||
// "__REACT_INTL_CONTEXT__",
|
||||
// "DD_RUM",
|
||||
// "_",
|
||||
// "filterCSS",
|
||||
// "filterXSS",
|
||||
// "__SEGMENT_INSPECTOR__",
|
||||
// "__NEXT_PRELOADREADY",
|
||||
// "Intercom",
|
||||
// "__MIDDLEWARE_MATCHERS",
|
||||
// "__STATSIG_SDK__",
|
||||
// "__STATSIG_JS_SDK__",
|
||||
// "__STATSIG_RERENDER_OVERRIDE__",
|
||||
// "_oaiHandleSessionExpired",
|
||||
// "__BUILD_MANIFEST",
|
||||
// "__SSG_MANIFEST",
|
||||
// "__intercomAssignLocation",
|
||||
// "__intercomReloadLocation"
|
||||
// ]
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
const CORES: &[usize] = &[8, 16, 24, 32];
|
||||
|
||||
const CACHED_SCRIPTS: &[&str] = &["https://cdn.oaistatic.com/_next/static/cXh69klOLzS0Gy2joLDRS/_ssgManifest.js?dpl=453ebaec0d44c2decab71692e1bfe39be35a24b3"];
|
||||
|
||||
const CACHED_DPL: &[&str] = &["prod-f501fe933b3edf57aea882da888e1a544df99840"];
|
||||
|
||||
const NAVIGATOR_KEY: &[&str] = &[
|
||||
"registerProtocolHandler−function registerProtocolHandler() { [native code] }",
|
||||
"storage−[object StorageManager]",
|
||||
"locks−[object LockManager]",
|
||||
"appCodeName−Mozilla",
|
||||
"permissions−[object Permissions]",
|
||||
"share−function share() { [native code] }",
|
||||
"webdriver−false",
|
||||
"managed−[object NavigatorManagedData]",
|
||||
"canShare−function canShare() { [native code] }",
|
||||
"vendor−Google Inc.",
|
||||
"vendor−Google Inc.",
|
||||
"mediaDevices−[object MediaDevices]",
|
||||
"vibrate−function vibrate() { [native code] }",
|
||||
"storageBuckets−[object StorageBucketManager]",
|
||||
"mediaCapabilities−[object MediaCapabilities]",
|
||||
"getGamepads−function getGamepads() { [native code] }",
|
||||
"bluetooth−[object Bluetooth]",
|
||||
"share−function share() { [native code] }",
|
||||
"cookieEnabled−true",
|
||||
"pdfViewerEnabled−true",
|
||||
"windowControlsOverlay−[object WindowControlsOverlay]",
|
||||
"scheduling−[object Scheduling]",
|
||||
"pdfViewerEnabled−true",
|
||||
"hardwareConcurrency−32",
|
||||
"xr−[object XRSystem]",
|
||||
"webdriver−false",
|
||||
"getInstalledRelatedApps−function getInstalledRelatedApps() { [native code] }",
|
||||
"getInstalledRelatedApps−function getInstalledRelatedApps() { [native code] }",
|
||||
"bluetooth−[object Bluetooth]"
|
||||
];
|
||||
|
||||
const DOCUMENT_KEY: &[&str] = &["_reactListeningo743lnnpvdg", "location"];
|
||||
|
||||
const WINDOW_KEY: &[&str] = &[
|
||||
"0",
|
||||
"window",
|
||||
"self",
|
||||
"document",
|
||||
"name",
|
||||
"location",
|
||||
"customElements",
|
||||
"history",
|
||||
"navigation",
|
||||
"locationbar",
|
||||
"menubar",
|
||||
"personalbar",
|
||||
"scrollbars",
|
||||
"statusbar",
|
||||
"toolbar",
|
||||
"status",
|
||||
"closed",
|
||||
"frames",
|
||||
"length",
|
||||
"top",
|
||||
"opener",
|
||||
"parent",
|
||||
"frameElement",
|
||||
"navigator",
|
||||
"origin",
|
||||
"external",
|
||||
"screen",
|
||||
"innerWidth",
|
||||
"innerHeight",
|
||||
"scrollX",
|
||||
"pageXOffset",
|
||||
"scrollY",
|
||||
"pageYOffset",
|
||||
"visualViewport",
|
||||
"screenX",
|
||||
"screenY",
|
||||
"outerWidth",
|
||||
"outerHeight",
|
||||
"devicePixelRatio",
|
||||
"clientInformation",
|
||||
"screenLeft",
|
||||
"screenTop",
|
||||
"styleMedia",
|
||||
"onsearch",
|
||||
"isSecureContext",
|
||||
"trustedTypes",
|
||||
"performance",
|
||||
"onappinstalled",
|
||||
"onbeforeinstallprompt",
|
||||
"crypto",
|
||||
"indexedDB",
|
||||
"sessionStorage",
|
||||
"localStorage",
|
||||
"onbeforexrselect",
|
||||
"onabort",
|
||||
"onbeforeinput",
|
||||
"onbeforematch",
|
||||
"onbeforetoggle",
|
||||
"onblur",
|
||||
"oncancel",
|
||||
"oncanplay",
|
||||
"oncanplaythrough",
|
||||
"onchange",
|
||||
"onclick",
|
||||
"onclose",
|
||||
"oncontentvisibilityautostatechange",
|
||||
"oncontextlost",
|
||||
"oncontextmenu",
|
||||
"oncontextrestored",
|
||||
"oncuechange",
|
||||
"ondblclick",
|
||||
"ondrag",
|
||||
"ondragend",
|
||||
"ondragenter",
|
||||
"ondragleave",
|
||||
"ondragover",
|
||||
"ondragstart",
|
||||
"ondrop",
|
||||
"ondurationchange",
|
||||
"onemptied",
|
||||
"onended",
|
||||
"onerror",
|
||||
"onfocus",
|
||||
"onformdata",
|
||||
"oninput",
|
||||
"oninvalid",
|
||||
"onkeydown",
|
||||
"onkeypress",
|
||||
"onkeyup",
|
||||
"onload",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onmousedown",
|
||||
"onmouseenter",
|
||||
"onmouseleave",
|
||||
"onmousemove",
|
||||
"onmouseout",
|
||||
"onmouseover",
|
||||
"onmouseup",
|
||||
"onmousewheel",
|
||||
"onpause",
|
||||
"onplay",
|
||||
"onplaying",
|
||||
"onprogress",
|
||||
"onratechange",
|
||||
"onreset",
|
||||
"onresize",
|
||||
"onscroll",
|
||||
"onsecuritypolicyviolation",
|
||||
"onseeked",
|
||||
"onseeking",
|
||||
"onselect",
|
||||
"onslotchange",
|
||||
"onstalled",
|
||||
"onsubmit",
|
||||
"onsuspend",
|
||||
"ontimeupdate",
|
||||
"ontoggle",
|
||||
"onvolumechange",
|
||||
"onwaiting",
|
||||
"onwebkitanimationend",
|
||||
"onwebkitanimationiteration",
|
||||
"onwebkitanimationstart",
|
||||
"onwebkittransitionend",
|
||||
"onwheel",
|
||||
"onauxclick",
|
||||
"ongotpointercapture",
|
||||
"onlostpointercapture",
|
||||
"onpointerdown",
|
||||
"onpointermove",
|
||||
"onpointerrawupdate",
|
||||
"onpointerup",
|
||||
"onpointercancel",
|
||||
"onpointerover",
|
||||
"onpointerout",
|
||||
"onpointerenter",
|
||||
"onpointerleave",
|
||||
"onselectstart",
|
||||
"onselectionchange",
|
||||
"onanimationend",
|
||||
"onanimationiteration",
|
||||
"onanimationstart",
|
||||
"ontransitionrun",
|
||||
"ontransitionstart",
|
||||
"ontransitionend",
|
||||
"ontransitioncancel",
|
||||
"onafterprint",
|
||||
"onbeforeprint",
|
||||
"onbeforeunload",
|
||||
"onhashchange",
|
||||
"onlanguagechange",
|
||||
"onmessage",
|
||||
"onmessageerror",
|
||||
"onoffline",
|
||||
"ononline",
|
||||
"onpagehide",
|
||||
"onpageshow",
|
||||
"onpopstate",
|
||||
"onrejectionhandled",
|
||||
"onstorage",
|
||||
"onunhandledrejection",
|
||||
"onunload",
|
||||
"crossOriginIsolated",
|
||||
"scheduler",
|
||||
"alert",
|
||||
"atob",
|
||||
"blur",
|
||||
"btoa",
|
||||
"cancelAnimationFrame",
|
||||
"cancelIdleCallback",
|
||||
"captureEvents",
|
||||
"clearInterval",
|
||||
"clearTimeout",
|
||||
"close",
|
||||
"confirm",
|
||||
"createImageBitmap",
|
||||
"fetch",
|
||||
"find",
|
||||
"focus",
|
||||
"getComputedStyle",
|
||||
"getSelection",
|
||||
"matchMedia",
|
||||
"moveBy",
|
||||
"moveTo",
|
||||
"open",
|
||||
"postMessage",
|
||||
"print",
|
||||
"prompt",
|
||||
"queueMicrotask",
|
||||
"releaseEvents",
|
||||
"reportError",
|
||||
"requestAnimationFrame",
|
||||
"requestIdleCallback",
|
||||
"resizeBy",
|
||||
"resizeTo",
|
||||
"scroll",
|
||||
"scrollBy",
|
||||
"scrollTo",
|
||||
"setInterval",
|
||||
"setTimeout",
|
||||
"stop",
|
||||
"structuredClone",
|
||||
"webkitCancelAnimationFrame",
|
||||
"webkitRequestAnimationFrame",
|
||||
"chrome",
|
||||
"caches",
|
||||
"cookieStore",
|
||||
"ondevicemotion",
|
||||
"ondeviceorientation",
|
||||
"ondeviceorientationabsolute",
|
||||
"launchQueue",
|
||||
"documentPictureInPicture",
|
||||
"getScreenDetails",
|
||||
"queryLocalFonts",
|
||||
"showDirectoryPicker",
|
||||
"showOpenFilePicker",
|
||||
"showSaveFilePicker",
|
||||
"originAgentCluster",
|
||||
"onpageswap",
|
||||
"onpagereveal",
|
||||
"credentialless",
|
||||
"speechSynthesis",
|
||||
"onscrollend",
|
||||
"webkitRequestFileSystem",
|
||||
"webkitResolveLocalFileSystemURL",
|
||||
"sendMsgToSolverCS",
|
||||
"webpackChunk_N_E",
|
||||
"__next_set_public_path__",
|
||||
"next",
|
||||
"__NEXT_DATA__",
|
||||
"__SSG_MANIFEST_CB",
|
||||
"__NEXT_P",
|
||||
"_N_E",
|
||||
"regeneratorRuntime",
|
||||
"__REACT_INTL_CONTEXT__",
|
||||
"DD_RUM",
|
||||
"_",
|
||||
"filterCSS",
|
||||
"filterXSS",
|
||||
"__SEGMENT_INSPECTOR__",
|
||||
"__NEXT_PRELOADREADY",
|
||||
"Intercom",
|
||||
"__MIDDLEWARE_MATCHERS",
|
||||
"__STATSIG_SDK__",
|
||||
"__STATSIG_JS_SDK__",
|
||||
"__STATSIG_RERENDER_OVERRIDE__",
|
||||
"_oaiHandleSessionExpired",
|
||||
"__BUILD_MANIFEST",
|
||||
"__SSG_MANIFEST",
|
||||
"__intercomAssignLocation",
|
||||
"__intercomReloadLocation"
|
||||
];
|
||||
|
||||
pub fn get_random_core_count() -> usize {
|
||||
return CORES[rand::rng().random_range(0..CORES.len())];
|
||||
}
|
||||
|
||||
|
||||
pub fn get_random_navigator_key() -> String {
|
||||
return NAVIGATOR_KEY[rand::rng().random_range(0..NAVIGATOR_KEY.len())].to_string();
|
||||
}
|
||||
|
||||
pub fn get_random_document_key() -> String {
|
||||
return DOCUMENT_KEY[rand::rng().random_range(0..DOCUMENT_KEY.len())].to_string();
|
||||
}
|
||||
|
||||
pub fn get_random_window_key() -> String {
|
||||
return WINDOW_KEY[rand::rng().random_range(0..WINDOW_KEY.len())].to_string();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod request;
|
||||
pub mod token;
|
||||
pub(crate) mod config;
|
||||
@@ -0,0 +1,225 @@
|
||||
use crate::sentinel_refresh::generate::config::{get_random_core_count, get_random_document_key, get_random_navigator_key, get_random_window_key};
|
||||
use chrono::FixedOffset;
|
||||
use chrono::Utc;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use rand::Rng;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use sha3::{Digest, Sha3_512};
|
||||
|
||||
// [
|
||||
// 4000, // first
|
||||
// "Fri Apr 11 2025 13:28:14 GMT+0530 (India Standard Time)", // second
|
||||
// None, // third
|
||||
// 28, // fourth
|
||||
// "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:137.0) Gecko/20100101 Firefox/137.0", // fifth
|
||||
// "https://sora-cdn.oaistatic.com/_next/static/chunks/f90f2e60-61da6d33257661c4.js", // sixth
|
||||
// None, // seventh
|
||||
// "en-US", // eighth
|
||||
// "en-US,en", // ninth
|
||||
// 29, // tenth
|
||||
// "productSub−20100101", // eleventh
|
||||
// "location", // twelfth
|
||||
// "ondrop", // thirteenth
|
||||
// 10520, // fourteenth - generated from time.perf_counter() * 1000
|
||||
// "48094014-a024-4d2d-8ec4-ba9c7cfef9c4", // fifteenth
|
||||
// "", // sixteenth
|
||||
// 16, // seventeenth - Number of cores
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GenerateSentinelRefreshRequest {
|
||||
first: usize,
|
||||
second: String, // Date
|
||||
third: Option<String>,
|
||||
fourth: Option<usize>,
|
||||
fifth: String, // User agent
|
||||
sixth: String, // URL
|
||||
seventh: Option<String>, // Language
|
||||
eighth: String, // Accept-Language
|
||||
ninth: String, // Product Sub
|
||||
tenth: Option<usize>,
|
||||
eleventh: String, // Product Sub - Navigator key
|
||||
twelfth: String, // Location - Document key
|
||||
thirteenth: String, // On Drop - Window key
|
||||
fourteenth: usize, // generated from time.perf_counter() * 1000
|
||||
fifteenth: String, // UUID
|
||||
sixteenth: String, // empty string
|
||||
seventeenth: usize, // Number of cores
|
||||
}
|
||||
|
||||
fn generate_date() -> String {
|
||||
let now_utc = Utc::now();
|
||||
let offset = FixedOffset::west_opt(5 * 60 * 60).unwrap();
|
||||
let now_with_offset = Utc::now().with_timezone(&offset);
|
||||
|
||||
let formatted_date = now_with_offset.format("%a %b %d %Y %H:%M:%S GMT-0500 (Eastern Standard Time)");
|
||||
return formatted_date.to_string();
|
||||
}
|
||||
|
||||
impl GenerateSentinelRefreshRequest {
|
||||
pub fn new() -> Self {
|
||||
let first_candidates = vec![1920 + 1080, 2560 + 1440, 1920 + 1200, 2560 + 1600];
|
||||
let first = first_candidates[rand::rng().random_range(0..first_candidates.len())];
|
||||
|
||||
let second = generate_date();
|
||||
|
||||
let third = None;
|
||||
|
||||
let fourth = None;
|
||||
|
||||
let fifth = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:137.0) Gecko/20100101 Firefox/137.0".to_string();
|
||||
|
||||
let sixth = "https://sora-cdn.oaistatic.com/_next/static/chunks/f90f2e60-61da6d33257661c4.js".to_string();
|
||||
|
||||
let seventh = None;
|
||||
|
||||
let eighth = "en-US".to_string();
|
||||
|
||||
let ninth = "en-US,en".to_string();
|
||||
|
||||
let tenth = None;
|
||||
|
||||
let eleventh = get_random_navigator_key();
|
||||
|
||||
let twelfth = get_random_document_key();
|
||||
|
||||
let thirteenth = get_random_window_key();
|
||||
|
||||
let fourteenth = 10520;
|
||||
|
||||
let fifteenth = generate_random_uuid();
|
||||
|
||||
let sixteenth = "".to_string();
|
||||
|
||||
let seventeenth = get_random_core_count();
|
||||
Self { first, second, third, fourth, fifth, sixth, seventh, eighth, ninth, tenth, eleventh: eleventh.to_string(), twelfth: twelfth.to_string(), thirteenth: thirteenth.to_string(), fourteenth, fifteenth, sixteenth, seventeenth }
|
||||
}
|
||||
|
||||
pub fn with_fourth(mut self, fourth: usize) -> Self {
|
||||
self.fourth = Some(fourth);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_tenth(mut self, tenth: usize) -> Self {
|
||||
self.tenth = Some(tenth);
|
||||
self
|
||||
}
|
||||
|
||||
//TODO(kasisnu): This is a temporary function to build the request.
|
||||
// It is extremely hacky and this whole struct should be rewritten to be more ergonomic.
|
||||
pub fn build(self) -> String {
|
||||
let mut request = String::new();
|
||||
request.push_str("[");
|
||||
request.push_str(&self.first.to_string());
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.second));
|
||||
request.push_str(",");
|
||||
request.push_str(&self.third.unwrap_or("null".to_string()));
|
||||
request.push_str(",");
|
||||
request.push_str(&self.fourth.unwrap_or(0).to_string());
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.fifth));
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.sixth));
|
||||
request.push_str(",");
|
||||
request.push_str(&self.seventh.unwrap_or("null".to_string()));
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.eighth));
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.ninth));
|
||||
request.push_str(",");
|
||||
request.push_str(&self.tenth.unwrap_or(0).to_string());
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.eleventh));
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.twelfth));
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.thirteenth));
|
||||
request.push_str(",");
|
||||
request.push_str(&self.fourteenth.to_string());
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.fifteenth));
|
||||
request.push_str(",");
|
||||
request.push_str(&string_with_quotes(self.sixteenth));
|
||||
request.push_str(",");
|
||||
request.push_str(&self.seventeenth.to_string());
|
||||
request.push_str("]");
|
||||
request
|
||||
}
|
||||
|
||||
pub fn hash_request(seed: f64, request: String) -> Vec<u8> {
|
||||
let mut hasher = Sha3_512::new();
|
||||
hasher.update(seed.to_string());
|
||||
let base64_request = BASE64_STANDARD.encode(request.as_bytes());
|
||||
hasher.update(base64_request);
|
||||
hasher.finalize().to_vec()
|
||||
}
|
||||
|
||||
pub fn with_fourth_and_tenth(mut self) -> (Self, String) {
|
||||
let mut fourth = 0;
|
||||
let mut tenth = 0;
|
||||
let max_iterations = 500000;
|
||||
let diff_len = 6;
|
||||
let target_bytes = vec![0x0f, 0xff, 0xff];
|
||||
let random_seed: f64 = rand::rng().random();
|
||||
|
||||
let mut base64_request = BASE64_STANDARD.encode(self.clone().build().as_bytes());
|
||||
for i in 0..max_iterations {
|
||||
fourth = i;
|
||||
tenth = i >> 1;
|
||||
let request = self.clone().with_fourth(fourth).with_tenth(tenth).build();
|
||||
let hash_bytes = Self::hash_request(random_seed, request.clone());
|
||||
base64_request = BASE64_STANDARD.encode(request.as_bytes());
|
||||
|
||||
let mut is_less_or_equal = true;
|
||||
for j in 0..std::cmp::min(diff_len, target_bytes.len()) {
|
||||
if j < hash_bytes.len() {
|
||||
if hash_bytes[j] > target_bytes[j] {
|
||||
is_less_or_equal = false;
|
||||
break;
|
||||
} else if hash_bytes[j] < target_bytes[j] {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_less_or_equal {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(self.with_fourth(fourth).with_tenth(tenth), format!("gAAAAAC{}", base64_request))
|
||||
}
|
||||
}
|
||||
|
||||
fn string_with_quotes(value: String) -> String {
|
||||
format!("\"{}\"", value)
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_generate_sentinel_refresh_request() {
|
||||
let request = GenerateSentinelRefreshRequest::new();
|
||||
println!("{}", request.build());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_fourth_and_tenth() {
|
||||
let request = GenerateSentinelRefreshRequest::new().with_fourth(100).with_tenth(200);
|
||||
println!("{}", request.build());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_request() {
|
||||
let request = GenerateSentinelRefreshRequest::new().with_fourth(100).with_tenth(200);
|
||||
let hash = GenerateSentinelRefreshRequest::hash_request(0.1, request.build());
|
||||
println!("{}", hash.iter().map(|b| format!("{:02x}", b)).collect::<String>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_fourth_and_tenth_2() {
|
||||
let (request, base64_request) = GenerateSentinelRefreshRequest::new().with_fourth_and_tenth();
|
||||
println!("{}", base64_request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
use errors::AnyhowResult;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
|
||||
use super::request::GenerateSentinelRefreshRequest;
|
||||
|
||||
const SORA_IMAGE_GEN_URL: &str = "https://chatgpt.com/backend-api/sentinel/req";
|
||||
|
||||
/// This user agent is tied to the sentinel generation. If we need to change it, we may need to change sentinel generation too.
|
||||
const USER_AGENT : &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36";
|
||||
|
||||
const SENTINEL_FLOW: &str = "sora_create_task";
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SentinelRequest {
|
||||
#[serde(rename = "p")]
|
||||
problem: String,
|
||||
|
||||
#[serde(rename = "id")]
|
||||
id: String,
|
||||
|
||||
#[serde(rename = "flow")]
|
||||
flow: String,
|
||||
}
|
||||
|
||||
|
||||
impl SentinelRequest {
|
||||
pub fn new(problem: String) -> Self {
|
||||
let id = generate_random_uuid();
|
||||
Self { problem, id, flow: SENTINEL_FLOW.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SentinelResponse {
|
||||
persona: String,
|
||||
token: String,
|
||||
}
|
||||
|
||||
|
||||
pub async fn generate_token() -> AnyhowResult<String> {
|
||||
let (_request, base64_request) = GenerateSentinelRefreshRequest::new().with_fourth_and_tenth();
|
||||
let request = SentinelRequest::new(base64_request);
|
||||
let client = reqwest::Client::new();
|
||||
let response = client.post(SORA_IMAGE_GEN_URL)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&request)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let response_json: SentinelResponse = response.json().await?;
|
||||
Ok(response_json.token)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_generate_token() {
|
||||
let token = generate_token().await.unwrap();
|
||||
println!("{}", token);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
pub use refresh::refresh_sentinel;
|
||||
pub mod refresh;
|
||||
pub mod refresh;
|
||||
|
||||
pub mod generate;
|
||||
@@ -0,0 +1,48 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SoraError {
|
||||
/// Unauthorized, cookie and/or bearer token expired. We'll need to ask for a refreshed login.
|
||||
///
|
||||
/// Example message, e.g. from the upload endpoint:
|
||||
/// {
|
||||
/// "error": {
|
||||
/// "message": "Your authentication token has expired. Please try signing in again.",
|
||||
/// "type": "invalid_request_error",
|
||||
/// "param": null,
|
||||
/// "code": "token_expired"
|
||||
/// }
|
||||
/// }
|
||||
UnauthorizedCookieOrBearerExpired,
|
||||
|
||||
/// Another error occurred.
|
||||
OtherBadStatus(anyhow::Error),
|
||||
|
||||
/// Reqwest Error
|
||||
ReqwestError(reqwest::Error),
|
||||
}
|
||||
|
||||
impl Error for SoraError {}
|
||||
|
||||
impl Display for SoraError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SoraError::UnauthorizedCookieOrBearerExpired => {
|
||||
write!(f, "Unauthorized: cookie and/or bearer token expired")
|
||||
}
|
||||
SoraError::OtherBadStatus(err) => {
|
||||
write!(f, "Other error: {}", err)
|
||||
}
|
||||
SoraError::ReqwestError(err) => {
|
||||
write!(f, "Reqwest error: {}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for SoraError {
|
||||
fn from(err: reqwest::Error) -> SoraError {
|
||||
SoraError::ReqwestError(err)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use errors::AnyhowResult;
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use crate::sora_error::SoraError;
|
||||
|
||||
const SORA_UPLOAD_MEDIA_URL: &str = "https://sora.com/backend/uploads";
|
||||
|
||||
@@ -25,6 +26,17 @@ Response:
|
||||
"size_bytes": 118501,
|
||||
"thumbnail_url": "https://videos.openai.com/vg-assets/assets%2Fclient_upload%2Fmedia%2Fuser-9wf6JFdgRSJLjvSJ53LNAbV8%2Fmedia_01jqt9vt20erx9zvryf3v1pecx.jpg?st=2025-04-02T02%3A39%3A01Z&se=2025-04-08T03%3A39%3A01Z&sks=b&skt=2025-04-02T02%3A39%3A01Z&ske=2025-04-08T03%3A39%3A01Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=3d249c53-07fa-4ba4-9b65-0bf8eb4ea46a&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=9aU9xKI0BkhRtrTb0RD05%2BT6prBnmXk1I7nV2nTsFbY%3D&az=oaivgprodscus"
|
||||
}
|
||||
|
||||
Error response:
|
||||
|
||||
Failed to upload scene media to Sora: Upload failed with status 401 Unauthorized: {
|
||||
"error": {
|
||||
"message": "Your authentication token has expired. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "token_expired"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -50,7 +62,13 @@ pub (crate) struct SoraMediaUploadRequest<'a> {
|
||||
pub credentials: &'a SoraCredentials,
|
||||
}
|
||||
|
||||
pub (crate) async fn upload_media_http_request(file_bytes: Vec<u8>, filename: String, mime_type: &str, credentials: &SoraCredentials) -> AnyhowResult<SoraMediaUploadResponse> {
|
||||
pub (crate) async fn upload_media_http_request(
|
||||
file_bytes: Vec<u8>,
|
||||
filename: String,
|
||||
mime_type: &str,
|
||||
credentials: &SoraCredentials,
|
||||
) -> Result<SoraMediaUploadResponse, SoraError> {
|
||||
|
||||
// Create multipart form
|
||||
let part = Part::bytes(file_bytes) // NB: Reqwest needs to own the bytes.
|
||||
.file_name(filename) // NB: Reqwest needs to own the bytes
|
||||
@@ -70,12 +88,29 @@ pub (crate) async fn upload_media_http_request(file_bytes: Vec<u8>, filename: St
|
||||
|
||||
// Check response status
|
||||
if !response.status().is_success() {
|
||||
let status = response.status();
|
||||
let text = response.text().await?;
|
||||
return Err(anyhow::anyhow!("Upload failed with status {}: {}", status, text));
|
||||
let error = classify_error(response).await;
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
// Parse response
|
||||
let upload_response = response.json::<SoraMediaUploadResponse>().await?;
|
||||
Ok(upload_response)
|
||||
}
|
||||
|
||||
async fn classify_error(response: reqwest::Response) -> SoraError {
|
||||
let status = response.status();
|
||||
let message = match response.text().await {
|
||||
Ok(text) => text,
|
||||
Err(err) => return SoraError::ReqwestError(err),
|
||||
};
|
||||
|
||||
let cookie_expired =
|
||||
message.contains("Your authentication token has expired. Please try signing in again.")
|
||||
|| message.contains("token_expired");
|
||||
|
||||
if cookie_expired {
|
||||
SoraError::UnauthorizedCookieOrBearerExpired
|
||||
} else {
|
||||
SoraError::OtherBadStatus(anyhow::anyhow!("Upload failed with status {}: {}", status, message))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
/// NB: `sqlx::query` is spammy and dumps all queries as "info"-level log lines.
|
||||
/// NB: `hyper::proto::h1::io` is incredibly spammy and logs every chunk of bytes in very large files being downloaded.
|
||||
pub const DEFAULT_RUST_LOG: &str = concat!(
|
||||
"debug,",
|
||||
"actix_web=info,",
|
||||
"sqlx::query=warn,",
|
||||
"hyper::proto::h1::io=warn,",
|
||||
|
||||
@@ -21,9 +21,14 @@ use crate::helpers::boolean_converters::i8_to_bool;
|
||||
use crate::payloads::generic_inference_args::generic_inference_args::{GenericInferenceArgs, PolymorphicInferenceArgs};
|
||||
use crate::queries::generic_inference::web::job_status::{GenericInferenceJobStatus, RequestDetails, ResultDetails, UserDetails};
|
||||
|
||||
pub enum SessionUser<'a> {
|
||||
User(&'a UserToken),
|
||||
Anonymous(&'a AnonymousVisitorTrackingToken),
|
||||
}
|
||||
|
||||
pub struct ListSessionJobsForUserArgs<'a> {
|
||||
// TODO(bt,2024-04-23): Support AnonymousVisitorToken
|
||||
pub user_token: &'a UserToken,
|
||||
pub user: SessionUser<'a>,
|
||||
pub maybe_include_job_statuses: Option<&'a HashSet<JobStatusPlus>>,
|
||||
pub maybe_exclude_job_statuses: Option<&'a HashSet<JobStatusPlus>>,
|
||||
}
|
||||
@@ -63,14 +68,29 @@ pub async fn list_session_jobs_from_connection(
|
||||
// Furthermore, even though the join query's `created_at` lacks an index, sort
|
||||
// by id will accomplish this quickly.
|
||||
{
|
||||
query_builder.push(r#"
|
||||
INNER JOIN (
|
||||
SELECT id
|
||||
FROM generic_inference_jobs
|
||||
WHERE maybe_creator_user_token =
|
||||
"#);
|
||||
|
||||
query_builder.push_bind(args.user_token.to_string());
|
||||
match args.user {
|
||||
SessionUser::User(user_token) => {
|
||||
query_builder.push(r#"
|
||||
INNER JOIN (
|
||||
SELECT id
|
||||
FROM generic_inference_jobs
|
||||
WHERE maybe_creator_user_token =
|
||||
"#);
|
||||
query_builder.push_bind(user_token.to_string());
|
||||
}
|
||||
SessionUser::Anonymous(avt_token) => {
|
||||
query_builder.push(r#"
|
||||
INNER JOIN (
|
||||
SELECT id
|
||||
FROM generic_inference_jobs
|
||||
WHERE maybe_creator_anonymous_visitor_token =
|
||||
"#);
|
||||
query_builder.push_bind(avt_token.to_string());
|
||||
query_builder.push(r#"
|
||||
AND maybe_creator_user_token IS NULL
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
||||
query_builder.push(r#"
|
||||
AND created_at > DATE_SUB(NOW(), INTERVAL 36 HOUR)
|
||||
|
||||
@@ -16,9 +16,12 @@ use log::warn;
|
||||
use mysql_queries::queries::media_files::get::get_media_file::get_media_file;
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::image_gen::SoraError;
|
||||
use openai_sora_client::sentinel_refresh::generate::token::generate_token;
|
||||
use openai_sora_client::sentinel_refresh::refresh_sentinel;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use shared_service_components::sora_redis_credentials::set_sora_credential_field_in_redis::set_sora_credential_field_in_redis;
|
||||
use shared_service_components::sora_redis_credentials::keys::RedisSoraCredentialSubkey;
|
||||
use sqlx::MySqlPool;
|
||||
use tempdir::TempDir;
|
||||
use utoipa::ToSchema;
|
||||
@@ -76,6 +79,9 @@ pub struct EnqueueStudioImageGenRequest {
|
||||
/// The user's image generation prompt.
|
||||
pub prompt: String,
|
||||
|
||||
/// Turn off the system prompt.
|
||||
pub disable_system_prompt: Option<bool>,
|
||||
|
||||
/// Additional images to include (optional). Up to nine images.
|
||||
pub maybe_additional_images: Option<Vec<MediaFileToken>>,
|
||||
|
||||
@@ -94,6 +100,7 @@ pub enum EnqueueImageGenRequestError {
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
RateLimited,
|
||||
TooManyConcurrentTasks,
|
||||
}
|
||||
|
||||
impl ResponseError for EnqueueImageGenRequestError {
|
||||
@@ -103,6 +110,7 @@ impl ResponseError for EnqueueImageGenRequestError {
|
||||
EnqueueImageGenRequestError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
EnqueueImageGenRequestError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
EnqueueImageGenRequestError::RateLimited => StatusCode::TOO_MANY_REQUESTS,
|
||||
EnqueueImageGenRequestError::TooManyConcurrentTasks => StatusCode::TOO_MANY_REQUESTS,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +120,7 @@ impl ResponseError for EnqueueImageGenRequestError {
|
||||
EnqueueImageGenRequestError::NotAuthorized => "unauthorized".to_string(),
|
||||
EnqueueImageGenRequestError::ServerError => "server error".to_string(),
|
||||
EnqueueImageGenRequestError::RateLimited => "rate limited".to_string(),
|
||||
EnqueueImageGenRequestError::TooManyConcurrentTasks => "too many concurrent tasks".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
@@ -283,30 +292,69 @@ pub async fn enqueue_studio_image_generation_handler(http_request: HttpRequest,
|
||||
None => 1,
|
||||
};
|
||||
|
||||
let prompt = request.prompt.clone();
|
||||
let prompt = create_prompt(&request);
|
||||
|
||||
let mut response = sora_image_gen_remix(SoraImageGenRemixRequest { prompt: prompt.clone(), num_images: NumImages::One, image_size: ImageSize::Square, sora_media_tokens: sora_media_tokens.clone(), credentials: &sora_credentials }).await;
|
||||
let mut response = sora_image_gen_remix(SoraImageGenRemixRequest {
|
||||
prompt: prompt.clone(),
|
||||
num_images: NumImages::One,
|
||||
image_size: ImageSize::Square,
|
||||
sora_media_tokens: sora_media_tokens.clone(),
|
||||
credentials: &sora_credentials
|
||||
}).await;
|
||||
|
||||
debug!("Sora image gen response: {:?}", response);
|
||||
|
||||
if let Err(SoraError::SentinelBlock(msg)) = &response {
|
||||
error!("Sora sentinel block, attempting refresh: {}", msg);
|
||||
|
||||
// Try to refresh sentinel
|
||||
match refresh_sentinel().await {
|
||||
Ok(new_sentinel) => {
|
||||
// Update the credentials with new sentinel
|
||||
match generate_token().await {
|
||||
Ok(sentinel_token) => {
|
||||
set_sora_credential_field_in_redis(
|
||||
&mut redis,
|
||||
RedisSoraCredentialSubkey::Sentinel,
|
||||
&sentinel_token
|
||||
).map_err(|e| {
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
let updated_sora_credentials = get_sora_credentials_from_request(&http_request, &mut redis).map_err(|e| {
|
||||
error!("sora credential error: {:?}", e);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
// Retry the request with new sentinel
|
||||
response = sora_image_gen_remix(SoraImageGenRemixRequest { prompt: prompt.clone(), num_images: NumImages::One, image_size: ImageSize::Square, sora_media_tokens: sora_media_tokens.clone(), credentials: &updated_sora_credentials }).await;
|
||||
response = sora_image_gen_remix(SoraImageGenRemixRequest {
|
||||
prompt: prompt.clone(),
|
||||
num_images: NumImages::One,
|
||||
image_size: ImageSize::Square,
|
||||
sora_media_tokens: sora_media_tokens.clone(),
|
||||
credentials: &updated_sora_credentials
|
||||
}).await;
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to refresh Sora sentinel: {:?}", e);
|
||||
},
|
||||
// Fallback to the old refresh stragegy relying on the external service
|
||||
match refresh_sentinel().await {
|
||||
Ok(new_sentinel) => {
|
||||
// Update the credentials with new sentinel
|
||||
let updated_sora_credentials = get_sora_credentials_from_request(&http_request, &mut redis).map_err(|e| {
|
||||
error!("sora credential error: {:?}", e);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
// Retry the request with new sentinel
|
||||
response = sora_image_gen_remix(SoraImageGenRemixRequest {
|
||||
prompt: prompt.clone(),
|
||||
num_images: NumImages::One,
|
||||
image_size: ImageSize::Square,
|
||||
sora_media_tokens: sora_media_tokens.clone(),
|
||||
credentials: &updated_sora_credentials
|
||||
}).await;
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to refresh Sora sentinel: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,21 +363,34 @@ pub async fn enqueue_studio_image_generation_handler(http_request: HttpRequest,
|
||||
SoraError::TokenExpired(msg) => {
|
||||
error!("Sora token expired, needs refresh: {}", msg);
|
||||
// TODO: Implement token refresh logic here
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
},
|
||||
SoraError::SentinelBlock(msg) => {
|
||||
error!("Sora sentinel block still occurring after refresh: {}", msg);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
},
|
||||
SoraError::TooManyConcurrentTasks(msg) => {
|
||||
error!("Sora too many concurrent tasks: {}", msg);
|
||||
EnqueueImageGenRequestError::TooManyConcurrentTasks
|
||||
},
|
||||
_ => {
|
||||
error!("Failed to call Sora image generation: {:?}", err);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
},
|
||||
}
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
debug!("Sora image gen response: {:?}", response);
|
||||
|
||||
// Store the actual inference args that will go to the database
|
||||
let inference_args = SoraImageGenArgs { prompt: Some(prompt), scene_snapshot_media_token: Some(request.snapshot_media_token.clone()), maybe_additional_media_file_tokens: Some(additional_images), maybe_number_of_samples: Some(number_of_samples), maybe_sora_media_upload_tokens: Some(sora_media_tokens), maybe_sora_task_id: Some(response.task_id) };
|
||||
let inference_args = SoraImageGenArgs {
|
||||
prompt: Some(prompt),
|
||||
scene_snapshot_media_token: Some(request.snapshot_media_token.clone()),
|
||||
maybe_additional_media_file_tokens: Some(additional_images),
|
||||
maybe_number_of_samples: Some(number_of_samples),
|
||||
maybe_sora_media_upload_tokens: Some(sora_media_tokens),
|
||||
maybe_sora_task_id: Some(response.task_id)
|
||||
};
|
||||
|
||||
// create the inference args here
|
||||
let maybe_avt_token = server_state.avt_cookie_manager.get_avt_token_from_request(&http_request);
|
||||
@@ -348,7 +409,10 @@ pub async fn enqueue_studio_image_generation_handler(http_request: HttpRequest,
|
||||
maybe_cover_image_media_file_token: None,
|
||||
maybe_raw_inference_text: None,
|
||||
maybe_max_duration_seconds: None,
|
||||
maybe_inference_args: Some(GenericInferenceArgs { inference_category: Some(InferenceCategoryAbbreviated::ImageGeneration), args: Some(PolymorphicInferenceArgs::Sg(inference_args)) }),
|
||||
maybe_inference_args: Some(GenericInferenceArgs {
|
||||
inference_category: Some(InferenceCategoryAbbreviated::ImageGeneration),
|
||||
args: Some(PolymorphicInferenceArgs::Sg(inference_args)),
|
||||
}),
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
@@ -412,7 +476,10 @@ async fn query_and_download_media_file(media_file_token: &MediaFileToken, downlo
|
||||
},
|
||||
};
|
||||
|
||||
let media_file_bucket_path = MediaFileBucketPath::from_object_hash(&media_file.public_bucket_directory_hash, media_file.maybe_public_bucket_prefix.as_deref(), media_file.maybe_public_bucket_extension.as_deref());
|
||||
let media_file_bucket_path = MediaFileBucketPath::from_object_hash(
|
||||
&media_file.public_bucket_directory_hash,
|
||||
media_file.maybe_public_bucket_prefix.as_deref(),
|
||||
media_file.maybe_public_bucket_extension.as_deref());
|
||||
|
||||
let extension = media_file.maybe_public_bucket_extension.clone().unwrap_or_else(|| ".jpg".to_string());
|
||||
let download_filename = format!("download_file_{}.{}", media_file.token.as_str(), extension);
|
||||
@@ -424,3 +491,30 @@ async fn query_and_download_media_file(media_file_token: &MediaFileToken, downlo
|
||||
|
||||
Ok(download_file_path)
|
||||
}
|
||||
|
||||
const SYSTEM_PROMPT_SINGLE_IMAGE: &str =
|
||||
"Use the image as a reference for the composition exactly. Copy the character pose, layout, scene, and the objects exactly - no deviation.";
|
||||
|
||||
const SYSTEM_PROMPT_MULTI_IMAGE: &str =
|
||||
"Use the first image as a reference for the composition exactly. Copy the character pose, layout, scene, and the objects exactly from the first image - no deviation.";
|
||||
|
||||
fn create_prompt(request: &EnqueueStudioImageGenRequest) -> String {
|
||||
let disable_system_prompt = request.disable_system_prompt
|
||||
.clone()
|
||||
.unwrap_or(false);
|
||||
|
||||
if disable_system_prompt {
|
||||
return request.prompt.clone();
|
||||
}
|
||||
|
||||
let extra_image_count = request.maybe_additional_images
|
||||
.as_ref()
|
||||
.map(|v| v.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
if extra_image_count == 0 {
|
||||
format!("{} {}", SYSTEM_PROMPT_SINGLE_IMAGE, request.prompt)
|
||||
} else {
|
||||
format!("{} {}", SYSTEM_PROMPT_MULTI_IMAGE, request.prompt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ use enums::by_table::generic_inference_jobs::inference_category::InferenceCatego
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use mysql_queries::queries::generic_inference::web::job_status::GenericInferenceJobStatus;
|
||||
use mysql_queries::queries::generic_inference::web::list_session_jobs::{list_session_jobs_from_connection, ListSessionJobsForUserArgs};
|
||||
use mysql_queries::queries::generic_inference::web::list_session_jobs::{list_session_jobs_from_connection, ListSessionJobsForUserArgs, SessionUser};
|
||||
use primitives::numerics::i64_to_u64_zero_clamped::i64_to_u64_zero_clamped;
|
||||
use redis_common::redis_keys::RedisKeys;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
@@ -238,11 +238,6 @@ pub async fn list_session_jobs_handler(
|
||||
ListSessionJobsError::ServerError
|
||||
})?;
|
||||
|
||||
let session = match maybe_user_session {
|
||||
Some(session) => session,
|
||||
None => return Err(ListSessionJobsError::NotAuthorized),
|
||||
};
|
||||
|
||||
let include_states = query.include_states
|
||||
.as_deref()
|
||||
.map(|s| s.split(",")
|
||||
@@ -255,8 +250,23 @@ pub async fn list_session_jobs_handler(
|
||||
.filter_map(|status| JobStatusPlus::from_str(status).ok())
|
||||
.collect::<HashSet<_>>());
|
||||
|
||||
let user;
|
||||
|
||||
match (maybe_user_session.as_ref(), maybe_avt_token.as_ref()) {
|
||||
(Some(session), _) => {
|
||||
user = SessionUser::User(&session.user_token_typed);
|
||||
}
|
||||
(None, Some(avt_token)) => {
|
||||
user = SessionUser::Anonymous(avt_token);
|
||||
}
|
||||
(None, None) => {
|
||||
// TODO(bt,2025-04-15): We should install an AVT cookie.
|
||||
return Err(ListSessionJobsError::NotAuthorized);
|
||||
}
|
||||
}
|
||||
|
||||
let args = ListSessionJobsForUserArgs {
|
||||
user_token: &session.user_token_typed,
|
||||
user,
|
||||
maybe_include_job_statuses: include_states.as_ref(),
|
||||
maybe_exclude_job_statuses: exclude_states.as_ref(),
|
||||
};
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": [
|
||||
"**/*"
|
||||
],
|
||||
"plugins": [
|
||||
"@nx"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx",
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"rules": {
|
||||
"@nx/enforce-module-boundaries": [
|
||||
"error",
|
||||
{
|
||||
"enforceBuildableLibDependency": true,
|
||||
"allow": [],
|
||||
"depConstraints": [
|
||||
{
|
||||
"sourceTag": "*",
|
||||
"onlyDependOnLibsWithTags": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@nx/typescript"
|
||||
],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.jsx"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:@nx/javascript"
|
||||
],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
|
||||
"nrwl.angular-console",
|
||||
"esbenp.prettier-vscode"
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
This is an `nx` monorepo that can contain multiple apps and shared libraries.
|
||||
|
||||
All commands to run these projects are performed from _this_ directory.
|
||||
|
||||
All commands to run these projects are performed from _this_ directory.
|
||||
|
||||
## Install dependencies
|
||||
|
||||
@@ -27,4 +26,33 @@ Run dev server:
|
||||
nx dev editor3d
|
||||
```
|
||||
|
||||
```
|
||||
call when starting on main or a new branch:
|
||||
./clean_modules
|
||||
|
||||
The names for @frontend and @storyteller come from the package.json file in the libs folder.
|
||||
import { Login } from "@frontend/login";
|
||||
import { api } from "@storyteller/api";
|
||||
|
||||
For shared UI components, import from @storyteller/ui-[componentname].
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
|
||||
building:
|
||||
npx nx build editor3d
|
||||
npx nx build editor2d
|
||||
|
||||
dev:
|
||||
npx nx dev editor3d
|
||||
npx nx dev editor2d
|
||||
|
||||
|
||||
|
||||
# component build
|
||||
1.
|
||||
npx nx g @nx/react:library libs/components/toaster --import-path=@storyteller/ui-toaster --bundler=vite
|
||||
npm install
|
||||
2.import it first in code
|
||||
3.nx sync
|
||||
4.nx build
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[build]
|
||||
#command = "./script/netlify_build.sh"
|
||||
command = "./apps/editor2d/script/netlify_build.sh"
|
||||
publish = "apps/editor2d/dist/"
|
||||
|
||||
#[build.environment]
|
||||
@@ -16,7 +16,6 @@ publish = "apps/editor2d/dist/"
|
||||
for = "/build/*"
|
||||
|
||||
[headers.values]
|
||||
|
||||
"Cache-Control" = "public, max-age=31560000, immutable"
|
||||
|
||||
# [[redirects]]
|
||||
|
||||
@@ -4,7 +4,14 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"nx": {
|
||||
"name": "editor2d"
|
||||
"name": "editor2d",
|
||||
"targets": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "echo 'editor2d' && vite",
|
||||
@@ -21,7 +28,6 @@
|
||||
"@fortawesome/pro-regular-svg-icons": "^6.7.2",
|
||||
"@fortawesome/pro-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@headlessui/react": "^2.1.3",
|
||||
"@preact/signals-react": "^2.2.0",
|
||||
"@tauri-apps/api": "^2.2.0",
|
||||
"@techstark/opencv-js": "^4.10.0-release.1",
|
||||
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -15,6 +15,6 @@ cp _redirects dist/
|
||||
echo "Copy netlify 404.html page into dist"
|
||||
cp "404.html" dist/
|
||||
|
||||
echo "List files after build"
|
||||
echo "List final files in build"
|
||||
find dist/
|
||||
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import { faFolder } from "@fortawesome/pro-solid-svg-icons"
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "~/components/ui"
|
||||
|
||||
const testUrl = "https://api.fakeyou.com/v1/media_files/list/user/hanashi?include_user_uploads=true&page_size=24&filter_media_classes=unknown";
|
||||
const getTestData = (callback: (data: any) => void) => {
|
||||
const netCall = async () => {
|
||||
// GET the test url
|
||||
const testResults = await fetch(testUrl);
|
||||
const testJson = await testResults.json();
|
||||
console.log("testResults", testJson);
|
||||
return testJson;
|
||||
}
|
||||
|
||||
netCall().then(callback);
|
||||
}
|
||||
|
||||
export const GalleryRootComponent = ({
|
||||
className
|
||||
}: {
|
||||
className?: string
|
||||
}) => {
|
||||
|
||||
const [testData, setTestData] = useState<any>();
|
||||
|
||||
useEffect(() => {
|
||||
getTestData(setTestData);
|
||||
}, [])
|
||||
|
||||
const aggregatedData = aggregateGalleryData({ data: testData });
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="m-32">
|
||||
<div className="flex w-fit gap-x-6 items-center">
|
||||
<span className="text-white font-bold text-5xl">My Gallery</span>
|
||||
<Button variant="secondary" icon={faFolder} className="h-8">
|
||||
Open Folder
|
||||
</Button>
|
||||
</div>
|
||||
<div className="overflow-y-auto mt-12 flex flex-col gap-y-8">
|
||||
{Array.from(aggregatedData.entries()).map(([dateEpoch, results]) => (<GallerySection dateEpoch={dateEpoch} results={results} />))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const GallerySection = ({
|
||||
dateEpoch,
|
||||
results
|
||||
}: {
|
||||
dateEpoch: number,
|
||||
results: any[]
|
||||
}) => {
|
||||
|
||||
const dateObj = new Date(dateEpoch);
|
||||
const title = dateObj.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric"
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<span className="text-lg">{title}</span>
|
||||
<div className="flex gap-4 flex-wrap mt-4">
|
||||
{results.map((result) => (<GalleryCard result={result} />))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const GalleryCard = ({ result }: {
|
||||
result: {
|
||||
media_links: {
|
||||
cdn_url: string,
|
||||
maybe_thumbnail_template: string,
|
||||
},
|
||||
}
|
||||
}) => {
|
||||
return (
|
||||
<div className="rounded-md size-48 flex items-center justify-center overflow-hidden">
|
||||
<img src={result.media_links.cdn_url} alt="Thumbnail" className="h-full w-full object-cover" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const aggregateGalleryData = ({
|
||||
data
|
||||
}: {
|
||||
data?: {
|
||||
results: any[]
|
||||
}
|
||||
}) => {
|
||||
const aggregateMap = new Map<number, any[]>();
|
||||
|
||||
if (!data) {
|
||||
return aggregateMap;
|
||||
}
|
||||
|
||||
data.results.forEach((result) => {
|
||||
if (result.media_type !== "image") {
|
||||
return;
|
||||
}
|
||||
|
||||
const dateString = result.updated_at as string;
|
||||
const dateObj = new Date(dateString);
|
||||
|
||||
// Move it the start of the day for comparison
|
||||
dateObj.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const dateEpoch = dateObj.getTime();
|
||||
if (!aggregateMap.has(dateEpoch)) {
|
||||
aggregateMap.set(dateEpoch, []);
|
||||
}
|
||||
|
||||
aggregateMap.get(dateEpoch)!.push(result);
|
||||
})
|
||||
|
||||
return aggregateMap;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./GalleryRootComponent";
|
||||
@@ -148,7 +148,6 @@ export class ApiManager {
|
||||
formData.append("file", blob);
|
||||
}
|
||||
|
||||
// console.log(`HELLO${formData}`);
|
||||
return this.fetchMultipartFormData<T>(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -281,10 +280,12 @@ export class Api extends ApiManager {
|
||||
}
|
||||
|
||||
public async enqueueImageGeneration({
|
||||
disableSystemPrompt,
|
||||
prompt,
|
||||
snapshotMediaToken,
|
||||
additionalImages,
|
||||
}: {
|
||||
disableSystemPrompt: boolean;
|
||||
prompt: string;
|
||||
snapshotMediaToken: string;
|
||||
additionalImages?: string[];
|
||||
@@ -294,6 +295,7 @@ export class Api extends ApiManager {
|
||||
const uuidIdempotencyToken = crypto.randomUUID(); // Generate a new UUID
|
||||
const body = {
|
||||
uuid_idempotency_token: uuidIdempotencyToken,
|
||||
disable_system_prompt: disableSystemPrompt,
|
||||
prompt,
|
||||
snapshot_media_token: snapshotMediaToken, // Changed from scene_media_token to snapshot_media_token
|
||||
additional_images: additionalImages,
|
||||
@@ -302,6 +304,7 @@ export class Api extends ApiManager {
|
||||
const postResponse = await this.post<
|
||||
{
|
||||
uuid_idempotency_token: string;
|
||||
disable_system_prompt: boolean;
|
||||
prompt: string;
|
||||
snapshot_media_token: string;
|
||||
additional_images?: string[];
|
||||
@@ -327,7 +330,10 @@ export class Api extends ApiManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
public async pollJobSession(jobToken: string): Promise<
|
||||
public async pollJobSession(
|
||||
jobToken: string,
|
||||
thumbnailWidth: number = 256,
|
||||
): Promise<
|
||||
ApiResponse<{
|
||||
job_token: string;
|
||||
request: {
|
||||
@@ -338,6 +344,9 @@ export class Api extends ApiManager {
|
||||
progress_percentage: number;
|
||||
};
|
||||
result: {
|
||||
image_url: string;
|
||||
thumbnail_url: string;
|
||||
public_bucket_path: string;
|
||||
generated_images?: string[];
|
||||
error?: string;
|
||||
};
|
||||
@@ -345,41 +354,53 @@ export class Api extends ApiManager {
|
||||
> {
|
||||
const endpoint = `${this.ApiTargets.BaseApi}/v1/jobs/job/${jobToken}`;
|
||||
|
||||
const response = await this.get<{
|
||||
success?: boolean;
|
||||
status?: string;
|
||||
result?: {
|
||||
generated_images?: string[];
|
||||
error?: string;
|
||||
};
|
||||
BadInput?: string;
|
||||
}>({
|
||||
const response = await this.get<any>({
|
||||
endpoint,
|
||||
});
|
||||
console.log("Job Polling Response:");
|
||||
console.log(response);
|
||||
|
||||
let image_url = response.state.maybe_result?.media_links?.cdn_url;
|
||||
let public_bucket_path =
|
||||
response.state.maybe_result?.maybe_public_bucket_media_path;
|
||||
|
||||
let thumbnail_url =
|
||||
response.state.maybe_result?.media_links?.maybe_thumbnail_template?.replace(
|
||||
"{WIDTH}",
|
||||
thumbnailWidth.toString(),
|
||||
);
|
||||
|
||||
let progress_percentage = response.state.status.progress_percentage;
|
||||
let status_string = response.state.status.status;
|
||||
|
||||
console.log("Image URL:", image_url);
|
||||
console.log("Thumbnail URL:", thumbnail_url);
|
||||
console.log("Progress Percentage:", progress_percentage);
|
||||
console.log("Status", status_string);
|
||||
|
||||
console.log("response FROM JOBS");
|
||||
console.log(response);
|
||||
|
||||
const success = response.success ?? false;
|
||||
const status = response.status ?? "";
|
||||
const result = response.result ?? {
|
||||
generated_images: [],
|
||||
error: undefined,
|
||||
};
|
||||
|
||||
const errorMessage = response.BadInput;
|
||||
|
||||
return {
|
||||
success,
|
||||
data: {
|
||||
result,
|
||||
result: {
|
||||
image_url: image_url || "",
|
||||
thumbnail_url: thumbnail_url || "",
|
||||
public_bucket_path: public_bucket_path || "",
|
||||
error: undefined,
|
||||
},
|
||||
job_token: jobToken,
|
||||
request: {
|
||||
maybe_model_title: "Image Generation",
|
||||
},
|
||||
status: {
|
||||
status: status.toLowerCase(),
|
||||
progress_percentage: 0,
|
||||
status: status_string.toLowerCase(),
|
||||
progress_percentage: progress_percentage,
|
||||
},
|
||||
},
|
||||
errorMessage,
|
||||
|
||||
@@ -62,6 +62,7 @@ export class EditEngine {
|
||||
this.editMode = EditModes.SELECT;
|
||||
|
||||
this.boardCanvasRef = boardCanvasRef;
|
||||
|
||||
this.stage = new Konva.Stage({
|
||||
container: this.boardCanvasRef,
|
||||
width: window.innerWidth,
|
||||
@@ -238,13 +239,19 @@ export class EditEngine {
|
||||
}
|
||||
|
||||
private onBoardCanvasResize() {
|
||||
this.realTimeDrawEngine.updateCaptureCanvas(undefined, undefined);
|
||||
this.realTimeDrawEngine.updateCaptureCanvas(0, 0);
|
||||
console.log("onBoardCanvasResize Event called");
|
||||
console.log("Board Canvas values below");
|
||||
console.log(this.boardCanvasRef.offsetWidth);
|
||||
console.log(this.boardCanvasRef.offsetHeight);
|
||||
|
||||
this.matteBox.updateSize({
|
||||
boardCanvasSize: {
|
||||
width: this.boardCanvasRef.offsetWidth,
|
||||
height: this.boardCanvasRef.offsetHeight,
|
||||
},
|
||||
});
|
||||
|
||||
this.uiLayer.draw();
|
||||
this.stage.width(this.boardCanvasRef.offsetWidth);
|
||||
this.stage.height(this.boardCanvasRef.offsetHeight);
|
||||
|
||||
@@ -55,7 +55,7 @@ export class Engine {
|
||||
private uiLayer: Konva.Layer;
|
||||
private previewLayer: Konva.Layer;
|
||||
private offScreenCanvas: OffscreenCanvas;
|
||||
private realTimeDrawEngine: RealTimeDrawEngine;
|
||||
public realTimeDrawEngine: RealTimeDrawEngine;
|
||||
private nodesManager: NodesManager;
|
||||
private nodeIsolator: NodeIsolator;
|
||||
private nodeTransformer: NodeTransformer;
|
||||
@@ -66,8 +66,8 @@ export class Engine {
|
||||
private backgroundNode: ShapeNode | null = null;
|
||||
|
||||
private sceneManager: SceneManager;
|
||||
private undoStackManager: UndoStackManager;
|
||||
private commandManager: CommandManager;
|
||||
public undoStackManager: UndoStackManager;
|
||||
public commandManager: CommandManager;
|
||||
private videoExtractionHandler: VideoExtractionHandler;
|
||||
|
||||
public segmentationButtonCanBePressed: boolean = true;
|
||||
@@ -510,7 +510,20 @@ export class Engine {
|
||||
}
|
||||
|
||||
private onBoardCanvasResize() {
|
||||
this.realTimeDrawEngine.updateCaptureCanvas(undefined, undefined);
|
||||
console.log("onBoardCanvasResize Event called");
|
||||
// Print window dimensions for debugging
|
||||
console.log("Window dimensions:", {
|
||||
innerWidth: window.innerWidth,
|
||||
innerHeight: window.innerHeight,
|
||||
outerWidth: window.outerWidth,
|
||||
outerHeight: window.outerHeight,
|
||||
});
|
||||
|
||||
this.realTimeDrawEngine.updateCaptureCanvas(
|
||||
window.innerWidth,
|
||||
window.innerHeight,
|
||||
);
|
||||
|
||||
this.matteBox.updateSize({
|
||||
boardCanvasSize: {
|
||||
width: this.boardCanvasRef.offsetWidth,
|
||||
|
||||
@@ -56,11 +56,8 @@ export class ShapeNode extends NetworkedNode {
|
||||
switch (shapeType) {
|
||||
case ShapeType.CIRCLE:
|
||||
shape = new Konva.Ellipse({
|
||||
x: size.width,
|
||||
y: size.height,
|
||||
radiusX: size.width,
|
||||
radiusY: size.height,
|
||||
...transform,
|
||||
radiusX: size.width / 2,
|
||||
radiusY: size.height / 2,
|
||||
fill: color,
|
||||
strokeScaleEnabled: false,
|
||||
draggable: true,
|
||||
@@ -80,8 +77,6 @@ export class ShapeNode extends NetworkedNode {
|
||||
|
||||
case ShapeType.TRIANGLE:
|
||||
shape = new Konva.Shape({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
...transform,
|
||||
@@ -113,12 +108,20 @@ export class ShapeNode extends NetworkedNode {
|
||||
this.size = size;
|
||||
this.color = color;
|
||||
|
||||
// Add shape to group
|
||||
// Center the shape
|
||||
const centerPosition = NodeUtilities.positionNodeOnCanvasCenter({
|
||||
canvasOffset: canvasPosition,
|
||||
componentSize: canvasSize,
|
||||
componentSize: this.size,
|
||||
maxSize: canvasSize,
|
||||
});
|
||||
|
||||
// If the shape is an ellipse, the center is the origin so we need to offset it
|
||||
if (shapeType === ShapeType.CIRCLE) {
|
||||
centerPosition.x += size.width / 2;
|
||||
centerPosition.y += size.height / 2;
|
||||
}
|
||||
|
||||
// Add shape to group
|
||||
// bring to the front
|
||||
this.kNode.moveToTop();
|
||||
this.kNode.setPosition(centerPosition);
|
||||
|
||||
@@ -516,36 +516,74 @@ export class RealTimeDrawEngine {
|
||||
width: number | undefined,
|
||||
height: number | undefined,
|
||||
) {
|
||||
console.log("updateCaptureCanvas");
|
||||
if (!this.captureCanvas) {
|
||||
return;
|
||||
}
|
||||
if (width) {
|
||||
this.width = width;
|
||||
}
|
||||
if (height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
// Ensures that all the nodes stag in the same place should
|
||||
// there be a window resize.
|
||||
// recompute the position
|
||||
// to ensure that the position of this stays
|
||||
|
||||
const scaleFactor = 0.9;
|
||||
// Store old values for calculating delta
|
||||
const oldPositionX = this.positionX;
|
||||
const oldPositionY = this.positionY;
|
||||
|
||||
// recompute the position
|
||||
const oldWidth = this.width;
|
||||
const oldHeight = this.height;
|
||||
|
||||
// Original aspect ratio
|
||||
const originalAspectRatio = oldWidth / oldHeight;
|
||||
|
||||
// Calculate new dimensions based on window size
|
||||
let newWidth: number;
|
||||
let newHeight: number;
|
||||
|
||||
if (width === undefined) {
|
||||
newWidth = window.innerWidth * scaleFactor;
|
||||
} else {
|
||||
newWidth = width * scaleFactor;
|
||||
}
|
||||
|
||||
if (height === undefined) {
|
||||
newHeight = window.innerHeight * scaleFactor;
|
||||
} else {
|
||||
newHeight = height * scaleFactor;
|
||||
}
|
||||
|
||||
// Preserve aspect ratio
|
||||
const newAspectRatio = newWidth / newHeight;
|
||||
|
||||
if (newAspectRatio > originalAspectRatio) {
|
||||
// Width is constraining factor
|
||||
newWidth = newHeight * originalAspectRatio;
|
||||
} else {
|
||||
// Height is constraining factor
|
||||
newHeight = newWidth / originalAspectRatio;
|
||||
}
|
||||
|
||||
this.width = newWidth;
|
||||
this.height = newHeight;
|
||||
|
||||
console.log("New dimensions:", this.width, this.height);
|
||||
|
||||
// Calculate new position to center the canvas in the window
|
||||
this.positionX = (window.innerWidth - this.width) / 2;
|
||||
this.positionY = (window.innerHeight - this.height) / 2;
|
||||
|
||||
console.log("New centered position:", this.positionX, this.positionY);
|
||||
|
||||
// Update the canvas position and size
|
||||
this.captureCanvas.setPosition({
|
||||
x: this.positionX / 2,
|
||||
y: this.positionY / 2,
|
||||
x: this.positionX,
|
||||
y: this.positionY,
|
||||
});
|
||||
this.captureCanvas.size({ width: this.width, height: this.height });
|
||||
|
||||
// this is the change in positions
|
||||
const deltaX = this.positionX - oldPositionX;
|
||||
const deltaY = this.positionY - oldPositionY;
|
||||
// Calculate scale factors to maintain proportions
|
||||
const scaleX = this.width / oldWidth;
|
||||
const scaleY = this.height / oldHeight;
|
||||
|
||||
// Use a uniform scale factor to preserve aspect ratio
|
||||
const uniformScale = Math.min(scaleX, scaleY);
|
||||
|
||||
// Update all children except the capture canvas
|
||||
var children = this.mediaLayerRef.getChildren();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let node = children[i];
|
||||
@@ -554,11 +592,27 @@ export class RealTimeDrawEngine {
|
||||
if (node.name() === "CaptureCanvas") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pos = node.getPosition();
|
||||
const oldScale = node.scale();
|
||||
|
||||
// Apply both position shift and scaling to maintain relative positioning
|
||||
node.setPosition({
|
||||
x: pos.x + deltaX,
|
||||
y: pos.y + deltaY,
|
||||
x: (pos.x - oldPositionX) * uniformScale + this.positionX,
|
||||
y: (pos.y - oldPositionY) * uniformScale + this.positionY,
|
||||
});
|
||||
|
||||
// Update scale proportionally if the node has a scale property
|
||||
if (
|
||||
oldScale &&
|
||||
typeof oldScale.x === "number" &&
|
||||
typeof oldScale.y === "number"
|
||||
) {
|
||||
node.scale({
|
||||
x: oldScale.x * uniformScale,
|
||||
y: oldScale.y * uniformScale,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.mediaLayerRef.batchDraw();
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { useCallback, useRef } from "react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
// Components
|
||||
import { KonvaCanvasContainer } from "./KonvaCanvasContainer";
|
||||
import { ContextualButtonRetry } from "./ContextualButtonRetry";
|
||||
import { ContextualToolbarNode } from "./ContextualToolbarNode";
|
||||
import { ContextualLoadingBar } from "./ContextualLoadingBar";
|
||||
import { SignaledCanvasDragDropFiles } from "./SignaledCanvasDragDropFiles";
|
||||
import { SignaledDialogs } from "./SignaledDialogs";
|
||||
import { SignaledToolbarMain } from "./SignaledToolbarMain";
|
||||
import { SignaledToolbarVideoExtraction } from "./SignaledToolbarVideoExtraction";
|
||||
|
||||
import { PromptBox } from "~/components/PromptBox";
|
||||
// The KonvaApp is the root of the Konva stage
|
||||
// and only entry point for anything in Konva JS
|
||||
@@ -22,9 +20,8 @@ import { useLayoutContext } from "./contextSignals/layout";
|
||||
// common hooks
|
||||
import { useRenderCounter } from "~/hooks/useRenderCounter";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SignaledMagicBox } from "./SignaledMagicBox";
|
||||
import { SignaledPromptSlider } from "./SignaledPromptSlider";
|
||||
import { SignaledPromptText } from "./SignaledPromptText";
|
||||
|
||||
import { UndoRedo } from "~/components/reusable/UndoRedo/UndoRedo";
|
||||
|
||||
export const KonvaRootComponent = ({
|
||||
className,
|
||||
@@ -39,44 +36,66 @@ export const KonvaRootComponent = ({
|
||||
const navigate = useNavigate();
|
||||
const appUiContext = useAppUiContext();
|
||||
const layoutContext = useLayoutContext();
|
||||
|
||||
// Add state to track engine initialization
|
||||
const [isEngineReady, setIsEngineReady] = useState(false);
|
||||
const engineRef = useRef<EngineType | null>(null);
|
||||
|
||||
|
||||
const konvaContainerCallbackRef = useCallback((node: HTMLDivElement) => {
|
||||
if (node !== null && engineRef.current === null) {
|
||||
const options = {
|
||||
navigate: navigate,
|
||||
sceneToken: sceneToken,
|
||||
};
|
||||
engineRef.current = KonvaApp(node, options);
|
||||
// Only initialize if we have a node and haven't initialized yet
|
||||
if (node !== null && !isEngineReady && engineRef.current === null) {
|
||||
try {
|
||||
const options = {
|
||||
navigate: navigate,
|
||||
sceneToken: sceneToken,
|
||||
};
|
||||
engineRef.current = KonvaApp(node, options);
|
||||
// Only set ready if initialization succeeded
|
||||
if (engineRef.current) {
|
||||
setIsEngineReady(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize KonvaApp:", error);
|
||||
engineRef.current = null;
|
||||
setIsEngineReady(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
}, [navigate, sceneToken, isEngineReady]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Always render the canvas container */}
|
||||
<KonvaCanvasContainer
|
||||
ref={konvaContainerCallbackRef}
|
||||
className={className}
|
||||
// retreive the classNames from the parent for sizing/styling
|
||||
/>
|
||||
{/* <SignaledMagicBox /> */}
|
||||
<SignaledCanvasDragDropFiles
|
||||
openAddImage={appUiContext.openAddImage}
|
||||
openAddVideo={appUiContext.openAddVideo}
|
||||
/>
|
||||
<PromptBox />
|
||||
<SignaledToolbarMain
|
||||
layoutSignal={layoutContext.signal}
|
||||
appUiContext={appUiContext}
|
||||
/>
|
||||
{/* <SignaledToolbarVideoExtraction /> */}
|
||||
<SignaledDialogs
|
||||
appUiSignal={appUiContext.signal}
|
||||
resetAll={appUiContext.resetAll}
|
||||
/>
|
||||
|
||||
{/* Conditionally render the loading overlay */}
|
||||
{!isEngineReady && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="text-gray-500">Initializing canvas...</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* <ContextualLoadingBar /> */}
|
||||
<ContextualToolbarNode />
|
||||
{/* <ContextualButtonRetry /> */}
|
||||
{/* Conditionally render the UI components that need the engine */}
|
||||
{isEngineReady && engineRef.current && (
|
||||
<UndoRedo engine={engineRef.current}>
|
||||
<SignaledCanvasDragDropFiles
|
||||
openAddImage={appUiContext.openAddImage}
|
||||
openAddVideo={appUiContext.openAddVideo}
|
||||
/>
|
||||
<PromptBox />
|
||||
<SignaledToolbarMain
|
||||
layoutSignal={layoutContext.signal}
|
||||
appUiContext={appUiContext}
|
||||
/>
|
||||
<SignaledDialogs
|
||||
appUiSignal={appUiContext.signal}
|
||||
resetAll={appUiContext.resetAll}
|
||||
/>
|
||||
<ContextualToolbarNode />
|
||||
</UndoRedo>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,38 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #363636 #171717;
|
||||
}
|
||||
|
||||
/* Works on Chrome, Edge, and Safari */
|
||||
*::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: #171717;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: #363636;
|
||||
border-radius: 4px;
|
||||
border: 2px solid #171717;
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
@apply select-none bg-ui-background font-sans text-white;
|
||||
@@ -27,28 +59,7 @@
|
||||
background-size: 36px 36px;
|
||||
@apply bg-center;
|
||||
}
|
||||
h1 {
|
||||
@apply text-3xl font-medium md:text-5xl;
|
||||
}
|
||||
h2 {
|
||||
@apply text-2xl font-medium md:text-4xl;
|
||||
}
|
||||
h3 {
|
||||
@apply text-xl font-medium md:text-3xl;
|
||||
}
|
||||
h4 {
|
||||
@apply text-lg font-medium;
|
||||
}
|
||||
h5,
|
||||
label {
|
||||
@apply text-base;
|
||||
}
|
||||
h6 {
|
||||
@apply text-sm;
|
||||
}
|
||||
p {
|
||||
@apply text-sm font-normal;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply font-medium text-primary-400 hover:text-primary-300;
|
||||
}
|
||||
@@ -125,6 +136,10 @@
|
||||
@apply relative border border-ui-border shadow-lg backdrop-blur-xl transition-colors duration-300;
|
||||
@apply bg-ui-controls/50 hover:bg-ui-controls/90 !important;
|
||||
}
|
||||
|
||||
.glass-no-hover {
|
||||
@apply hover:bg-ui-controls/50 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { useRef } from "react";
|
||||
import { useNavigate, useSearchParams, Link } from "react-router-dom";
|
||||
import { useSignalEffect } from "@preact/signals-react/runtime";
|
||||
|
||||
import { faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button, Input, LoadingSpinner } from "~/components/ui";
|
||||
import { authentication } from "~/signals";
|
||||
|
||||
import { paperWrapperStyles } from "~/components/styles";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const Login = () => {
|
||||
@@ -62,24 +59,21 @@ export const Login = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fixed flex h-full w-full flex-col items-center justify-center bg-[url('/svg/bg-dots.svg')] bg-[length:200px_200px] bg-center bg-repeat">
|
||||
<div className="my-7 flex w-10/12 max-w-2xl items-center justify-center gap-4">
|
||||
<div className="pegboard fixed flex h-full w-full flex-col items-center justify-center bg-center bg-repeat">
|
||||
<div className="mb-7 flex w-10/12 max-w-2xl items-center justify-center gap-4">
|
||||
<img
|
||||
src="/brand/Storyteller-Logo-Black.png"
|
||||
alt="Storyteller Logo"
|
||||
className="h-11 select-none"
|
||||
src="/brand/artcraft-logo.png"
|
||||
alt="ArtCraft Logo"
|
||||
className="h-9 select-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full max-w-xl rounded-xl shadow-lg">
|
||||
<div
|
||||
className={twMerge(
|
||||
paperWrapperStyles,
|
||||
"relative mx-auto w-full rounded-none rounded-t-xl p-8 shadow-none",
|
||||
"glass glass-no-hover relative mx-auto w-full rounded-none rounded-t-xl p-6 shadow-none",
|
||||
)}
|
||||
>
|
||||
<h1 className="mb-9 text-center text-2xl font-bold">
|
||||
Log in to Board
|
||||
</h1>
|
||||
<h1 className="mb-6 text-center text-2xl font-bold">Log in</h1>
|
||||
<form
|
||||
ref={formRef}
|
||||
onSubmit={handleOnSumbit}
|
||||
@@ -106,7 +100,7 @@ export const Login = () => {
|
||||
required
|
||||
/>
|
||||
|
||||
<Button className="mt-6 py-3">Continue</Button>
|
||||
<Button className="mt-4 py-3">Continue</Button>
|
||||
</form>
|
||||
{shouldShowLoader && (
|
||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center">
|
||||
@@ -114,10 +108,10 @@ export const Login = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="align-items flex w-full max-w-xl rounded-b-xl border border-t-0 bg-gray-100 px-8 py-4">
|
||||
<div className="align-items flex w-full max-w-xl rounded-b-xl bg-ui-controls px-6 py-4">
|
||||
<a
|
||||
href="https://storyteller.ai/password-reset"
|
||||
className="text-brand-primary hover:text-brand-primary-400 grow text-sm transition-all duration-150"
|
||||
className="grow text-sm !text-primary-400 transition-all duration-150 hover:!text-primary-300"
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { withProtectionRoute } from "~/components/hoc";
|
||||
import { useRenderCounter } from "~/hooks/useRenderCounter";
|
||||
import { Resource, invoke } from '@tauri-apps/api/core';
|
||||
|
||||
// Components of the page
|
||||
import { ToolbarUserProfile } from "~/components/features";
|
||||
@@ -14,9 +15,22 @@ import { appMode } from "~/signals";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { GenerationRootComponent } from "~/GenerationRootComponent/GenerationRootComponent";
|
||||
import { EditModeRootComponent } from "~/EditModeRootComponent/EditModeRootComponent";
|
||||
import { useRef } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { EngineType } from "~/KonvaApp";
|
||||
import { GenerationEngine } from "~/KonvaApp/GenerationEngine";
|
||||
import { GalleryRootComponent } from "~/GalleryRootComponent";
|
||||
import { BaseDialog } from "~/components/ui/BaseDialog";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import {
|
||||
faArrowRight,
|
||||
faMagicWandSparkles,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export const isTauri = (): boolean => {
|
||||
console.log(window.__TAURI_INTERNALS__);
|
||||
return window.__TAURI_INTERNALS__;
|
||||
};
|
||||
|
||||
export const Main = withProtectionRoute(() => {
|
||||
// This is a hook that will log the number of times the component has rerendered
|
||||
@@ -31,7 +45,8 @@ export const Main = withProtectionRoute(() => {
|
||||
|
||||
const realtimeEngineRef = useRef<EngineType | null>(null);
|
||||
const generationEngineRef = useRef<GenerationEngine | null>(null);
|
||||
|
||||
const [firstTimeDialogOpen, setFirstTimeDialogOpen] = useState(true);
|
||||
const [signinDialogOpen, setSigninDialogOpen] = useState(false);
|
||||
const appModeValue = appMode.value;
|
||||
let childView;
|
||||
|
||||
@@ -59,21 +74,87 @@ export const Main = withProtectionRoute(() => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed grid h-full w-full grid-cols-12 grid-rows-12">
|
||||
{childView}
|
||||
<div className="absolute top-0 w-full p-3.5">
|
||||
<div className="relative flex w-full">
|
||||
<div className="absolute left-0">
|
||||
<ToolbarTopLeft />
|
||||
</div>
|
||||
<div className="flex w-full justify-center">
|
||||
<ToolbarTopCenter />
|
||||
</div>
|
||||
<div className="absolute right-0">
|
||||
<ToolbarTopRight />
|
||||
<>
|
||||
<div className="fixed h-full w-full">
|
||||
<div className="fixed grid h-full w-full grid-cols-12 grid-rows-12">
|
||||
{childView}
|
||||
</div>
|
||||
<div className="absolute top-0 w-full p-3.5">
|
||||
<div className="relative flex w-full">
|
||||
<div className="absolute left-0">
|
||||
<ToolbarTopLeft />
|
||||
</div>
|
||||
<div className="flex w-full justify-center">
|
||||
<ToolbarTopCenter />
|
||||
</div>
|
||||
<div className="absolute right-0">
|
||||
<ToolbarTopRight />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{appModeValue === "gallery" && (
|
||||
<GalleryRootComponent className="z-1 fixed h-full w-full bg-black" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<BaseDialog
|
||||
isOpen={firstTimeDialogOpen}
|
||||
onClose={() => {
|
||||
setFirstTimeDialogOpen(false);
|
||||
}}
|
||||
className="max-w-5xl"
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<h1 className="text-3xl font-bold">
|
||||
<FontAwesomeIcon
|
||||
icon={faMagicWandSparkles}
|
||||
className="mr-3 text-[24px]"
|
||||
/>
|
||||
Welcome to ArtCraft
|
||||
</h1>
|
||||
<div className="text-center">
|
||||
<p className="text-lg font-medium text-white/80">
|
||||
Your creative canvas for digital art and design
|
||||
</p>
|
||||
<p className="text-white/60">
|
||||
Draw freely, create collages, and design with our intuitive
|
||||
tool. Bring your ideas to life in seconds!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="aspect-video w-full overflow-hidden rounded-md">
|
||||
<video autoPlay muted loop controls={false}>
|
||||
<source src="/videos/demo_video.mp4" type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
<Button
|
||||
className="font-semibold"
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
onClick={async () => {
|
||||
if (isTauri()) {
|
||||
await invoke('open_login_command');
|
||||
setFirstTimeDialogOpen(false);
|
||||
} else {
|
||||
console.error('Tauri is not available in this environment');
|
||||
}
|
||||
}}
|
||||
>
|
||||
Signin to OpenAI to get Started
|
||||
</Button>
|
||||
{/* <Button
|
||||
className="font-semibold"
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
onClick={() => {
|
||||
setFirstTimeDialogOpen(false);
|
||||
}}
|
||||
>
|
||||
Start creating now
|
||||
</Button> */}
|
||||
</div>
|
||||
</BaseDialog>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { useRef } from "react";
|
||||
import { useNavigate, useSearchParams, Link } from "react-router-dom";
|
||||
import { useSignalEffect } from "@preact/signals-react/runtime";
|
||||
|
||||
import { faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button, Input, LoadingSpinner } from "~/components/ui";
|
||||
import { authentication } from "~/signals";
|
||||
|
||||
import { paperWrapperStyles } from "~/components/styles";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const Signup = () => {
|
||||
@@ -77,22 +74,21 @@ export const Signup = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fixed flex h-full w-full flex-col items-center justify-center bg-[url('/svg/bg-dots.svg')] bg-[length:200px_200px] bg-center bg-repeat">
|
||||
<div className="my-7 flex w-10/12 max-w-2xl items-center justify-center gap-4">
|
||||
<div className="pegboard fixed flex h-full w-full flex-col items-center justify-center bg-center bg-repeat">
|
||||
<div className="mb-7 flex w-10/12 max-w-2xl items-center justify-center gap-4">
|
||||
<img
|
||||
src="/brand/Storyteller-Logo-Black.png"
|
||||
alt="Storyteller Logo"
|
||||
className="h-11 select-none"
|
||||
src="/brand/artcraft-logo.png"
|
||||
alt="ArtCraft Logo"
|
||||
className="h-9 select-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full max-w-xl rounded-xl shadow-lg">
|
||||
<div
|
||||
className={twMerge(
|
||||
paperWrapperStyles,
|
||||
"relative mx-auto w-full rounded-none rounded-t-xl p-8 shadow-none",
|
||||
"glass glass-no-hover relative mx-auto w-full rounded-none rounded-t-xl p-6 shadow-none",
|
||||
)}
|
||||
>
|
||||
<h1 className="mb-9 text-center text-2xl font-bold">
|
||||
<h1 className="mb-6 text-center text-2xl font-bold">
|
||||
Create your Account
|
||||
</h1>
|
||||
<form
|
||||
@@ -139,7 +135,7 @@ export const Signup = () => {
|
||||
required
|
||||
/>
|
||||
|
||||
<Button className="mt-6 py-3">Create Account</Button>
|
||||
<Button className="mt-4 py-3">Create Account</Button>
|
||||
</form>
|
||||
{shouldShowLoader && (
|
||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center">
|
||||
@@ -147,7 +143,7 @@ export const Signup = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="align-items flex w-full max-w-xl rounded-b-xl border border-t-0 bg-gray-100 px-8 py-4">
|
||||
<div className="align-items flex w-full max-w-xl rounded-b-xl bg-ui-controls px-6 py-4">
|
||||
<div className="flex w-full justify-end gap-1 text-sm">
|
||||
<p className="opacity-75">Already have an account?</p>
|
||||
<Link to="/login">Log In</Link>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { createContext, useContext, useState } from "react";
|
||||
|
||||
interface JobContextType {
|
||||
jobToken: string | null;
|
||||
setJobToken: (token: string | null) => void;
|
||||
jobTokens: string[];
|
||||
addJobToken: (token: string) => void;
|
||||
removeJobToken: (token: string) => void;
|
||||
clearJobTokens: () => void;
|
||||
}
|
||||
|
||||
const JobContext = createContext<JobContextType | undefined>(undefined);
|
||||
@@ -10,10 +12,24 @@ const JobContext = createContext<JobContextType | undefined>(undefined);
|
||||
export const JobProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [jobToken, setJobToken] = useState<string | null>(null);
|
||||
const [jobTokens, setJobTokens] = useState<string[]>([]);
|
||||
|
||||
const addJobToken = (token: string) => {
|
||||
setJobTokens((prevTokens) => [...prevTokens, token]);
|
||||
};
|
||||
|
||||
const removeJobToken = (token: string) => {
|
||||
setJobTokens((prevTokens) => prevTokens.filter((t) => t !== token));
|
||||
};
|
||||
|
||||
const clearJobTokens = () => {
|
||||
setJobTokens([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<JobContext.Provider value={{ jobToken, setJobToken }}>
|
||||
<JobContext.Provider
|
||||
value={{ jobTokens, addJobToken, removeJobToken, clearJobTokens }}
|
||||
>
|
||||
{children}
|
||||
</JobContext.Provider>
|
||||
);
|
||||
|
||||
@@ -47,7 +47,8 @@ export const PromptBox = () => {
|
||||
const isDesktopApp = window.navigator.userAgent.includes("Tauri");
|
||||
console.log("Is this a desktop app?", isDesktopApp);
|
||||
|
||||
const { jobToken, setJobToken } = useJobContext();
|
||||
const { jobTokens, addJobToken, removeJobToken, clearJobTokens } =
|
||||
useJobContext();
|
||||
|
||||
//const { lastRenderedBitmap } = useCanvasSignal();
|
||||
const [prompt, setPrompt] = useState("");
|
||||
@@ -119,7 +120,7 @@ export const PromptBox = () => {
|
||||
setUploadingImages((prev) =>
|
||||
prev.filter((img) => img.id !== uploadId),
|
||||
);
|
||||
console.log("Reference image added:", referenceImage);
|
||||
toast.success("Reference image added.");
|
||||
} else if (
|
||||
newState.status === UploaderStates.assetError ||
|
||||
newState.status === UploaderStates.imageCreateError
|
||||
@@ -127,7 +128,8 @@ export const PromptBox = () => {
|
||||
setUploadingImages((prev) =>
|
||||
prev.filter((img) => img.id !== uploadId),
|
||||
);
|
||||
console.error("Upload failed");
|
||||
|
||||
toast.error("Upload failed. Please try again.");
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -175,7 +177,100 @@ export const PromptBox = () => {
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setPrompt(e.target.value);
|
||||
};
|
||||
const handleTauriEnqueue = async () => {
|
||||
const api = new Api();
|
||||
let image = getCanvasRenderBitmap();
|
||||
if (image === undefined) {
|
||||
return;
|
||||
}
|
||||
const base64Bitmap = await EncodeImageBitmapToBase64(image);
|
||||
|
||||
const byteString = atob(base64Bitmap);
|
||||
const mimeString = "image/png";
|
||||
|
||||
const ab = new ArrayBuffer(byteString.length);
|
||||
const ia = new Uint8Array(ab);
|
||||
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
ia[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
|
||||
const uuid = crypto.randomUUID(); // Generate a new UUID
|
||||
const file = new File([ab], `${uuid}.png`, { type: mimeString });
|
||||
|
||||
const snapshotMediaToken = await api.uploadSceneSnapshot({
|
||||
screenshot: file,
|
||||
});
|
||||
|
||||
if (snapshotMediaToken.data === undefined) {
|
||||
toast.error("Error: Unable to upload scene snapshot Please try again.");
|
||||
return;
|
||||
}
|
||||
console.log("useSystemPrompt", useSystemPrompt);
|
||||
console.log("Snapshot media token:", snapshotMediaToken.data);
|
||||
|
||||
const generateResponse = await invoke("sora_image_generation_command", {
|
||||
snapshot_media_token: snapshotMediaToken.data,
|
||||
disable_system_prompt: !useSystemPrompt,
|
||||
prompt: prompt,
|
||||
maybe_additional_images: referenceImages.map((image) => image.mediaToken),
|
||||
maybe_number_of_samples: 1,
|
||||
});
|
||||
toast.success("Please wait while we process your image.");
|
||||
};
|
||||
|
||||
const handleWebEnqueue = async () => {
|
||||
const api = new Api();
|
||||
let image = getCanvasRenderBitmap();
|
||||
if (image === undefined) {
|
||||
toast.error(
|
||||
"Error: Unable to generate image. Please check the input and try again.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const base64Bitmap = await EncodeImageBitmapToBase64(image);
|
||||
|
||||
const byteString = atob(base64Bitmap);
|
||||
const mimeString = "image/png";
|
||||
|
||||
const ab = new ArrayBuffer(byteString.length);
|
||||
const ia = new Uint8Array(ab);
|
||||
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
ia[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
|
||||
const uuid = crypto.randomUUID(); // Generate a new UUID
|
||||
const file = new File([ab], `${uuid}.png`, { type: mimeString });
|
||||
|
||||
const snapshotMediaToken = await api.uploadSceneSnapshot({
|
||||
screenshot: file,
|
||||
});
|
||||
|
||||
if (snapshotMediaToken.data === undefined) {
|
||||
toast.error("Error: Unable to upload scene snapshot Please try again.");
|
||||
return;
|
||||
}
|
||||
console.log("useSystemPrompt", useSystemPrompt);
|
||||
console.log("Snapshot media token:", snapshotMediaToken.data);
|
||||
|
||||
const response = await api.enqueueImageGeneration({
|
||||
disableSystemPrompt: !useSystemPrompt,
|
||||
prompt: prompt,
|
||||
snapshotMediaToken: snapshotMediaToken.data,
|
||||
additionalImages: referenceImages.map((image) => image.mediaToken),
|
||||
});
|
||||
|
||||
if (response.success === true) {
|
||||
toast.success("Please wait while we process your image.");
|
||||
if (response.data) {
|
||||
addJobToken(response.data);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
toast.error("Failed to enqueue image generation. Please try again.");
|
||||
}
|
||||
};
|
||||
const handleEnqueue = async () => {
|
||||
if (!prompt.trim()) return;
|
||||
|
||||
@@ -189,71 +284,10 @@ export const PromptBox = () => {
|
||||
referenceImages,
|
||||
);
|
||||
|
||||
if (isDesktopApp) {
|
||||
let image = getCanvasRenderBitmap();
|
||||
if (image === undefined) {
|
||||
return;
|
||||
}
|
||||
const base64Bitmap = await EncodeImageBitmapToBase64(image);
|
||||
const generateResponse = await invoke("image_generation_command", {
|
||||
image: base64Bitmap,
|
||||
prompt: prompt,
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
if (!isDesktopApp) {
|
||||
const api = new Api();
|
||||
let image = getCanvasRenderBitmap();
|
||||
if (image === undefined) {
|
||||
console.error(
|
||||
"Error: Unable to generate image. Please check the input and try again.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const base64Bitmap = await EncodeImageBitmapToBase64(image);
|
||||
const byteString = atob(base64Bitmap);
|
||||
const mimeString = "image/png";
|
||||
const ab = new ArrayBuffer(byteString.length);
|
||||
const ia = new Uint8Array(ab);
|
||||
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
ia[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
|
||||
const uuid = crypto.randomUUID(); // Generate a new UUID
|
||||
const file = new File([ab], `${uuid}.png`, { type: mimeString });
|
||||
|
||||
const snapshotMediaToken = await api.uploadSceneSnapshot({
|
||||
screenshot: file,
|
||||
});
|
||||
|
||||
if (snapshotMediaToken.data === undefined) {
|
||||
console.error("Error: Unable to upload scene snapshot.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Snapshot media token:", snapshotMediaToken.data);
|
||||
|
||||
const response = await api.enqueueImageGeneration({
|
||||
prompt: prompt,
|
||||
snapshotMediaToken: snapshotMediaToken.data,
|
||||
additionalImages: referenceImages.map((image) => image.mediaToken),
|
||||
});
|
||||
|
||||
if (response.success === true) {
|
||||
console.error("Successfully enqueued image generation.");
|
||||
|
||||
if (response.data) {
|
||||
console.log("Response:", response.data);
|
||||
setJobToken(response.data);
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
console.log("Image generation Not Successful.");
|
||||
toast.error("Failed to generate image. Please try again.");
|
||||
}
|
||||
if (isDesktopApp == true) {
|
||||
handleTauriEnqueue();
|
||||
} else {
|
||||
handleWebEnqueue();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error during image generation:", error);
|
||||
|
||||
@@ -63,12 +63,11 @@ export const ToolbarMain = ({
|
||||
buttonProps={buttonProps.SELECT}
|
||||
tooltip="Select"
|
||||
/>
|
||||
{/* <ToolbarButton
|
||||
<ToolbarButton
|
||||
icon={faText}
|
||||
buttonProps={buttonProps.ADD_TEXT}
|
||||
tooltip="Add Text"
|
||||
/> */}
|
||||
|
||||
/>
|
||||
<Popover className="relative">
|
||||
{({ close }) => (
|
||||
<>
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
GenerationLoadingState,
|
||||
generationSignal,
|
||||
} from "~/signals";
|
||||
import { faChevronRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
|
||||
// import { paperWrapperStyles } from "~/components/styles";
|
||||
// import { faPlus, faQuestion } from "@fortawesome/pro-solid-svg-icons";
|
||||
@@ -32,6 +34,10 @@ export const ToolbarTopLeft = () => {
|
||||
|
||||
const generationState = generationSignal.value;
|
||||
|
||||
const handleClick = () => {
|
||||
window.location.href = "https://storyteller-3d.netlify.app/";
|
||||
};
|
||||
|
||||
console.log(appMode.value);
|
||||
|
||||
return (
|
||||
@@ -41,19 +47,30 @@ export const ToolbarTopLeft = () => {
|
||||
)}
|
||||
>
|
||||
<img
|
||||
src="/brand/mira-logo.png"
|
||||
src="/brand/artcraft-logo.png"
|
||||
alt="logo"
|
||||
className="h-[30px] select-none pb-1"
|
||||
className="h-[28px] select-none"
|
||||
/>
|
||||
|
||||
<TabSelector
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={faChevronRight}
|
||||
iconClassName="text-xs"
|
||||
iconFlip={true}
|
||||
onClick={handleClick}
|
||||
className="bg-transparent p-0 text-sm text-white/80 hover:bg-transparent hover:text-white hover:underline hover:underline-offset-2"
|
||||
>
|
||||
Go to 3D Stage Editor
|
||||
</Button>
|
||||
|
||||
{/* <TabSelector
|
||||
tabs={tabs}
|
||||
activeTab={activeTab.value}
|
||||
onTabChange={handleTabChange}
|
||||
disabled={
|
||||
generationState.loadingState === GenerationLoadingState.GENERATING
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
{/* <ToolbarButton icon={faPlus}>
|
||||
<span className="font-semibold">New Board</span>
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
import { faGear } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Activity } from "~/components/ui/Activity/Activity";
|
||||
// import { paperWrapperStyles } from "~/components/styles";
|
||||
// import { faPlus, faQuestion } from "@fortawesome/pro-solid-svg-icons";
|
||||
// import { ToolbarButton } from "~/components/features/ToolbarButton";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { SettingsModal } from "@storyteller/ui-settings-modal";
|
||||
import { useState } from "react";
|
||||
|
||||
export const ToolbarTopRight = () => {
|
||||
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
|
||||
return (
|
||||
<div
|
||||
className={twMerge("relative z-50 flex h-fit w-fit items-center gap-2")}
|
||||
>
|
||||
<Activity />
|
||||
</div>
|
||||
<>
|
||||
<div
|
||||
className={twMerge("relative z-50 flex h-fit w-fit items-center gap-2")}
|
||||
>
|
||||
<Button
|
||||
icon={faGear}
|
||||
className="h-[42px] w-[42px] bg-[#5F5F68]/60 backdrop-blur-lg hover:bg-[#5F5F68]/90"
|
||||
onClick={() => setIsSettingsModalOpen(true)}
|
||||
/>
|
||||
<Activity />
|
||||
</div>
|
||||
|
||||
<SettingsModal
|
||||
isOpen={isSettingsModalOpen}
|
||||
onClose={() => setIsSettingsModalOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||