diff --git a/Cargo.lock b/Cargo.lock index bcddcfe..4e65b7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,6 +174,18 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "any_spawner" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1384d3fe1eecb464229fcf6eebb72306591c56bf27b373561489458a7c73027d" +dependencies = [ + "futures", + "thiserror 2.0.15", + "tokio", + "wasm-bindgen-futures", +] + [[package]] name = "anyhow" version = "1.0.99" @@ -317,6 +329,23 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d2119f741b79fe9907f5396d19bffcb46568cfcc315e78677d731972ac7085" +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + [[package]] name = "async-openai" version = "0.28.3" @@ -379,10 +408,10 @@ dependencies = [ ] [[package]] -name = "async-recursion" -version = "1.1.1" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -406,9 +435,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attribute-derive" -version = "0.9.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1ee502851995027b06f99f5ffbeffa1406b38d0b318a1ebfa469332c6cbafd" +checksum = "0053e96dd3bec5b4879c23a138d6ef26f2cb936c9cdc96274ac2b9ed44b5bb54" dependencies = [ "attribute-derive-macro", "derive-where", @@ -420,14 +449,14 @@ dependencies = [ [[package]] name = "attribute-derive-macro" -version = "0.9.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3601467f634cfe36c4780ca9c75dea9a5b34529c1f2810676a337e7e0997f954" +checksum = "463b53ad0fd5b460af4b1915fe045ff4d946d025fb6c4dc3337752eaa980f71b" dependencies = [ "collection_literals", "interpolator", "manyhow", - "proc-macro-utils 0.8.0", + "proc-macro-utils", "proc-macro2", "quote", "quote-use", @@ -470,6 +499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ "axum-core", + "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -482,6 +512,7 @@ dependencies = [ "matchit", "memchr", "mime", + "multer", "percent-encoding", "pin-project-lite", "rustversion", @@ -489,8 +520,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -546,6 +579,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base16" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" + [[package]] name = "base16ct" version = "0.2.0" @@ -577,7 +616,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash 2.1.1", + "rustc-hash", "shlex", "syn 2.0.106", ] @@ -668,6 +707,16 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "built" version = "0.7.7" @@ -927,33 +976,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - [[package]] name = "clang-sys" version = "1.8.1" @@ -1005,6 +1027,17 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "codee" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd8bbfdadf2f8999c6e404697bc08016dce4a3d77dec465b36c9a0652fdb3327" +dependencies = [ + "serde", + "serde_json", + "thiserror 2.0.15", +] + [[package]] name = "collection_literals" version = "1.0.2" @@ -1049,16 +1082,25 @@ dependencies = [ ] [[package]] -name = "config" -version = "0.14.1" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "convert_case", - "nom", + "crossbeam-utils", +] + +[[package]] +name = "config" +version = "0.15.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4092bf3922a966e2bd74640b80f36c73eaa7251a4fd0fbcda1f8a4de401352" +dependencies = [ + "convert_case 0.6.0", "pathdiff", "serde", - "toml", + "toml 0.9.5", + "winnow", ] [[package]] @@ -1084,16 +1126,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "console_log" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f" -dependencies = [ - "log", - "web-sys", -] - [[package]] name = "const-random" version = "0.1.18" @@ -1114,6 +1146,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "const-str" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451d0640545a0553814b4c646eb549343561618838e9b42495f466131fe3ad49" + [[package]] name = "const_format" version = "0.2.34" @@ -1134,6 +1172,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "const_str_slice_concat" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67855af358fcb20fac58f9d714c94e2b228fe5694c1c9b4ead4a366343eda1b" + [[package]] name = "conv" version = "0.3.3" @@ -1152,6 +1196,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1374,11 +1427,12 @@ dependencies = [ [[package]] name = "dashmap" -version = "5.5.3" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", + "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", @@ -1391,6 +1445,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + [[package]] name = "derive-where" version = "1.6.0" @@ -1541,6 +1601,16 @@ dependencies = [ "serde", ] +[[package]] +name = "either_of" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216d23e0ec69759a17f05e1c553f3a6870e5ec73420fbb07807a6f34d5d1d5a4" +dependencies = [ + "paste", + "pin-project-lite", +] + [[package]] name = "embeddings-engine" version = "0.1.0" @@ -1624,6 +1694,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "erased" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1731451909bde27714eacba19c2566362a7f35224f52b153d3f42cf60f72472" + [[package]] name = "errno" version = "0.3.13" @@ -1640,6 +1716,27 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "eventsource-stream" version = "0.2.3" @@ -1856,6 +1953,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -2219,6 +2317,19 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + [[package]] name = "gloo-net" version = "0.6.0" @@ -2253,6 +2364,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "guardian" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f" + [[package]] name = "h2" version = "0.4.12" @@ -2377,6 +2494,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + [[package]] name = "httparse" version = "1.10.1" @@ -2389,6 +2512,22 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hydration_context" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8714ae4adeaa846d838f380fbd72f049197de629948f91bf045329e0cf0a283" +dependencies = [ + "futures", + "js-sys", + "once_cell", + "or_poisoned", + "pin-project-lite", + "serde", + "throw_error", + "wasm-bindgen", +] + [[package]] name = "hyper" version = "1.6.0" @@ -2937,94 +3076,125 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "leptos" -version = "0.6.15" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cbb3237c274dadf00dcc27db96c52601b40375117178fb24a991cda073624f0" +checksum = "b7a8710b4908a0e7b693113b906e4cf1bc87123b685404d090cdcd3e220bcab4" dependencies = [ + "any_spawner", + "base64 0.22.1", "cfg-if", + "either_of", + "futures", + "getrandom 0.3.3", + "hydration_context", "leptos_config", "leptos_dom", + "leptos_hot_reload", "leptos_macro", - "leptos_reactive", "leptos_server", + "oco_ref", + "or_poisoned", + "paste", + "rand 0.9.2", + "reactive_graph", + "rustc-hash", + "rustc_version", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", "server_fn", - "tracing", + "slotmap", + "tachys", + "thiserror 2.0.15", + "throw_error", "typed-builder", "typed-builder-macro", "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_split_helpers", "web-sys", ] [[package]] -name = "leptos-chat" +name = "leptos-app" version = "0.1.0" dependencies = [ "async-openai-wasm", + "axum", "console_error_panic_hook", - "console_log", "either", "futures-util", - "gloo-net", "js-sys", "leptos", + "leptos_axum", "leptos_meta", "leptos_router", - "log", "serde", "serde_json", + "tokio", "uuid", "wasm-bindgen", "web-sys", ] [[package]] -name = "leptos_config" -version = "0.6.15" +name = "leptos_axum" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ed778611380ddea47568ac6ad6ec5158d39b5bd59e6c4dcd24efc15dc3dc0d" +checksum = "1ba0d920d78828a8811bc52bdb25c7982d3c707f3497c3bdbb8a6c796d843a9c" +dependencies = [ + "any_spawner", + "axum", + "dashmap", + "futures", + "hydration_context", + "leptos", + "leptos_integration_utils", + "leptos_macro", + "leptos_meta", + "leptos_router", + "parking_lot", + "server_fn", + "tachys", + "tokio", + "tower", + "tower-http", +] + +[[package]] +name = "leptos_config" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240b4cb96284256a44872563cf029f24d6fe14bc341dcf0f4164e077cb5a1471" dependencies = [ "config", "regex", "serde", - "thiserror 1.0.69", + "thiserror 2.0.15", "typed-builder", ] [[package]] name = "leptos_dom" -version = "0.6.15" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8401c46c86c1f4c16dcb7881ed319fcdca9cda9b9e78a6088955cb423afcf119" +checksum = "4e920c8b2fd202b25786b0c72a00c745a6962fa923e600df6f3ec352d844be91" dependencies = [ - "async-recursion", - "cfg-if", - "drain_filter_polyfill", - "futures", - "getrandom 0.2.16", - "html-escape", - "indexmap", - "itertools 0.12.1", "js-sys", - "leptos_reactive", - "once_cell", - "pad-adapter", - "paste", - "rustc-hash 1.1.0", - "serde", - "serde_json", - "server_fn", - "smallvec", - "tracing", + "or_poisoned", + "reactive_graph", + "send_wrapper", + "tachys", "wasm-bindgen", - "wasm-bindgen-futures", "web-sys", ] [[package]] name = "leptos_hot_reload" -version = "0.6.15" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb53d4794240b684a2f4be224b84bee9e62d2abc498cf2bcd643cd565e01d96" +checksum = "0d61ec3e1ff8aaee8c5151688550c0363f85bc37845450764c31ff7584a33f38" dependencies = [ "anyhow", "camino", @@ -3039,109 +3209,113 @@ dependencies = [ ] [[package]] -name = "leptos_macro" -version = "0.6.15" +name = "leptos_integration_utils" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b13bc3db70715cd8218c4535a5af3ae3c0e5fea6f018531fc339377b36bc0e0" +checksum = "ea02f77bee8de1e9dd4c7ef1c401dd3a1afef8dab6e240af6f7d6f73403a259f" +dependencies = [ + "futures", + "hydration_context", + "leptos", + "leptos_config", + "leptos_meta", + "leptos_router", + "reactive_graph", +] + +[[package]] +name = "leptos_macro" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a758b2ac32dc9ec2739c92726363d555e601327d77467e8083c12a8781f206d" dependencies = [ "attribute-derive", "cfg-if", - "convert_case", + "convert_case 0.8.0", "html-escape", - "itertools 0.12.1", + "itertools 0.14.0", "leptos_hot_reload", "prettyplease", "proc-macro-error2", "proc-macro2", "quote", "rstml", + "rustc_version", "server_fn_macro", "syn 2.0.106", - "tracing", "uuid", ] [[package]] name = "leptos_meta" -version = "0.6.15" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25acc2f63cf91932013e400a95bf6e35e5d3dbb44a7b7e25a8e3057d12005b3b" +checksum = "2d489e38d3f541e9e43ecc2e3a815527840345a2afca629b3e23fcc1dd254578" dependencies = [ - "cfg-if", - "indexmap", - "leptos", - "tracing", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "leptos_reactive" -version = "0.6.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4161acbf80f59219d8d14182371f57302bc7ff81ee41aba8ba1ff7295727f23" -dependencies = [ - "base64 0.22.1", - "cfg-if", "futures", "indexmap", - "js-sys", - "oco_ref", - "paste", - "pin-project", - "rustc-hash 1.1.0", - "self_cell", - "serde", - "serde-wasm-bindgen", - "serde_json", - "slotmap", - "thiserror 1.0.69", - "tracing", + "leptos", + "or_poisoned", + "send_wrapper", "wasm-bindgen", - "wasm-bindgen-futures", "web-sys", ] [[package]] name = "leptos_router" -version = "0.6.15" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d71dea7d42c0d29c40842750232d3425ed1cf10e313a1f898076d20871dad32" +checksum = "f13e0e0a70f40e4717da84415f6f1002091146ef247246b6a447d76947ab2e30" dependencies = [ - "cfg-if", + "any_spawner", + "either_of", + "futures", "gloo-net", - "itertools 0.12.1", "js-sys", - "lazy_static", "leptos", - "linear-map", - "once_cell", + "leptos_router_macro", + "or_poisoned", "percent-encoding", + "reactive_graph", + "rustc_version", "send_wrapper", - "serde", - "serde_json", - "serde_qs 0.13.0", - "thiserror 1.0.69", - "tracing", + "tachys", + "thiserror 2.0.15", + "url", "wasm-bindgen", - "wasm-bindgen-futures", "web-sys", ] [[package]] -name = "leptos_server" -version = "0.6.15" +name = "leptos_router_macro" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a97eb90a13f71500b831c7119ddd3bdd0d7ae0a6b0487cade4fddeed3b8c03f" +checksum = "571042420d79f4f5b6b0d149dc1561b03f47e08c37c8fa0dfc80c73ad67be8af" dependencies = [ - "inventory", - "lazy_static", - "leptos_macro", - "leptos_reactive", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "leptos_server" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38acbf32649a4b127c8d4ccaed8fb388e19a746430a0ea8f8160e51e28c36e2d" +dependencies = [ + "any_spawner", + "base64 0.22.1", + "codee", + "futures", + "hydration_context", + "or_poisoned", + "reactive_graph", + "send_wrapper", "serde", + "serde_json", "server_fn", - "thiserror 1.0.69", - "tracing", + "tachys", ] [[package]] @@ -3256,10 +3430,6 @@ name = "linear-map" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" -dependencies = [ - "serde", - "serde_test", -] [[package]] name = "linux-raw-sys" @@ -3349,9 +3519,9 @@ dependencies = [ [[package]] name = "manyhow" -version = "0.10.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91ea592d76c0b6471965708ccff7e6a5d277f676b90ab31f4d3f3fc77fade64" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" dependencies = [ "manyhow-macros", "proc-macro2", @@ -3361,11 +3531,11 @@ dependencies = [ [[package]] name = "manyhow-macros" -version = "0.10.4" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64621e2c08f2576e4194ea8be11daf24ac01249a4f53cd8befcbb7077120ead" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" dependencies = [ - "proc-macro-utils 0.8.0", + "proc-macro-utils", "proc-macro2", "quote", ] @@ -3524,6 +3694,23 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "multimap" version = "0.10.1" @@ -3612,6 +3799,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "next_tuple" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" + [[package]] name = "nom" version = "7.1.3" @@ -3848,7 +4041,7 @@ dependencies = [ "sha2", "tar", "thiserror 1.0.69", - "toml", + "toml 0.8.23", "ureq", "url", "uuid", @@ -3857,12 +4050,12 @@ dependencies = [ [[package]] name = "oco_ref" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708" +checksum = "ed0423ff9973dea4d6bd075934fdda86ebb8c05bdf9d6b0507067d4a1226371d" dependencies = [ "serde", - "thiserror 1.0.69", + "thiserror 2.0.15", ] [[package]] @@ -3949,6 +4142,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "or_poisoned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c04f5d74368e4d0dfe06c45c8627c81bd7c317d52762d118fb9b3076f6420fd" + [[package]] name = "ordered-float" version = "2.10.1" @@ -3997,12 +4196,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "pad-adapter" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" - [[package]] name = "palette" version = "0.7.6" @@ -4027,6 +4220,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.4" @@ -4255,6 +4454,8 @@ dependencies = [ "axum", "embeddings-engine", "inference-engine", + "leptos-app", + "mime_guess", "reqwest", "rust-embed", "serde", @@ -4341,17 +4542,6 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "proc-macro-utils" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" -dependencies = [ - "proc-macro2", - "quote", - "smallvec", -] - [[package]] name = "proc-macro-utils" version = "0.10.0" @@ -4572,7 +4762,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "socket2 0.5.10", "thiserror 2.0.15", @@ -4592,7 +4782,7 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "rustls-pki-types", "slab", @@ -4641,7 +4831,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" dependencies = [ - "proc-macro-utils 0.10.0", + "proc-macro-utils", "proc-macro2", "quote", "syn 2.0.106", @@ -4837,6 +5027,59 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reactive_graph" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e4f808d01701256dc220e398d518684781bcd1b3b1a6c1c107fd41374f0624" +dependencies = [ + "any_spawner", + "async-lock", + "futures", + "guardian", + "hydration_context", + "indexmap", + "or_poisoned", + "pin-project-lite", + "rustc-hash", + "rustc_version", + "send_wrapper", + "serde", + "slotmap", + "thiserror 2.0.15", + "web-sys", +] + +[[package]] +name = "reactive_stores" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79983e88dfd1a2925e29a4853ab9161b234ea78dd0d44ed33a706c9cd5e35762" +dependencies = [ + "dashmap", + "guardian", + "itertools 0.14.0", + "or_poisoned", + "paste", + "reactive_graph", + "reactive_stores_macro", + "rustc-hash", + "send_wrapper", +] + +[[package]] +name = "reactive_stores_macro" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa40919eb2975100283b2a70e68eafce1e8bcf81f0622ff168e4c2b3f8d46bb" +dependencies = [ + "convert_case 0.8.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "realfft" version = "3.5.0" @@ -5013,16 +5256,17 @@ dependencies = [ [[package]] name = "rstml" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe542870b8f59dd45ad11d382e5339c9a1047cde059be136a7016095bbdefa77" +checksum = "61cf4616de7499fc5164570d40ca4e1b24d231c6833a88bff0fe00725080fd56" dependencies = [ + "derive-where", "proc-macro2", "proc-macro2-diagnostics", "quote", "syn 2.0.106", "syn_derive", - "thiserror 1.0.69", + "thiserror 2.0.15", ] [[package]] @@ -5067,6 +5311,7 @@ version = "8.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" dependencies = [ + "globset", "sha2", "walkdir", ] @@ -5077,12 +5322,6 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -5274,12 +5513,6 @@ dependencies = [ "libc", ] -[[package]] -name = "self_cell" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" - [[package]] name = "semver" version = "1.0.26" @@ -5310,17 +5543,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_derive" version = "1.0.219" @@ -5365,24 +5587,13 @@ dependencies = [ [[package]] name = "serde_qs" -version = "0.12.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", - "thiserror 1.0.69", -] - -[[package]] -name = "serde_qs" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" -dependencies = [ - "percent-encoding", - "serde", - "thiserror 1.0.69", + "thiserror 2.0.15", ] [[package]] @@ -5395,10 +5606,10 @@ dependencies = [ ] [[package]] -name = "serde_test" -version = "1.0.177" +name = "serde_spanned" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -5417,25 +5628,36 @@ dependencies = [ [[package]] name = "server_fn" -version = "0.6.15" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fae7a3038a32e5a34ba32c6c45eb4852f8affaf8b794ebfcd4b1099e2d62ebe" +checksum = "4efa7bb741386fb31a68269c81b1469c917d9adb1f4102a2d2684f11e3235389" dependencies = [ + "axum", + "base64 0.22.1", "bytes", - "ciborium", + "const-str", "const_format", "dashmap", "futures", "gloo-net", "http", + "http-body-util", + "hyper", + "inventory", "js-sys", - "once_cell", + "pin-project-lite", + "rustc_version", + "rustversion", "send_wrapper", "serde", "serde_json", - "serde_qs 0.12.0", + "serde_qs", "server_fn_macro_default", - "thiserror 1.0.69", + "thiserror 2.0.15", + "throw_error", + "tokio", + "tower", + "tower-layer", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -5446,28 +5668,40 @@ dependencies = [ [[package]] name = "server_fn_macro" -version = "0.6.15" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaaf648c6967aef78177c0610478abb5a3455811f401f3c62d10ae9bd3901a1" +checksum = "5d1a916793571234d1c4622153d42495d26605ed7b9d5d38a2699666cfef46b3" dependencies = [ "const_format", - "convert_case", + "convert_case 0.8.0", "proc-macro2", "quote", + "rustc_version", "syn 2.0.106", "xxhash-rust", ] [[package]] name = "server_fn_macro_default" -version = "0.6.15" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440" +checksum = "63eb08f80db903d3c42f64e60ebb3875e0305be502bdc064ec0a0eab42207f00" dependencies = [ "server_fn_macro", "syn 2.0.106", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -5549,7 +5783,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ - "serde", "version_check", ] @@ -5596,6 +5829,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spm_precompiled" version = "0.1.4" @@ -5875,11 +6114,11 @@ dependencies = [ [[package]] name = "syn_derive" -version = "0.1.8" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +checksum = "cdb066a04799e45f5d582e8fc6ec8e6d6896040d00898eb4e6a835196815b219" dependencies = [ - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.106", @@ -5963,10 +6202,44 @@ dependencies = [ "cfg-expr", "heck", "pkg-config", - "toml", + "toml 0.8.23", "version-compare", ] +[[package]] +name = "tachys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dacbb26ffb2bbe6743702ee27c3e994c0caae86c92137278de9a9d92d383765c" +dependencies = [ + "any_spawner", + "async-trait", + "const_str_slice_concat", + "drain_filter_polyfill", + "either_of", + "erased", + "futures", + "html-escape", + "indexmap", + "itertools 0.14.0", + "js-sys", + "linear-map", + "next_tuple", + "oco_ref", + "or_poisoned", + "parking_lot", + "paste", + "reactive_graph", + "reactive_stores", + "rustc-hash", + "rustc_version", + "send_wrapper", + "slotmap", + "throw_error", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "tar" version = "0.4.44" @@ -6057,6 +6330,15 @@ dependencies = [ "ordered-float", ] +[[package]] +name = "throw_error" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e42a6afdde94f3e656fae18f837cb9bbe500a5ac5de325b09f3ec05b9c28e3" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "tiff" version = "0.9.1" @@ -6199,6 +6481,18 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.16" @@ -6219,11 +6513,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_edit", ] +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -6233,6 +6540,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -6241,12 +6557,21 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", "winnow", ] +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + [[package]] name = "toml_write" version = "0.1.2" @@ -6286,11 +6611,20 @@ checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "bitflags 2.9.2", "bytes", + "futures-core", "futures-util", "http", "http-body", + "http-body-util", + "http-range-header", + "httpdate", "iri-string", + "mime", + "mime_guess", + "percent-encoding", "pin-project-lite", + "tokio", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -6404,6 +6738,23 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "sha1", + "thiserror 2.0.15", + "utf-8", +] + [[package]] name = "twox-hash" version = "1.6.3" @@ -6422,18 +6773,18 @@ checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56" [[package]] name = "typed-builder" -version = "0.18.2" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" +checksum = "fef81aec2ca29576f9f6ae8755108640d0a86dd3161b2e8bca6cfa554e98f77d" dependencies = [ "typed-builder-macro", ] [[package]] name = "typed-builder-macro" -version = "0.18.2" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" +checksum = "1ecb9ecf7799210407c14a8cfdfe0173365780968dc57973ed082211958e0b18" dependencies = [ "proc-macro2", "quote", @@ -6582,6 +6933,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8-width" version = "0.1.7" @@ -6802,6 +7159,31 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm_split_helpers" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50e0e45d0d871605a21fc4ee93a5380b7bdc41b5eda22e42f0777a4ce79b65c" +dependencies = [ + "async-once-cell", + "or_poisoned", + "wasm_split_macros", +] + +[[package]] +name = "wasm_split_macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f8a7f0bf54b0129a337aadfe8b716d843689f69c75b2a6413a0cff2e0d00982" +dependencies = [ + "base16", + "digest", + "quote", + "sha2", + "syn 2.0.106", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.77" diff --git a/Cargo.toml b/Cargo.toml index b67d266..4b0627a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "crates/predict-otron-9000", "crates/inference-engine", "crates/embeddings-engine", - "crates/leptos-chat" + "crates/leptos-app" ] default-members = ["crates/predict-otron-9000"] resolver = "2" @@ -12,6 +12,5 @@ resolver = "2" [[workspace.metadata.leptos]] # project name -name = "leptos-project" -bin-package = "leptos-chat" -lib-package = "leptos-chat" \ No newline at end of file +bin-package = "leptos-app" +lib-package = "leptos-app" \ No newline at end of file diff --git a/crates/leptos-app/.cargo/config.toml b/crates/leptos-app/.cargo/config.toml new file mode 100644 index 0000000..628502f --- /dev/null +++ b/crates/leptos-app/.cargo/config.toml @@ -0,0 +1,3 @@ +# Ensure getrandom works on wasm32-unknown-unknown without needing manual RUSTFLAGS +[target.wasm32-unknown-unknown] +rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""] diff --git a/crates/leptos-app/.gitignore b/crates/leptos-app/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/crates/leptos-app/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/crates/leptos-app/Cargo.toml b/crates/leptos-app/Cargo.toml new file mode 100644 index 0000000..16d164b --- /dev/null +++ b/crates/leptos-app/Cargo.toml @@ -0,0 +1,135 @@ +[package] +name = "leptos-app" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +leptos = { version = "0.8.0" } +leptos_router = { version = "0.8.0" } +axum = { version = "0.8.0", optional = true } +console_error_panic_hook = { version = "0.1", optional = true } +leptos_axum = { version = "0.8.0", optional = true } +leptos_meta = { version = "0.8.0" } +tokio = { version = "1", features = ["rt-multi-thread"], optional = true } +wasm-bindgen = { version = "=0.2.100", optional = true } + +# Chat interface dependencies +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +async-openai-wasm = { version = "0.29", default-features = false } +futures-util = "0.3" +js-sys = { version = "0.3", optional = true } +either = { version = "1.9", features = ["serde"] } + +web-sys = { version = "0.3", optional = true, features = [ + "console", + "Window", + "Document", + "Element", + "HtmlElement", + "HtmlInputElement", + "HtmlSelectElement", + "HtmlTextAreaElement", + "Event", + "EventTarget", + "KeyboardEvent", +] } + +[dependencies.uuid] +version = "1.0" +features = [ + "v4", + "fast-rng", + "macro-diagnostics", + "js", +] + +[features] +hydrate = [ + "leptos/hydrate", + "dep:console_error_panic_hook", + "dep:wasm-bindgen", + "dep:js-sys", + "dep:web-sys", +] +ssr = [ + "dep:axum", + "dep:tokio", + "dep:leptos_axum", + "leptos/ssr", + "leptos_meta/ssr", + "leptos_router/ssr", +] + +# Defines a size-optimized profile for the WASM bundle in release mode +[profile.wasm-release] +inherits = "release" +opt-level = 'z' +lto = true +codegen-units = 1 +panic = "abort" + +[package.metadata.leptos] +# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name +output-name = "leptos-app" + +# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup. +site-root = "target/site" + +# The site-root relative folder where all compiled output (JS, WASM and CSS) is written +# Defaults to pkg +site-pkg-dir = "pkg" + +# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to //app.css +style-file = "style/main.scss" +# Assets source dir. All files found here will be copied and synchronized to site-root. +# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir. +# +# Optional. Env: LEPTOS_ASSETS_DIR. +assets-dir = "public" + +# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup. +site-addr = "127.0.0.1:3000" + +# The port to use for automatic reload monitoring +reload-port = 3001 + +# [Optional] Command to use when running end2end tests. It will run in the end2end dir. +# [Windows] for non-WSL use "npx.cmd playwright test" +# This binary name can be checked in Powershell with Get-Command npx +end2end-cmd = "npx playwright test" +end2end-dir = "end2end" + +# The browserlist query used for optimizing the CSS. +browserquery = "defaults" + +# The environment Leptos will run in, usually either "DEV" or "PROD" +env = "DEV" + +# The features to use when compiling the bin target +# +# Optional. Can be over-ridden with the command line parameter --bin-features +bin-features = ["ssr"] + +# If the --no-default-features flag should be used when compiling the bin target +# +# Optional. Defaults to false. +bin-default-features = false + +# The features to use when compiling the lib target +# +# Optional. Can be over-ridden with the command line parameter --lib-features +lib-features = ["hydrate"] + +# If the --no-default-features flag should be used when compiling the lib target +# +# Optional. Defaults to false. +lib-default-features = false + +# The profile to use for the lib target when compiling for release +# +# Optional. Defaults to "release". +lib-profile-release = "wasm-release" diff --git a/crates/leptos-app/Dockerfile b/crates/leptos-app/Dockerfile new file mode 100644 index 0000000..5c2b22c --- /dev/null +++ b/crates/leptos-app/Dockerfile @@ -0,0 +1,21 @@ +# Build stage +FROM rust:1-alpine AS builder + +# Install build dependencies +RUN apk add --no-cache npm nodejs musl-dev pkgconfig openssl-dev git curl bash + +RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + +WORKDIR /app + +# Copy manifest first (cache deps) +COPY . . + +# Install cargo-leptos +RUN cargo binstall cargo-leptos + +# Build release artifacts +RUN cargo leptos build --release + +EXPOSE 8788 +CMD ["cargo", "leptos", "serve", "--release"] \ No newline at end of file diff --git a/crates/leptos-app/end2end/.gitignore b/crates/leptos-app/end2end/.gitignore new file mode 100644 index 0000000..169a2af --- /dev/null +++ b/crates/leptos-app/end2end/.gitignore @@ -0,0 +1,3 @@ +node_modules +playwright-report +test-results diff --git a/crates/leptos-app/end2end/package.json b/crates/leptos-app/end2end/package.json new file mode 100644 index 0000000..a80ac59 --- /dev/null +++ b/crates/leptos-app/end2end/package.json @@ -0,0 +1,15 @@ +{ + "name": "end2end", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": {}, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.44.1", + "@types/node": "^20.12.12", + "typescript": "^5.4.5" + } +} diff --git a/crates/leptos-app/end2end/playwright.config.ts b/crates/leptos-app/end2end/playwright.config.ts new file mode 100644 index 0000000..aee2d46 --- /dev/null +++ b/crates/leptos-app/end2end/playwright.config.ts @@ -0,0 +1,105 @@ +import type { PlaywrightTestConfig } from "@playwright/test"; +import { devices, defineConfig } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./tests", + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + }, + }, + + { + name: "firefox", + use: { + ...devices["Desktop Firefox"], + }, + }, + + { + name: "webkit", + use: { + ...devices["Desktop Safari"], + }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { + // ...devices['Pixel 5'], + // }, + // }, + // { + // name: 'Mobile Safari', + // use: { + // ...devices['iPhone 12'], + // }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}); diff --git a/crates/leptos-app/end2end/tests/example.spec.ts b/crates/leptos-app/end2end/tests/example.spec.ts new file mode 100644 index 0000000..0139fc3 --- /dev/null +++ b/crates/leptos-app/end2end/tests/example.spec.ts @@ -0,0 +1,9 @@ +import { test, expect } from "@playwright/test"; + +test("homepage has title and heading text", async ({ page }) => { + await page.goto("http://localhost:3000/"); + + await expect(page).toHaveTitle("Welcome to Leptos"); + + await expect(page.locator("h1")).toHaveText("Welcome to Leptos!"); +}); diff --git a/crates/leptos-app/end2end/tsconfig.json b/crates/leptos-app/end2end/tsconfig.json new file mode 100644 index 0000000..e075f97 --- /dev/null +++ b/crates/leptos-app/end2end/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/crates/leptos-app/public/favicon.ico b/crates/leptos-app/public/favicon.ico new file mode 100644 index 0000000..2ba8527 Binary files /dev/null and b/crates/leptos-app/public/favicon.ico differ diff --git a/crates/leptos-chat/src/lib.rs b/crates/leptos-app/src/app.rs similarity index 50% rename from crates/leptos-chat/src/lib.rs rename to crates/leptos-app/src/app.rs index 4ccc0cd..b757b48 100644 --- a/crates/leptos-chat/src/lib.rs +++ b/crates/leptos-app/src/app.rs @@ -1,12 +1,23 @@ -use leptos::*; -use leptos_meta::*; -use leptos_router::*; +use leptos::prelude::*; +use leptos_meta::{provide_meta_context, MetaTags, Stylesheet, Title}; +use leptos_router::{ + components::{Route, Router, Routes}, + StaticSegment, +}; + +#[cfg(feature = "hydrate")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "hydrate")] use std::collections::VecDeque; +#[cfg(feature = "hydrate")] use uuid::Uuid; +#[cfg(feature = "hydrate")] use js_sys::Date; +#[cfg(feature = "hydrate")] use web_sys::{HtmlInputElement, KeyboardEvent, SubmitEvent}; +#[cfg(feature = "hydrate")] use futures_util::StreamExt; +#[cfg(feature = "hydrate")] use async_openai_wasm::{ types::{ ChatCompletionRequestAssistantMessageArgs, ChatCompletionRequestSystemMessageArgs, @@ -14,9 +25,14 @@ use async_openai_wasm::{ }, Client, }; +#[cfg(feature = "hydrate")] use async_openai_wasm::config::OpenAIConfig; -use async_openai_wasm::types::{ChatCompletionResponseStream, Model, Role, FinishReason}; +#[cfg(feature = "hydrate")] +use async_openai_wasm::types::{Role, FinishReason}; +#[cfg(feature = "hydrate")] +use leptos::task::spawn_local; +#[cfg(feature = "hydrate")] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { pub id: String, @@ -25,12 +41,15 @@ pub struct Message { pub timestamp: f64, } +#[cfg(feature = "hydrate")] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MessageContent(pub either::Either>>); +#[cfg(feature = "hydrate")] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MessageInnerContent(pub either::Either>); +#[cfg(feature = "hydrate")] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatMessage { pub role: String, @@ -38,59 +57,12 @@ pub struct ChatMessage { pub name: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ChatRequest { - pub model: String, - pub messages: Vec, - pub max_tokens: Option, - pub temperature: Option, - pub top_p: Option, - pub stream: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ChatResponse { - pub id: String, - pub object: String, - pub created: u64, - pub model: String, - pub choices: Vec, - pub usage: Usage, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Choice { - pub index: usize, - pub message: ChatMessage, - pub finish_reason: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Usage { - pub prompt_tokens: usize, - pub completion_tokens: usize, - pub total_tokens: usize, -} - -#[component] -pub fn App() -> impl IntoView { - provide_meta_context(); - - view! { - - - <Router> - <main> - <Routes> - <Route path="/" view=ChatInterface/> - </Routes> - </main> - </Router> - } -} +#[cfg(feature = "hydrate")] +const DEFAULT_MODEL: &str = "default"; +#[cfg(feature = "hydrate")] async fn fetch_available_models() -> Result<Vec<OpenAIModel>, String> { - log::info!("[DEBUG_LOG] fetch_available_models: Starting model fetch from http://localhost:8080/v1"); + leptos::logging::log!("[DEBUG_LOG] fetch_available_models: Starting model fetch from http://localhost:8080/v1"); let config = OpenAIConfig::new().with_api_base("http://localhost:8080/v1".to_string()); let client = Client::with_config(config); @@ -98,126 +70,110 @@ async fn fetch_available_models() -> Result<Vec<OpenAIModel>, String> { match client.models().list().await { Ok(response) => { let model_count = response.data.len(); - log::info!("[DEBUG_LOG] fetch_available_models: Successfully fetched {} models", model_count); + leptos::logging::log!("[DEBUG_LOG] fetch_available_models: Successfully fetched {} models", model_count); if model_count > 0 { let model_names: Vec<String> = response.data.iter().map(|m| m.id.clone()).collect(); - log::debug!("[DEBUG_LOG] fetch_available_models: Available models: {:?}", model_names); + leptos::logging::log!("[DEBUG_LOG] fetch_available_models: Available models: {:?}", model_names); } else { - log::warn!("[DEBUG_LOG] fetch_available_models: No models returned by server"); + leptos::logging::log!("[DEBUG_LOG] fetch_available_models: No models returned by server"); } Ok(response.data) }, Err(e) => { - log::error!("[DEBUG_LOG] fetch_available_models: Failed to fetch models: {:?}", e); - - let error_details = format!("{:?}", e); - if error_details.contains("400") || error_details.contains("Bad Request") { - log::error!("[DEBUG_LOG] fetch_available_models: HTTP 400 - Server rejected models request"); - } else if error_details.contains("404") || error_details.contains("Not Found") { - log::error!("[DEBUG_LOG] fetch_available_models: HTTP 404 - Models endpoint not found"); - } else if error_details.contains("Connection") || error_details.contains("connection") { - log::error!("[DEBUG_LOG] fetch_available_models: Connection error - server may be down"); - } - + leptos::logging::log!("[DEBUG_LOG] fetch_available_models: Failed to fetch models: {:?}", e); Err(format!("Failed to fetch models: {}", e)) } } } -async fn send_chat_request(chat_request: ChatRequest) -> ChatCompletionResponseStream { - let config = OpenAIConfig::new().with_api_base("http://localhost:8080/v1".to_string()); - let client = Client::with_config(config); - - let mut typed_chat = async_openai_wasm::types::CreateChatCompletionRequest { - messages: vec![], - model: "".to_string(), - store: None, - reasoning_effort: None, - metadata: None, - frequency_penalty: None, - logit_bias: None, - logprobs: None, - top_logprobs: None, - max_tokens: None, - max_completion_tokens: None, - n: None, - modalities: None, - prediction: None, - audio: None, - presence_penalty: None, - response_format: None, - seed: None, - service_tier: None, - stop: None, - stream: None, - stream_options: None, - temperature: None, - top_p: None, - tools: None, - tool_choice: None, - parallel_tool_calls: None, - user: None, - function_call: None, - functions: None, - web_search_options: None, - extra_params: None, - }; - - typed_chat.messages = chat_request.messages - .iter() - .map(|msg| { - let content = match &msg.content { - Some(MessageContent(either::Either::Left(text))) => text.clone(), - _ => "".to_string() - }; - let role = msg.role.clone(); - match role.as_str() { - "system" => ChatCompletionRequestSystemMessageArgs::default() - .content(content) - .build() - .expect("failed to build system message") - .into(), - "user" => ChatCompletionRequestUserMessageArgs::default() - .content(content) - .build() - .expect("failed to build user message") - .into(), - "assistant" => ChatCompletionRequestAssistantMessageArgs::default() - .content(content) - .build() - .expect("failed to build assistant message") - .into(), - _ => ChatCompletionRequestUserMessageArgs::default() - .content(content) - .build() - .expect("failed to build default message") - .into() - } - }) - .collect(); - client.chat().create_stream(typed_chat).await.unwrap() +pub fn shell(options: LeptosOptions) -> impl IntoView { + view! { + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1"/> + <AutoReload options=options.clone() /> + <HydrationScripts options/> + <MetaTags/> + </head> + <body> + <App/> + </body> + </html> + } } -// #[cfg(not(target_arch = "wasm32"))] -// async fn send_chat_request(_chat_request: ChatRequest) -> Result<ChatResponse, String> { -// Err("leptos-chat chat request only supported on wasm32 target".to_string()) -// } +#[component] +pub fn App() -> impl IntoView { + // Provides context that manages stylesheets, titles, meta tags, etc. + provide_meta_context(); -const DEFAULT_MODEL: &str = "default"; + view! { + // injects a stylesheet into the document <head> + // id=leptos means cargo-leptos will hot-reload this stylesheet + <Stylesheet id="leptos" href="/pkg/leptos-app.css"/> + // sets the document title + <Title text="Chat Interface"/> + + // content for this chat interface + <Router> + <main> + <Routes fallback=|| "Page not found.".into_view()> + <Route path=StaticSegment("") view=ChatInterface/> + </Routes> + </main> + </Router> + } +} + +/// Renders the home page of your application. +#[component] +fn HomePage() -> impl IntoView { + // Creates a reactive value to update the button + let count = RwSignal::new(0); + let on_click = move |_| *count.write() += 1; + + view! { + <h1>"Welcome to Leptos!"</h1> + <button on:click=on_click>"Click Me: " {count}</button> + } +} + +/// Renders the chat interface #[component] fn ChatInterface() -> impl IntoView { - let (messages, set_messages) = create_signal::<VecDeque<Message>>(VecDeque::new()); - let (input_value, set_input_value) = create_signal(String::new()); - let (is_loading, set_is_loading) = create_signal(false); - let (available_models, set_available_models) = create_signal::<Vec<OpenAIModel>>(Vec::new()); - let (selected_model, set_selected_model) = create_signal(DEFAULT_MODEL.to_string()); - let (models_loading, set_models_loading) = create_signal(false); + #[cfg(feature = "hydrate")] + { + ChatInterfaceImpl() + } + + #[cfg(not(feature = "hydrate"))] + { + view! { + <div class="chat-container"> + <h1>"Chat Interface"</h1> + <p>"Loading chat interface..."</p> + </div> + } + } +} + +#[cfg(feature = "hydrate")] +#[component] +fn ChatInterfaceImpl() -> impl IntoView { + let (messages, set_messages) = RwSignal::new(VecDeque::<Message>::new()).split(); + let (input_value, set_input_value) = RwSignal::new(String::new()).split(); + let (is_loading, set_is_loading) = RwSignal::new(false).split(); + let (available_models, set_available_models) = RwSignal::new(Vec::<OpenAIModel>::new()).split(); + let (selected_model, set_selected_model) = RwSignal::new(DEFAULT_MODEL.to_string()).split(); + let (models_loading, set_models_loading) = RwSignal::new(false).split(); // Fetch models on component initialization - create_effect(move |_| { + Effect::new(move |_| { spawn_local(async move { set_models_loading.set(true); match fetch_available_models().await { @@ -226,8 +182,7 @@ fn ChatInterface() -> impl IntoView { set_models_loading.set(false); } Err(e) => { - log::error!("Failed to fetch models: {}", e); - // Set a default model if fetching fails + leptos::logging::log!("Failed to fetch models: {}", e); set_available_models.set(vec![]); set_models_loading.set(false); } @@ -235,17 +190,15 @@ fn ChatInterface() -> impl IntoView { }); }); - let send_message = create_action(move |content: &String| { + let send_message = Action::new_unsync(move |content: &String| { let content = content.clone(); async move { if content.trim().is_empty() { - log::debug!("[DEBUG_LOG] send_message: Empty content, skipping"); + leptos::logging::log!("[DEBUG_LOG] send_message: Empty content, skipping"); return; } - log::info!("[DEBUG_LOG] send_message: Starting message send process"); - log::debug!("[DEBUG_LOG] send_message: User message content length: {}", content.len()); - + leptos::logging::log!("[DEBUG_LOG] send_message: Starting message send process"); set_is_loading.set(true); // Add user message to chat @@ -269,43 +222,26 @@ fn ChatInterface() -> impl IntoView { chat_messages.push(system_message.into()); // Add history messages - let history_count = messages.with_untracked(|msgs| { - let count = msgs.len(); - for msg in msgs.iter() { - match msg.role.as_str() { - "user" => { - let message = ChatCompletionRequestUserMessageArgs::default() - .content(msg.content.clone()) - .build() - .expect("failed to build user message"); - chat_messages.push(message.into()); - } - "assistant" => { - let message = ChatCompletionRequestAssistantMessageArgs::default() - .content(msg.content.clone()) - .build() - .expect("failed to build assistant message"); - chat_messages.push(message.into()); - } - "system" => { - let message = ChatCompletionRequestSystemMessageArgs::default() - .content(msg.content.clone()) - .build() - .expect("failed to build system message"); - chat_messages.push(message.into()); - } - _ => { - // Default to user message for unknown roles - let message = ChatCompletionRequestUserMessageArgs::default() - .content(msg.content.clone()) - .build() - .expect("failed to build default message"); - chat_messages.push(message.into()); - } + let history_count = messages.get_untracked().len(); + for msg in messages.get_untracked().iter() { + match msg.role.as_str() { + "user" => { + let message = ChatCompletionRequestUserMessageArgs::default() + .content(msg.content.clone()) + .build() + .expect("failed to build user message"); + chat_messages.push(message.into()); } + "assistant" => { + let message = ChatCompletionRequestAssistantMessageArgs::default() + .content(msg.content.clone()) + .build() + .expect("failed to build assistant message"); + chat_messages.push(message.into()); + } + _ => {} } - count - }); + } // Add current user message let message = ChatCompletionRequestUserMessageArgs::default() @@ -317,45 +253,36 @@ fn ChatInterface() -> impl IntoView { let current_model = selected_model.get_untracked(); let total_messages = chat_messages.len(); - log::info!("[DEBUG_LOG] send_message: Preparing request - model: '{}', history_count: {}, total_messages: {}", - current_model, history_count, total_messages); + leptos::logging::log!("[DEBUG_LOG] send_message: Preparing request - model: '{}', history_count: {}, total_messages: {}", + current_model, history_count, total_messages); let request = CreateChatCompletionRequestArgs::default() .model(current_model.as_str()) .max_tokens(512u32) .messages(chat_messages) - .stream(true) // ensure server streams + .stream(true) .build() .expect("failed to build request"); - // Log request details for debugging server issues - log::info!("[DEBUG_LOG] send_message: Request configuration - model: '{}', max_tokens: 512, stream: true, messages_count: {}", - current_model, total_messages); - log::debug!("[DEBUG_LOG] send_message: Request details - history_messages: {}, system_messages: 1, user_messages: {}", - history_count, total_messages - history_count - 1); - // Send request let config = OpenAIConfig::new().with_api_base("http://localhost:8080/v1".to_string()); let client = Client::with_config(config); - log::info!("[DEBUG_LOG] send_message: Sending request to http://localhost:8080/v1 with model: '{}'", current_model); - + leptos::logging::log!("[DEBUG_LOG] send_message: Sending request to http://localhost:8080/v1 with model: '{}'", current_model); match client.chat().create_stream(request).await { Ok(mut stream) => { - log::info!("[DEBUG_LOG] send_message: Successfully created stream, starting to receive response"); + leptos::logging::log!("[DEBUG_LOG] send_message: Successfully created stream"); - // Defer creating assistant message until we receive role=assistant from the stream let mut assistant_created = false; let mut content_appended = false; let mut chunks_received = 0; - // Stream loop: handle deltas and finish events + while let Some(next) = stream.next().await { match next { Ok(chunk) => { chunks_received += 1; if let Some(choice) = chunk.choices.get(0) { - // 1) Create assistant message when role arrives if !assistant_created { if let Some(role) = &choice.delta.role { if role == &Role::Assistant { @@ -373,10 +300,8 @@ fn ChatInterface() -> impl IntoView { } } - // 2) Append content tokens when provided if let Some(content) = &choice.delta.content { if !content.is_empty() { - // If content arrives before role, create assistant message now if !assistant_created { assistant_created = true; let assistant_id = Uuid::new_v4().to_string(); @@ -401,23 +326,21 @@ fn ChatInterface() -> impl IntoView { } } - // 3) Stop on finish_reason=="stop" (mirrors [DONE]) if let Some(reason) = &choice.finish_reason { if reason == &FinishReason::Stop { - log::info!("[DEBUG_LOG] send_message: Received finish_reason=stop after {} chunks", chunks_received); + leptos::logging::log!("[DEBUG_LOG] send_message: Received finish_reason=stop after {} chunks", chunks_received); break; } } } } Err(e) => { - log::error!("[DEBUG_LOG] send_message: Stream error after {} chunks: {:?}", chunks_received, e); - log::error!("[DEBUG_LOG] send_message: Stream error details - model: '{}', chunks_received: {}", current_model, chunks_received); + leptos::logging::log!("[DEBUG_LOG] send_message: Stream error after {} chunks: {:?}", chunks_received, e); set_messages.update(|msgs| { msgs.push_back(Message { id: Uuid::new_v4().to_string(), role: "system".to_string(), - content: format!("Stream error after {} chunks: {}", chunks_received, e), + content: format!("Stream error: {}", e), timestamp: Date::now(), }); }); @@ -426,7 +349,6 @@ fn ChatInterface() -> impl IntoView { } } - // Cleanup: If we created an assistant message but no content ever arrived, remove the empty message if assistant_created && !content_appended { set_messages.update(|msgs| { let should_pop = msgs @@ -434,45 +356,19 @@ fn ChatInterface() -> impl IntoView { .map(|m| m.role == "assistant" && m.content.is_empty()) .unwrap_or(false); if should_pop { - log::info!("[DEBUG_LOG] send_message: Removing empty assistant message (no content received)"); msgs.pop_back(); } }); } - log::info!("[DEBUG_LOG] send_message: Stream completed successfully, received {} chunks", chunks_received); + leptos::logging::log!("[DEBUG_LOG] send_message: Stream completed successfully, received {} chunks", chunks_received); } Err(e) => { - // Detailed error logging for different types of errors - log::error!("[DEBUG_LOG] send_message: Request failed with error: {:?}", e); - log::error!("[DEBUG_LOG] send_message: Request context - model: '{}', total_messages: {}, endpoint: http://localhost:8080/v1", - current_model, total_messages); - - // Try to extract more specific error information - let error_details = format!("{:?}", e); - let user_message = if error_details.contains("400") || error_details.contains("Bad Request") { - log::error!("[DEBUG_LOG] send_message: HTTP 400 Bad Request detected - possible issues:"); - log::error!("[DEBUG_LOG] send_message: - Invalid model name: '{}'", current_model); - log::error!("[DEBUG_LOG] send_message: - Invalid message format or content"); - log::error!("[DEBUG_LOG] send_message: - Server configuration issue"); - format!("Error: HTTP 400 Bad Request - Check model '{}' and message format. See console for details.", current_model) - } else if error_details.contains("404") || error_details.contains("Not Found") { - log::error!("[DEBUG_LOG] send_message: HTTP 404 Not Found - server endpoint may be incorrect"); - "Error: HTTP 404 Not Found - Server endpoint not found".to_string() - } else if error_details.contains("500") || error_details.contains("Internal Server Error") { - log::error!("[DEBUG_LOG] send_message: HTTP 500 Internal Server Error - server-side issue"); - "Error: HTTP 500 Internal Server Error - Server problem".to_string() - } else if error_details.contains("Connection") || error_details.contains("connection") { - log::error!("[DEBUG_LOG] send_message: Connection error - server may be down"); - "Error: Cannot connect to server at http://localhost:8080".to_string() - } else { - format!("Error: Request failed - {}", e) - }; - + leptos::logging::log!("[DEBUG_LOG] send_message: Request failed with error: {:?}", e); let error_message = Message { id: Uuid::new_v4().to_string(), role: "system".to_string(), - content: user_message, + content: format!("Error: Request failed - {}", e), timestamp: Date::now(), }; set_messages.update(|msgs| msgs.push_back(error_message)); @@ -524,7 +420,7 @@ fn ChatInterface() -> impl IntoView { </div> } }) - .collect_view() + .collect::<Vec<_>>() }; let loading_indicator = move || { @@ -551,21 +447,21 @@ fn ChatInterface() -> impl IntoView { > {move || { if models_loading.get() { - view! { - <option value="">"Loading models..."</option> - }.into_view() + vec![view! { + <option value={String::from("")} selected=false>{String::from("Loading models...")}</option> + }] } else { let models = available_models.get(); if models.is_empty() { - view! { - <option selected=true value="gemma-3b-it">"gemma-3b-it (default)"</option> - }.into_view() + vec![view! { + <option value={String::from("default")} selected=true>{String::from("default")}</option> + }] } else { models.into_iter().map(|model| { view! { - <option value=model.id.clone() selected={model.id == DEFAULT_MODEL}>{model.id}</option> + <option value=model.id.clone() selected={model.id == DEFAULT_MODEL}>{model.id.clone()}</option> } - }).collect_view() + }).collect::<Vec<_>>() } } }} @@ -596,14 +492,3 @@ fn ChatInterface() -> impl IntoView { </div> } } - -#[wasm_bindgen::prelude::wasm_bindgen(start)] -pub fn main() { - // Set up error handling and logging for WebAssembly - console_error_panic_hook::set_once(); - console_log::init_with_level(log::Level::Debug).expect("error initializing logger"); - - // Mount the App component to the document body - - leptos::mount_to_body(App) -} \ No newline at end of file diff --git a/crates/leptos-app/src/lib.rs b/crates/leptos-app/src/lib.rs new file mode 100644 index 0000000..0ebb5ec --- /dev/null +++ b/crates/leptos-app/src/lib.rs @@ -0,0 +1,30 @@ +pub mod app; + +#[cfg(feature = "hydrate")] +#[wasm_bindgen::prelude::wasm_bindgen] +pub fn hydrate() { + use crate::app::*; + console_error_panic_hook::set_once(); + leptos::mount::hydrate_body(App); +} + +#[cfg(feature = "ssr")] +pub fn create_leptos_router() -> axum::Router { + use axum::Router; + use leptos::prelude::*; + use leptos_axum::{generate_route_list, LeptosRoutes}; + use crate::app::*; + + let conf = get_configuration(None).unwrap(); + let leptos_options = conf.leptos_options; + // Generate the list of routes in your Leptos App + let routes = generate_route_list(App); + + Router::new() + .leptos_routes(&leptos_options, routes, { + let leptos_options = leptos_options.clone(); + move || shell(leptos_options.clone()) + }) + .fallback(leptos_axum::file_and_error_handler(shell)) + .with_state(leptos_options) +} diff --git a/crates/leptos-app/src/main.rs b/crates/leptos-app/src/main.rs new file mode 100644 index 0000000..6a6515b --- /dev/null +++ b/crates/leptos-app/src/main.rs @@ -0,0 +1,39 @@ + +#[cfg(feature = "ssr")] +#[tokio::main] +async fn main() { + use axum::Router; + use leptos::logging::log; + use leptos::prelude::*; + use leptos_axum::{generate_route_list, LeptosRoutes}; + use leptos_app::app::*; + + let conf = get_configuration(None).unwrap(); + let addr = conf.leptos_options.site_addr; + let leptos_options = conf.leptos_options; + // Generate the list of routes in your Leptos App + let routes = generate_route_list(App); + + let app = Router::new() + .leptos_routes(&leptos_options, routes, { + let leptos_options = leptos_options.clone(); + move || shell(leptos_options.clone()) + }) + .fallback(leptos_axum::file_and_error_handler(shell)) + .with_state(leptos_options); + + // run our app with hyper + // `axum::Server` is a re-export of `hyper::Server` + log!("listening on http://{}", &addr); + let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); +} + +#[cfg(not(feature = "ssr"))] +pub fn main() { + // no client-side main function + // unless we want this to work with e.g., Trunk for pure client-side testing + // see lib.rs for hydration function instead +} diff --git a/crates/leptos-app/style/main.scss b/crates/leptos-app/style/main.scss new file mode 100644 index 0000000..e4538e1 --- /dev/null +++ b/crates/leptos-app/style/main.scss @@ -0,0 +1,4 @@ +body { + font-family: sans-serif; + text-align: center; +} \ No newline at end of file diff --git a/crates/leptos-chat/.dockerignore b/crates/leptos-chat/.dockerignore deleted file mode 100644 index c3a75d1..0000000 --- a/crates/leptos-chat/.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -* -!src/ -!style/ -!Cargo.toml/ -!index.html -!Trunk.toml - diff --git a/crates/leptos-chat/Cargo.toml b/crates/leptos-chat/Cargo.toml deleted file mode 100644 index 52aface..0000000 --- a/crates/leptos-chat/Cargo.toml +++ /dev/null @@ -1,64 +0,0 @@ -[package] -name = "leptos-chat" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -leptos = { version = "0.6", features = ["csr"] } -leptos_meta = { version = "0.6", features = ["csr"] } -leptos_router = { version = "0.6", features = ["csr"] } -wasm-bindgen = "0.2" -console_error_panic_hook = "0.1" -console_log = "1" -log = "0.4" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -js-sys = "0.3" -either = { version = "1.9", features = ["serde"] } -# Make async-openai optional and only included for non-wasm targets -async-openai-wasm = { default-features = false, version = "0.29" } -# Only include tokio for non-wasm targets -#tokio = { version = "1", default-features = false, features = ["sync", "macros", "io-util", "rt"] } -#reqwest = {version = "0.12.23", default-features = false, optional = false} -futures-util = "0.3" - - - -web-sys = { version = "0.3", features = [ - "console", - "Window", - "Document", - "Element", - "HtmlElement", - "HtmlInputElement", - "HtmlSelectElement", - "HtmlTextAreaElement", - "Event", - "EventTarget", - "KeyboardEvent", -] } -gloo-net = "0.6.0" - -[dependencies.uuid] -version = "1.0" -features = [ - "v4", # Lets you generate random UUIDs - "fast-rng", # Use a faster (but still sufficiently random) RNG - "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs - "js", # Enable JavaScript RNG for WASM targets -] - -# generates docker compose configuration -[package.metadata.compose] -image = "ghcr.io/geoffsee/leptos-chat:latest" -port = 8788 - - -# generates kubernetes manifests -[package.metadata.kube] -image = "ghcr.io/geoffsee/leptos-chat:latest" -replicas = 1 -port = 8788 \ No newline at end of file diff --git a/crates/leptos-chat/Dockerfile b/crates/leptos-chat/Dockerfile deleted file mode 100644 index aa0e39b..0000000 --- a/crates/leptos-chat/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# Build stage -FROM rust:1-alpine AS builder - -# Install build dependencies -RUN apk add --no-cache npm nodejs musl-dev pkgconfig openssl-dev git curl bash - -RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash - -WORKDIR /app - -# Copy manifest first (cache deps) -COPY . . -# Install trunk -RUN cargo install wasm-bindgen-cli - -RUN cargo binstall trunk - -# Build release artifacts -RUN rustup target add wasm32-unknown-unknown && export RUSTFLAGS='--cfg getrandom_backend="wasm_js"' && trunk build --release - -# Final stage: static web server -FROM caddy:2-alpine - -# Copy built assets into Caddy's web root -COPY --from=builder /app/dist /usr/share/caddy - -EXPOSE 8788 -CMD ["caddy", "file-server", "--root", "/usr/share/caddy", "--listen", ":8788"] \ No newline at end of file diff --git a/crates/leptos-chat/Trunk.toml b/crates/leptos-chat/Trunk.toml deleted file mode 100644 index e19ec80..0000000 --- a/crates/leptos-chat/Trunk.toml +++ /dev/null @@ -1,7 +0,0 @@ -[build] -# Set the RUSTFLAGS environment variable for getrandom's WebAssembly support -rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""] - -[serve] -# Use the same port as in the run.sh script -port = 8788 \ No newline at end of file diff --git a/crates/leptos-chat/index.html b/crates/leptos-chat/index.html deleted file mode 100644 index cbcae54..0000000 --- a/crates/leptos-chat/index.html +++ /dev/null @@ -1,15 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <title>Chat Interface - - - - - - \ No newline at end of file diff --git a/crates/leptos-chat/run.sh b/crates/leptos-chat/run.sh deleted file mode 100755 index 7a902bd..0000000 --- a/crates/leptos-chat/run.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env sh - -# Set RUSTFLAGS for getrandom's WebAssembly support -export RUSTFLAGS='--cfg getrandom_backend="wasm_js"' - -trunk serve --port 8788 \ No newline at end of file diff --git a/crates/leptos-chat/style/main.css b/crates/leptos-chat/style/main.css deleted file mode 100644 index 7d84a19..0000000 --- a/crates/leptos-chat/style/main.css +++ /dev/null @@ -1,165 +0,0 @@ -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - margin: 0; - padding: 0; - background-color: #f5f5f5; -} - -.chat-container { - max-width: 800px; - margin: 0 auto; - height: 100vh; - display: flex; - flex-direction: column; - background-color: white; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); -} - -h1 { - background-color: #4a90e2; - color: white; - margin: 0; - padding: 20px; - text-align: center; - font-size: 24px; - font-weight: 600; -} - -.messages-container { - flex: 1; - overflow-y: auto; - padding: 20px; - display: flex; - flex-direction: column; - gap: 15px; -} - -.message { - display: flex; - flex-direction: column; - max-width: 70%; - padding: 12px 16px; - border-radius: 18px; - word-wrap: break-word; -} - -.user-message { - align-self: flex-end; - background-color: #4a90e2; - color: white; -} - -.assistant-message { - align-self: flex-start; - background-color: #e9ecef; - color: #333; -} - -.system-message { - align-self: center; - background-color: #ffebcc; - color: #856404; - border: 1px solid #ffeaa7; -} - -.message-role { - font-size: 12px; - font-weight: 600; - margin-bottom: 4px; - opacity: 0.7; - text-transform: capitalize; -} - -.message-content { - font-size: 14px; - line-height: 1.4; -} - -.input-form { - display: flex; - padding: 20px; - gap: 10px; - background-color: #f8f9fa; - border-top: 1px solid #dee2e6; -} - -.message-input { - flex: 1; - padding: 12px 16px; - border: 1px solid #ced4da; - border-radius: 25px; - font-size: 14px; - outline: none; - transition: border-color 0.2s ease; -} - -.message-input:focus { - border-color: #4a90e2; - box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.25); -} - -.message-input:disabled { - background-color: #f8f9fa; - color: #6c757d; - cursor: not-allowed; -} - -.send-button { - padding: 12px 24px; - background-color: #4a90e2; - color: white; - border: none; - border-radius: 25px; - font-size: 14px; - font-weight: 600; - cursor: pointer; - transition: background-color 0.2s ease; - min-width: 80px; -} - -.send-button:hover:not(:disabled) { - background-color: #357abd; -} - -.send-button:disabled { - background-color: #6c757d; - cursor: not-allowed; -} - -/* Scrollbar styling */ -.messages-container::-webkit-scrollbar { - width: 8px; -} - -.messages-container::-webkit-scrollbar-track { - background: #f1f1f1; -} - -.messages-container::-webkit-scrollbar-thumb { - background: #c1c1c1; - border-radius: 4px; -} - -.messages-container::-webkit-scrollbar-thumb:hover { - background: #a1a1a1; -} - -/* Responsive design */ -@media (max-width: 768px) { - .chat-container { - height: 100vh; - } - - .message { - max-width: 85%; - } - - .input-form { - padding: 15px; - } - - h1 { - padding: 15px; - font-size: 20px; - } -} \ No newline at end of file diff --git a/crates/predict-otron-9000/Cargo.toml b/crates/predict-otron-9000/Cargo.toml index 42ebfa9..571c0e1 100644 --- a/crates/predict-otron-9000/Cargo.toml +++ b/crates/predict-otron-9000/Cargo.toml @@ -12,14 +12,14 @@ path = "src/main.rs" axum = "0.8.4" tokio = { version = "1.45.1", features = ["full"] } tower = "0.5.2" -tower-http = { version = "0.6.6", features = ["trace", "cors"] } +tower-http = { version = "0.6.6", features = ["trace", "cors", "fs"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } uuid = { version = "1.7.0", features = ["v4"] } reqwest = { version = "0.12", features = ["json"] } -rust-embed = "8.7.2" +rust-embed = { version = "8.7.2", features = ["include-exclude"] } # Dependencies for embeddings functionality embeddings-engine = { path = "../embeddings-engine" } @@ -27,6 +27,11 @@ embeddings-engine = { path = "../embeddings-engine" } # Dependencies for inference functionality inference-engine = { path = "../inference-engine" } +# Dependencies for leptos web app +leptos-app = { path = "../leptos-app", features = ["ssr"] } + +mime_guess = "2.0.5" + # generates docker compose configuration [package.metadata.compose] name = "predict-otron-9000" @@ -39,4 +44,4 @@ port = 8080 image = "ghcr.io/geoffsee/predict-otron-9000:latest" replicas = 1 port = 8080 -env = { SERVER_CONFIG = "" } \ No newline at end of file +env = { SERVER_CONFIG = "" } diff --git a/crates/predict-otron-9000/src/config.rs b/crates/predict-otron-9000/src/config.rs index 79f8a9a..fe8c5ba 100644 --- a/crates/predict-otron-9000/src/config.rs +++ b/crates/predict-otron-9000/src/config.rs @@ -89,7 +89,7 @@ impl ServerConfig { } } Err(_) => { - tracing::info!("SERVER_CONFIG not set, using default Local mode"); + tracing::info!("SERVER_CONFIG not set, Standalone mode active"); ServerConfig::default() } } diff --git a/crates/predict-otron-9000/src/main.rs b/crates/predict-otron-9000/src/main.rs index 5509a27..34ed4b9 100644 --- a/crates/predict-otron-9000/src/main.rs +++ b/crates/predict-otron-9000/src/main.rs @@ -1,22 +1,21 @@ -mod middleware; mod config; +mod middleware; mod proxy; -use axum::{ - Router, - serve, -}; -use std::env; +use axum::response::IntoResponse; use axum::routing::get; -use tokio::net::TcpListener; -use tower_http::trace::TraceLayer; -use tower_http::cors::{Any, CorsLayer}; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -use inference_engine::AppState; -use middleware::{MetricsStore, MetricsLoggerFuture, MetricsLayer}; +use axum::{Router, http::Uri, response::Html, serve}; use config::ServerConfig; +use inference_engine::AppState; +use middleware::{MetricsLayer, MetricsLoggerFuture, MetricsStore}; use proxy::create_proxy_router; - +use rust_embed::Embed; +use std::env; +use tokio::net::TcpListener; +use tower_http::classify::ServerErrorsFailureClass::StatusCode; +use tower_http::cors::{Any, CorsLayer}; +use tower_http::trace::TraceLayer; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[tokio::main] async fn main() { @@ -34,13 +33,12 @@ async fn main() { .with(tracing_subscriber::fmt::layer()) .init(); - // Initialize metrics store for performance tracking let metrics_store = MetricsStore::new(); - + // Create a metrics logger that will periodically log metrics (every 60 seconds) let metrics_logger = MetricsLoggerFuture::new(metrics_store.clone(), 60); - + // Spawn the metrics logger in a background task tokio::spawn(metrics_logger); @@ -55,19 +53,22 @@ async fn main() { let service_router = if server_config.clone().is_high_availability() { tracing::info!("Running in HighAvailability mode - proxying to external services"); tracing::info!(" Inference service URL: {}", server_config.inference_url()); - tracing::info!(" Embeddings service URL: {}", server_config.embeddings_url()); + tracing::info!( + " Embeddings service URL: {}", + server_config.embeddings_url() + ); // Use proxy router that forwards requests to external services create_proxy_router(server_config.clone()) } else { - tracing::info!("Running in Local mode - using embedded services"); + tracing::info!("Running in Standalone mode - using embedded services"); // Create unified router by merging embeddings and inference routers (existing behavior) let embeddings_router = embeddings_engine::create_embeddings_router(); // Create AppState with correct model configuration - use inference_engine::server::{PipelineArgs, build_pipeline}; use inference_engine::Which; + use inference_engine::server::{PipelineArgs, build_pipeline}; let mut pipeline_args = PipelineArgs::default(); pipeline_args.model_id = "google/gemma-3-1b-it".to_string(); pipeline_args.which = Which::InstructV3_1B; @@ -98,39 +99,41 @@ async fn main() { // Create metrics layer let metrics_layer = MetricsLayer::new(metrics_store); + // Create the leptos router for the web frontend + let leptos_router = leptos_app::create_leptos_router(); + // Merge the service router with base routes and add middleware layers let app = Router::new() - .route("/", get(|| async { "API ready. This can serve the Leptos web app, but it doesn't." })) .route("/health", get(|| async { "ok" })) .merge(service_router) - .layer(metrics_layer) // Add metrics tracking + .merge(leptos_router) // Add leptos web frontend routes + .layer(metrics_layer) // Add metrics tracking .layer(cors) .layer(TraceLayer::new_for_http()); // Server configuration - let server_host = env::var("SERVER_HOST").unwrap_or_else(|_| { - String::from(default_host) - }); + let server_host = env::var("SERVER_HOST").unwrap_or_else(|_| String::from(default_host)); - let server_port = env::var("SERVER_PORT").map(|v| v.parse::().unwrap_or(default_port)).unwrap_or_else(|_| { - default_port - }); + let server_port = env::var("SERVER_PORT") + .map(|v| v.parse::().unwrap_or(default_port)) + .unwrap_or_else(|_| default_port); let server_address = format!("{}:{}", server_host, server_port); - let listener = TcpListener::bind(&server_address).await.unwrap(); - tracing::info!("Unified predict-otron-9000 server listening on {}", listener.local_addr().unwrap()); + tracing::info!( + "Unified predict-otron-9000 server listening on {}", + listener.local_addr().unwrap() + ); tracing::info!("Performance metrics tracking enabled - summary logs every 60 seconds"); tracing::info!("Available endpoints:"); - tracing::info!(" GET / - Root endpoint from embeddings-engine"); - tracing::info!(" POST /v1/embeddings - Text embeddings"); - tracing::info!(" POST /v1/chat/completions - Chat completions"); + tracing::info!(" GET / - Leptos chat web application"); + tracing::info!(" GET /health - Health check"); + tracing::info!(" POST /v1/embeddings - Text embeddings API"); + tracing::info!(" POST /v1/chat/completions - Chat completions API"); serve(listener, app).await.unwrap(); } - - // Chat completions handler that properly uses the inference server crate's error handling // This function is no longer needed as we're using the inference_engine router directly diff --git a/docker-compose.yml b/docker-compose.yml index 42a1878..4c10e31 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,22 +25,6 @@ services: retries: 10 start_period: 10s - # Web frontend - Leptos WASM chat interface - leptos-chat: - build: - context: crates/leptos-chat - dockerfile: Dockerfile - ports: - - "8788:8788" - depends_on: - predict-otron-9000: - condition: service_healthy - networks: - - predict-otron-network - environment: - # Configure API endpoint for the frontend to connect to backend - - API_BASE_URL=http://predict-otron-9000:8080 - volumes: # Persistent storage for Hugging Face model cache hf-cache: diff --git a/docs/SERVER_CONFIG.md b/docs/SERVER_CONFIG.md index 9eff670..294f2d6 100644 --- a/docs/SERVER_CONFIG.md +++ b/docs/SERVER_CONFIG.md @@ -173,7 +173,7 @@ The server logs the selected mode on startup: **Local Mode:** ``` -INFO predict_otron_9000: Running in Local mode - using embedded services +INFO predict_otron_9000: Running in Standalone mode ``` **HighAvailability Mode:** diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 81f5339..0000000 --- a/package-lock.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "predict-otron-9000", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "openai": "^5.16.0" - } - }, - "node_modules/openai": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-5.16.0.tgz", - "integrity": "sha512-hoEH8ZNvg1HXjU9mp88L/ZH8O082Z8r6FHCXGiWAzVRrEv443aI57qhch4snu07yQydj+AUAWLenAiBXhu89Tw==", - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - } - } -} diff --git a/test_predict_otron.sh b/test_predict_otron.sh index 8cf53ab..3d33100 100755 --- a/test_predict_otron.sh +++ b/test_predict_otron.sh @@ -59,11 +59,11 @@ echo "[INFO] Server is ready!" # Run first CLI request echo "[INFO] Running first CLI request - listing models..." -bun run cli.ts --list-models +./cli.ts --list-models echo "" echo "[INFO] Running second CLI request - chat completion..." -bun run cli.ts "What is 2+2?" +./cli.ts "What is 2+2?" echo "" echo "[INFO] Both CLI requests completed successfully!" \ No newline at end of file