Modularize darm test a bit

This commit is contained in:
Tristan D. 2025-03-18 00:55:28 +01:00
parent 206c0d4e57
commit 4861b479a7
Signed by: tristan
SSH key fingerprint: SHA256:9oFM1J63hYWJjCnLG6C0fxBS15rwNcWwdQNMOHYKJ/4
8 changed files with 449 additions and 309 deletions

431
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
- maud vs hypertext vs minijinja - maud vs hypertext vs minijinja
- try porting to hypertext::maud ! - try porting to hypertext::maud !
* Todos * Todos
** Starter boilerplate ** [-] Starter boilerplate
*** [X] Server: Axum *** [X] Server: Axum
**** [X] Typed routes via macro **** [X] Typed routes via macro
**** [X] Nested Router **** [X] Nested Router
@ -11,7 +11,7 @@
*** [X] "html tmpl": minijinja *** [X] "html tmpl": minijinja
- only index yaml for now, test in app wether inline or external templates feel better - only index yaml for now, test in app wether inline or external templates feel better
*** [ ] CSS Basic *** [ ] CSS Basic
- UnoCSS - [X] UnoCSS
- daisyUI? - daisyUI?
*** [ ] UI framework *** [ ] UI framework
- data* js basic - data* js basic
@ -19,14 +19,28 @@
*** [ ] Asset bundle testing *** [ ] Asset bundle testing
*** [ ] DB *** [ ] DB
- SqlX/SurrealDB? - SqlX/SurrealDB?
** Basic streaming chat *** [ ] Build scripts
- [ ] remove npm ?
- [ ] add fish or cargo make scripts ?
- [ ] features:
- [ ] make dev
- make js?
- "bundle"/download data* ?
- make css
- make rust app
- [ ] make watch-dev
- [ ] make prod
** [X] Chat mockup
** [X] Streaming chat with data*
** [ ] Basic streaming chat
- build with inline html via maud - build with inline html via maud
- add messaging foo?
*** Stream same token in loop *** Stream same token in loop
*** ??? *** ???
*** Finish *** Finish
** Basic Proxy Settings ** [ ] Basic Proxy Settings
- build with jinja templates - build with jinja templates
** Markdown streaming chat ** [ ] Markdown streaming chat
*** Moar Feats *** Moar Feats
**** Tauri webview/wry? **** Tauri webview/wry?
**** Jinja tmplts for models ?? **** Jinja tmplts for models ??

61
darm_test/src/assets.rs Normal file
View file

