Compare 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 | ||
|
|
14aa429878 | ||
|
|
f902be970f | ||
|
|
783761736e | ||
|
|
794fcf4efb | ||
|
|
183b480e6d | ||
|
|
ae215a7901 | ||
|
|
2117566fbd | ||
|
|
d492b93842 | ||
|
|
ebcc317f31 | ||
|
|
8a19e914dc | ||
|
|
5df413af49 | ||
|
|
b1eb621998 | ||
|
|
00357e0fdd | ||
|
|
c7eed7b52d | ||
|
|
9c4072016d | ||
|
|
3d964c83c0 | ||
|
|
41975bf18e | ||
|
|
e0d984d1ac | ||
|
|
b26abbd39f | ||
|
|
114fd66204 | ||
|
|
4cc370d40d | ||
|
|
d5d7bb46be | ||
|
|
f059e74b26 | ||
|
|
765b03c600 | ||
|
|
a37e6d5692 | ||
|
|
baef6f170b | ||
|
|
10137ddcb8 | ||
|
|
c02694a8ec |
@@ -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,16 +7864,23 @@ 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",
|
||||
"tokio",
|
||||
"toml 0.7.8",
|
||||
"workspace-build-acceleration",
|
||||
@@ -10604,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"
|
||||
|
||||
@@ -117,7 +117,7 @@ members = [
|
||||
"crates/vendor/candle/tensor-tools",
|
||||
"crates/vendor/limitation",
|
||||
# NB(bt,2023-12-12): Newrelic keeps breaking with upgrades. Kill for now.
|
||||
#"crates/vendor/newrelic-telemetry-sdk-rust",
|
||||
#"crates/vendor/newrelic-telemetry-sdk-rust",
|
||||
|
||||
# cargo-hakari workspace hack (compilation speedup hack)
|
||||
# see usage: https://crates.io/crates/cargo-hakari
|
||||
@@ -131,12 +131,12 @@ publish = false
|
||||
[workspace.dependencies]
|
||||
# Hakari workspace hack (faster compile times)
|
||||
#
|
||||
# If an unrelated leaf package starts causing general build problems,
|
||||
# If an unrelated leaf package starts causing general build problems,
|
||||
# for packages including hakari, look up what depends on the bad package:
|
||||
#
|
||||
# `cargo tree --target all -p {BAD_PACKAGE_NAME} -i`
|
||||
#
|
||||
# Packages to remove from hakari to ensure we can build in Netlify,
|
||||
# Packages to remove from hakari to ensure we can build in Netlify,
|
||||
# Github Actions (without package installs), etc.:
|
||||
#
|
||||
# - cudarc
|
||||
@@ -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,9 +164,10 @@ serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_derive = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
serde_yaml = "0.9.1"
|
||||
sha3 = "0.10.8"
|
||||
thiserror = "2"
|
||||
toml = "0.7.4"
|
||||
url = "2.5.4"
|
||||
|
||||
# Internal libs
|
||||
crockford = { path = "crates/lib/data/crockford" }
|
||||
errors = { path = "crates/lib/errors" }
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP INDEX `index_maybe_creator_anonymous_visitor_token`
|
||||
ON `generic_inference_jobs`;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD INDEX index_maybe_creator_anonymous_visitor_token (maybe_creator_anonymous_visitor_token),
|
||||
ALGORITHM=INPLACE, LOCK=NONE;
|
||||
@@ -297,6 +297,7 @@ CREATE TABLE generic_inference_jobs (
|
||||
KEY fk_maybe_input_source_token_and_type (maybe_input_source_token, maybe_input_source_token_type),
|
||||
KEY fk_on_success_result_entity_token (on_success_result_entity_token),
|
||||
KEY fk_maybe_creator_user_token (maybe_creator_user_token),
|
||||
KEY index_maybe_creator_anonymous_visitor_token (maybe_creator_anonymous_visitor_token),
|
||||
KEY index_creator_ip_address (creator_ip_address),
|
||||
KEY index_priority_level (priority_level),
|
||||
KEY index_is_debug_request (is_debug_request),
|
||||
|
||||
@@ -10,7 +10,7 @@ pub fn encode_rgb_image_base64_png(
|
||||
|
||||
image.write_to(&mut Cursor::new(&mut bytes), ImageFormat::Png)?;
|
||||
|
||||
let bytes = BASE64_STANDARD.encode(bytes);
|
||||
let bytes = (&BASE64_STANDARD).encode(bytes);
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ workspace-build-acceleration = { workspace = true }
|
||||
crockford = { path = "../../data/crockford" }
|
||||
errors = { path = "../../errors" }
|
||||
subprocess_common = { path = "../../subprocess_common" }
|
||||
idempotency = { path = "../../idempotency" }
|
||||
|
||||
# External
|
||||
anyhow = { workspace = true }
|
||||
@@ -28,8 +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" }
|
||||
|
||||
@@ -1,13 +1,30 @@
|
||||
use anyhow::anyhow;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use errors::AnyhowResult;
|
||||
use crate::credentials::SoraCredentials;
|
||||
use thiserror::Error;
|
||||
|
||||
const SORA_IMAGE_GEN_URL: &str = "https://sora.com/backend/video_gen";
|
||||
|
||||
/// 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";
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SoraError {
|
||||
#[error("Sora token expired: {0}")]
|
||||
TokenExpired(String),
|
||||
|
||||
#[error("Sora sentinel block: {0}")]
|
||||
SentinelBlock(String),
|
||||
|
||||
#[error("Sora too many concurrent tasks: {0}")]
|
||||
TooManyConcurrentTasks(String),
|
||||
|
||||
#[error("Sora API error: {0}")]
|
||||
GenericError(String),
|
||||
|
||||
#[error("Network error: {0}")]
|
||||
NetworkError(String),
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub (crate) enum VideoGenType {
|
||||
@@ -85,22 +102,51 @@ pub (crate) struct RawSoraResponse {
|
||||
// type "invalid_request_error"
|
||||
// param null
|
||||
// code null
|
||||
#[derive(Serialize)]
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub (crate) struct RawSoraErrorResponse {
|
||||
error: String,
|
||||
pub struct RawSoraErrorResponse {
|
||||
error: RawSoraErrorInner,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RawSoraError {
|
||||
Error(RawSoraErrorInner),
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SoraErrorCode {
|
||||
SentinelBlock,
|
||||
TokenExpired,
|
||||
TooManyConcurrentTasks,
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Error)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct RawSoraErrorInner {
|
||||
message: String,
|
||||
r#type: String,
|
||||
param: Option<String>,
|
||||
code: Option<String>,
|
||||
code: Option<SoraErrorCode>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RawSoraErrorInner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Sora API error: {}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
/// Don't expose the internal request implementation as there are only a few "correct" ways to call the API.
|
||||
pub (crate) async fn image_gen_http_request(sora_request: RawSoraImageGenRequest, credentials: &SoraCredentials) -> AnyhowResult<RawSoraResponse> {
|
||||
pub (crate) async fn image_gen_http_request(sora_request: RawSoraImageGenRequest, credentials: &SoraCredentials) -> Result<RawSoraResponse, SoraError> {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let sentinel = match credentials.sentinel.as_deref() {
|
||||
None => return Err(anyhow!("Calls to image gen require a sentinel in the credentials payload.")),
|
||||
None => return Err(SoraError::GenericError("Calls to image gen require a sentinel in the credentials payload.".to_string())),
|
||||
Some(sentinel) => sentinel,
|
||||
};
|
||||
|
||||
@@ -111,21 +157,45 @@ pub (crate) async fn image_gen_http_request(sora_request: RawSoraImageGenRequest
|
||||
.header("Content-Type", "application/json")
|
||||
.header("OpenAI-Sentinel-Token", sentinel);
|
||||
|
||||
let http_request = http_request.json(&sora_request).build()?;
|
||||
let http_request = http_request.json(&sora_request).build()
|
||||
.map_err(|e| SoraError::NetworkError(e.to_string()))?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|e| SoraError::NetworkError(e.to_string()))?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = &response.text().await?;
|
||||
let response_body = &response.text().await
|
||||
.map_err(|e| SoraError::NetworkError(e.to_string()))?;
|
||||
|
||||
if status != reqwest::StatusCode::OK {
|
||||
//TODO: Kasisnu: We should parse the error response and return a custom error. status 400 + code "sentinel_block" means we should retry with new sentinel token.
|
||||
return Err(anyhow::anyhow!("the request failed; status = {:?}, message = {:?}", status, response_body));
|
||||
let error_response: RawSoraErrorResponse = serde_json::from_str(response_body)
|
||||
.map_err(|e| SoraError::GenericError(format!("Failed to parse error response: {}", e)))?;
|
||||
|
||||
// Check for specific error codes
|
||||
if let Some(code) = &error_response.error.code {
|
||||
match code {
|
||||
SoraErrorCode::TokenExpired => {
|
||||
return Err(SoraError::TokenExpired(error_response.error.message));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(SoraError::GenericError(error_response.error.message));
|
||||
}
|
||||
|
||||
let response = serde_json::from_str(response_body)?;
|
||||
let response = serde_json::from_str(response_body)
|
||||
.map_err(|e| SoraError::GenericError(format!("Failed to parse success response: {}", e)))?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub (crate) mod image_gen_http_request;
|
||||
pub use image_gen_http_request::SoraError;
|
||||
pub mod common;
|
||||
pub mod sora_image_gen_remix;
|
||||
pub mod sora_image_gen_simple;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use errors::AnyhowResult;
|
||||
use crate::credentials::SoraCredentials;
|
||||
use crate::image_gen::common::{ImageSize, NumImages, SoraImageGenResponse};
|
||||
use crate::image_gen::image_gen_http_request::{image_gen_http_request, InpaintItem, InpaintItemType, OperationType, RawSoraImageGenRequest, VideoGenType};
|
||||
use crate::image_gen::image_gen_http_request::{image_gen_http_request, InpaintItem, InpaintItemType, OperationType, RawSoraImageGenRequest, SoraError, VideoGenType};
|
||||
|
||||
pub struct SoraImageGenRemixRequest<'a> {
|
||||
pub prompt: String,
|
||||
@@ -13,7 +13,7 @@ pub struct SoraImageGenRemixRequest<'a> {
|
||||
|
||||
/// The "remix" commands let you supply additional images as context.
|
||||
/// Sora "media tokens" of previously uploaded images must be supplied.
|
||||
pub async fn sora_image_gen_remix(request: SoraImageGenRemixRequest<'_>) -> AnyhowResult<SoraImageGenResponse> {
|
||||
pub async fn sora_image_gen_remix(request: SoraImageGenRemixRequest<'_>) -> Result<SoraImageGenResponse, SoraError> {
|
||||
let args = RawSoraImageGenRequest {
|
||||
r#type: VideoGenType::ImageGen,
|
||||
operation: OperationType::Remix,
|
||||
@@ -36,7 +36,6 @@ pub async fn sora_image_gen_remix(request: SoraImageGenRemixRequest<'_>) -> Anyh
|
||||
}).collect(),
|
||||
};
|
||||
|
||||
// TODO: Error handling.
|
||||
let result = image_gen_http_request(args, &request.credentials).await?;
|
||||
|
||||
Ok(SoraImageGenResponse {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod credentials;
|
||||
pub mod image_gen;
|
||||
pub mod job;
|
||||
pub mod upload;
|
||||
pub mod upload;
|
||||
pub mod sentinel_refresh;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub use refresh::refresh_sentinel;
|
||||
pub mod refresh;
|
||||
|
||||
pub mod generate;
|
||||
@@ -0,0 +1,25 @@
|
||||
// Send a POST request to env variable SORA_SENTINEL_REFRESH_URL
|
||||
|
||||
use reqwest::Client;
|
||||
use std::env;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
|
||||
const SORA_SENTINEL_REFRESH_URL: &str = "http://oai-token-server/refresh_token";
|
||||
|
||||
|
||||
// Refresh the Sora sentinel
|
||||
// The response body does _not_ contain the new sentinel
|
||||
// We should read it again from redis or generate a new one here
|
||||
// For now we refresh in the server handler
|
||||
pub async fn refresh_sentinel() -> AnyhowResult<String> {
|
||||
let client = Client::new();
|
||||
let url = env::var("SORA_SENTINEL_REFRESH_URL").unwrap_or(SORA_SENTINEL_REFRESH_URL.to_string());
|
||||
|
||||
let response = client.post(url).send().await?;
|
||||
|
||||
|
||||
let body = response.text().await?;
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
@@ -15,6 +15,8 @@ use log::{debug, error, info};
|
||||
use log::warn;
|
||||
use mysql_queries::queries::media_files::get::get_media_file::get_media_file;
|
||||
use openai_sora_client::credentials::SoraCredentials;
|
||||
use openai_sora_client::image_gen::SoraError;
|
||||
use openai_sora_client::sentinel_refresh::refresh_sentinel;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use sqlx::MySqlPool;
|
||||
@@ -28,16 +30,9 @@ use enums::by_table::generic_inference_jobs::inference_model_type::InferenceMode
|
||||
use enums::common::visibility::Visibility;
|
||||
use http_server_common::request::get_request_header_optional::get_request_header_optional;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::payloads::generic_inference_args::generic_inference_args::{
|
||||
GenericInferenceArgs,
|
||||
InferenceCategoryAbbreviated,
|
||||
PolymorphicInferenceArgs,
|
||||
};
|
||||
use mysql_queries::payloads::generic_inference_args::generic_inference_args::{GenericInferenceArgs, InferenceCategoryAbbreviated, PolymorphicInferenceArgs};
|
||||
use mysql_queries::payloads::generic_inference_args::inner_payloads::sora_image_gen_args::SoraImageGenArgs;
|
||||
use mysql_queries::queries::generic_inference::web::insert_generic_inference_job::{
|
||||
insert_generic_inference_job,
|
||||
InsertGenericInferenceArgs,
|
||||
};
|
||||
use mysql_queries::queries::generic_inference::web::insert_generic_inference_job::{insert_generic_inference_job, InsertGenericInferenceArgs};
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use openai_sora_client::image_gen::common::{ImageSize, NumImages};
|
||||
use openai_sora_client::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
@@ -58,10 +53,10 @@ use crate::util::allowed_studio_access::allowed_studio_access;
|
||||
/// This is the number of images (batch size) to generate for each request.
|
||||
/// We should allow all users to have multiple images generated at once as this
|
||||
/// is what other providers do.
|
||||
const MINIMUM_IMAGE_COUNT : u32 = 1;
|
||||
const MINIMUM_IMAGE_COUNT: u32 = 1;
|
||||
|
||||
/// The maximum number of images (batch size) to generate for each request.
|
||||
const MAXIMUM_IMAGE_COUNT : u32 = 2;
|
||||
const MAXIMUM_IMAGE_COUNT: u32 = 2;
|
||||
|
||||
/// Debug requests can get routed to special "debug-only" workers, which can
|
||||
/// be used to trial new code, run debugging, etc.
|
||||
@@ -81,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>>,
|
||||
|
||||
@@ -147,12 +145,7 @@ impl Display for EnqueueImageGenRequestError {
|
||||
),
|
||||
params(("request" = EnqueueStudioImageGenRequest, description = "Payload for Image Generation Request"))
|
||||
)]
|
||||
pub async fn enqueue_studio_image_generation_handler(
|
||||
http_request: HttpRequest,
|
||||
request: Json<EnqueueStudioImageGenRequest>,
|
||||
server_state: Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, EnqueueImageGenRequestError> {
|
||||
|
||||
pub async fn enqueue_studio_image_generation_handler(http_request: HttpRequest, request: Json<EnqueueStudioImageGenRequest>, server_state: Data<Arc<ServerState>>) -> Result<HttpResponse, EnqueueImageGenRequestError> {
|
||||
validate_request(&request)?;
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await.map_err(|err| {
|
||||
@@ -162,12 +155,10 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
|
||||
// ==================== USER SESSION ==================== //
|
||||
|
||||
let maybe_user_session = server_state.session_checker
|
||||
.maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection).await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
let maybe_user_session = server_state.session_checker.maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection).await.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
let mut maybe_user_token: Option<UserToken> = None;
|
||||
|
||||
@@ -178,10 +169,7 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
// ==================== PLANS ==================== //
|
||||
|
||||
// Plan should handle "first anonymous use" and "investor" cases.
|
||||
let plan = get_correct_plan_for_session(
|
||||
server_state.server_environment_old,
|
||||
maybe_user_session.as_ref()
|
||||
);
|
||||
let plan = get_correct_plan_for_session(server_state.server_environment_old, maybe_user_session.as_ref());
|
||||
|
||||
// Separate priority for animation.
|
||||
let priority_level = plan.web_vc_base_priority_level();
|
||||
@@ -190,9 +178,7 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
|
||||
let is_debug_request = get_request_header_optional(&http_request, DEBUG_HEADER_NAME).is_some();
|
||||
|
||||
let maybe_routing_tag = get_request_header_optional(&http_request, ROUTING_TAG_HEADER_NAME).map(
|
||||
|routing_tag| routing_tag.trim().to_string()
|
||||
);
|
||||
let maybe_routing_tag = get_request_header_optional(&http_request, ROUTING_TAG_HEADER_NAME).map(|routing_tag| routing_tag.trim().to_string());
|
||||
|
||||
// ==================== BANNED USERS ==================== //
|
||||
|
||||
@@ -224,12 +210,10 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
return Err(EnqueueImageGenRequestError::BadInput(reason));
|
||||
}
|
||||
|
||||
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error inserting idempotency token: {:?}", err);
|
||||
EnqueueImageGenRequestError::BadInput("invalid idempotency token".to_string())
|
||||
})?;
|
||||
insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection).await.map_err(|err| {
|
||||
error!("Error inserting idempotency token: {:?}", err);
|
||||
EnqueueImageGenRequestError::BadInput("invalid idempotency token".to_string())
|
||||
})?;
|
||||
|
||||
// ==================== DOWNLOAD FILES AND UPLOAD TO SORA ==================== //
|
||||
|
||||
@@ -241,30 +225,19 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
|
||||
let scene_media_token = request.snapshot_media_token.clone();
|
||||
|
||||
let scene_media_path = query_and_download_media_file(
|
||||
&scene_media_token,
|
||||
&work_temp_dir,
|
||||
&public_bucket_client,
|
||||
&server_state.mysql_pool
|
||||
).await.map_err(|err| {
|
||||
let scene_media_path = query_and_download_media_file(&scene_media_token, &work_temp_dir, &public_bucket_client, &server_state.mysql_pool).await.map_err(|err| {
|
||||
error!("Failed to download scene media file: {:?}", err);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
let mut files_to_upload = Vec::with_capacity(1 + request.maybe_additional_images
|
||||
.as_ref().map(|v| v.len()).unwrap_or(0));
|
||||
let mut files_to_upload = Vec::with_capacity(1 + request.maybe_additional_images.as_ref().map(|v| v.len()).unwrap_or(0));
|
||||
|
||||
files_to_upload.push(scene_media_path.clone());
|
||||
|
||||
let additional_images = request.maybe_additional_images.clone().unwrap_or_else(|| vec![]);
|
||||
|
||||
for media_file_token in &additional_images {
|
||||
let media_file_path = query_and_download_media_file(
|
||||
&media_file_token,
|
||||
&work_temp_dir,
|
||||
&public_bucket_client,
|
||||
&server_state.mysql_pool
|
||||
).await.map_err(|err| {
|
||||
let media_file_path = query_and_download_media_file(&media_file_token, &work_temp_dir, &public_bucket_client, &server_state.mysql_pool).await.map_err(|err| {
|
||||
error!("Failed to download additional media file: {:?}", err);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
@@ -274,30 +247,25 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
|
||||
// ==================== HANDLE SORA CREDENTIALS ==================== //
|
||||
|
||||
let mut redis = server_state.redis_pool
|
||||
.get()
|
||||
.map_err(|e| {
|
||||
error!("redis error: {:?}", e);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
let mut redis = server_state.redis_pool.get().map_err(|e| {
|
||||
error!("redis error: {:?}", e);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
let sora_credentials = get_sora_credentials_from_request(&http_request, &mut redis)
|
||||
.map_err(|e| {
|
||||
error!("sora credential error: {:?}", e);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
let sora_credentials = get_sora_credentials_from_request(&http_request, &mut redis).map_err(|e| {
|
||||
error!("sora credential error: {:?}", e);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
// ==================== HANDLE SORA UPLOAD ==================== //
|
||||
|
||||
let mut sora_media_tokens = Vec::with_capacity(files_to_upload.len());
|
||||
|
||||
for file_path in files_to_upload {
|
||||
let sora_upload_response = sora_media_upload_from_file(file_path, &sora_credentials)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to upload scene media to Sora: {:?}", err);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
let sora_upload_response = sora_media_upload_from_file(file_path, &sora_credentials).await.map_err(|err| {
|
||||
error!("Failed to upload scene media to Sora: {:?}", err);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
debug!("Uploaded media to Sora : {:?}", sora_upload_response);
|
||||
sora_media_tokens.push(sora_upload_response.id);
|
||||
@@ -306,40 +274,83 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
// ==================== HANDLE SORA PROMPT ==================== //
|
||||
|
||||
let number_of_samples = match request.maybe_number_of_samples {
|
||||
Some(val) => if val > MAXIMUM_IMAGE_COUNT {
|
||||
MAXIMUM_IMAGE_COUNT
|
||||
} else if val < MINIMUM_IMAGE_COUNT {
|
||||
MINIMUM_IMAGE_COUNT
|
||||
} else {
|
||||
val
|
||||
Some(val) => {
|
||||
if val > MAXIMUM_IMAGE_COUNT {
|
||||
MAXIMUM_IMAGE_COUNT
|
||||
} else if val < MINIMUM_IMAGE_COUNT {
|
||||
MINIMUM_IMAGE_COUNT
|
||||
} else {
|
||||
val
|
||||
}
|
||||
},
|
||||
None => 1,
|
||||
};
|
||||
|
||||
let prompt = request.prompt.clone();
|
||||
let prompt = create_prompt(&request);
|
||||
|
||||
let 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
|
||||
.map_err(|err| {
|
||||
error!("Failed to call Sora image generation: {:?}", err);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
let mut response = sora_image_gen_remix(SoraImageGenRemixRequest {
|
||||
prompt: prompt.clone(),
|
||||
num_images: NumImages::One,
|
||||
image_size: ImageSize::Square,
|
||||
sora_media_tokens: sora_media_tokens.clone(),
|
||||
credentials: &sora_credentials
|
||||
}).await;
|
||||
|
||||
debug!("Sora image gen response: {:?}", response);
|
||||
|
||||
if let Err(SoraError::SentinelBlock(msg)) = &response {
|
||||
error!("Sora sentinel block, attempting refresh: {}", msg);
|
||||
|
||||
// Try to refresh sentinel
|
||||
match refresh_sentinel().await {
|
||||
Ok(new_sentinel) => {
|
||||
// Update the credentials with new sentinel
|
||||
let updated_sora_credentials = get_sora_credentials_from_request(&http_request, &mut redis).map_err(|e| {
|
||||
error!("sora credential error: {:?}", e);
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
// Retry the request with new sentinel
|
||||
response = sora_image_gen_remix(SoraImageGenRemixRequest {
|
||||
prompt: prompt.clone(),
|
||||
num_images: NumImages::One,
|
||||
image_size: ImageSize::Square,
|
||||
sora_media_tokens: sora_media_tokens.clone(),
|
||||
credentials: &updated_sora_credentials
|
||||
}).await;
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to refresh Sora sentinel: {:?}", e);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let response = response.map_err(|err| {
|
||||
match err {
|
||||
SoraError::TokenExpired(msg) => {
|
||||
error!("Sora token expired, needs refresh: {}", msg);
|
||||
// TODO: Implement token refresh logic here
|
||||
},
|
||||
SoraError::SentinelBlock(msg) => {
|
||||
error!("Sora sentinel block still occurring after refresh: {}", msg);
|
||||
},
|
||||
_ => {
|
||||
error!("Failed to call Sora image generation: {:?}", err);
|
||||
},
|
||||
}
|
||||
EnqueueImageGenRequestError::ServerError
|
||||
})?;
|
||||
|
||||
debug!("Sora image gen response: {:?}", response);
|
||||
|
||||
// Store the actual inference args that will go to the database
|
||||
let inference_args = SoraImageGenArgs {
|
||||
prompt: Some(prompt),
|
||||
scene_snapshot_media_token: Some(request.snapshot_media_token.clone()),
|
||||
maybe_additional_media_file_tokens: Some(additional_images),
|
||||
maybe_number_of_samples: Some(number_of_samples),
|
||||
maybe_sora_media_upload_tokens: Some(sora_media_tokens),
|
||||
maybe_sora_task_id: Some(response.task_id),
|
||||
let inference_args = SoraImageGenArgs {
|
||||
prompt: Some(prompt),
|
||||
scene_snapshot_media_token: Some(request.snapshot_media_token.clone()),
|
||||
maybe_additional_media_file_tokens: Some(additional_images),
|
||||
maybe_number_of_samples: Some(number_of_samples),
|
||||
maybe_sora_media_upload_tokens: Some(sora_media_tokens),
|
||||
maybe_sora_task_id: Some(response.task_id)
|
||||
};
|
||||
|
||||
// create the inference args here
|
||||
@@ -352,15 +363,15 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
maybe_product_category: None, // This is not a product anymore
|
||||
inference_category: InferenceCategory::ImageGeneration,
|
||||
maybe_model_type: Some(InferenceModelType::ImageGenApi), // NB: Model is static during inference
|
||||
maybe_model_token: None, // NB: Model is static during inference
|
||||
maybe_model_token: None, // NB: Model is static during inference
|
||||
maybe_input_source_token: None,
|
||||
maybe_input_source_token_type: None,
|
||||
maybe_download_url: None,
|
||||
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),
|
||||
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(),
|
||||
@@ -372,7 +383,8 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
is_debug_request,
|
||||
maybe_routing_tag: maybe_routing_tag.as_deref(),
|
||||
mysql_pool: &server_state.mysql_pool,
|
||||
}).await;
|
||||
})
|
||||
.await;
|
||||
|
||||
let job_token = match query_result {
|
||||
Ok((job_token, _id)) => job_token,
|
||||
@@ -382,24 +394,18 @@ pub async fn enqueue_studio_image_generation_handler(
|
||||
return Err(EnqueueImageGenRequestError::BadInput("Duplicate idempotency token".to_string()));
|
||||
}
|
||||
return Err(EnqueueImageGenRequestError::ServerError);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let response: EnqueueImageGenRequestSuccessResponse = EnqueueImageGenRequestSuccessResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
};
|
||||
let response: EnqueueImageGenRequestSuccessResponse = EnqueueImageGenRequestSuccessResponse { success: true, inference_job_token: job_token };
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|_e| EnqueueImageGenRequestError::ServerError)?;
|
||||
let body = serde_json::to_string(&response).map_err(|_e| EnqueueImageGenRequestError::ServerError)?;
|
||||
|
||||
// Error handling 101 rust result type returned like so.
|
||||
Ok(HttpResponse::Ok().content_type("application/json").body(body))
|
||||
}
|
||||
|
||||
fn validate_request(
|
||||
request: &Json<EnqueueStudioImageGenRequest>,
|
||||
) -> Result<(), EnqueueImageGenRequestError> {
|
||||
fn validate_request(request: &Json<EnqueueStudioImageGenRequest>) -> Result<(), EnqueueImageGenRequestError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -416,28 +422,19 @@ impl From<anyhow::Error> for DownloadMediaFileError {
|
||||
}
|
||||
}
|
||||
|
||||
async fn query_and_download_media_file(
|
||||
media_file_token: &MediaFileToken,
|
||||
download_dir: &TempDir,
|
||||
bucket_client: &BucketClient,
|
||||
mysql_pool: &MySqlPool,
|
||||
) -> Result<PathBuf, DownloadMediaFileError> {
|
||||
let media_file_result = get_media_file(
|
||||
&media_file_token,
|
||||
false,
|
||||
mysql_pool
|
||||
).await;
|
||||
async fn query_and_download_media_file(media_file_token: &MediaFileToken, download_dir: &TempDir, bucket_client: &BucketClient, mysql_pool: &MySqlPool) -> Result<PathBuf, DownloadMediaFileError> {
|
||||
let media_file_result = get_media_file(&media_file_token, false, mysql_pool).await;
|
||||
|
||||
let media_file= match media_file_result {
|
||||
let media_file = match media_file_result {
|
||||
Ok(Some(result)) => result,
|
||||
Ok(None) => {
|
||||
warn!("could not find media file in database: {:?}", media_file_token);
|
||||
return Err(DownloadMediaFileError::MediaFileNotFound)
|
||||
}
|
||||
return Err(DownloadMediaFileError::MediaFileNotFound);
|
||||
},
|
||||
Err(e) => {
|
||||
error!("could not query media file: {:?}", e);
|
||||
return Err(DownloadMediaFileError::Other(e))
|
||||
}
|
||||
return Err(DownloadMediaFileError::Other(e));
|
||||
},
|
||||
};
|
||||
|
||||
let media_file_bucket_path = MediaFileBucketPath::from_object_hash(
|
||||
@@ -451,10 +448,34 @@ async fn query_and_download_media_file(
|
||||
|
||||
info!("Downloading from bucket...");
|
||||
|
||||
bucket_client.download_file_to_disk(
|
||||
&media_file_bucket_path.to_full_object_pathbuf(),
|
||||
&download_file_path,
|
||||
).await?;
|
||||
bucket_client.download_file_to_disk(&media_file_bucket_path.to_full_object_pathbuf(), &download_file_path).await?;
|
||||
|
||||
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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
|
||||
"nrwl.angular-console",
|
||||
"esbenp.prettier-vscode"
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,101 +1,30 @@
|
||||
# Frontend
|
||||
|
||||
<a alt="Nx logo" href="https://nx.dev" target="_blank" rel="noreferrer"><img src="https://raw.githubusercontent.com/nrwl/nx/master/images/nx-logo.png" width="45"></a>
|
||||
This is an `nx` monorepo that can contain multiple apps and shared libraries.
|
||||
|
||||
✨ Your new, shiny [Nx workspace](https://nx.dev) is ready ✨.
|
||||
All commands to run these projects are performed from _this_ directory.
|
||||
|
||||
[Learn more about this workspace setup and its capabilities](https://nx.dev/getting-started/tutorials/react-monorepo-tutorial?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or run `npx nx graph` to visually explore what was created. Now, let's get you up to speed!
|
||||
|
||||
## Run tasks
|
||||
## Install dependencies
|
||||
|
||||
To run the dev server for your app, use:
|
||||
|
||||
```sh
|
||||
npx nx serve editor3d
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
To create a production bundle:
|
||||
## Editor2d
|
||||
|
||||
```sh
|
||||
npx nx build editor3d
|
||||
Run dev server:
|
||||
|
||||
```bash
|
||||
nx dev editor2d
|
||||
```
|
||||
|
||||
To see all available targets to run for a project, run:
|
||||
## Editor3d
|
||||
|
||||
```sh
|
||||
npx nx show project editor3d
|
||||
Run dev server:
|
||||
|
||||
```bash
|
||||
nx dev editor3d
|
||||
```
|
||||
|
||||
These targets are either [inferred automatically](https://nx.dev/concepts/inferred-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) or defined in the `project.json` or `package.json` files.
|
||||
|
||||
[More about running tasks in the docs »](https://nx.dev/features/run-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
## Add new projects
|
||||
|
||||
While you could add new projects to your workspace manually, you might want to leverage [Nx plugins](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) and their [code generation](https://nx.dev/features/generate-code?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) feature.
|
||||
|
||||
Use the plugin's generator to create new projects.
|
||||
|
||||
To generate a new application, use:
|
||||
|
||||
```sh
|
||||
npx nx g @nx/react:app demo
|
||||
```
|
||||
|
||||
To generate a new library, use:
|
||||
|
||||
```sh
|
||||
npx nx g @nx/react:lib mylib
|
||||
```
|
||||
|
||||
You can use `npx nx list` to get a list of installed plugins. Then, run `npx nx list <plugin-name>` to learn about more specific capabilities of a particular plugin. Alternatively, [install Nx Console](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) to browse plugins and generators in your IDE.
|
||||
|
||||
[Learn more about Nx plugins »](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) | [Browse the plugin registry »](https://nx.dev/plugin-registry?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
## Set up CI!
|
||||
|
||||
### Step 1
|
||||
|
||||
To connect to Nx Cloud, run the following command:
|
||||
|
||||
```sh
|
||||
npx nx connect
|
||||
```
|
||||
|
||||
Connecting to Nx Cloud ensures a [fast and scalable CI](https://nx.dev/ci/intro/why-nx-cloud?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects) pipeline. It includes features such as:
|
||||
|
||||
- [Remote caching](https://nx.dev/ci/features/remote-cache?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Task distribution across multiple machines](https://nx.dev/ci/features/distribute-task-execution?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Automated e2e test splitting](https://nx.dev/ci/features/split-e2e-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Task flakiness detection and rerunning](https://nx.dev/ci/features/flaky-tasks?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
### Step 2
|
||||
|
||||
Use the following command to configure a CI workflow for your workspace:
|
||||
|
||||
```sh
|
||||
npx nx g ci-workflow
|
||||
```
|
||||
|
||||
[Learn more about Nx on CI](https://nx.dev/ci/intro/ci-with-nx#ready-get-started-with-your-provider?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
## Install Nx Console
|
||||
|
||||
Nx Console is an editor extension that enriches your developer experience. It lets you run tasks, generate code, and improves code autocompletion in your IDE. It is available for VSCode and IntelliJ.
|
||||
|
||||
[Install Nx Console »](https://nx.dev/getting-started/editor-setup?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
## Useful links
|
||||
|
||||
Learn more:
|
||||
|
||||
- [Learn more about this workspace setup](https://nx.dev/getting-started/tutorials/react-monorepo-tutorial?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Learn about Nx on CI](https://nx.dev/ci/intro/ci-with-nx?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [Releasing Packages with Nx release](https://nx.dev/features/manage-releases?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
- [What are Nx plugins?](https://nx.dev/concepts/nx-plugins?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
And join the Nx community:
|
||||
- [Discord](https://go.nx.dev/community)
|
||||
- [Follow us on X](https://twitter.com/nxdevtools) or [LinkedIn](https://www.linkedin.com/company/nrwl)
|
||||
- [Our Youtube channel](https://www.youtube.com/@nxdevtools)
|
||||
- [Our blog](https://nx.dev/blog?utm_source=nx_project&utm_medium=readme&utm_campaign=nx_projects)
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Editor3d</title>
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/src/styles.css" />
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/x-ico" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Syne:wght@600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<title>Board | Storyteller.AI</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script type="module" src="/src/app/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[build]
|
||||
command = "./script/netlify_build.sh"
|
||||
publish = "dist/"
|
||||
command = "./apps/editor2d/script/netlify_build.sh"
|
||||
publish = "apps/editor2d/dist/"
|
||||
|
||||
#[build.environment]
|
||||
#REACT_APP_PUBLIC_POSTHOG_KEY = "phc_jBFgac0mVALAFk3negnSfYCcHgvkT00yBLQDmCDYNBb"
|
||||
@@ -16,7 +16,6 @@ publish = "dist/"
|
||||
for = "/build/*"
|
||||
|
||||
[headers.values]
|
||||
|
||||
"Cache-Control" = "public, max-age=31560000, immutable"
|
||||
|
||||
# [[redirects]]
|
||||
@@ -1,8 +1,72 @@
|
||||
{
|
||||
"name": "@frontend/editor2d",
|
||||
"version": "0.0.1",
|
||||
"name": "editor2d",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"nx": {
|
||||
"name": "editor2d"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "echo 'editor2d' && vite",
|
||||
"build_old": "tsc -b && vite build",
|
||||
"build": "vite build",
|
||||
"build2": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@devnomic/marquee": "^1.0.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.7.2",
|
||||
"@fortawesome/pro-regular-svg-icons": "^6.7.2",
|
||||
"@fortawesome/pro-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@headlessui/react": "^2.1.3",
|
||||
"@preact/signals-react": "^2.2.0",
|
||||
"@tauri-apps/api": "^2.2.0",
|
||||
"@techstark/opencv-js": "^4.10.0-release.1",
|
||||
"@zip.js/zip.js": "^2.7.52",
|
||||
"dayjs": "^1.11.13",
|
||||
"fortawesome": "^0.0.1-security",
|
||||
"konva": "^9.3.14",
|
||||
"onnxruntime-web": "^1.19.2",
|
||||
"posthog-js": "^1.174.3",
|
||||
"react": "^18.3.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-drag-drop-files": "^2.3.10",
|
||||
"react-easy-crop": "^5.1.0",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"sonner": "^2.0.1",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
"@preact/signals-react-transform": "^0.4.0",
|
||||
"@tauri-apps/cli": "^2.4.0",
|
||||
"@types/deep-equal": "^1.0.4",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"deep-equal": "^2.2.3",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.9",
|
||||
"globals": "^15.9.0",
|
||||
"i": "^0.3.7",
|
||||
"npm": "^10.8.3",
|
||||
"postcss": "^8.4.41",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.6",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"vite": "^5.4.1",
|
||||
"vite-plugin-static-copy": "^1.0.6",
|
||||
"vite-tsconfig-paths": "^5.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 701 B After Width: | Height: | Size: 701 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 351 KiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 244 KiB |
|
Before Width: | Height: | Size: 267 KiB After Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 271 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 320 KiB |
|
Before Width: | Height: | Size: 564 KiB After Width: | Height: | Size: 564 KiB |
|
Before Width: | Height: | Size: 366 KiB After Width: | Height: | Size: 366 KiB |
|
Before Width: | Height: | Size: 452 KiB After Width: | Height: | Size: 452 KiB |
|
Before Width: | Height: | Size: 337 KiB After Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 302 KiB After Width: | Height: | Size: 302 KiB |
|
Before Width: | Height: | Size: 446 KiB After Width: | Height: | Size: 446 KiB |
|
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 344 KiB After Width: | Height: | Size: 344 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 299 KiB After Width: | Height: | Size: 299 KiB |
|
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 318 KiB After Width: | Height: | Size: 318 KiB |
|
Before Width: | Height: | Size: 521 KiB After Width: | Height: | Size: 521 KiB |
|
Before Width: | Height: | Size: 318 KiB After Width: | Height: | Size: 318 KiB |
|
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 316 KiB After Width: | Height: | Size: 316 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 378 KiB |
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 341 KiB |
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 462 KiB After Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 615 B |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 428 KiB After Width: | Height: | Size: 428 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 525 KiB After Width: | Height: | Size: 525 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 943 KiB After Width: | Height: | Size: 943 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 692 KiB After Width: | Height: | Size: 692 KiB |