mod components; use std::time::Duration; use axum::{extract::State, response::IntoResponse}; use axum_controller::*; use datastar::{ axum::ReadSignals, prelude::{FragmentMergeMode, MergeFragments}, Sse, }; use hypertext::{maud, GlobalAttributes, Renderable}; use serde::Deserialize; use crate::{ui::components::*, AppState}; pub mod html_elements { use hypertext::elements; pub use hypertext::html_elements::*; elements! { bla { blub } my_element { my_attribute } } } #[derive(Deserialize, Debug)] struct Signals { msginput: String, } pub struct UiController {} #[controller( state = AppState )] impl UiController { #[route(GET "/msg")] #[tracing::instrument] async fn message( State(_): State, ReadSignals(signals): ReadSignals, ) -> impl IntoResponse { tokio::time::sleep(Duration::from_millis(250)).await; Sse(async_stream::stream! { yield MergeFragments::new(maud! { div #user-1 class="mb-2 animated animated-bounce" { strong { "You:" } (signals.msginput) } }.render()) .selector("#chat") .merge_mode(FragmentMergeMode::Append) .into(); tokio::time::sleep(Duration::from_millis(250)).await; yield MergeFragments::new(maud! { div #llm-1 class="mb-2 animated animated-bounce" { strong { "LLM:" } "This is a mock response."} }.render()) .selector("#chat") .merge_mode(FragmentMergeMode::Append) .into(); }) } #[route(GET "/")] #[tracing::instrument] async fn index(State(_): State) -> impl IntoResponse { main_page(maud! { div .container .p-4 .mx-auto { h1 .m-4 .text-2xl .font-bold { "LLM Chat App" } div .p-6 .bg-white .rounded-lg .shadow-md { div #chat .m-4 { // Chat messages will appear here } form id="chat-form" { textarea #user-input .p-2 .m-2 .w-full .rounded-lg .border data-bind-msginput placeholder="Type your message..." {} button #send-btn class="btn" type="button" data-on-click="@get('/msg')" { "Send" } } } } } ) .render() } }