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
|
||||
- try porting to hypertext::maud !
|
||||
* Todos
|
||||
** Starter boilerplate
|
||||
** [-] Starter boilerplate
|
||||
*** [X] Server: Axum
|
||||
**** [X] Typed routes via macro
|
||||
**** [X] Nested Router
|
||||
|
@ -11,7 +11,7 @@
|
|||
*** [X] "html tmpl": minijinja
|
||||
- only index yaml for now, test in app wether inline or external templates feel better
|
||||
*** [ ] CSS Basic
|
||||
- UnoCSS
|
||||
- [X] UnoCSS
|
||||
- daisyUI?
|
||||
*** [ ] UI framework
|
||||
- data* js basic
|
||||
|
@ -19,14 +19,28 @@
|
|||
*** [ ] Asset bundle testing
|
||||
*** [ ] DB
|
||||
- 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
|
||||
- add messaging foo?
|
||||
*** Stream same token in loop
|
||||
*** ???
|
||||
*** Finish
|
||||
** Basic Proxy Settings
|
||||
** [ ] Basic Proxy Settings
|
||||
- build with jinja templates
|
||||
** Markdown streaming chat
|
||||
** [ ] Markdown streaming chat
|
||||
*** Moar Feats
|
||||
**** Tauri webview/wry?
|
||||
**** 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 assets::AssetsController;
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::State,
|
||||
http::{header, Response, StatusCode, Uri},
|
||||
http::{StatusCode, Uri},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
};
|
||||
use axum_controller::*;
|
||||
use hypertext::{maud, GlobalAttributes, Renderable};
|
||||
use rust_embed::RustEmbed;
|
||||
use ui::UiController;
|
||||
|
||||
mod 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)
|
||||
}
|
||||
}
|
||||
use crate::ui::html_elements;
|
||||
|
||||
fn markup_404(uri: String) -> impl Renderable {
|
||||
maud! {
|
||||
|
@ -135,35 +49,6 @@ async fn handle_405() -> impl IntoResponse {
|
|||
)
|
||||
.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() {
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
|
@ -197,7 +82,7 @@ async fn main() {
|
|||
|
||||
let router: axum::Router = axum::Router::new()
|
||||
.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))
|
||||
.method_not_allowed_fallback(handle_405)
|
||||
.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,5 +1,5 @@
|
|||
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
max_width = 100
|
||||
tab_spaces = 4
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue