Compare commits
2 commits
481c2e6c15
...
6eaad79f9a
Author | SHA1 | Date | |
---|---|---|---|
6eaad79f9a | |||
c545161878 |
40 changed files with 2459 additions and 98 deletions
2
.npmrc
Normal file
2
.npmrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
audit=false
|
||||
fund=false
|
229
Cargo.lock
generated
229
Cargo.lock
generated
|
@ -33,6 +33,12 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.8"
|
||||
|
@ -296,8 +302,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"axum-core 0.4.5",
|
||||
"axum-macros 0.4.2",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
|
@ -306,7 +312,7 @@ dependencies = [
|
|||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa 1.0.14",
|
||||
"matchit",
|
||||
"matchit 0.7.3",
|
||||
"memchr",
|
||||
"mime",
|
||||
"multer",
|
||||
|
@ -325,6 +331,40 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
|
||||
dependencies = [
|
||||
"axum-core 0.5.0",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa 1.0.14",
|
||||
"matchit 0.8.4",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
|
@ -346,6 +386,26 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.2.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.4.2"
|
||||
|
@ -357,6 +417,37 @@ dependencies = [
|
|||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-typed-routing"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/jvdwrf/axum-typed-routing#160684a406d616974d851bbfc6d0d9ffa65367e5"
|
||||
dependencies = [
|
||||
"axum 0.8.1",
|
||||
"axum-macros 0.5.0",
|
||||
"axum-typed-routing-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-typed-routing-macros"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/jvdwrf/axum-typed-routing#160684a406d616974d851bbfc6d0d9ffa65367e5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
|
@ -884,6 +975,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
|
@ -1092,6 +1192,28 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darm_test"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"axum 0.8.1",
|
||||
"axum-typed-routing",
|
||||
"datastar",
|
||||
"maud",
|
||||
"mime_guess",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dary_heap"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04d2cd9c18b9f454ed67da600630b021a8a80bf33f8c95896ab33aaf1c26b728"
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.3"
|
||||
|
@ -1106,6 +1228,14 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "datastar"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/starfederation/datastar.git#3bc1f2801963ffbfed5a1a5bc98cc8e29574f623"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.9"
|
||||
|
@ -2497,6 +2627,29 @@ dependencies = [
|
|||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include-flate"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df49c16750695486c1f34de05da5b7438096156466e7f76c38fcdf285cf0113e"
|
||||
dependencies = [
|
||||
"include-flate-codegen",
|
||||
"lazy_static",
|
||||
"libflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "include-flate-codegen"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c5b246c6261be723b85c61ecf87804e8ea4a35cb68be0ff282ed84b95ffe7d7"
|
||||
dependencies = [
|
||||
"libflate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
|
@ -2700,7 +2853,7 @@ version = "0.6.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "910681b920c48a43508b2bd0261bdb67c4ef9456a0b3613f956a0d30e832e9de"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"cfg-if",
|
||||
"futures",
|
||||
"http-body-util",
|
||||
|
@ -2912,6 +3065,30 @@ version = "0.2.169"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"core2",
|
||||
"crc32fast",
|
||||
"dary_heap",
|
||||
"libflate_lz77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libflate_lz77"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d"
|
||||
dependencies = [
|
||||
"core2",
|
||||
"hashbrown 0.14.5",
|
||||
"rle-decode-fast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.5.2"
|
||||
|
@ -2995,7 +3172,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-broadcast",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"bytes",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
|
@ -3053,7 +3230,7 @@ name = "llama_proxy_man"
|
|||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"derive_more 2.0.1",
|
||||
"figment",
|
||||
"futures",
|
||||
|
@ -3173,6 +3350,36 @@ version = "0.7.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "maud"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e"
|
||||
dependencies = [
|
||||
"axum-core 0.5.0",
|
||||
"http 1.2.0",
|
||||
"itoa 1.0.14",
|
||||
"maud_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maud_macros"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
|
@ -4606,6 +4813,12 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rle-decode-fast"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.7"
|
||||
|
@ -4646,6 +4859,8 @@ version = "8.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"include-flate",
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
|
@ -5030,7 +5245,7 @@ version = "0.6.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fae7a3038a32e5a34ba32c6c45eb4852f8affaf8b794ebfcd4b1099e2d62ebe"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum 0.7.9",
|
||||
"bytes",
|
||||
"ciborium",
|
||||
"const_format",
|
||||
|
|
|
@ -20,7 +20,7 @@ lto = "fat"
|
|||
panic = "abort"
|
||||
|
||||
[workspace]
|
||||
members = ["llama_forge_rs", "llama_proxy_man", "redvault_el_rs"]
|
||||
members = ["darm_test", "llama_forge_rs", "llama_proxy_man", "redvault_el_rs"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
|
@ -29,7 +29,7 @@ description = "The redvault AI monorepo"
|
|||
license = "AGPL-3.0-or-later"
|
||||
publish = false
|
||||
readme = "README.md"
|
||||
repository = "https://git.vlt81.de/oekonzept/oeko-mono"
|
||||
repository = "https://git.vlt81.de/vault81/redvault-ai"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
|
||||
|
|
|
@ -75,10 +75,12 @@ dependencies = ["mksitedir", "make-docset", "cp-docset"]
|
|||
|
||||
[tasks.make-docset]
|
||||
workspace = false
|
||||
dependencies = ["mksitedir"]
|
||||
script = "cargo docset --workspace --no-clean --platform-family redvault-ai && sleep 1 && sync"
|
||||
|
||||
[tasks.cp-docset]
|
||||
workspace = false
|
||||
dependencies = ["make-docset"]
|
||||
script = "cp -r target/docset/redvault-ai.docset ~/.local/share/Zeal/Zeal/docsets/"
|
||||
|
||||
[tasks.watch-test]
|
||||
|
|
22
darm_test/Cargo.toml
Normal file
22
darm_test/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "darm_test"
|
||||
authors.workspace = true
|
||||
description.workspace = true
|
||||
license.workspace = true
|
||||
publish.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.8", features = ["http2"] }
|
||||
axum-typed-routing = { git = "https://github.com/jvdwrf/axum-typed-routing", version = "0.2.0" }
|
||||
datastar = { git = "https://github.com/starfederation/datastar.git", version = "0.1.0" }
|
||||
maud = { version = "0.27.0", features = ["axum"] }
|
||||
mime_guess = "2.0.5"
|
||||
rust-embed = { version = "8.5.0", features = ["axum", "compression"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.43", features = ["full", "tracing"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
30
darm_test/TODO.org
Normal file
30
darm_test/TODO.org
Normal file
|
@ -0,0 +1,30 @@
|
|||
* Todos
|
||||
** Starter boilerplate
|
||||
*** [X] Server: Axum
|
||||
**** [X] Typed routes via macro
|
||||
**** [X] Nested Router
|
||||
**** [X] Fallbacks
|
||||
*** [X] "embd tmpl": Maud
|
||||
*** [ ] "html tmpl": minijinja
|
||||
- only index yaml for now, test in app wether inline or external templates feel better
|
||||
*** [ ] CSS Basic
|
||||
- UnoCSS
|
||||
- daisyUI?
|
||||
*** [ ] UI framework
|
||||
- data* js basic
|
||||
- data* axum sdk
|
||||
*** [ ] Asset bundle testing
|
||||
*** [ ] DB
|
||||
- SqlX/SurrealDB?
|
||||
** Basic streaming chat
|
||||
- build with inline html via maud
|
||||
*** Stream same token in loop
|
||||
*** ???
|
||||
*** Finish
|
||||
** Basic Proxy Settings
|
||||
- build with jinja templates
|
||||
** Markdown streaming chat
|
||||
*** Moar Feats
|
||||
**** Tauri webview/wry?
|
||||
**** Jinja tmplts for models ??
|
||||
**** Proxy integration ?
|
1
darm_test/public/datastar.min.js
vendored
Symbolic link
1
darm_test/public/datastar.min.js
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
css/datastar-1-0-0-beta-7.js
|
13
darm_test/public/js/datastar-1-0-0-beta-7.js
Normal file
13
darm_test/public/js/datastar-1-0-0-beta-7.js
Normal file
File diff suppressed because one or more lines are too long
7
darm_test/public/js/datastar-1-0-0-beta-7.js.map
Normal file
7
darm_test/public/js/datastar-1-0-0-beta-7.js.map
Normal file
File diff suppressed because one or more lines are too long
1
darm_test/public/js/datastar-1-0-0-beta-7.json
Normal file
1
darm_test/public/js/datastar-1-0-0-beta-7.json
Normal file
File diff suppressed because one or more lines are too long
1
darm_test/public/js/datastar.min.js
vendored
Symbolic link
1
darm_test/public/js/datastar.min.js
vendored
Symbolic link
|
@ -0,0 +1 @@
|
|||
datastar-1-0-0-beta-7.js
|
1
darm_test/public/styles.min.css
vendored
Normal file
1
darm_test/public/styles.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }.static{position:static}.m-1,.m1,[m-1=""],m1{margin:.25rem}.ms,[ms=""]{margin-inline-start:1rem}contents{display:contents}.h1{height:.25rem}.b,[b=""]{border-width:1px}[pe=""]{padding-inline-end:1rem}
|
1
darm_test/public/test.html
Normal file
1
darm_test/public/test.html
Normal file
|
@ -0,0 +1 @@
|
|||
<h1> Test </h1>
|
132
darm_test/src/main.rs
Normal file
132
darm_test/src/main.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use std::sync::Once;
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::State,
|
||||
http::{header, Response, StatusCode, Uri},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
};
|
||||
use axum_typed_routing::{route, TypedRouter};
|
||||
use maud::{html, Markup};
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[route(GET "/hello")]
|
||||
async fn hello_world(State(_): State<String>) -> Markup {
|
||||
html! {
|
||||
h1 { "Hello, World!" }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[route(GET "/item/:id?amount&offset")]
|
||||
async fn item_handler(
|
||||
id: u32,
|
||||
amount: Option<u32>,
|
||||
offset: Option<u32>,
|
||||
State(state): State<String>,
|
||||
// Json(json): Json<u32>,
|
||||
) -> Markup {
|
||||
// todo!("handle request")
|
||||
|
||||
html! {
|
||||
h1 { "Item" }
|
||||
p {
|
||||
(format!("{id:?} {amount:?} {offset:?} {state:?}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We use a wildcard matcher ("/dist/*file") to match against everything
|
||||
// within our defined assets directory. This is the directory on our Asset
|
||||
// struct below, where folder = "examples/public/".
|
||||
#[route(GET "/dist/*path")]
|
||||
async fn static_handler(path: String, _: State<String>) -> impl IntoResponse {
|
||||
let path = path.trim_start_matches('/').to_string();
|
||||
|
||||
StaticFile(path)
|
||||
}
|
||||
|
||||
fn markup_404(uri: String) -> Markup {
|
||||
html! {
|
||||
h1 { "404" }
|
||||
p { (format!("{uri:?} Not Found")) }
|
||||
}
|
||||
}
|
||||
|
||||
fn markup_405() -> Markup {
|
||||
html! {
|
||||
h1 { "404" }
|
||||
p { "Method not allowed!" }
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, we use a fallback route for anything that didn't match.
|
||||
async fn handle_404(uri: Uri) -> impl IntoResponse {
|
||||
(StatusCode::NOT_FOUND, markup_404(format!("{uri:?}"))).into_response()
|
||||
}
|
||||
|
||||
async fn handle_405() -> impl IntoResponse {
|
||||
(StatusCode::METHOD_NOT_ALLOWED, markup_405()).into_response()
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "public/"]
|
||||
struct Asset;
|
||||
|
||||
pub struct StaticFile<T>(pub T);
|
||||
|
||||
impl<T> IntoResponse for StaticFile<T>
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
fn into_response(self) -> Response<Body> {
|
||||
let path = self.0.into();
|
||||
|
||||
match Asset::get(path.as_str()) {
|
||||
Some(content) => {
|
||||
let mime = mime_guess::from_path(path).first_or_octet_stream();
|
||||
|
||||
([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response()
|
||||
}
|
||||
None => (StatusCode::NOT_FOUND, markup_404(path)).into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_logger() {
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| {
|
||||
let env_filter = tracing_subscriber::EnvFilter::builder()
|
||||
.with_default_directive(tracing::level_filters::LevelFilter::INFO.into())
|
||||
.from_env_lossy();
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.pretty()
|
||||
.with_env_filter(env_filter)
|
||||
.init();
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
initialize_logger();
|
||||
|
||||
// TODO pick free port/config
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap();
|
||||
|
||||
let ui_router = axum::Router::new()
|
||||
.typed_route(item_handler)
|
||||
.typed_route(hello_world);
|
||||
|
||||
let router: axum::Router = axum::Router::new()
|
||||
.merge(ui_router.clone())
|
||||
.nest("/ui", ui_router)
|
||||
.typed_route(static_handler)
|
||||
.fallback_service(get(handle_404))
|
||||
.method_not_allowed_fallback(handle_405)
|
||||
.with_state("state".to_string());
|
||||
|
||||
let _ = axum::serve(listener, router.into_make_service()).await;
|
||||
}
|
24
darm_test/src/ui/index.html
Normal file
24
darm_test/src/ui/index.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>My Website</title>
|
||||
<!-- TODO!!!: Add favico -->
|
||||
<!-- <link rel="icon" href="./favicon.ico" type="image/x-icon"> -->
|
||||
<script type="module" src="/dist/datastar.min.js"></script>
|
||||
<link rel="stylesheet" href="./dist/styles.min.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Welcome to My Website</h1>
|
||||
<div m-1>div</div>
|
||||
<div class="m-1">div</div>
|
||||
<div class="m1">div</div>
|
||||
<m1>div</m1>
|
||||
</main>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
0
darm_test/src/ui/mod.rs
Normal file
0
darm_test/src/ui/mod.rs
Normal file
0
darm_test/src/ui/pages/chat/mod.rs
Normal file
0
darm_test/src/ui/pages/chat/mod.rs
Normal file
0
darm_test/src/ui/pages/mod.rs
Normal file
0
darm_test/src/ui/pages/mod.rs
Normal file
0
darm_test/src/ui/pages/proxy_dashboard/mod.rs
Normal file
0
darm_test/src/ui/pages/proxy_dashboard/mod.rs
Normal file
1
darm_test/style/main.scss
Normal file
1
darm_test/style/main.scss
Normal file
|
@ -0,0 +1 @@
|
|||
@use 'uno';
|
15
darm_test/style/uno.css
Normal file
15
darm_test/style/uno.css
Normal file
|
@ -0,0 +1,15 @@
|
|||
/* layer: preflights */
|
||||
*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgba(0,0,0,0);--un-ring-shadow:0 0 rgba(0,0,0,0);--un-shadow-inset: ;--un-shadow:0 0 rgba(0,0,0,0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}
|
||||
/* layer: default */
|
||||
.static{position:static;}
|
||||
.m-1,
|
||||
.m1,
|
||||
[m-1=""],
|
||||
m1{margin:0.25rem;}
|
||||
.ms,
|
||||
[ms=""]{margin-inline-start:1rem;}
|
||||
contents{display:contents;}
|
||||
.h1{height:0.25rem;}
|
||||
.b,
|
||||
[b=""]{border-width:1px;}
|
||||
[pe=""]{padding-inline-end:1rem;}
|
31
darm_test/uno.config.ts
Normal file
31
darm_test/uno.config.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
// import presetAttributify from '@unocss/preset-attributify'
|
||||
import {
|
||||
defineConfig,
|
||||
presetAttributify,
|
||||
presetIcons,
|
||||
presetTagify,
|
||||
presetWind,
|
||||
transformerDirectives,
|
||||
transformerVariantGroup,
|
||||
} from "unocss";
|
||||
|
||||
export default defineConfig({
|
||||
content: {
|
||||
filesystem: ["**/*.{html,js,ts,jsx,tsx,rs,rsx}"],
|
||||
},
|
||||
cli: {
|
||||
entry: {
|
||||
patterns: ["**/*.{html,js,ts,jsx,tsx,rs,rsx}"],
|
||||
outFile: "./style/uno.css",
|
||||
},
|
||||
},
|
||||
presets: [
|
||||
presetAttributify({
|
||||
/* preset options */
|
||||
}),
|
||||
presetTagify({}),
|
||||
presetIcons({}),
|
||||
presetWind({}),
|
||||
],
|
||||
transformers: [transformerDirectives(), transformerVariantGroup()],
|
||||
});
|
|
@ -84,7 +84,13 @@ mime_guess = { version = "2.0.4", optional = true }
|
|||
tracing-test = "0.2.4"
|
||||
sysinfo = { version = "0.30.11", optional = true }
|
||||
derive_more = { version = "0.99.17", features = ["nightly"] }
|
||||
sqlx-macros = { version = "0.7.4", optional = true, features = ["chrono", "json", "migrate", "sqlite", "uuid"] }
|
||||
sqlx-macros = { version = "0.7.4", optional = true, features = [
|
||||
"chrono",
|
||||
"json",
|
||||
"migrate",
|
||||
"sqlite",
|
||||
"uuid",
|
||||
] }
|
||||
pulldown-cmark = { version = "0.12.2", features = ["serde"] }
|
||||
# qdrant-client = "1.11.2"
|
||||
# swiftide = "0.9.1"
|
||||
|
|
|
@ -68,7 +68,9 @@ pub struct LlamaService {
|
|||
|
||||
impl LlamaService {
|
||||
pub fn new(id: Uuid) -> Self {
|
||||
Self { id }
|
||||
Self {
|
||||
id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -177,7 +177,10 @@ mod tests {
|
|||
use crate::{
|
||||
api::{ChannelMessage, ChatMessage, ChatRole},
|
||||
server::backends::{
|
||||
llama_chat::LlamaService, BackendService, BackendServiceStatus, ChatService,
|
||||
llama_chat::LlamaService,
|
||||
BackendService,
|
||||
BackendServiceStatus,
|
||||
ChatService,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -216,7 +219,7 @@ mod tests {
|
|||
tracing::debug!("response: {}", response);
|
||||
assert!(response.contains('4'));
|
||||
|
||||
service_handle.stop().await;
|
||||
service_handle.stop().await.expect("Stop failed");
|
||||
|
||||
assert_eq!(service_handle.status().await, BackendServiceStatus::Stopped);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ impl<S> Layer<S> for LoggingLayer {
|
|||
type Service = LoggingService<S>;
|
||||
|
||||
fn layer(&self, inner: S) -> Self::Service {
|
||||
LoggingService { inner }
|
||||
LoggingService {
|
||||
inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,20 +6,26 @@ use axum::{
|
|||
http::Request,
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Extension, Router,
|
||||
Extension,
|
||||
Router,
|
||||
};
|
||||
use leptos::*;
|
||||
use leptos_axum::{generate_route_list, handle_server_fns_with_context, LeptosRoutes};
|
||||
use leptos_router::RouteListing;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous},
|
||||
ConnectOptions, SqlitePool,
|
||||
ConnectOptions,
|
||||
SqlitePool,
|
||||
};
|
||||
use tower::Layer;
|
||||
use tower_http::{
|
||||
compression::CompressionLayer,
|
||||
trace::{
|
||||
DefaultMakeSpan, DefaultOnEos, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse,
|
||||
DefaultMakeSpan,
|
||||
DefaultOnEos,
|
||||
DefaultOnFailure,
|
||||
DefaultOnRequest,
|
||||
DefaultOnResponse,
|
||||
TraceLayer,
|
||||
},
|
||||
CompressionLevel,
|
||||
|
|
|
@ -15,7 +15,13 @@ serde = { version = "1.0", features = ["derive"] }
|
|||
serde_yaml = "0.9"
|
||||
axum = { version = "0.7", features = ["macros"] }
|
||||
hyper = { version = "1.4", features = ["full"] }
|
||||
reqwest = { version = "0.12", features = ["cookies", "multipart", "json", "stream", "native-tls"] }
|
||||
reqwest = { version = "0.12", features = [
|
||||
"cookies",
|
||||
"multipart",
|
||||
"json",
|
||||
"stream",
|
||||
"native-tls",
|
||||
] }
|
||||
futures = "0.3.30"
|
||||
anyhow = { version = "1.0.89", features = ["backtrace"] }
|
||||
thiserror = "1.0.63"
|
||||
|
@ -26,7 +32,13 @@ pin-project-lite = "0.2.14"
|
|||
tower = { version = "0.4", features = ["tokio", "tracing"] }
|
||||
tower-http = { version = "0.5.2", features = ["trace"] }
|
||||
reqwest-retry = "0.6.1"
|
||||
reqwest-middleware = { version = "0.3.3", features = ["charset", "http2", "json", "multipart", "rustls-tls"] }
|
||||
reqwest-middleware = { version = "0.3.3", features = [
|
||||
"charset",
|
||||
"http2",
|
||||
"json",
|
||||
"multipart",
|
||||
"rustls-tls",
|
||||
] }
|
||||
itertools = "0.13.0"
|
||||
openport = { version = "0.1.1", features = ["rand"] }
|
||||
derive_more = { version = "2.0.1", features = ["deref"] }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, fs};
|
||||
|
||||
use figment::{
|
||||
providers::{Env, Format, Json, Toml, Yaml},
|
||||
Figment,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct AppConfig {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use std::io;
|
||||
|
||||
use anyhow::Error as AnyError;
|
||||
use axum::{http, response::IntoResponse};
|
||||
use hyper;
|
||||
use reqwest;
|
||||
use reqwest_middleware;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{config::ModelSpec, error::AppError, state::AppState, util::parse_size};
|
||||
use std::{process::Stdio, sync::Arc};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use itertools::Itertools;
|
||||
use std::{process::Stdio, sync::Arc};
|
||||
use tokio::{
|
||||
net::TcpStream,
|
||||
process::{Child, Command},
|
||||
|
@ -9,6 +9,8 @@ use tokio::{
|
|||
time::{sleep, Duration},
|
||||
};
|
||||
|
||||
use crate::{config::ModelSpec, error::AppError, state::AppState, util::parse_size};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InferenceProcess {
|
||||
pub spec: ModelSpec,
|
||||
|
|
|
@ -6,12 +6,17 @@ pub mod proxy;
|
|||
pub mod state;
|
||||
pub mod util;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use axum::{routing::any, Router};
|
||||
use config::{AppConfig, ModelSpec};
|
||||
use state::AppState;
|
||||
use std::net::SocketAddr;
|
||||
use tower_http::trace::{
|
||||
DefaultMakeSpan, DefaultOnEos, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse,
|
||||
DefaultMakeSpan,
|
||||
DefaultOnEos,
|
||||
DefaultOnFailure,
|
||||
DefaultOnRequest,
|
||||
DefaultOnResponse,
|
||||
TraceLayer,
|
||||
};
|
||||
use tracing::Level;
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
sync::{Arc, Once},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
use axum::{body::Body, http::Request};
|
||||
use pin_project_lite::pin_project;
|
||||
use tower::{Layer, Service};
|
||||
|
@ -18,7 +16,9 @@ impl<S> Layer<S> for LoggingLayer {
|
|||
type Service = LoggingService<S>;
|
||||
|
||||
fn layer(&self, inner: S) -> Self::Service {
|
||||
LoggingService { inner }
|
||||
LoggingService {
|
||||
inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
use crate::{
|
||||
config::ModelSpec, error::AppError, inference_process::InferenceProcess, state::AppState,
|
||||
util::parse_size,
|
||||
};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http::{Request, Response},
|
||||
|
@ -11,6 +7,14 @@ use reqwest::Client;
|
|||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
||||
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
||||
|
||||
use crate::{
|
||||
config::ModelSpec,
|
||||
error::AppError,
|
||||
inference_process::InferenceProcess,
|
||||
state::AppState,
|
||||
util::parse_size,
|
||||
};
|
||||
|
||||
pub async fn proxy_request(
|
||||
req: Request<Body>,
|
||||
spec: &ModelSpec,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::{config::AppConfig, inference_process::InferenceProcess, util::parse_size};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{config::AppConfig, inference_process::InferenceProcess, util::parse_size};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResourceManager {
|
||||
pub total_ram: u64,
|
||||
|
|
1835
package-lock.json
generated
1835
package-lock.json
generated
File diff suppressed because it is too large
Load diff
15
package.json
15
package.json
|
@ -4,10 +4,23 @@
|
|||
"version": "0.1.1",
|
||||
"author": "Tristan Druyen <tristan@vault81.mozmail.com>",
|
||||
"license": "AGPL",
|
||||
"scripts": {
|
||||
"watch-unocss": "cd ./darm_test && unocss --watch",
|
||||
"watch-bundle": "npm run build-bundle",
|
||||
"build-unocss": "cd ./darm_test && unocss",
|
||||
"build-bundle": "cd ./darm_test && sass -scompressed style/main.scss > public/styles.min.css",
|
||||
"watch-all": "npm run watch-unocss & npm run watch-bundle",
|
||||
"build-all": "npm run build-unocss & npm run build-bundle"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@starfederation/datastar": "^1.0.0-beta.7",
|
||||
"@tailwindcss/forms": "0.5.7",
|
||||
"@tailwindcss/typography": "0.5.10",
|
||||
"@unocss/reset": "^66.0.0",
|
||||
"daisyui": "4.7.3",
|
||||
"tailwindcss": "3.4.17"
|
||||
"sass": "^1.85.0",
|
||||
"tailwindcss": "3.4.17",
|
||||
"typescript": "^5.7.3",
|
||||
"unocss": "^0.54.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use emacs::{defun, Env, IntoLisp, Result, Value};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use emacs::{defun, Env, IntoLisp, Result, Value};
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
|
||||
// Emacs won't load the module without this.
|
||||
|
@ -48,12 +49,9 @@ fn init(env: &Env) -> Result<Value<'_>> {
|
|||
#[defun]
|
||||
fn say_hello(env: &Env, name: String) -> Result<Value<'_>> {
|
||||
// env.message(&format!("Helloo Broooooooo, {}!", name))
|
||||
env.call(
|
||||
"message",
|
||||
[format!("Henlo whatsup, {}!!!!", name)
|
||||
env.call("message", [format!("Henlo whatsup, {}!!!!", name)
|
||||
.as_str()
|
||||
.into_lisp(env)?],
|
||||
)?;
|
||||
.into_lisp(env)?])?;
|
||||
RUNTIME
|
||||
.get()
|
||||
.ok_or_else(|| anyhow::anyhow!("No runtime"))?
|
||||
|
|
23
rustfmt.toml
Normal file
23
rustfmt.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
|
||||
edition = "2021"
|
||||
max_width = 100
|
||||
tab_spaces = 4
|
||||
|
||||
|
||||
# unstable
|
||||
format_macro_bodies = true
|
||||
format_macro_matchers = true
|
||||
format_strings = true
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Crate"
|
||||
imports_layout = "HorizontalVertical"
|
||||
overflow_delimited_expr = true
|
||||
reorder_impl_items = true
|
||||
struct_field_align_threshold = 4
|
||||
struct_lit_single_line = false
|
||||
trailing_comma = "Vertical"
|
||||
unstable_features = true
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
wrap_comments = false
|
||||
# format_brace_macros = true
|
Loading…
Add table
Reference in a new issue