Modularize darm test a bit
This commit is contained in:
parent
206c0d4e57
commit
4861b479a7
8 changed files with 449 additions and 309 deletions
431
Cargo.lock
generated
431
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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
61
darm_test/src/assets.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
24
darm_test/src/ui/components/mod.rs
Normal file
24
darm_test/src/ui/components/mod.rs
Normal 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
69
darm_test/src/ui/mod.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
max_width = 100
|
max_width = 100
|
||||||
tab_spaces = 4
|
tab_spaces = 4
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue