diff --git a/Cargo.lock b/Cargo.lock index dea6b57..85618c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,6 +201,28 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -1232,11 +1254,13 @@ dependencies = [ name = "darm_test" version = "0.1.1" dependencies = [ + "async-stream", "axum 0.8.1", "axum-controller", "axum-controller-macros", "axum-typed-routing 0.2.0 (git+https://github.com/jvdwrf/axum-typed-routing)", "datastar", + "futures", "hypertext", "mime_guess", "rust-embed", @@ -1269,9 +1293,15 @@ dependencies = [ [[package]] name = "datastar" version = "0.1.0" -source = "git+https://github.com/starfederation/datastar.git#db376dfbe4d100ea997c932f1301fbe72e08dfe8" +source = "git+https://github.com/starfederation/datastar.git#90b8b1cf3506d528ab9af97c4dc3c9d7f9d42fc5" dependencies = [ + "axum 0.8.1", "futures-util", + "http-body", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", ] [[package]] @@ -3172,7 +3202,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] diff --git a/darm_test/Cargo.toml b/darm_test/Cargo.toml index 9ca108c..33de3e9 100644 --- a/darm_test/Cargo.toml +++ b/darm_test/Cargo.toml @@ -10,15 +10,17 @@ version.workspace = true edition.workspace = true [dependencies] +async-stream = "0.3.6" axum = { version = "0.8", features = ["http2"] } axum-controller = { version = "0.2.0", path = "../../axum-controller/axum-controller" } axum-controller-macros = { version = "0.2.0", path = "../../axum-controller/axum-controller-macros" } axum-typed-routing = { git = "https://github.com/jvdwrf/axum-typed-routing", version = "0.2.0" } -datastar = { git = "https://github.com/starfederation/datastar.git", version = "0.1.0" } +datastar = { git = "https://github.com/starfederation/datastar.git", version = "0.1.0", features = ["axum"] } +futures = "0.3.31" hypertext = { version = "0.6.0", features = ["axum"] } mime_guess = "2.0.5" rust-embed = { version = "8.5.0", features = ["axum", "compression"] } serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.43", features = ["full", "tracing"] } -tracing = "0.1.41" +tracing = { version= "0.1.41", features = [ "max_level_debug", "release_max_level_info" ] } tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } diff --git a/darm_test/public/datastar.min.js b/darm_test/public/datastar.min.js index 7341c59..e920129 120000 --- a/darm_test/public/datastar.min.js +++ b/darm_test/public/datastar.min.js @@ -1 +1 @@ -js/datastar-1-0-0-beta-7.js \ No newline at end of file +js/datastar-1-0-0-beta-9-3caff1580ebe0e7c.js \ No newline at end of file diff --git a/darm_test/src/main.rs b/darm_test/src/main.rs index 6cf2962..7c9bc22 100644 --- a/darm_test/src/main.rs +++ b/darm_test/src/main.rs @@ -54,7 +54,7 @@ pub fn initialize_logger() { INIT.call_once(|| { let env_filter = tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::level_filters::LevelFilter::INFO.into()) + .with_default_directive(tracing::level_filters::LevelFilter::DEBUG.into()) .from_env_lossy(); tracing_subscriber::fmt() diff --git a/darm_test/src/ui/components/mod.rs b/darm_test/src/ui/components/mod.rs index b0e77c2..b3b39c2 100644 --- a/darm_test/src/ui/components/mod.rs +++ b/darm_test/src/ui/components/mod.rs @@ -4,14 +4,12 @@ use crate::ui::html_elements; pub fn main_page(body: impl Renderable) -> impl Renderable { maud! { + !DOCTYPE html lang="en" { head { - meta charset="UTF-8"; meta name="viewport" content="width=device-width, initial-scale=1.0"; - title { - "LLM Chat App" - } + title { "LLM Chat App" } script type="module" src="/dist/datastar.min.js" {} link rel="stylesheet" href="/dist/styles.min.css"; link rel="icon" href="/dist/favicon.ico"; diff --git a/darm_test/src/ui/mod.rs b/darm_test/src/ui/mod.rs index f2d37e7..f252f8d 100644 --- a/darm_test/src/ui/mod.rs +++ b/darm_test/src/ui/mod.rs @@ -1,7 +1,14 @@ mod components; +use std::time::Duration; + use axum::{extract::State, response::IntoResponse}; use axum_controller::*; +use datastar::{ + prelude::{FragmentMergeMode, MergeFragments}, + Sse, +}; use hypertext::{maud, GlobalAttributes, Renderable}; +use tokio; use crate::{ui::components::*, AppState}; @@ -25,6 +32,29 @@ pub struct UiController {} state = AppState )] impl UiController { + #[route(GET "/msg")] + async fn message(State(_): State) -> impl IntoResponse { + tokio::time::sleep(Duration::from_millis(500)).await; + + let sse_stream: Sse<_> = Sse(async_stream::stream! { + yield MergeFragments::new(maud! { div #user-1 .mb-2 { strong { "You:" } "userInput" } }.render()) + // .id("chat") + .selector("#chat") + .merge_mode(FragmentMergeMode::Append) + .into(); + + tokio::time::sleep(Duration::from_millis(500)).await; + + yield MergeFragments::new(maud! { div #llm-1 class="mb-2" { strong { "LLM:" } "This is a mock response."} }.render()) + // .id("chat") + .selector("#chat") + .merge_mode(FragmentMergeMode::Append) + .into(); + }); + + sse_stream + } + #[route(GET "/")] async fn index(State(_): State) -> impl IntoResponse { main_page(maud! { @@ -32,36 +62,18 @@ impl UiController { h1 class="text-2xl font-bold mb-4" { "LLM Chat App" } - div class="bg-white p-6 rounded-lg shadow-md" { - div id="chat" class="mb-4" { + div .bg-white .p-6 .rounded-lg .shadow-md { + div #chat .mb-4 { // Chat messages will appear here } form id="chat-form" { textarea id="user-input" class="w-full p-2 border rounded-lg mb-2" placeholder="Type your message..." {} - button type="submit" class="bg-blue-500 text-white p-2 rounded-lg" { + button type="button" data-on-click="@get('/msg')" class="bg-blue-500 text-white p-2 rounded-lg" { "Send" } } } } - script { - (hypertext::Raw(" - console.log(\"asd\"); - document.getElementById('chat-form').addEventListener('submit', function(event) { - event.preventDefault(); - const userInput = document.getElementById('user-input').value; - const chatContainer = document.getElementById('chat'); - chatContainer.innerHTML += `
You: ${userInput}
`; - document.getElementById('user-input').value = ''; - console.log(\"asd\"); - - // Mock response from LLM - setTimeout(() => { - chatContainer.innerHTML += `
LLM: This is a mock response.
`; - }, 1000); - }); - ")) - } } ) .render()