@ -0,0 +1,61 @@
use axum::{
body::Body,
extract::State,
http::{header, Response, StatusCode},
response::IntoResponse,
};
use axum_controller::*;
use hypertext::{maud, GlobalAttributes, Renderable};
use rust_embed::RustEmbed;
use crate::{ui::html_elements, AppState};
pub struct AxumEmbedAsset<T>(pub T);
impl<T> IntoResponse for AxumEmbedAsset<T>
where
T: Into<String>,
{
fn into_response(self) -> Response<Body> {
let path = self.0.into();
tracing::debug!(?path);
#[derive(RustEmbed)]
#[folder = "public/"]
struct EmbedAsset;
fn markup_404(uri: String) -> impl Renderable {
maud! {
h1 { "404" }
p { (uri) " Not Found" }
@for i in 0..5 {
div .{"m-" (i)} { (i) }
}
}
}
match EmbedAsset::get(path.as_str()) {
Some(content) => {
let mime = mime_guess::from_path(path).first_or_octet_stream();
([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response()
}
None => (
StatusCode::NOT_FOUND,
axum::response::Html(markup_404(path).render()),
)
.into_response(),
}
}
}
pub struct AssetsController;
#[controller(state=AppState, path="/dist")]
impl AssetsController {
#[route(GET "/*path")]
async fn static_handler(path: String, _: State<AppState>) -> impl IntoResponse {
let path = path.trim_start_matches('/').to_string();
AxumEmbedAsset(path)
}
}

View file

@ -1,103 +1,17 @@
mod assets;
mod ui;
use std::sync::Once; use std::sync::Once;
use assets::AssetsController;
use axum::{ use axum::{
body::Body, http::{StatusCode, Uri},
extract::State,
http::{header, Response, StatusCode, Uri},
response::IntoResponse, response::IntoResponse,
routing::get, routing::get,
}; };
use axum_controller::*;
use hypertext::{maud, GlobalAttributes, Renderable}; use hypertext::{maud, GlobalAttributes, Renderable};
use rust_embed::RustEmbed; use ui::UiController;
mod html_elements { use crate::ui::html_elements;
use hypertext::elements;
pub use hypertext::html_elements::*;
elements! {
bla {
blub
}
my_element {
my_attribute
}
}
}
struct UiController {}
#[controller(
state = AppState
)]
impl UiController {
#[route(GET "/")]
async fn index(State(_): State<AppState>) -> impl IntoResponse {
maud! {
html lang="en" {
head {
meta charset="UTF-8";
meta name="viewport" content="width=device-width, initial-scale=1.0";
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";
}
body class="bg-gray-100" {
div class="container mx-auto p-4" {
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" {
// 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" {
"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 += `<div class='mb-2'><strong>You:</strong> ${userInput}</div>`;
document.getElementById('user-input').value = '';
console.log(\"asd\");
// Mock response from LLM
setTimeout(() => {
chatContainer.innerHTML += `<div class='mb-2'><strong>LLM:</strong> This is a mock response.</div>`;
}, 1000);
});
"))
}
}
}
}
.render()
}
}
struct DistController;
#[controller(state=AppState, path="/dist")]
impl DistController {
#[route(GET "/*path")]
async fn static_handler(path: String, _: State<AppState>) -> impl IntoResponse {
let path = path.trim_start_matches('/').to_string();
StaticFile(path)
}
}
fn markup_404(uri: String) -> impl Renderable { fn markup_404(uri: String) -> impl Renderable {
maud! { maud! {
@ -135,35 +49,6 @@ async fn handle_405() -> impl IntoResponse {
) )
.into_response() .into_response()
} }
pub struct StaticFile<T>(pub T);
impl<T> IntoResponse for StaticFile<T>
where
T: Into<String>,
{
fn into_response(self) -> Response<Body> {
let path = self.0.into();
tracing::debug!(?path);
#[derive(RustEmbed)]
#[folder = "public/"]
struct Asset;
match Asset::get(path.as_str()) {
Some(content) => {
let mime = mime_guess::from_path(path).first_or_octet_stream();
([(header::CONTENT_TYPE, mime.as_ref())], content.data).into_response()
}
None => (
StatusCode::NOT_FOUND,
axum::response::Html(markup_404(path).render()),
)
.into_response(),
}
}
}
pub fn initialize_logger() { pub fn initialize_logger() {
static INIT: Once = Once::new(); static INIT: Once = Once::new();
@ -197,7 +82,7 @@ async fn main() {
let router: axum::Router = axum::Router::new() let router: axum::Router = axum::Router::new()
.merge(UiController::into_router(app_state.clone())) .merge(UiController::into_router(app_state.clone()))
.merge(DistController::into_router(app_state.clone())) .merge(AssetsController::into_router(app_state.clone()))
.fallback_service(get(handle_404)) .fallback_service(get(handle_404))
.method_not_allowed_fallback(handle_405) .method_not_allowed_fallback(handle_405)
.with_state(app_state); .with_state(app_state);

View file

@ -0,0 +1,24 @@
use hypertext::{maud, GlobalAttributes, Renderable};
use crate::ui::html_elements;
pub fn main_page(body: impl Renderable) -> impl Renderable {
maud! {
html lang="en" {
head {
meta charset="UTF-8";
meta name="viewport" content="width=device-width, initial-scale=1.0";
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";
}
body class="bg-gray-100" {
(body)
}
}
}
}

69
darm_test/src/ui/mod.rs Normal file
View file

@ -0,0 +1,69 @@
mod components;
use axum::{extract::State, response::IntoResponse};
use axum_controller::*;
use hypertext::{maud, GlobalAttributes, Renderable};
use crate::{ui::components::*, AppState};
pub mod html_elements {
use hypertext::elements;
pub use hypertext::html_elements::*;
elements! {
bla {
blub
}
my_element {
my_attribute
}
}
}
pub struct UiController {}
#[controller(
state = AppState
)]
impl UiController {
#[route(GET "/")]
async fn index(State(_): State<AppState>) -> impl IntoResponse {
main_page(maud! {
div class="container mx-auto p-4" {
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" {
// 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" {
"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 += `<div class='mb-2'><strong>You:</strong> ${userInput}</div>`;
document.getElementById('user-input').value = '';
console.log(\"asd\");
// Mock response from LLM
setTimeout(() => {
chatContainer.innerHTML += `<div class='mb-2'><strong>LLM:</strong> This is a mock response.</div>`;
}, 1000);
});
"))
}
}
)
.render()
}
}

View file

@ -1,16 +1,16 @@
[toolchain] [toolchain]
channel = "nightly-2025-03-01" channel = "nightly-2025-03-01"
components = [ components = [
"cargo", "cargo",
"rust-analyzer", "rust-analyzer",
"rust-src", "rust-src",
"rustc-codegen-cranelift", "rustc-codegen-cranelift",
"rustc-dev", "rustc-dev",
"rustfmt", "rustfmt",
] ]
profile = "default" profile = "default"
targets = [ targets = [
"wasm32-unknown-unknown", "wasm32-unknown-unknown",
"x86_64-pc-windows-msvc", "x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu", "x86_64-unknown-linux-gnu",
] ]

View file

@ -1,5 +1,5 @@
edition = "2021" edition = "2024"
max_width = 100 max_width = 100
tab_spaces = 4 tab_spaces = 4