Compare commits

...
Author SHA1 Message Date
Michael Chung b031a68000 Redo and Undo for osx fixed correctly.
Issues with the app not properly loading not allowing the engine reference to be set.
2025-04-15 16:35:47 -04:00
Brandon Thomas e4b648b55d bump Rust toolchains to 1.86.0 2025-04-15 15:05:06 -04:00
Brandon Thomas d78ccbca25 system prompt enhancement 2025-04-15 14:57:16 -04:00
Kasisnu bae6bc6863 move sentinel token generation into rust 2025-04-15 21:44:58 +05:30
Brandon Thomas faa43a7361 remove node-sass 2025-04-15 05:14:34 -04:00
Brandon Thomas f8b3b708c0 unblock CI builds for now 2025-04-15 05:05:21 -04:00
ArEnScandMichael Chung 4c40379470 Jobs should add the image to the list (#512)
Jobs also show completed images in the gallery.

Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-04-15 02:49:45 -04:00
Brandon Thomas 0c32a89828 script to clean up node modules 2025-04-15 00:30:57 -04:00
Brandon Thomas d7365ea045 remove more entries from package-lock.json 2025-04-14 19:26:57 -04:00
Brandon Thomas 3386247858 remove pnpm-lock.yaml to unblock CI - it's causing new failures 2025-04-14 19:10:44 -04:00
Brandon Thomas 7ea9c96d77 remove dead references from editor3d package 2025-04-14 19:08:49 -04:00
Brandon Thomas 059f4b4e38 add pnpm to solve build issues 2025-04-14 18:56:05 -04:00
Brandon Thomas 3c36e041ca remove "workspace:*" URL. Not working on all machines? 2025-04-14 18:40:23 -04:00
Tanish MankuandTanish Manku f027fcb29a Write Gallery component with test data (#508)
Co-authored-by: Tanish Manku <-h>
2025-04-14 10:49:35 -07:00
BFlat 6f2ed9e61e dismiss onboarding helper and local storage 2025-04-14 14:57:33 +07:00
Brandon Thomas 2b40b2f8d6 add index on jobs anonymous visitor tokens 2025-04-14 02:58:49 -04:00
Brandon Thomas 9996b41bc2 Add nx library component and import it 2025-04-14 02:24:29 -04:00
ArEnScandMichael Chung b9a5768679 Feature/polling (#505)
add text back and toasters

Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-04-13 23:54:20 -04:00
Brandon Thomas 614a5edc55 cleanup netlify.toml files 2025-04-13 21:17:09 -04:00
Brandon Thomas 01468020bd cleanup build script 2025-04-13 21:06:24 -04:00
Brandon Thomas 289ef6f352 debug branch build (#507) 2025-04-13 21:01:09 -04:00
62 changed files with 3652 additions and 4182 deletions
Generated
+31
View File
@@ -5035,6 +5035,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 +6136,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 +7864,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 +10626,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"
+3 -1
View File
@@ -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),
@@ -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)
}
@@ -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));
}
@@ -0,0 +1,675 @@
// 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();
}
pub fn get_random_cached_script() -> String {
return CACHED_SCRIPTS[rand::rng().random_range(0..CACHED_SCRIPTS.len())].to_string();
}
@@ -0,0 +1,3 @@
pub mod request;
pub mod token;
pub(crate) mod config;
@@ -0,0 +1,226 @@
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 today_utc = now_utc.date_naive();
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,68 @@
use errors::AnyhowResult;
use serde_derive::{Deserialize, Serialize};
use thiserror::Error;
use idempotency::uuid::generate_random_uuid;
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(problem: String) -> AnyhowResult<String> {
let request = SentinelRequest::new(problem);
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::*;
use crate::sentinel_refresh::generate::request::GenerateSentinelRefreshRequest;
#[tokio::test]
async fn test_generate_token() {
let (_request, base64_request) = GenerateSentinelRefreshRequest::new().with_fourth_and_tenth();
let token = generate_token(base64_request.to_string()).await.unwrap();
println!("{}", token);
}
}
@@ -1,2 +1,4 @@
pub use refresh::refresh_sentinel;
pub mod refresh;
pub mod refresh;
pub mod generate;
@@ -76,6 +76,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>>,
@@ -283,9 +286,15 @@ 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);
@@ -296,13 +305,19 @@ pub async fn enqueue_studio_image_generation_handler(http_request: HttpRequest,
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;
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);
@@ -329,7 +344,14 @@ pub async fn enqueue_studio_image_generation_handler(http_request: HttpRequest,
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 +370,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 +437,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 +452,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)
}
}
+1
View File
@@ -0,0 +1 @@
node_modules
+56
View File
@@ -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": {}
}
]
}
+2 -2
View File
@@ -1,7 +1,7 @@
{
"recommendations": [
"nrwl.angular-console",
"esbenp.prettier-vscode"
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint"
]
}
+1 -2
View File
@@ -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]]
@@ -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";
+36 -19
View File
@@ -148,7 +148,6 @@ export class ApiManager {
formData.append("file", blob);
}
// console.log(`HELLO${formData}`);
return this.fetchMultipartFormData<T>(endpoint, {
method: "POST",
headers: {
@@ -327,7 +326,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 +340,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 +350,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,
@@ -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;
@@ -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>
)}
</>
);
};
@@ -17,6 +17,7 @@ import { EditModeRootComponent } from "~/EditModeRootComponent/EditModeRootCompo
import { useRef } from "react";
import { EngineType } from "~/KonvaApp";
import { GenerationEngine } from "~/KonvaApp/GenerationEngine";
import { GalleryRootComponent } from "~/GalleryRootComponent";
export const Main = withProtectionRoute(() => {
// This is a hook that will log the number of times the component has rerendered
@@ -59,8 +60,10 @@ export const Main = withProtectionRoute(() => {
}
return (
<div className="fixed grid h-full w-full grid-cols-12 grid-rows-12">
{childView}
<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">
@@ -74,6 +77,8 @@ export const Main = withProtectionRoute(() => {
</div>
</div>
</div>
{appModeValue === "gallery" &&
(<GalleryRootComponent className="fixed h-full w-full z-1 bg-black" />)}
</div>
);
});
@@ -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.");
}
},
});
@@ -206,7 +208,7 @@ export const PromptBox = () => {
const api = new Api();
let image = getCanvasRenderBitmap();
if (image === undefined) {
console.error(
toast.error(
"Error: Unable to generate image. Please check the input and try again.",
);
return;
@@ -229,7 +231,9 @@ export const PromptBox = () => {
});
if (snapshotMediaToken.data === undefined) {
console.error("Error: Unable to upload scene snapshot.");
toast.error(
"Error: Unable to upload scene snapshot Please try again.",
);
return;
}
@@ -242,17 +246,13 @@ export const PromptBox = () => {
});
if (response.success === true) {
console.error("Successfully enqueued image generation.");
toast.success("Please wait while we process your image.");
if (response.data) {
console.log("Response:", response.data);
setJobToken(response.data);
addJobToken(response.data);
}
return;
} else {
console.log("Image generation Not Successful.");
toast.error("Failed to generate image. Please try again.");
toast.error("Failed to enqueue image generation. Please try again.");
}
}
} catch (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 }) => (
<>
@@ -1,4 +1,5 @@
import { twMerge } from "tailwind-merge";
import { Login } from "~/app/pages/login";
import { Activity } from "~/components/ui/Activity/Activity";
// import { paperWrapperStyles } from "~/components/styles";
// import { faPlus, faQuestion } from "@fortawesome/pro-solid-svg-icons";
@@ -0,0 +1,73 @@
import { useRef, useCallback, useContext, ReactNode, useEffect } from "react";
import { EngineType } from "~/KonvaApp";
interface UndoRedoProps {
children: ReactNode;
engine: EngineType | null;
}
/**
* A transparent component that provides undo/redo functionality
* with keyboard shortcuts for macOS (Command+Z, Command+Shift+Z)
*/
export const UndoRedo = ({
engine,
children,
}: UndoRedoProps) => {
console.log("UndoRedo");
// We don't need a separate ref since we want to track the prop changes
// Create the event handler with useCallback
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (!engine) {
return;
}
// Don't capture events when user is editing text
const target = e.target as HTMLElement;
const isEditingText = target.tagName === 'INPUT' ||
target.tagName === 'TEXTAREA' ||
target.isContentEditable;
// Command+Z for Undo
// Ctrl+Shift+Z for Redo (Windows/Linux alternative)
// Windows/Linux: Ctrl+Shift+Z for Redo
if (!navigator.userAgent.includes('Mac') && e.ctrlKey && e.shiftKey && e.key === 'z') {
console.log("Ctrl+Shift+Z pressed: Redo (Windows/Linux)");
e.preventDefault();
engine.undoStackManager.redo();
}
// Delete key handling - only if not editing text
if (!isEditingText && (e.key === 'Delete' || e.key === 'Backspace')) {
console.log("Delete/Backspace pressed: Delete selected nodes");
e.preventDefault();
engine.commandManager.deleteNodes();
}
// macOS: Command+Z for Undo
if (navigator.userAgent.includes('Mac') && e.metaKey && e.key === 'z' && !e.shiftKey) {
console.log("Command+Z pressed: Undo");
e.preventDefault();
engine.undoStackManager.undo();
}
// macOS: Command+Shift+Z for Redo
if (navigator.userAgent.includes('Mac') && e.metaKey && e.key === 'z' && e.shiftKey) {
console.log("Command+Shift+Z pressed: Redo");
e.preventDefault();
engine.undoStackManager.redo();
engine.realTimeDrawEngine.render();
}
}, [engine]);
// Actually set up the event listeners using useEffect
useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
// Cleanup function to remove the event listener
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [handleKeyDown]); // Depend on the memoized handler
return children;
};
@@ -6,6 +6,8 @@ import { CompletedCard } from "./CompletedCard";
import { InProgressCard } from "./InProgressCard";
import { useJobContext } from "~/components/JobContext";
import { Api } from "~/KonvaApp/Api";
import { toast } from "sonner";
// Dummy data types
interface CompletedItem {
token: string;
@@ -65,90 +67,72 @@ const dummyCompletedItems: CompletedItem[] = [
// ];
export function Activity() {
const { jobToken, setJobToken } = useJobContext();
const { jobTokens, addJobToken, removeJobToken, clearJobTokens } =
useJobContext();
const [completedItems, setCompletedItems] = useState<CompletedItem[]>([]);
const [jobs, setJobs] = useState<ActiveJob[]>([]);
const [loading, setLoading] = useState(true);
// Dummy polling function
useEffect(() => {
const pollData = async () => {
if (jobToken) {
console.log("Current Job Token:", jobToken);
if (jobTokens.length > 0) {
console.log("Current Job Tokens:", jobTokens);
const api = new Api();
const job = await api.pollJobSession(jobToken);
console.log("Job response:", job);
if (job.success && job.data) {
const { status, progress_percentage } = job.data.status;
const { job_token } = job.data;
// Create a map to store the latest job data
const updatedJobs = new Map<string, ActiveJob>();
console.log("Job status:", status);
console.log("Progress percentage:", progress_percentage);
console.log("Job token:", job_token);
for (const jobToken of jobTokens) {
const job = await api.pollJobSession(jobToken);
// Update jobs with real job data
if (
status.toLowerCase() === "completed" ||
status.toLowerCase() === "failed"
) {
// If job is completed or failed, remove it from active jobs
setJobs((prevJobs) =>
prevJobs.filter((j) => j.job_token !== job_token),
);
if (job.success && job.data) {
const { status, progress_percentage } = job.data.status;
const { job_token } = job.data;
// Add to completed items if it was successful
if (
status.toLowerCase() === "completed" &&
job.data.result.generated_images
status.toLowerCase() === "complete_success" ||
status.toLowerCase() === "failed"
) {
const newCompletedItem: CompletedItem = {
id: job_token,
title: job.data.request.maybe_model_title || "Image Generation",
timestamp: new Date().toISOString(),
type: "image",
imageUrl: job.data.result.generated_images[0] || "",
};
// If job is complete_success or failed, we'll remove it later
removeJobToken(job_token);
setCompletedItems((prev) => [newCompletedItem, ...prev]);
// Add to complete_success items if it was successful
if (status.toLowerCase() === "complete_success") {
const newCompletedItem: CompletedItem = {
token: job_token,
maybe_title:
job.data.request.maybe_model_title || "Image Generation",
public_bucket_path: job.data.result.image_url || "",
updated_at: new Date().toISOString(),
};
toast.success("Done Processing Image");
setCompletedItems((prev) => [newCompletedItem, ...prev]);
}
} else {
// Store the job in our map
const updatedJob: ActiveJob = {
job_token,
request: {
maybe_model_title:
job.data.request.maybe_model_title || "Image Generation",
},
status: {
status: status.toUpperCase(),
progress_percentage: progress_percentage || 0,
},
};
updatedJobs.set(job_token, updatedJob);
}
} else {
// Update or add the job to the active jobs list
const updatedJob: ActiveJob = {
job_token,
request: {
maybe_model_title:
job.data.request.maybe_model_title || "Image Generation",
},
status: {
status: status.toUpperCase(),
progress_percentage: progress_percentage || 0,
},
};
setJobs((prevJobs) => {
const existingJobIndex = prevJobs.findIndex(
(j) => j.job_token === job_token,
);
if (existingJobIndex >= 0) {
// Update existing job
const newJobs = [...prevJobs];
newJobs[existingJobIndex] = updatedJob;
return newJobs;
} else {
// Add new job
return [updatedJob, ...prevJobs];
}
});
console.error("Failed to fetch job status:", job.errorMessage);
}
} else {
console.error("Failed to fetch job status:", job.errorMessage);
}
} else {
console.log("No Job Token available.");
}
if (jobs.length === 0) {
// Update jobs state with the deduplicated jobs
setJobs(Array.from(updatedJobs.values()));
}
if (jobTokens.length === 0) {
setJobs([]);
setLoading(false);
}
};
@@ -163,7 +147,7 @@ export function Activity() {
}, 5000);
return () => clearInterval(interval);
}, [jobToken]);
}, [jobTokens, removeJobToken]);
return (
<PopoverMenu
@@ -17,8 +17,10 @@ interface Props {
export function CompletedCard({ job }: Props) {
const [loadError, setLoadError] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const imageUrl = job.public_bucket_path + "-thumb.gif";
const imageUrl = job.public_bucket_path;
const fullImageUrl = job.public_bucket_path;
console.log("I am hjere");
console.log(job);
return (
<>
@@ -40,7 +40,7 @@ export class JobsApi extends ApiManager {
public ListRecentJobs(): Promise<ApiResponse<Job[]>> {
const endpoint = `${this.ApiTargets.BaseApi}/v1/jobs/session`;
return this.get<{
success: boolean;
jobs: Job[];
@@ -15,7 +15,7 @@ export const CloseButton = ({
}: CloseButtonProps) => {
const sizeClasses = {
sm: "h-5 w-5 text-sm",
md: "h-7 w-7 text-lg",
md: "h-7 w-7 text-md",
lg: "h-9 w-9 text-xl",
};
+1
View File
@@ -33,4 +33,5 @@ export interface JobStatusResponse {
maybe_first_started_at: string;
requires_keepalive: boolean;
status: JobStatus;
progress_percentage: number;
}
@@ -5,10 +5,160 @@ import { PopoverMenu } from "~/components/reusable/Popover/Popover";
import { CompletedCard } from "./CompletedCard";
import { InProgressCard } from "./InProgressCard";
import { H3, P, Tooltip } from "~/components";
import { activeImageGenerationJobs, userMovies } from "~/signals";
import { activeImageGenerationJobs, toasts, userMovies } from "~/signals";
import { useEffect, useState } from "react";
import { JobsApi } from "~/Classes/ApiManager/JobsApi";
import { toast } from "sonner";
// TODO ensure we de-dupe all this extra code.
interface CompletedItem {
token: string;
maybe_title: string;
public_bucket_path: string;
updated_at: string;
}
interface ActiveJob {
job_token: string;
request: {
maybe_model_title: string;
};
status: {
status: string;
progress_percentage: number;
};
}
// {
// "job_token": "jinf_421nnr6rj8wy2sj0s11v9fns32s",
// "request": {
// "inference_category": "image_generation",
// "maybe_model_type": "image_gen_api",
// "maybe_model_token": null,
// "maybe_model_title": "Image Generation",
// "maybe_raw_inference_text": null,
// "maybe_style_name": null,
// "maybe_live_portrait_details": null,
// "maybe_lipsync_details": null
// },
// "status": {
// "status": "complete_success",
// "maybe_extra_status_description": null,
// "maybe_assigned_worker": "inference-job-4o-image-848c55d5dd-555kn",
// "maybe_assigned_cluster": "storyteller-eks-1",
// "maybe_first_started_at": "2025-04-15T04:41:01Z",
// "maybe_current_execution_duration_seconds": null,
// "attempt_count": 1,
// "requires_keepalive": false,
// "maybe_failure_category": null,
// "progress_percentage": 100
// },
// "maybe_result": {
// "entity_type": "media_file",
// "entity_token": "m_ytrjp1fcd5tb43ebcs3ewhq6y05tfb",
// "maybe_public_bucket_media_path": "/media/p/t/7/y/g/pt7yg2xfv4ynemssfv1mwb7yxe26e2nb/image_pt7yg2xfv4ynemssfv1mwb7yxe26e2nb.png",
// "media_links": {
// "cdn_url": "https://cdn-2.fakeyou.com/media/p/t/7/y/g/pt7yg2xfv4ynemssfv1mwb7yxe26e2nb/image_pt7yg2xfv4ynemssfv1mwb7yxe26e2nb.png",
// "maybe_thumbnail_template": "https://cdn-2.fakeyou.com/cdn-cgi/image/width={WIDTH},quality=95/media/p/t/7/y/g/pt7yg2xfv4ynemssfv1mwb7yxe26e2nb/image_pt7yg2xfv4ynemssfv1mwb7yxe26e2nb.png",
// "maybe_video_previews": null
// },
// "maybe_successfully_completed_at": "2025-04-15T04:42:14Z"
// },
// "created_at": "2025-04-15T04:41:01Z",
// "updated_at": "2025-04-15T04:42:14Z"
// }
export function Activity() {
useSignals();
const [completedItems, setCompletedItems] = useState<CompletedItem[]>([]);
const [loading, setLoading] = useState(true);
const [jobs, setJobs] = useState<ActiveJob[]>([]);
useEffect(() => {
const pollData = async () => {
const fetchJobs = async () => {
try {
const jobsApi = new JobsApi();
const jobsResponse = await jobsApi.ListRecentJobs();
if (jobsResponse.success && jobsResponse.data) {
// Process active jobs
const activeJobs = jobsResponse.data
.filter(job => {
const status = job.status?.status?.toLowerCase();
return status && status !== "complete_success" && status !== "failed";
})
.map(job => ({
job_token: job.job_token,
request: {
maybe_model_title: job.request?.maybe_model_title || "Image Generation"
},
status: {
status: job.status?.status?.toUpperCase() || "",
progress_percentage: job.status?.progress_percentage || 0
}
}));
// Process completed items
// Get completed items and filter out any that are already in our list
const successfulJobs = jobsResponse.data
.filter(job => job.status?.status?.toLowerCase() === "complete_success");
// Create a set of existing tokens for faster lookup
const existingTokens = new Set(completedItems.map(item => item.token));
// Filter out jobs we already have in our list
const newCompletedItems = successfulJobs
.filter(job => !existingTokens.has(job.job_token))
.map(job => ({
token: job.job_token,
maybe_title: job.request?.maybe_model_title || "Image Generation",
public_bucket_path: job.maybe_result?.maybe_public_bucket_media_path || "",
updated_at: job.updated_at || new Date().toISOString()
}));
// Show toast notification for newly completed items
if (newCompletedItems.length > 0) {
const count = newCompletedItems.length;
const message = count === 1
? `${newCompletedItems[0].maybe_title} completed successfully`
: `${count} images completed successfully`;
toast.success(message);
}
// Update completed items with a maximum limit of 50 items
setCompletedItems(prev => {
const combined = [...newCompletedItems, ...prev];
// Remove duplicates based on token
const uniqueItems = Array.from(
new Map(combined.map(item => [item.token, item])).values()
);
// Sort by updated_at in descending order and limit to 50 items
return uniqueItems
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
.slice(0, 50);
});
setJobs(activeJobs);
setLoading(false);
}
} catch (error) {
toast.error("Error Fetching Recent Jobs");
console.error("Failed to fetch recent jobs:", error);
}
};
await fetchJobs();
};
// Initial load
// Initial call to pollData
pollData();
// Set up polling interval (every 5 seconds)
const interval = setInterval(async () => {
await pollData();
}, 5000);
return () => clearInterval(interval);
}, []);
return (
<Tooltip content="Activity" position="bottom" closeOnClick={true}>
@@ -30,11 +180,10 @@ export function Activity() {
>
<div className="max-h-[480px] overflow-y-auto">
{/* In progress */}
{activeImageGenerationJobs.value &&
activeImageGenerationJobs.value.length > 0 && (
{jobs && jobs.length > 0 && (
<div>
{activeImageGenerationJobs.value.map((movieJob) => (
<InProgressCard key={movieJob.job_token} movie={movieJob} />
{jobs.map((job) => (
<InProgressCard key={job.job_token} movie={job} />
))}
</div>
)}
@@ -62,11 +211,17 @@ export function Activity() {
<div>
{/* Completed */}
<div className="flex flex-col">
{userMovies.value?.map((movie) => (
<CompletedCard key={movie.token} movie={movie} />
{completedItems.map((item) => (
<CompletedCard
key={item.token}
movie={item}
setMovieId={(item_id) => {
console.log("item_id", item_id);
}}
/>
))}
</div>
</div>
</div>
)}
</div>
</PopoverMenu>
@@ -6,7 +6,7 @@ import { BucketConfig } from "~/api/BucketConfig";
import dayjs from "dayjs";
import environmentVariables from "~/Classes/EnvironmentVariables";
import { useSignals } from "@preact/signals-react/runtime";
import { downloadFile } from "~/pages/PageEnigma/comps/GenerateModals/utils/downloadFile";
import { downloadFileImage } from "~/pages/PageEnigma/comps/GenerateModals/utils/downloadFile";
import { styleList } from "~/pages/PageEnigma/styleList";
import { GetCdnOrigin } from "~/api/GetCdnOrigin";
@@ -27,13 +27,20 @@ export function CompletedCard({ movie, setMovieId }: Props) {
const bucketConfig = useRef<BucketConfig>(new BucketConfig());
const [loadError, setLoadError] = useState(false);
const cdnOrigin = GetCdnOrigin();
const downloadLink = `https://${cdnOrigin}${movie.public_bucket_path}`;
const imageUrl = bucketConfig.current.getCdnUrl(
movie.public_bucket_path + "-thumb.gif",
360,
20,
);
const downloadLink = `${cdnOrigin}${movie.public_bucket_path}`;
// console.log(downloadLink);
// console.log("This is the completed item", movie);
// console.log("item completed card", movie);
// to do use the thumbnail image
const imageUrl = downloadLink; // bucketConfig.current.getCdnUrl(
// movie.public_bucket_path + "-thumb.gif",
// 360,
// 20,
// );
const styleName =
movie.maybe_style_name && getStyleName(movie.maybe_style_name);
@@ -71,21 +78,21 @@ export function CompletedCard({ movie, setMovieId }: Props) {
</div>
</div>
</div>
{/* <div className="pr-5">
<div className="pr-5">
<button
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
const title =
movie.maybe_title !== null ? movie.maybe_title : "Untitled";
downloadFile({ url: downloadLink, title });
downloadFileImage({ url: downloadLink, title });
}}
className="text-[15px] font-medium text-white/50 transition-all duration-150 hover:text-white/100"
>
<FontAwesomeIcon icon={faArrowDownToLine} className="mr-2" />
Download
</button>
</div> */}
</div>
</div>
);
}
@@ -24,4 +24,28 @@ export const downloadFile = ({
.catch(() => addToast(ToastTypes.ERROR, "Could not download file."));
};
export const downloadFileImage = ({
url,
title,
}: {
url: string;
title: string;
}) => {
// console.log(url);
fetch(url)
.then((resp) => resp.blob())
.then((blob) => {
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.style.display = "none";
a.href = blobUrl;
a.download = `${title}.png`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
.catch(() => addToast(ToastTypes.ERROR, "Could not download file."));
};
//check
@@ -1,10 +1,41 @@
import { useState, useEffect } from "react";
import { Transition } from "@headlessui/react";
import { Key, KeyGroup, Mouse, Plus } from "./ControlsTopButtons/Help/Help";
import { CloseButton } from "~/components";
const STORAGE_KEY = "onboarding_helper_dismissed";
const EXPIRATION_DAYS = 3;
export const OnboardingHelper = () => {
const [actionPerformed, setActionPerformed] = useState(false);
const [keyPressed, setKeyPressed] = useState(false);
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
// Check if the helper was dismissed
const dismissedData = localStorage.getItem(STORAGE_KEY);
if (dismissedData) {
const { timestamp } = JSON.parse(dismissedData);
const expirationTime = timestamp + EXPIRATION_DAYS * 24 * 60 * 60 * 1000; // 3 days in milliseconds
if (Date.now() < expirationTime) {
setIsVisible(false);
} else {
// Clear expired data
localStorage.removeItem(STORAGE_KEY);
}
}
}, []);
const handleClose = () => {
setIsVisible(false);
// Store dismissal timestamp
localStorage.setItem(
STORAGE_KEY,
JSON.stringify({
timestamp: Date.now(),
})
);
};
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@@ -30,7 +61,7 @@ export const OnboardingHelper = () => {
return (
<Transition
show={!(actionPerformed && keyPressed)}
show={isVisible && !(actionPerformed && keyPressed)}
enter="transition-opacity duration-500"
enterFrom="opacity-0"
enterTo="opacity-100"
@@ -38,7 +69,7 @@ export const OnboardingHelper = () => {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="glass pointer-events-none absolute left-1/2 top-48 z-10 flex -translate-x-1/2 transform items-center justify-center gap-2 rounded-2xl border-2 border-brand-primary !bg-brand-primary/10 px-5 py-4">
<div className="glass pointer-events-none absolute left-1/2 top-48 z-10 flex -translate-x-1/2 transform items-center justify-center gap-2 rounded-2xl border-2 border-brand-primary !bg-brand-primary/10 px-5 py-4">
<div className="flex items-center">
<p className="pr-3 text-lg font-semibold">Hold</p>
<Mouse button="left" />
@@ -56,6 +87,9 @@ export const OnboardingHelper = () => {
</div>
</div>
<p className="text-lg font-semibold">to move around the scene</p>
<div className="pointer-events-auto ml-2">
<CloseButton onClick={handleClose} />
</div>
</div>
</Transition>
);
+3
View File
@@ -16,6 +16,7 @@ import normalizeCss from "./styles/normalize.css?url";
import tailwindCss from "./styles/tailwind.css?url";
import baseCss from "./styles/base.css?url";
import { Environment, Configs } from "./configs";
//import { api } from "@storyteller/api";
// The following import prevents a Font Awesome icon server-side rendering bug,
// where the icons flash from a very large icon down to a properly sized one:
@@ -71,6 +72,8 @@ export async function loader() {
uploadApiVideo = "http://localhost:12345";
break;
}
//console.log(">>>> API LIBRARY IMPORT TEST", api());
const env = {
// @ts-expect-error ProvessEnv is correct
+4 -6
View File
@@ -1,15 +1,14 @@
[build]
#command = "./script/netlify_build.sh"
#publish = "build/client"
command = "./apps/editor3d/script/netlify_build.sh"
publish = "apps/editor3d/dist/"
[build.environment]
REACT_APP_PUBLIC_POSTHOG_KEY = "phc_jBFgac0mVALAFk3negnSfYCcHgvkT00yBLQDmCDYNBb"
REACT_APP_PUBLIC_POSTHOG_UI = "https://us.i.posthog.com"
[dev]
command = "npm run dev"
framework = "vite"
#[dev]
#command = "npm run dev"
#framework = "vite"
# Set immutable caching for static files, because they have fingerprinted filenames
@@ -17,7 +16,6 @@ framework = "vite"
for = "/build/*"
[headers.values]
"Cache-Control" = "public, max-age=31560000, immutable"
[[redirects]]
+1 -3
View File
@@ -21,6 +21,7 @@
"test": "jest"
},
"dependencies": {
"@storyteller/api": "file:../../libs/api",
"@awesome.me/kit-fde2be5eb0": "^1.0.7",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-brands-svg-icons": "^6.5.2",
@@ -44,14 +45,12 @@
"@zip.js/zip.js": "^2.7.51",
"ammo.js": "^0.0.10",
"ccapture.js": "github:aceslowman/ccapture.js#npm-fix",
"common": "file:common",
"csstype": "^3.1.3",
"dayjs": "^1.11.10",
"deep-equal": "^2.2.3",
"detect-gpu": "^5.0.38",
"http-proxy-middleware": "^3.0.0",
"isbot": "^4.1.0",
"kalidokit": "file:./vendor/kalidokit",
"lodash.debounce": "^4.0.8",
"posthog-js": "^1.141.0",
"react": "^18.2.0",
@@ -101,7 +100,6 @@
"eslint-plugin-storybook": "^0.8.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"node-sass": "^9.0.0",
"postcss": "^8.4.37",
"postcss-import": "^16.0.1",
"postcss-nesting": "^12.1.0",
+10 -32
View File
@@ -2,48 +2,26 @@
set -euxo pipefail
# echo "Run build script"
# npm install
# npm run build
#
# #echo "Emit built files."
# #pwd
# #find .
#
# # echo "Copy test files into dist"
# # cp -r test/* dist/
# #
# # echo "Copy netlify configs into dist"
# # cp _headers dist/
# # cp _redirects dist/
# #
# # echo "Copy netlify 404.html page into dist"
# # cp "404.html" dist/
# #
# # echo "List files after build"
# # find dist/
echo "Run build script (TODO: Make strict)"
echo "Build Editor 3D"
nx build editor3d
echo "Change to project dir"
pushd apps/editor3d/
echo "List directory files"
pwd
ls -lA .
echo "List build directory files"
pwd
ls -lA build/
#echo "Copy netlify configs into dist"
#cp _headers dist/
#cp _redirects dist/
echo "Make output directory"
mkdir dist/
#echo "Copy netlify 404.html page into dist"
echo "Copy files to putput directory"
cp -r build/server/* dist/
cp -r build/client/* dist/
cp _headers dist/
#cp _redirects dist/
#cp "404.html" dist/
echo "List files after build"
echo "List final files in build"
find dist/
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
set -euxo pipefail
echo "Removing Node Modules..."
rm -rf ./node_modules/
rm -rf ./apps/editor2d/node_modules/
rm -rf ./apps/editor3d/node_modules/
+50
View File
@@ -0,0 +1,50 @@
{
"extends": [
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.json"
],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": [
"{projectRoot}/eslint.config.{js,cjs,mjs}",
"{projectRoot}/vite.config.{js,ts,mjs,mts}"
]
}
]
}
}
]
}
+11
View File
@@ -0,0 +1,11 @@
# api
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build api` to build the library.
## Running unit tests
Run `nx test api` to execute the unit tests via [Vitest](https://vitest.dev/).
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@storyteller/api",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"dependencies": {}
}
+1
View File
@@ -0,0 +1 @@
export * from './lib/api.js';
+7
View File
@@ -0,0 +1,7 @@
import { api } from './api.js';
describe('api', () => {
it('should work', () => {
expect(api()).toEqual('api');
})
})
+3
View File
@@ -0,0 +1,3 @@
export function api(): string {
return 'api library built and imported!';
}
+13
View File
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
+35
View File
@@ -0,0 +1,35 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": ".",
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo",
"emitDeclarationOnly": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"forceConsistentCasingInFileNames": true,
"types": [
"node",
"vite/client"
]
},
"include": [
"src/**/*.ts"
],
"references": [],
"exclude": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
]
}
+36
View File
@@ -0,0 +1,36 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"module": "nodenext",
"moduleResolution": "nodenext",
"forceConsistentCasingInFileNames": true
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
+48
View File
@@ -0,0 +1,48 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../node_modules/.vite/libs/api',
plugins: [dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@storyteller/api',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: []
},
},
test: {
watch: false,
globals: true,
environment: 'node',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));
+17 -1
View File
@@ -1,5 +1,6 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"npmScope": "storyteller",
"namedInputs": {
"default": [
"{projectRoot}/**/*",
@@ -9,7 +10,9 @@
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/src/test-setup.[jt]s"
"!{projectRoot}/src/test-setup.[jt]s",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/eslint.config.mjs"
],
"sharedGlobals": []
},
@@ -65,6 +68,12 @@
"watchDepsTargetName": "watch-deps",
"staticServeTargetName": "static-serve"
}
},
{
"plugin": "@nx/eslint/plugin",
"options": {
"targetName": "eslint:lint"
}
}
],
"generators": {
@@ -83,5 +92,12 @@
"linter": "none"
}
}
},
"targetDefaults": {
"test": {
"dependsOn": [
"^build"
]
}
}
}
+1575 -3954
View File
File diff suppressed because it is too large Load Diff
+12 -1
View File
@@ -7,11 +7,14 @@
"dependencies": {
"@preact/signals-react": "^3.0.1",
"@remix-run/serve": "^2.15.0",
"pnpm": "^10.8.1",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@netlify/remix-edge-adapter": "^3.4.3",
"@nx/eslint": "20.7.2",
"@nx/eslint-plugin": "20.7.2",
"@nx/js": "20.7.2",
"@nx/react": "20.7.2",
"@nx/remix": "^20.7.2",
@@ -26,20 +29,28 @@
"@types/node": "^20.0.0",
"@types/react": "19.0.0",
"@types/react-dom": "19.0.0",
"@typescript-eslint/eslint-plugin": "^7.16.0",
"@typescript-eslint/parser": "^7.16.0",
"@vitejs/plugin-react": "^4.2.0",
"@vitest/coverage-v8": "^3.0.5",
"@vitest/ui": "^3.0.0",
"eslint": "~8.57.0",
"eslint-config-prettier": "^10.0.0",
"jiti": "2.4.2",
"jsdom": "~22.1.0",
"jsonc-eslint-parser": "^2.1.0",
"nx": "20.7.2",
"tslib": "^2.3.0",
"typescript": "~5.7.2",
"vite": "^6.0.0",
"vite-plugin-banner": "^0.8.0",
"vite-plugin-dts": "~4.5.0",
"vite-plugin-static-copy": "^2.3.1",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.0"
},
"workspaces": [
"apps/*"
"apps/*",
"libs/*"
]
}
+4
View File
@@ -0,0 +1,4 @@
packages:
- 'apps/*'
- 'libs/*'
+3
View File
@@ -8,6 +8,9 @@
},
{
"path": "./apps/editor3d"
},
{
"path": "./libs/api"
}
]
}
+1
View File
@@ -0,0 +1 @@
export default ['**/vite.config.{mjs,js,ts,mts}', '**/vitest.config.{mjs,js,ts,mts}'];
+1 -1
View File
@@ -33,7 +33,7 @@ RUN apt-get update \
FROM ubuntu:jammy as rust-base
# NB: This can be "stable" or another version.
ARG RUST_TOOLCHAIN="1.85.1"
ARG RUST_TOOLCHAIN="1.86.0"
WORKDIR /tmp
+2 -1
View File
@@ -25,7 +25,8 @@ RUN apt-get update \
# TODO(bt, 2023-02-23): This has not been verified to work yet.
RUN export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig
ARG RUST_TOOLCHAIN="1.81.0"
ARG RUST_TOOLCHAIN="1.86.0"
# Install Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- --default-toolchain $RUST_TOOLCHAIN -y