mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-26 03:39:28 -04:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6a9d52e19 | ||
|
|
97fe9f90c9 | ||
|
|
5cec883b15 | ||
|
|
70fa084e30 | ||
|
|
8111c84d12 | ||
|
|
a55e3f84dc | ||
|
|
221861eb9f | ||
|
|
e9e6c66ba1 | ||
|
|
d2da91a8bd | ||
|
|
ee5efc378e | ||
|
|
dbd06ee7b3 | ||
|
|
6077232cd3 | ||
|
|
f013373180 | ||
|
|
f0c1fcf516 | ||
|
|
9f392de9c5 | ||
|
|
f5fb340de9 | ||
|
|
0392bda03c |
Generated
+219
-46
@@ -609,6 +609,7 @@ dependencies = [
|
||||
"candle-core",
|
||||
"candle-nn",
|
||||
"candle-transformers",
|
||||
"cudarc",
|
||||
"filesys",
|
||||
"hf-hub",
|
||||
"image",
|
||||
@@ -1424,14 +1425,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "candle-core"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "855dfedff437d2681d68e1f34ae559d88b0dd84aa5a6b63f2c8e75ebdd875bbf"
|
||||
checksum = "bef79e00300acaaa6b0b33168e2ed5050f06125d1add87e7103c40a119083581"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"candle-kernels",
|
||||
"cudarc",
|
||||
"gemm",
|
||||
"gemm 0.17.1",
|
||||
"half",
|
||||
"memmap2",
|
||||
"num-traits",
|
||||
@@ -1449,9 +1450,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "candle-kernels"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53343628fa470b7075c28c589b98735b4220b464e37ddbb8e117040e199f4787"
|
||||
checksum = "e3134cc19a080a326f95bdcbe057ecaa9658598e5146e37ba1a8cd550c342a9b"
|
||||
dependencies = [
|
||||
"bindgen_cuda",
|
||||
]
|
||||
@@ -2228,9 +2229,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cudarc"
|
||||
version = "0.12.1"
|
||||
version = "0.13.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38cd60a9a42ec83a2ed7effb0b1f073270264ea99da7acfc44f7e8d74dee0384"
|
||||
checksum = "4e29ce3bfa797c1183053ceb496316203ef561c183941c3c181500d9ade6daf4"
|
||||
dependencies = [
|
||||
"half",
|
||||
"libloading 0.8.6",
|
||||
@@ -2891,6 +2892,15 @@ dependencies = [
|
||||
"reborrow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-stack"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490bd48eb68fffcfed519b4edbfd82c69cbe741d175b84f0e0cbe8c57cbe0bdd"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "easyenv"
|
||||
version = "0.1.0"
|
||||
@@ -3743,17 +3753,37 @@ version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32"
|
||||
dependencies = [
|
||||
"dyn-stack",
|
||||
"gemm-c32",
|
||||
"gemm-c64",
|
||||
"gemm-common",
|
||||
"gemm-f16",
|
||||
"gemm-f32",
|
||||
"gemm-f64",
|
||||
"dyn-stack 0.10.0",
|
||||
"gemm-c32 0.17.1",
|
||||
"gemm-c64 0.17.1",
|
||||
"gemm-common 0.17.1",
|
||||
"gemm-f16 0.17.1",
|
||||
"gemm-f32 0.17.1",
|
||||
"gemm-f64 0.17.1",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid",
|
||||
"raw-cpuid 10.7.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gemm"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab96b703d31950f1aeddded248bc95543c9efc7ac9c4a21fda8703a83ee35451"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"gemm-c32 0.18.2",
|
||||
"gemm-c64 0.18.2",
|
||||
"gemm-common 0.18.2",
|
||||
"gemm-f16 0.18.2",
|
||||
"gemm-f32 0.18.2",
|
||||
"gemm-f64 0.18.2",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid 11.4.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
@@ -3763,12 +3793,27 @@ version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0"
|
||||
dependencies = [
|
||||
"dyn-stack",
|
||||
"gemm-common",
|
||||
"dyn-stack 0.10.0",
|
||||
"gemm-common 0.17.1",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid",
|
||||
"raw-cpuid 10.7.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gemm-c32"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6db9fd9f40421d00eea9dd0770045a5603b8d684654816637732463f4073847"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"gemm-common 0.18.2",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid 11.4.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
@@ -3778,12 +3823,27 @@ version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a"
|
||||
dependencies = [
|
||||
"dyn-stack",
|
||||
"gemm-common",
|
||||
"dyn-stack 0.10.0",
|
||||
"gemm-common 0.17.1",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid",
|
||||
"raw-cpuid 10.7.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gemm-c64"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfcad8a3d35a43758330b635d02edad980c1e143dc2f21e6fd25f9e4eada8edf"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"gemm-common 0.18.2",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid 11.4.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
@@ -3794,17 +3854,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"dyn-stack",
|
||||
"dyn-stack 0.10.0",
|
||||
"half",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"pulp",
|
||||
"raw-cpuid",
|
||||
"pulp 0.18.22",
|
||||
"raw-cpuid 10.7.0",
|
||||
"rayon",
|
||||
"seq-macro",
|
||||
"sysctl",
|
||||
"sysctl 0.5.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gemm-common"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a352d4a69cbe938b9e2a9cb7a3a63b7e72f9349174a2752a558a8a563510d0f3"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"dyn-stack 0.13.0",
|
||||
"half",
|
||||
"libm",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"pulp 0.21.4",
|
||||
"raw-cpuid 11.4.0",
|
||||
"rayon",
|
||||
"seq-macro",
|
||||
"sysctl 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3813,14 +3894,32 @@ version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4"
|
||||
dependencies = [
|
||||
"dyn-stack",
|
||||
"gemm-common",
|
||||
"gemm-f32",
|
||||
"dyn-stack 0.10.0",
|
||||
"gemm-common 0.17.1",
|
||||
"gemm-f32 0.17.1",
|
||||
"half",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid",
|
||||
"raw-cpuid 10.7.0",
|
||||
"rayon",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gemm-f16"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff95ae3259432f3c3410eaa919033cd03791d81cebd18018393dc147952e109"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"gemm-common 0.18.2",
|
||||
"gemm-f32 0.18.2",
|
||||
"half",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid 11.4.0",
|
||||
"rayon",
|
||||
"seq-macro",
|
||||
]
|
||||
@@ -3831,12 +3930,27 @@ version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113"
|
||||
dependencies = [
|
||||
"dyn-stack",
|
||||
"gemm-common",
|
||||
"dyn-stack 0.10.0",
|
||||
"gemm-common 0.17.1",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid",
|
||||
"raw-cpuid 10.7.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gemm-f32"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc8d3d4385393304f407392f754cd2dc4b315d05063f62cf09f47b58de276864"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"gemm-common 0.18.2",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid 11.4.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
@@ -3846,12 +3960,27 @@ version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0"
|
||||
dependencies = [
|
||||
"dyn-stack",
|
||||
"gemm-common",
|
||||
"dyn-stack 0.10.0",
|
||||
"gemm-common 0.17.1",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid",
|
||||
"raw-cpuid 10.7.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gemm-f64"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35b2a4f76ce4b8b16eadc11ccf2e083252d8237c1b589558a49b0183545015bd"
|
||||
dependencies = [
|
||||
"dyn-stack 0.13.0",
|
||||
"gemm-common 0.18.2",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
"paste",
|
||||
"raw-cpuid 11.4.0",
|
||||
"seq-macro",
|
||||
]
|
||||
|
||||
@@ -5237,9 +5366,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
@@ -6238,7 +6367,7 @@ version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.1.0",
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
@@ -7323,6 +7452,20 @@ dependencies = [
|
||||
"reborrow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulp"
|
||||
version = "0.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95fb7a99b37aaef4c7dd2fd15a819eb8010bfc7a2c2155230d51f497316cad6d"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cfg-if 1.0.0",
|
||||
"libm",
|
||||
"num-complex",
|
||||
"reborrow",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
@@ -7636,6 +7779,15 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "529468c1335c1c03919960dfefdb1b3648858c20d7ec2d0663e728e4a717efbc"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -9774,6 +9926,20 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysctl"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"byteorder",
|
||||
"enum-as-inner",
|
||||
"libc",
|
||||
"thiserror 1.0.63",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
@@ -10846,27 +11012,34 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "ug"
|
||||
version = "0.0.2"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4eef2ebfc18c67a6dbcacd9d8a4d85e0568cc58c82515552382312c2730ea13"
|
||||
checksum = "03719c61a91b51541f076dfdba45caacf750b230cefaa4b32d6f5411c3f7f437"
|
||||
dependencies = [
|
||||
"gemm 0.18.2",
|
||||
"half",
|
||||
"libloading 0.8.6",
|
||||
"memmap2",
|
||||
"num",
|
||||
"num-traits",
|
||||
"num_cpus",
|
||||
"rayon",
|
||||
"safetensors",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.63",
|
||||
"tracing",
|
||||
"yoke",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ug-cuda"
|
||||
version = "0.0.2"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c4dcab280ad0ef3957e153a82dcad608c954d02cf253b695322f502d1f8902e"
|
||||
checksum = "50758486d7941f8b0a636ba7e29455c07071f41590beac1fd307ec893e8db69a"
|
||||
dependencies = [
|
||||
"cudarc",
|
||||
"half",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.63",
|
||||
"ug",
|
||||
]
|
||||
@@ -11192,9 +11365,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "videos"
|
||||
|
||||
@@ -23,6 +23,9 @@ tauri-build = { version = "2.0.4", features = [] }
|
||||
filesys = { path = "../../lib/files/filesys" }
|
||||
memory_caching = { path = "../../lib/caching/memory_caching" }
|
||||
|
||||
# Bypass cudarc issues on Windows https://github.com/coreylowman/cudarc/issues/332
|
||||
cudarc = "0.13.7"
|
||||
|
||||
# External
|
||||
anyhow = "1.0"
|
||||
bytes = "1.10"
|
||||
@@ -44,4 +47,10 @@ tokenizers = "0.15.2"
|
||||
|
||||
[features]
|
||||
default = ["cuda"]
|
||||
cuda = ["candle-core/cuda", "candle-nn/cuda", "candle-transformers/cuda"]
|
||||
cuda = [
|
||||
"candle-core/cuda",
|
||||
# TODO(bt,2025-02-22): cuDNN might speed up Stable Diffusion
|
||||
# "candle-core/cudnn",
|
||||
"candle-nn/cuda",
|
||||
"candle-transformers/cuda",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
use candle_core::shape::Dim;
|
||||
use candle_core::Tensor;
|
||||
|
||||
/// Tensor Cumulative Product
|
||||
///
|
||||
/// Candle doesn't implement cumprod, only cumsum.
|
||||
/// This implementation is adapted from cumsum in candle-ext:
|
||||
/// https://github.com/mokeyish/candle-ext/blob/main/tests/cumsum.rs
|
||||
///
|
||||
/// See Also: https://pytorch.org/docs/stable/generated/torch.cumprod.html
|
||||
/// See Also: https://numpy.org/doc/stable/reference/generated/numpy.cumprod.html
|
||||
/// See Also: https://github.com/huggingface/candle/issues/1646
|
||||
///
|
||||
pub fn cumprod<D: Dim>(input: &Tensor, dim: D) -> candle_core::Result<Tensor> {
|
||||
let dim = dim.to_index(input.shape(), "cumprod")?;
|
||||
let dim_size = input.dim(dim)?;
|
||||
let rank = input.rank();
|
||||
if rank == 0 {
|
||||
return Ok(input.clone());
|
||||
}
|
||||
|
||||
let mut tensors = Vec::with_capacity(dim_size);
|
||||
|
||||
let mut a = input.clone();
|
||||
for i in 0..dim_size {
|
||||
if i > 0 {
|
||||
a = a.narrow(dim, 1, dim_size - i)?;
|
||||
let b = input.narrow(dim, 0, dim_size - i)?;
|
||||
a = (a * b)?;
|
||||
}
|
||||
tensors.push(a.narrow(dim, 0, 1)?);
|
||||
}
|
||||
|
||||
let cumsum = Tensor::cat(&tensors, dim)?;
|
||||
Ok(cumsum)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ml::cumprod::cumprod;
|
||||
use candle_core::{Device, Tensor};
|
||||
|
||||
mod base_cases {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn zeroes_vector() -> anyhow::Result<()> {
|
||||
let a = Tensor::new(&[0i64, 0, 0, 0], &Device::Cpu)?;
|
||||
let b = cumprod(&a, 0)?;
|
||||
assert_eq!(b.to_vec1::<i64>()?, &[0, 0, 0, 0]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zeroes_tensor() -> anyhow::Result<()> {
|
||||
let a = Tensor::new(&[
|
||||
[0i64, 0, 0],
|
||||
[0i64, 0, 0],
|
||||
[0i64, 0, 0],
|
||||
], &Device::Cpu)?;
|
||||
|
||||
let b = cumprod(&a, 0)?;
|
||||
|
||||
assert_eq!(b.to_vec2::<i64>()?, &[
|
||||
[0, 0, 0],
|
||||
[0, 0, 0],
|
||||
[0, 0, 0],
|
||||
]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ones_vector() -> anyhow::Result<()> {
|
||||
let a = Tensor::new(&[1i64, 1, 1, 1], &Device::Cpu)?;
|
||||
let b = cumprod(&a, 0)?;
|
||||
assert_eq!(b.to_vec1::<i64>()?, &[1, 1, 1, 1]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ones_tensor() -> anyhow::Result<()> {
|
||||
let a = Tensor::new(&[
|
||||
[1i64, 1, 1],
|
||||
[1i64, 1, 1],
|
||||
[1i64, 1, 1],
|
||||
], &Device::Cpu)?;
|
||||
|
||||
let b = cumprod(&a, 0)?;
|
||||
|
||||
assert_eq!(b.to_vec2::<i64>()?, &[
|
||||
[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod test_cases {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn vector() -> anyhow::Result<()> {
|
||||
let a = Tensor::new(&[1i64, 2, 3, 4, 5], &Device::Cpu)?;
|
||||
let b = cumprod(&a, 0)?;
|
||||
assert_eq!(b.to_vec1::<i64>()?, &[1, 2, 6, 24, 120]);
|
||||
|
||||
let a = Tensor::new(&[0, 1i64, 2, 3, 4, 5], &Device::Cpu)?;
|
||||
let b = cumprod(&a, 0)?;
|
||||
assert_eq!(b.to_vec1::<i64>()?, &[0, 0, 0, 0, 0, 0]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tensor_dim_0() -> anyhow::Result<()> {
|
||||
let a = Tensor::new(&[
|
||||
[1i64, 2, 3],
|
||||
[4i64, 5, 6],
|
||||
[7i64, 8, 9],
|
||||
], &Device::Cpu)?;
|
||||
|
||||
let b = cumprod(&a, 0)?;
|
||||
|
||||
assert_eq!(b.to_vec2::<i64>()?, &[
|
||||
[1, 2, 3],
|
||||
[4, 10, 18],
|
||||
[28, 80, 162],
|
||||
]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tensor_dim_1() -> anyhow::Result<()> {
|
||||
let a = Tensor::new(&[
|
||||
[1i64, 2, 3],
|
||||
[4i64, 5, 6],
|
||||
[7i64, 8, 9],
|
||||
], &Device::Cpu)?;
|
||||
|
||||
let b = cumprod(&a, 1)?;
|
||||
|
||||
assert_eq!(b.to_vec2::<i64>()?, &[
|
||||
[1, 2, 6],
|
||||
[4, 20, 120],
|
||||
[7, 56, 504],
|
||||
]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
use anyhow::anyhow;
|
||||
use candle_core::{DType, Device, Shape, Tensor, WithDType};
|
||||
use candle_transformers::models::stable_diffusion::schedulers::Scheduler;
|
||||
use candle_transformers::models::stable_diffusion::utils::linspace;
|
||||
use log::error;
|
||||
|
||||
/*
|
||||
Notes on LCM implementation:
|
||||
|
||||
- Robust technical comparison of samplers: https://stable-diffusion-art.com/samplers/
|
||||
- Apple Swift LCM Implementation ticket: https://github.com/apple/ml-stable-diffusion/issues/319
|
||||
- Reference Swift Implementation: https://github.com/GuernikaCore/Schedulers/blob/main/Sources/Schedulers/LCMScheduler.swift
|
||||
|
||||
Candle is missing several "batteries" we'll have to implement, and some we might want to upstream:
|
||||
|
||||
- t[::-1] (syntax to reverse the tensor)
|
||||
|
||||
- torch/numpy.cumprod :
|
||||
- https://numpy.org/doc/stable/reference/generated/numpy.cumprod.html
|
||||
- https://pytorch.org/docs/stable/generated/torch.cumprod.html
|
||||
|
||||
Remaining work:
|
||||
|
||||
- [ ] implement add_noise
|
||||
- [ ] implement cumprod
|
||||
- [ ] initialize step_index / begin_index
|
||||
- [ ] find out how `set_timestamps` is called in diffusers and see if we want to change it to CTOR param in candle
|
||||
- [ ] try to plug this into candle and run
|
||||
- [ ] fix timesteps() method to cache values
|
||||
- [ ] compare with diffusers
|
||||
- [ ] compare with candle schedulers
|
||||
- [ ] compare with swift schedulers
|
||||
- [ ] test + unit test
|
||||
*/
|
||||
|
||||
struct LcmScheduler {
|
||||
alphas: Tensor,
|
||||
alphas_cumprod: Tensor,
|
||||
final_alpha_cumprod: Tensor,
|
||||
|
||||
betas: Tensor,
|
||||
|
||||
timesteps: Tensor,
|
||||
|
||||
// Standard deviation of the initial noise distribution
|
||||
// NB: Only in diffusers, and seems to only hold the value "1.0". Swift implementation omits it.
|
||||
init_noise_sigma: f64,
|
||||
|
||||
// TODO: This is set by the set_timesteps() method after construction in python.
|
||||
// We might want to handle this upfront in the constructor ourselves
|
||||
num_inference_steps: Option<usize>,
|
||||
|
||||
_step_index: Option<usize>,
|
||||
_begin_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl LcmScheduler {
|
||||
pub fn new(
|
||||
num_train_timestamps: i64,
|
||||
beta_start: f64,
|
||||
beta_end: f64,
|
||||
beta_schedule: &str,
|
||||
trained_betas: Option<Tensor>,
|
||||
set_alpha_to_one: bool,
|
||||
rescale_betas_zero_snr: bool,
|
||||
device: &Device,
|
||||
) -> anyhow::Result<Self> {
|
||||
let betas;
|
||||
|
||||
if let Some(trained) = trained_betas {
|
||||
betas = trained;
|
||||
} else {
|
||||
betas = match beta_schedule {
|
||||
"scaled_linear" => {
|
||||
// this schedule is very specific to the latent diffusion model.
|
||||
let t = linspace(
|
||||
beta_start.powf(0.5),
|
||||
beta_end.powf(0.5),
|
||||
num_train_timestamps as usize
|
||||
)?;
|
||||
t.to_device(device)?
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!("schedule {beta_schedule} is not implemented"))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if rescale_betas_zero_snr {
|
||||
return Err(anyhow!("rescale_betas_zero_snr is not used upstream"));
|
||||
}
|
||||
|
||||
let alphas = (1.0 - &betas)?;
|
||||
|
||||
// TODO: Implement cumprod: https://github.com/huggingface/candle/issues/1646
|
||||
// https://numpy.org/doc/stable/reference/generated/numpy.cumprod.html
|
||||
error!("THIS IS USING CUMSUM, NOT CUMPROD. WE NEED TO IMPLEMENT CUMPROD.");
|
||||
// self.alphas_cumprod = torch.cumprod(self.alphas, dim=0)
|
||||
let alphas_cumprod = alphas.cumsum(0)?;
|
||||
|
||||
// TODO: Check implementation
|
||||
// At every step in ddim, we are looking into the previous alphas_cumprod
|
||||
// For the final step, there is no previous alphas_cumprod because we are already at 0
|
||||
// `set_alpha_to_one` decides whether we set this parameter simply to one or
|
||||
// whether we use the final alpha of the "non-previous" one.
|
||||
let final_alpha_cumprod = if set_alpha_to_one {
|
||||
initialize_scalar_tensor(1.0, device)? // NB: `set_alpha_to_one` is false in python
|
||||
} else {
|
||||
alphas_cumprod.get(0)?
|
||||
};
|
||||
|
||||
// standard deviation of the initial noise distribution
|
||||
let init_noise_sigma = 1.0;
|
||||
|
||||
let timesteps = initialize_timesteps(num_train_timestamps, device)?;
|
||||
|
||||
Ok(Self {
|
||||
alphas,
|
||||
alphas_cumprod,
|
||||
final_alpha_cumprod,
|
||||
betas,
|
||||
init_noise_sigma,
|
||||
timesteps,
|
||||
num_inference_steps: None,
|
||||
_step_index: None,
|
||||
_begin_index: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_begin_index(&mut self, begin_index: usize) {
|
||||
self._begin_index = Some(begin_index);
|
||||
}
|
||||
|
||||
pub fn init_step_index(&mut self, timestep: usize) -> candle_core::Result<()> {
|
||||
match self._begin_index {
|
||||
None => {
|
||||
todo!("implement begin index none branch")
|
||||
}
|
||||
Some(begin_index) => {
|
||||
self._step_index = Some(begin_index);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_timesteps(&mut self, num_inference_steps: usize, device: &Device) -> candle_core::Result<()> {
|
||||
// NB: The diffusers version of this method passes `None` for `original_inference_steps` and `timesteps`.
|
||||
// It passes strength = 1.0, and config.original_inference_steps = 50.
|
||||
const STRENGTH : f64 = 1.0;
|
||||
const CONFIG_ORIGINAL_INFERENCE_STEPS : usize = 50;
|
||||
const CONFIG_NUM_TRAIN_TIMESTEPS : usize = 1000;
|
||||
|
||||
let original_steps = CONFIG_ORIGINAL_INFERENCE_STEPS;
|
||||
|
||||
// LCM Timesteps Setting
|
||||
// The skipping step parameter k from the paper.
|
||||
let k = (CONFIG_NUM_TRAIN_TIMESTEPS as f64 / original_steps as f64).floor();
|
||||
|
||||
let end = (original_steps as f64 * STRENGTH) as i64 + 1;
|
||||
let lcm_origin_timesteps = ((Tensor::arange::<i64>(1, end, device)? * k)? - 1.0)?;
|
||||
|
||||
if num_inference_steps > CONFIG_NUM_TRAIN_TIMESTEPS {
|
||||
return Err(candle_core::Error::Msg("num_inference_steps cannot be larger than train timesteps".to_string()));
|
||||
}
|
||||
|
||||
let skipping_step = (lcm_origin_timesteps.dims().len() as f64 / num_inference_steps as f64).floor() as usize;
|
||||
|
||||
self.num_inference_steps = Some(num_inference_steps);
|
||||
|
||||
// TODO: This is not efficient
|
||||
let lcm_origin_timesteps = lcm_origin_timesteps.to_vec1::<i64>()?
|
||||
.into_iter()
|
||||
.map(i64::from)
|
||||
.rev()
|
||||
.collect::<Vec<i64>>();
|
||||
|
||||
// TODO: This might not be exact. We should unit test this
|
||||
let inference_indices = linspace(0.0, lcm_origin_timesteps.len() as f64, num_inference_steps)?;
|
||||
|
||||
let inference_indices = inference_indices.floor()?
|
||||
.to_dtype(DType::I64)?;
|
||||
|
||||
let timestamps = {
|
||||
// TODO: This code is awful
|
||||
let inference_indices_t: Vec<usize> = inference_indices.to_vec1::<i64>()?
|
||||
.into_iter()
|
||||
.map(|n| n as usize)
|
||||
.collect();
|
||||
|
||||
let mut timesteps: Vec<i64> = Vec::with_capacity(inference_indices.dims().len());
|
||||
|
||||
for i in inference_indices_t {
|
||||
if let Some(val) = lcm_origin_timesteps.get(i) {
|
||||
timesteps.push(*val);
|
||||
}
|
||||
}
|
||||
|
||||
let shape = Shape::from_dims(&[timesteps.len()]);
|
||||
|
||||
Tensor::from_vec(timesteps, shape, device)?
|
||||
.to_dtype(DType::I64)?
|
||||
.to_device(device)?
|
||||
};
|
||||
|
||||
self.timesteps = timestamps;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Scheduler for LcmScheduler {
|
||||
fn timesteps(&self) -> &[usize] {
|
||||
// TODO: We can't use the implementation from DDIM. This method requires
|
||||
// (1) an infallible response and (2) a reference we can't dynamically allocate. We must
|
||||
// cache this somewhere.
|
||||
&[]
|
||||
}
|
||||
|
||||
fn add_noise(&self, original_samples: &Tensor, noise: Tensor, timestep: usize) -> candle_core::Result<Tensor> {
|
||||
// TODO: Make sure alphas_cumprod and timestep have same device and dtype as original_samples
|
||||
// Move the self.alphas_cumprod to device to avoid redundant CPU to GPU data movement
|
||||
// for the subsequent add_noise calls
|
||||
let mut alphas_cumprod = self.alphas_cumprod.to_device(original_samples.device())?;
|
||||
alphas_cumprod = alphas_cumprod.to_dtype(original_samples.dtype())?;
|
||||
//let timesteps = timesteps.to_device(original_samples.device())?;
|
||||
|
||||
let sqrt_alpha_prod = alphas_cumprod.get(timestep)?.powf(0.5)?;
|
||||
let mut sqrt_alpha_prod = sqrt_alpha_prod.flatten_all()?; // TODO: Verify that flatten_all works here
|
||||
|
||||
|
||||
while sqrt_alpha_prod.dims().len() < original_samples.dims().len() {
|
||||
// TODO: Verify that this works for `unsqueeze(-1)`
|
||||
sqrt_alpha_prod = sqrt_alpha_prod.unsqueeze(sqrt_alpha_prod.dims().len())?;
|
||||
}
|
||||
|
||||
let sqrt_one_minus_alpha_prod = (1.0 - alphas_cumprod.get(timestep)?)?.powf(0.5)?;
|
||||
let mut sqrt_one_minus_alpha_prod = sqrt_one_minus_alpha_prod.flatten_all()?; // TODO: Verify flatten all works here.
|
||||
|
||||
while sqrt_one_minus_alpha_prod.dims().len() < original_samples.dims().len() {
|
||||
// TODO: Verify that this works for `unsqueeze(-1)`
|
||||
sqrt_one_minus_alpha_prod = sqrt_one_minus_alpha_prod.unsqueeze(sqrt_one_minus_alpha_prod.dims().len())?;
|
||||
}
|
||||
|
||||
let noisy_samples = ((sqrt_alpha_prod * original_samples)? + (sqrt_one_minus_alpha_prod * noise)?)?;
|
||||
|
||||
Ok(noisy_samples)
|
||||
}
|
||||
|
||||
fn init_noise_sigma(&self) -> f64 {
|
||||
self.init_noise_sigma
|
||||
}
|
||||
|
||||
/// Ensures interchangeability with schedulers that need to scale the denoising model input
|
||||
/// depending on the current timestep. (For LCM, this is a pass through no-op!)
|
||||
fn scale_model_input(&self, sample: Tensor, _timestep: usize) -> candle_core::Result<Tensor> {
|
||||
Ok(sample) // NB: The diffusers scheduling_lcm.py implementation is a pass through no-op !
|
||||
}
|
||||
|
||||
/// Predict the sample from the previous timestep by reversing the SDE. This function
|
||||
/// propagates the diffusion process from the learned model outputs (most often the
|
||||
/// predicted noise).
|
||||
fn step(&mut self, model_output: &Tensor, timestep: usize, sample: &Tensor) -> candle_core::Result<Tensor> {
|
||||
|
||||
if self.num_inference_steps.is_none() {
|
||||
return Err(candle_core::Error::Msg(
|
||||
"Number of inference steps is 'None', you need to run 'set_timesteps' after creating the scheduler"
|
||||
.to_string()));
|
||||
}
|
||||
|
||||
if self._step_index.is_none() {
|
||||
self.init_step_index(timestep)?;
|
||||
}
|
||||
|
||||
let step_index = self._step_index.clone().expect("unsafe unwrap; should exist per above");
|
||||
|
||||
// 1. Get previous step value
|
||||
let prev_step_index = step_index + 1;
|
||||
let prev_timestep;
|
||||
|
||||
if prev_step_index < self.timesteps.elem_count() {
|
||||
prev_timestep = self.timesteps.get(timestep)?.to_scalar::<i64>()? as usize;
|
||||
} else {
|
||||
prev_timestep = timestep;
|
||||
}
|
||||
|
||||
// 2. Compute alphas, betas
|
||||
let alpha_prod_t = self.alphas_cumprod.get(timestep)?;
|
||||
let alpha_prod_t_prev = if prev_timestep >= 0 {
|
||||
self.alphas_cumprod.get(prev_timestep)?
|
||||
} else {
|
||||
self.final_alpha_cumprod.clone()
|
||||
};
|
||||
|
||||
let beta_prod_t = (1. - &alpha_prod_t)?;
|
||||
let beta_prod_t_prev = (1. - &alpha_prod_t_prev)?;
|
||||
|
||||
// 3. Get scaling for boundary conditions
|
||||
// NB: timestep_scaling seems to be always set to 10.0 in diffusers examples, but it is passed
|
||||
// via a config object. Hardcoding this for now until we need to change it.
|
||||
const TIMESTEP_SCALING : f64 = 10.0;
|
||||
let (c_skip, c_out) = get_scalings_for_boundary_condition_discrete(timestep, TIMESTEP_SCALING);
|
||||
|
||||
// 4. Compute the predicted original sample x_0 based on the model parameterization
|
||||
// NB: Prediction type is "epsilon" in the diffusers examples, but that is configurable.
|
||||
// This is the only one we'll implement for now.
|
||||
let predicted_original_sample = ((sample - (beta_prod_t.sqrt()? * model_output)?)? / &alpha_prod_t.sqrt()?)?;
|
||||
|
||||
// 5. (skip clip/threshold - not used in diffusers example)
|
||||
|
||||
// 6. Denoise model output using boundary conditions
|
||||
let denoised = ((c_out * predicted_original_sample)? + (c_skip * sample)?)?;
|
||||
|
||||
// TODO: Step 7. We have to return the previous example somehow.
|
||||
// Diffusers tolerates it not being sent back, but the quality degrades as if one step is absent.
|
||||
Ok(denoised)
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_scalar_tensor<D: WithDType>(value: D, device: &Device) -> anyhow::Result<Tensor> {
|
||||
let scalar = Tensor::from_vec(vec![value], Shape::from(vec![]), device)?;
|
||||
Ok(scalar)
|
||||
}
|
||||
|
||||
// TODO(bt,2025-02-23): Use native implementations
|
||||
fn initialize_timesteps(num_train_timesteps: i64, device: &Device) -> anyhow::Result<Tensor> {
|
||||
// Testing LCM scheduler assumptions:
|
||||
// [::-1] is shorthand for reversing a numpy vector, eg. [0, 1,2,3,4] -> [4, 3, 2, 1, 0]
|
||||
// self.timesteps = torch.from_numpy(np.arange(0, num_train_timesteps)[::-1].copy().astype(np.int64))
|
||||
//let tensor = Tensor::arange::<i64>(0, 5, &Device::Cpu)?; // This works, but we can't reverse/flip it!
|
||||
|
||||
// https://github.com/tracel-ai/burn/pull/1468 flip impl? Will this work in place?
|
||||
// https://github.com/huggingface/candle/issues/1875
|
||||
|
||||
let data = (0..num_train_timesteps)
|
||||
.map(i64::from)
|
||||
.rev()
|
||||
.collect();
|
||||
|
||||
let shape = Shape::from_dims(&[num_train_timesteps as usize]);
|
||||
|
||||
let tensor = Tensor::from_vec(data, shape, device)?;
|
||||
|
||||
Ok(tensor)
|
||||
}
|
||||
|
||||
fn get_scalings_for_boundary_condition_discrete(timestep: usize, timestep_scaling: f64) -> (f64, f64) {
|
||||
let sigma_data = 0.5f64; // Default 0.5
|
||||
let scaled_timestep = timestep as f64 * timestep_scaling;
|
||||
|
||||
let c_skip = sigma_data.powf(2.0) / (scaled_timestep.powf(2.0) + sigma_data.powf(2.0));
|
||||
let c_out = scaled_timestep / (scaled_timestep.powf(2.0) + sigma_data.powf(2.0)).powf(0.5);
|
||||
|
||||
(c_skip, c_out)
|
||||
}
|
||||
|
||||
// The Candle sources, and moreover, the Candle tests, are a great source of information for how the library works.
|
||||
//assert_eq!(t.i(..-1)?, 1.0);
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ml::lcm_scheduler::{initialize_scalar_tensor, initialize_timesteps};
|
||||
use crate::ml::lcm_scheduler::tests::test_helper_fn::*;
|
||||
use candle_core::Device;
|
||||
|
||||
mod helper_function_unit_tests {
|
||||
use crate::ml::lcm_scheduler::get_scalings_for_boundary_condition_discrete;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_initialize_scalar_tensor() -> anyhow::Result<()> {
|
||||
let t = initialize_scalar_tensor(1.0, &Device::Cpu)?;
|
||||
let empty: [usize; 0] = [];
|
||||
assert_eq!(t.dims(), &empty);
|
||||
assert_eq!(t.to_vec0::<f64>()?, 1.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_initialize_timesteps() -> anyhow::Result<()> {
|
||||
let tensor = initialize_timesteps(5, &Device::Cpu)?;
|
||||
assert_eq!(tensor.to_vec1::<i64>()?, &[4, 3, 2, 1,0]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_scalings_for_boundary_condition_discrete() -> anyhow::Result<()> {
|
||||
// Base cases
|
||||
let v = get_scalings_for_boundary_condition_discrete(0, 0.0);
|
||||
assert_eq!(v, (1.0, 0.0));
|
||||
let v = get_scalings_for_boundary_condition_discrete(1, 0.0);
|
||||
assert_eq!(v, (1.0, 0.0));
|
||||
let v = get_scalings_for_boundary_condition_discrete(0, 1.0);
|
||||
assert_eq!(v, (1.0, 0.0));
|
||||
|
||||
// Production code has "timestep_scaling" = 10.0
|
||||
let v = get_scalings_for_boundary_condition_discrete(0, 10.0);
|
||||
assert_eq!(v, (1.0, 0.0));
|
||||
|
||||
// Production code has "timestep_scaling" = 10.0
|
||||
let v = get_scalings_for_boundary_condition_discrete(1, 10.0);
|
||||
assert!((v.0 - 0.0024937655860349127).abs() < 0.01);
|
||||
assert!((v.1 - 0.9987523388778445).abs() < 0.01);
|
||||
|
||||
// TODO: More test cases -
|
||||
//let v = get_scalings_for_boundary_condition_discrete(10, 10.0);
|
||||
//let v = get_scalings_for_boundary_condition_discrete(100, 10.0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod confirm_various_pytorch_behaviors {
|
||||
use candle_core::IndexOp;
|
||||
use super::*;
|
||||
|
||||
// NB: Non-unit tests, just to test understanding while porting code
|
||||
|
||||
#[test]
|
||||
fn unsqueeze_zero() -> anyhow::Result<()> {
|
||||
let t = tensor_1234()?;
|
||||
let x = t.unsqueeze(0)?;
|
||||
assert_eq!(x.to_vec2::<i64>()?, &[&[1, 2, 3, 4]]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsqueeze_simulating_negative_one() -> anyhow::Result<()> {
|
||||
let t = tensor_1234()?;
|
||||
// NB: It looks like this matches the behavior of unsqueeze(-1)
|
||||
// Per: https://pytorch.org/docs/stable/generated/torch.unsqueeze.html
|
||||
let x = t.unsqueeze(t.dims().len())?;
|
||||
assert_eq!(x.to_vec2::<i64>()?, &[&[1],&[2],&[3],&[4]]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_minus_vector() -> anyhow::Result<()> {
|
||||
let t = tensor_01234()?;
|
||||
let x = (1. - t)?;
|
||||
assert_eq!(x.rank(), 1); // NB: 1-dimensional output
|
||||
assert_eq!(x.to_vec1::<i64>()?, &[1, 0, -1, -2, -3]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn reverse_vector() -> anyhow::Result<()> {
|
||||
// let t = tensor_1234()?;
|
||||
// let x = t.i(5..0)?;
|
||||
// assert_eq!(x.rank(), 1); // NB: 1-dimensional output
|
||||
// assert_eq!(x.to_vec1::<i64>()?, &[1, 0, -1, -2, -3]);
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
|
||||
pub mod test_helper_fn {
|
||||
use candle_core::{Device, Shape, Tensor};
|
||||
|
||||
pub fn tensor_1234() -> anyhow::Result<Tensor> {
|
||||
// Return an example tensor
|
||||
let data: Vec<i64> = vec![1, 2, 3, 4];
|
||||
let t = Tensor::from_vec(data, Shape::from(vec![4]), &Device::Cpu)?;
|
||||
assert_eq!(t.to_vec1::<i64>()?, &[1, 2, 3, 4]);
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
pub fn tensor_01234() -> anyhow::Result<Tensor> {
|
||||
// Return an example tensor
|
||||
let data: Vec<i64> = vec![0, 1, 2, 3, 4];
|
||||
let t = Tensor::from_vec(data, Shape::from(vec![5]), &Device::Cpu)?;
|
||||
assert_eq!(t.to_vec1::<i64>()?, &[0, 1, 2, 3, 4]);
|
||||
Ok(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod cumprod;
|
||||
pub mod image;
|
||||
pub mod lcm_scheduler;
|
||||
pub mod model_cache;
|
||||
pub mod model_file;
|
||||
pub mod models;
|
||||
|
||||
Generated
+10
@@ -4525,6 +4525,8 @@
|
||||
},
|
||||
"node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
@@ -5265,6 +5267,8 @@
|
||||
},
|
||||
"node_modules/npm/node_modules/cross-spawn/node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "ISC",
|
||||
@@ -5313,6 +5317,8 @@
|
||||
},
|
||||
"node_modules/npm/node_modules/debug/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT"
|
||||
@@ -7139,6 +7145,8 @@
|
||||
},
|
||||
"node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
@@ -7176,6 +7184,8 @@
|
||||
},
|
||||
"node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
@@ -27,8 +27,9 @@ import { NavigateFunction } from "react-router-dom";
|
||||
import { LoadingVideosProvider } from "./EngineUtitlities/LoadingVideosProvider";
|
||||
|
||||
import { VideoExtractionHandler } from "./EngineUtitlities/VideoExtractionHandler/VideoExtractionHandler";
|
||||
import { RealTimeDrawEngine, ShapeType } from "./RenderingPrimitives/RealTimeDrawEngine";
|
||||
|
||||
import { RealTimeDrawEngine } from "./RenderingPrimitives/RealTimeDrawEngine";
|
||||
import { NodeColor } from "~/signals/uiEvents/toolbarNode";
|
||||
import { ShapeType } from "./Nodes";
|
||||
// for testing loading files from system
|
||||
// import { FileUtilities } from "./FileUtilities/FileUtilities";
|
||||
|
||||
@@ -180,8 +181,8 @@ export class Engine {
|
||||
this.setupEventSystem();
|
||||
this.setAppMode(AppModes.SELECT);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -364,7 +365,7 @@ export class Engine {
|
||||
// Listen to Toolbar Main
|
||||
uiEvents.toolbarMain.UNDO.onClick(() => this.undoStackManager.undo());
|
||||
uiEvents.toolbarMain.REDO.onClick(() => this.undoStackManager.redo());
|
||||
|
||||
|
||||
uiEvents.toolbarMain.SAVE.onClick(async (/*event*/) => {
|
||||
await this.realTimeDrawEngine.saveOutput();
|
||||
|
||||
@@ -398,7 +399,7 @@ export class Engine {
|
||||
this.addImage(image);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
uiEvents.onAddTextToEngine((textdata) => {
|
||||
this.addText(textdata);
|
||||
@@ -430,7 +431,7 @@ export class Engine {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
uiEvents.promptEvents.onPromptStrengthChanged((strength) => {
|
||||
this.realTimeDrawEngine.currentStrength = strength;
|
||||
});
|
||||
@@ -439,21 +440,32 @@ export class Engine {
|
||||
this.realTimeDrawEngine.currentPrompt = prompt;
|
||||
});
|
||||
|
||||
uiEvents.onAddShapeToEngine((shapeData)=> {
|
||||
|
||||
uiEvents.onAddShapeToEngine((shapeData) => {
|
||||
switch (shapeData.shape) {
|
||||
case "circle":
|
||||
this.addShape(ShapeType.Circle, 100);
|
||||
this.addShape(ShapeType.CIRCLE, 100);
|
||||
break;
|
||||
case "square":
|
||||
this.addShape(ShapeType.Square, 100);
|
||||
case "square":
|
||||
this.addShape(ShapeType.SQUARE, 100);
|
||||
break;
|
||||
case "triangle":
|
||||
this.addShape(ShapeType.Triangle, 100);
|
||||
this.addShape(ShapeType.TRIANGLE, 100);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
uiEvents.toolbarNode.color.onConfirmChanged((nodeColor) => {
|
||||
console.log("Color change event triggered with:", nodeColor);
|
||||
if (!nodeColor) {
|
||||
console.warn("nodeColor is undefined or null");
|
||||
return;
|
||||
}
|
||||
if (!nodeColor.kNodeId) {
|
||||
console.warn("nodeColor.kNodeId is undefined or null");
|
||||
return;
|
||||
}
|
||||
this.changeNodeColor(nodeColor);
|
||||
})
|
||||
}
|
||||
|
||||
disableAllButtons() {
|
||||
@@ -489,7 +501,7 @@ export class Engine {
|
||||
}
|
||||
|
||||
// Sandbox is quickly a way to test your idea.
|
||||
public async sandbox() {}
|
||||
public async sandbox() { }
|
||||
|
||||
public onMessage(event: MessageEvent) {
|
||||
console.log("Message From Shared Worker");
|
||||
@@ -536,6 +548,40 @@ export class Engine {
|
||||
this.addKeyboardShortcuts();
|
||||
}
|
||||
|
||||
private changeNodeColor(nodeColor: NodeColor) {
|
||||
console.log('Changing node color:', nodeColor);
|
||||
// Find the node that is selected with the id
|
||||
const idSelector = '#' + nodeColor.kNodeId;
|
||||
const selectedNode = this.mediaLayer.findOne(idSelector);
|
||||
console.log('Selected node:', selectedNode);
|
||||
console.log('ID selector:', idSelector);
|
||||
console.log('Media layer:', this.mediaLayer);
|
||||
// TODO: Change the color of the node
|
||||
// Turns out shapes are image nodes... how do we change the image's fill??
|
||||
if (selectedNode) {
|
||||
// Get the shape node from the real time drawing engine's image nodes
|
||||
const shapeNode = this.realTimeDrawEngine.findImageNodeById(nodeColor.kNodeId);
|
||||
if (shapeNode && shapeNode instanceof ShapeNode) {
|
||||
// Create new shape node with updated color
|
||||
const newShapeNode = new ShapeNode({
|
||||
canvasPosition: this.realTimeDrawEngine.captureCanvas.position(),
|
||||
canvasSize: this.realTimeDrawEngine.captureCanvas.size(),
|
||||
shapeType: shapeNode.shapeType,
|
||||
size: shapeNode.size,
|
||||
color: nodeColor.color,
|
||||
mediaLayerRef: this.mediaLayer,
|
||||
selectionManagerRef: this.selectionManager
|
||||
});
|
||||
newShapeNode.kNode.position(shapeNode.kNode.position());
|
||||
newShapeNode.kNode.zIndex(shapeNode.kNode.zIndex());
|
||||
|
||||
// Remove old node and add new one
|
||||
shapeNode.kNode.destroy();
|
||||
this.commandManager.createNode(newShapeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public addText(textNodeData: TextNodeData) {
|
||||
const textNode = new TextNode({
|
||||
textNodeData: textNodeData,
|
||||
@@ -558,8 +604,10 @@ export class Engine {
|
||||
selectionManagerRef: this.selectionManager
|
||||
});
|
||||
shapeNode.kNode.zIndex(1); // Replace desiredZIndex with a number
|
||||
|
||||
|
||||
this.commandManager.createNode(shapeNode);
|
||||
console.debug("Added shapenode:", shapeNode);
|
||||
console.debug("Added node's ID:", shapeNode.kNode._id);
|
||||
//this.realTimeDrawEngine.addNodes(shapeNode);
|
||||
}
|
||||
|
||||
@@ -617,7 +665,7 @@ export class Engine {
|
||||
if (
|
||||
longestVideoNode === undefined ||
|
||||
node.videoComponent.duration >
|
||||
longestVideoNode.videoComponent.duration
|
||||
longestVideoNode.videoComponent.duration
|
||||
) {
|
||||
longestVideoNode = node;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,10 @@ export class CommandManager {
|
||||
renderEngineRef: this.renderEngineRef,
|
||||
});
|
||||
this.undoStackManagerRef.executeCommand(command);
|
||||
|
||||
// Set the kNode's manual id to same as kNode's internal _id
|
||||
// This helps identify the kNode in the Konva layer with find
|
||||
node.kNode.id(node.kNode._id.toString());
|
||||
}
|
||||
deleteNodes() {
|
||||
const nodes = this.selectionManagerRef.getSelectedNodes();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Konva from "konva";
|
||||
import { SelectionManager } from "../NodesManagers";
|
||||
import { Position, Size, NodeData, TransformationData } from "../types";
|
||||
import { BaseNode } from "./BaseNode";
|
||||
import { NodeType } from "./constants";
|
||||
import { NodeUtilities } from "./NodeUtilities";
|
||||
import { NetworkedNode } from "./NetworkedNode";
|
||||
@@ -24,6 +23,13 @@ interface ShapeNodeConstructor {
|
||||
}
|
||||
|
||||
export class ShapeNode extends NetworkedNode {
|
||||
|
||||
retry(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
public shapeType: ShapeType;
|
||||
public size: number;
|
||||
|
||||
constructor({
|
||||
canvasPosition,
|
||||
@@ -159,6 +165,9 @@ export class ShapeNode extends NetworkedNode {
|
||||
kNode: shape,
|
||||
});
|
||||
|
||||
this.shapeType = shapeType;
|
||||
this.size = size;
|
||||
|
||||
// Add shape to group
|
||||
const centerPosition = NodeUtilities.positionNodeOnCanvasCenter({
|
||||
canvasOffset: canvasPosition,
|
||||
|
||||
@@ -3,4 +3,4 @@ export { BaseNode } from "./BaseNode";
|
||||
export { ImageNode } from "./ImageNode";
|
||||
export { VideoNode } from "./VideoNode";
|
||||
export { TextNode } from "./TextNode";
|
||||
export { ShapeNode } from "./ShapeNode";
|
||||
export { ShapeNode , ShapeType} from "./ShapeNode";
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
calculateContextualsPosition,
|
||||
getImageNodeButtonStates,
|
||||
getMultiSelectButtonStates,
|
||||
getShapeNodeButtonStates,
|
||||
getTextNodeButtonStates,
|
||||
getVideoNodeButtonStates,
|
||||
} from "./ToolbarNodeUtilities";
|
||||
@@ -271,22 +272,31 @@ export class SelectionManager {
|
||||
return; // no node
|
||||
}
|
||||
if (this.selectedNodes.size === 1) {
|
||||
if (node instanceof ImageNode || node instanceof ShapeNode) {
|
||||
if (node instanceof ImageNode) {
|
||||
showOrUpdate({
|
||||
knodeIds: [node.kNode.id()],
|
||||
locked: node.isLocked(),
|
||||
buttonStates: getImageNodeButtonStates({ locked: node.isLocked() }),
|
||||
});
|
||||
}
|
||||
else if (node instanceof ShapeNode) {
|
||||
console.debug("Selected node is of type ShapeNode", node.kNode.id())
|
||||
showOrUpdate({
|
||||
knodeIds: [node.kNode.id()],
|
||||
locked: node.isLocked(),
|
||||
buttonStates: getShapeNodeButtonStates({ locked: node.isLocked() }),
|
||||
color: node.getNodeData({ x: 0, y: 0 })?.textNodeData?.color ?? "#000000",
|
||||
});
|
||||
}
|
||||
if (node instanceof TextNode) {
|
||||
else if (node instanceof TextNode) {
|
||||
showOrUpdate({
|
||||
// TODO: Add ID to identify/mutate the color?
|
||||
knodeIds: [node.kNode.id()],
|
||||
locked: node.isLocked(),
|
||||
buttonStates: getTextNodeButtonStates({ locked: node.isLocked() }),
|
||||
|
||||
color: node.getNodeData({ x: 0, y: 0 })?.textNodeData?.color ?? "#000000",
|
||||
});
|
||||
}
|
||||
if (node instanceof VideoNode) {
|
||||
else if (node instanceof VideoNode) {
|
||||
showOrUpdate({
|
||||
locked: node.isLocked(),
|
||||
buttonStates: getVideoNodeButtonStates({ locked: node.isLocked() }),
|
||||
@@ -317,7 +327,7 @@ export class SelectionManager {
|
||||
this.hideContextComponents();
|
||||
return; // no nodes
|
||||
}
|
||||
|
||||
|
||||
// console.log("SelectionManager > updateContextComponents for node:", node);
|
||||
const coord = calculateContextualsPosition(
|
||||
this.nodeTransformerRef.getKonvaNode(),
|
||||
|
||||
@@ -152,6 +152,50 @@ export function getVideoNodeButtonStates(
|
||||
{} as ContextualToolbarProps["buttonStates"],
|
||||
);
|
||||
}
|
||||
|
||||
export function getShapeNodeButtonStates(
|
||||
props: { locked?: boolean | "unknown" } = {},
|
||||
) {
|
||||
const ButtonNames = ToolbarNodeButtonNames;
|
||||
return Object.values(ButtonNames).reduce(
|
||||
(buttonStates, buttonName) => {
|
||||
// transform button depends on locked state
|
||||
if (props.locked !== undefined && buttonName === ButtonNames.TRANSFORM) {
|
||||
buttonStates.TRANSFORM = {
|
||||
disabled: props.locked === "unknown" || props.locked === true,
|
||||
hidden: false,
|
||||
active: true,
|
||||
};
|
||||
return buttonStates;
|
||||
}
|
||||
|
||||
// hidden buttons
|
||||
if (
|
||||
buttonName === ButtonNames.AI_STYLIZE ||
|
||||
buttonName === ButtonNames.SEGMENTATION ||
|
||||
buttonName === ButtonNames.DOWNLOAD ||
|
||||
buttonName === ButtonNames.CHROMA
|
||||
) {
|
||||
buttonStates[buttonName] = {
|
||||
disabled: true,
|
||||
hidden: true,
|
||||
active: false,
|
||||
};
|
||||
return buttonStates;
|
||||
}
|
||||
|
||||
// all other buttons
|
||||
buttonStates[buttonName] = {
|
||||
disabled: false,
|
||||
hidden: false,
|
||||
active: false,
|
||||
};
|
||||
return buttonStates;
|
||||
},
|
||||
{} as ContextualToolbarProps["buttonStates"],
|
||||
);
|
||||
}
|
||||
|
||||
export function getMultiSelectButtonStates(
|
||||
props: { locked?: boolean | "unknown" } = {},
|
||||
) {
|
||||
|
||||
@@ -6,18 +6,14 @@ import { Group } from "konva/lib/Group";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
import { FileUtilities } from "../FileUtilities/FileUtilities";
|
||||
import { ImageNode, VideoNode, TextNode, ShapeNode } from "../Nodes";
|
||||
import { ImageNode, VideoNode, TextNode, ShapeNode, ShapeType } from "../Nodes";
|
||||
import { MediaNode } from "../types";
|
||||
|
||||
import { RenderTask } from "./RenderTask";
|
||||
import { OffScreenSceneCanvas } from "./OffScreenSceneCanvas";
|
||||
|
||||
// https://www.aiseesoft.com/resource/phone-aspect-ratio-screen-resolution.html#:~:text=16%3A9%20Aspect%20Ratio
|
||||
export enum ShapeType {
|
||||
Square = "square",
|
||||
Triangle = "triangle",
|
||||
Circle = "circle"
|
||||
}
|
||||
|
||||
export class RealTimeDrawEngine {
|
||||
private videoNodes: VideoNode[];
|
||||
private imageNodes: (ImageNode | TextNode | ShapeNode)[];
|
||||
@@ -80,7 +76,6 @@ export class RealTimeDrawEngine {
|
||||
this.offScreenCanvas.width = this.width;
|
||||
this.offScreenCanvas.height = this.height;
|
||||
|
||||
|
||||
// this is the whole canvas
|
||||
this.mediaLayerRef = mediaLayerRef;
|
||||
|
||||
@@ -123,8 +118,79 @@ export class RealTimeDrawEngine {
|
||||
// send back
|
||||
this.captureCanvas.setZIndex(0);
|
||||
this.previewCanvas.setZIndex(1);
|
||||
// Add mouse events for preview canvas copying
|
||||
//this.previewCopyListener();
|
||||
|
||||
}
|
||||
|
||||
public previewCopyListener() {
|
||||
this.previewCanvas.on('mousedown touchstart', () => {
|
||||
if (!this.outputBitmap) {
|
||||
console.log("No preview image to copy");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO create it as a node.
|
||||
|
||||
// const imageNode = new ImageNode({
|
||||
// mediaLayerRef: this.mediaLayer,
|
||||
// canvasPosition: this.renderEngine.captureCanvas.position(),
|
||||
// canvasSize: this.renderEngine.captureCanvas.size(),
|
||||
// imageFile: imageFile,
|
||||
// selectionManagerRef: this.selectionManager,
|
||||
// });
|
||||
|
||||
// Create draggable preview copy
|
||||
const previewCopy = new Konva.Image({
|
||||
x: this.previewCanvas.x(),
|
||||
y: this.previewCanvas.y(),
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
image: this.outputBitmap,
|
||||
draggable: true,
|
||||
listening: true
|
||||
});
|
||||
|
||||
this.mediaLayerRef.add(previewCopy);
|
||||
previewCopy.moveToTop();
|
||||
this.mediaLayerRef.draw();
|
||||
|
||||
// Start dragging immediately
|
||||
previewCopy.startDrag();
|
||||
|
||||
// Handle drag events
|
||||
previewCopy.on('dragmove', () => {
|
||||
this.mediaLayerRef.draw();
|
||||
});
|
||||
|
||||
previewCopy.on('dragend', () => {
|
||||
const previewBox = previewCopy.getClientRect();
|
||||
const captureBox = this.captureCanvas.getClientRect();
|
||||
|
||||
if (Konva.Util.haveIntersection(previewBox, captureBox)) {
|
||||
// Snap to capture canvas position
|
||||
previewCopy.position({
|
||||
x: this.captureCanvas.x(),
|
||||
y: this.captureCanvas.y()
|
||||
});
|
||||
this.mediaLayerRef.batchDraw();
|
||||
} else {
|
||||
// Remove if dropped outside capture area
|
||||
previewCopy.destroy();
|
||||
this.mediaLayerRef.batchDraw();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public findImageNodeById(id: string): (ImageNode | TextNode | ShapeNode | undefined) {
|
||||
return this.imageNodes.find(node => {
|
||||
if (node.kNode) {
|
||||
return node.kNode.id() === id;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
async updateCaptureCanvas(
|
||||
width: number | undefined,
|
||||
height: number | undefined,
|
||||
@@ -467,7 +533,7 @@ export class RealTimeDrawEngine {
|
||||
})) as ImageBitmap;
|
||||
|
||||
// Test code
|
||||
if (true) {
|
||||
if (false) {
|
||||
this.outputBitmap = bitmap;
|
||||
this.previewCanvas.image(bitmap);
|
||||
this.isProcessing = false;
|
||||
|
||||
@@ -26,6 +26,10 @@ export type VideoNodeData = {
|
||||
extractionUrl?: string;
|
||||
};
|
||||
|
||||
export type ShapeNodeData = {
|
||||
shape: "triangle" | "square" | "circle";
|
||||
}
|
||||
|
||||
export type NodeData = {
|
||||
type: NodeType;
|
||||
transform: TransformationData;
|
||||
|
||||
@@ -50,6 +50,20 @@ export const ContextualToolbarNode = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleColorChange = (color: string) => {
|
||||
// Don't do anything if no node is known to be bound
|
||||
if (uiAccess.toolbarNode.signal.value.knodeIds.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the UI component
|
||||
uiAccess.toolbarNode.setColor(color);
|
||||
|
||||
// Signal the engine with the node ID (just grab the first -and expected only- node)
|
||||
const nodeID = uiAccess.toolbarNode.signal.value.knodeIds[0];
|
||||
dispatchUiEvents.toolbarNode.setSelectedColor({ kNodeId: nodeID, color });
|
||||
}
|
||||
|
||||
const boundedTopPosition = Math.min(position.y, winSize.height - 164);
|
||||
return (
|
||||
<Transition
|
||||
@@ -72,7 +86,7 @@ export const ContextualToolbarNode = () => {
|
||||
dispatchUiEvents.toolbarNode.lock(e);
|
||||
}}
|
||||
color={uiAccess.toolbarNode.signal.value.color}
|
||||
onColorChange={(color) => { uiAccess.toolbarNode.setColor(color) }}
|
||||
onColorChange={handleColorChange}
|
||||
/>
|
||||
</Transition>
|
||||
);
|
||||
|
||||
@@ -78,7 +78,7 @@ const Landing = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-12 mt-12 aspect-video w-full transform overflow-hidden rounded-xl border border-white/[15%] bg-transparent md:mt-16 xl:col-span-9 xl:col-start-4 xl:mt-0">
|
||||
<div className="col-span-12 mt-12 aspect-video w-full transform overflow-hidden rounded-xl border border-white/[16%] bg-transparent md:mt-16 xl:col-span-9 xl:col-start-4 xl:mt-0">
|
||||
<img
|
||||
src="/images/landing_hero.png"
|
||||
alt="AI Video Creation"
|
||||
@@ -118,6 +118,52 @@ const Landing = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Installation Steps Section */}
|
||||
<div className="mt-36">
|
||||
<h1 className="mb-12 text-5xl font-bold">
|
||||
How to install Mira AI Editor
|
||||
</h1>
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
<div className="rounded-xl bg-[#1C1C20] p-8">
|
||||
<div className="mb-4 w-fit rounded-full bg-white/15 px-3 py-1 text-sm font-medium text-white">
|
||||
STEP 1
|
||||
</div>
|
||||
<h3 className="mb-4 text-2xl font-semibold">Download the app</h3>
|
||||
<p className="mb-6 text-gray-400">
|
||||
Download and install our application to start creating amazing
|
||||
AI-powered content.
|
||||
</p>
|
||||
<Button className="rounded-lg bg-[#2D81FF] px-4 py-2 text-sm font-medium hover:bg-[#438AF6]">
|
||||
Download App
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl bg-[#1C1C20] p-8">
|
||||
<div className="mb-4 w-fit rounded-full bg-white/15 px-3 py-1 text-sm font-medium text-white">
|
||||
STEP 2
|
||||
</div>
|
||||
<h3 className="mb-4 text-2xl font-semibold">Create an account</h3>
|
||||
<p className="mb-6 text-gray-400">
|
||||
Sign up for an account to access all features and start your
|
||||
journey.
|
||||
</p>
|
||||
<Button className="text-sm font-medium" variant="secondary">
|
||||
Sign up
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl bg-[#1C1C20] p-8">
|
||||
<div className="mb-4 w-fit rounded-full bg-white/15 px-3 py-1 text-sm font-medium text-white">
|
||||
STEP 3
|
||||
</div>
|
||||
<h3 className="mb-4 text-2xl font-semibold">Start creating</h3>
|
||||
<p className="mb-6 text-gray-400">
|
||||
Begin creating with our realtime AI editor!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
@@ -97,7 +97,7 @@ export const ToolbarNode = ({
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<ColorPicker color={color} onChange={onColorChange} faIcon={faPalette} borderStyle="" />
|
||||
<ColorPicker color={color} onChange={onColorChange} faIcon={faPalette} borderStyle="" key={idx} />
|
||||
)
|
||||
}
|
||||
})}
|
||||
|
||||
@@ -10,7 +10,7 @@ type ButtonStates = {
|
||||
};
|
||||
};
|
||||
export interface ContextualToolbarProps extends ContextualUi {
|
||||
knodeIds: number[];
|
||||
knodeIds: string[];
|
||||
disabled: boolean;
|
||||
downloadUrl?: string;
|
||||
locked: boolean | "unknown";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { signal, effect } from "@preact/signals-react";
|
||||
import { TextNodeData } from "~/KonvaApp/types";
|
||||
import { ShapeNodeData, TextNodeData } from "~/KonvaApp/types";
|
||||
|
||||
// ADDING IMAGES
|
||||
const stagedImage = signal<File | null>(null);
|
||||
@@ -50,8 +50,6 @@ const onAddTextToEngine = (callback: (data: TextNodeData) => void) => {
|
||||
};
|
||||
|
||||
// Add shapes
|
||||
// TODO: Remove this line and replace with actual shape data that engine needs
|
||||
type ShapeNodeData = any;
|
||||
const stagedShape = signal<ShapeNodeData | null>(null);
|
||||
const addShapeToEngine = (data: ShapeNodeData) => {
|
||||
stagedShape.value = data;
|
||||
|
||||
@@ -44,6 +44,11 @@ const buttonEventsRecords = Object.values(ButtonNames).reduce(
|
||||
|
||||
const buttonEventsHandlers = Object.values(ButtonNames).reduce(
|
||||
(acc, buttonName) => {
|
||||
// We define custom events/handlers for color changes
|
||||
if (buttonName === ButtonNames.COLOR) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc[buttonName] = {
|
||||
onClick: (callback: MouseEventHandler<HTMLButtonElement>) => {
|
||||
const { eventSignal, effectCleanup, lastEventTimestamp } =
|
||||
@@ -111,9 +116,27 @@ const retryEventHandler = (callback: MouseEventHandler<HTMLButtonElement>) => {
|
||||
const retryDispatcher = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
retryEvent.value = e;
|
||||
};
|
||||
|
||||
export type NodeColor = {
|
||||
kNodeId: string;
|
||||
color: string;
|
||||
}
|
||||
const selectedColorEvent = signal<NodeColor | null>();
|
||||
const setSelectedColor = (nodeColor: NodeColor) => {
|
||||
selectedColorEvent.value = nodeColor;
|
||||
}
|
||||
const onSelectedColorChanged = (callback: (color: NodeColor) => void) => {
|
||||
effect(() => {
|
||||
if (selectedColorEvent.value) {
|
||||
callback(selectedColorEvent.value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const dispatchers = {
|
||||
lock: lockDispatcher,
|
||||
retry: retryDispatcher,
|
||||
setSelectedColor,
|
||||
...buttonDispatchers,
|
||||
};
|
||||
export const eventsHandlers = {
|
||||
@@ -123,5 +146,8 @@ export const eventsHandlers = {
|
||||
retry: {
|
||||
onClick: retryEventHandler,
|
||||
},
|
||||
color: {
|
||||
onConfirmChanged: onSelectedColorChanged,
|
||||
},
|
||||
...buttonEventsHandlers,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user