mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-24 02:29:32 -04:00
Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f211332016 | ||
|
|
7ef20bc885 | ||
|
|
b031a68000 | ||
|
|
e4b648b55d | ||
|
|
d78ccbca25 | ||
|
|
bae6bc6863 | ||
|
|
faa43a7361 | ||
|
|
f8b3b708c0 | ||
|
|
4c40379470 | ||
|
|
0c32a89828 | ||
|
|
d7365ea045 | ||
|
|
3386247858 | ||
|
|
7ea9c96d77 | ||
|
|
059f4b4e38 | ||
|
|
3c36e041ca | ||
|
|
f027fcb29a | ||
|
|
6f2ed9e61e | ||
|
|
2b40b2f8d6 | ||
|
|
9996b41bc2 | ||
|
|
b9a5768679 | ||
|
|
614a5edc55 | ||
|
|
01468020bd | ||
|
|
289ef6f352 |
Generated
+31
@@ -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
@@ -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" }
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP INDEX `index_maybe_creator_anonymous_visitor_token`
|
||||
ON `generic_inference_jobs`;
|
||||
+7
@@ -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;
|
||||
+62
-7
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
|
||||
"nrwl.angular-console",
|
||||
"esbenp.prettier-vscode"
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,30 +231,28 @@ 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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 (
|
||||
<>
|
||||
|
||||
@@ -87,19 +87,22 @@ export class EngineApi extends ApiManager {
|
||||
}
|
||||
|
||||
public async enqueueImageGeneration({
|
||||
disableSystemPrompt,
|
||||
prompt,
|
||||
snapshotMediaToken,
|
||||
additionalImages,
|
||||
}: {
|
||||
disableSystemPrompt: boolean;
|
||||
prompt: string;
|
||||
snapshotMediaToken: string;
|
||||
additionalImages?: string[];
|
||||
}): Promise<ApiResponse<string>> {
|
||||
const endpoint = `${this.ApiTargets.BaseApi}/v1/image_studio/prompt`;
|
||||
// for now ...
|
||||
|
||||
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,
|
||||
@@ -108,6 +111,7 @@ export class EngineApi extends ApiManager {
|
||||
const postResponse = await this.post<
|
||||
{
|
||||
uuid_idempotency_token: string;
|
||||
disable_system_prompt: boolean;
|
||||
prompt: string;
|
||||
snapshot_media_token: string;
|
||||
additional_images?: string[];
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
+17
-10
@@ -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
@@ -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>
|
||||
);
|
||||
|
||||
@@ -430,8 +430,10 @@ export const PromptBox = () => {
|
||||
screenshot: snapshot.file,
|
||||
sceneMediaToken: "",
|
||||
});
|
||||
|
||||
|
||||
console.log("useSystemPrompt", useSystemPrompt);
|
||||
await engineApi.enqueueImageGeneration({
|
||||
disableSystemPrompt: !useSystemPrompt,
|
||||
prompt: prompt,
|
||||
snapshotMediaToken: snapshotResult.data || "",
|
||||
additionalImages: referenceImages.map((image) => image.mediaToken),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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/
|
||||
|
||||
|
||||
|
||||
Executable
+9
@@ -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/
|
||||
|
||||
@@ -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}"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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/).
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './lib/api.js';
|
||||
@@ -0,0 +1,7 @@
|
||||
import { api } from './api.js';
|
||||
|
||||
describe('api', () => {
|
||||
it('should work', () => {
|
||||
expect(api()).toEqual('api');
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
export function api(): string {
|
||||
return 'api library built and imported!';
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1575
-3954
File diff suppressed because it is too large
Load Diff
+12
-1
@@ -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/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
- 'apps/*'
|
||||
- 'libs/*'
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
},
|
||||
{
|
||||
"path": "./apps/editor3d"
|
||||
},
|
||||
{
|
||||
"path": "./libs/api"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default ['**/vite.config.{mjs,js,ts,mts}', '**/vitest.config.{mjs,js,ts,mts}'];
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user