mirror of
https://github.com/perstarkse/minne.git
synced 2026-05-01 21:24:17 +02:00
feat: release build bundles templates in bin
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
use axum_session::SessionStore;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use common::create_template_engine;
|
||||
use common::storage::db::SurrealDbClient;
|
||||
use common::utils::config::AppConfig;
|
||||
use minijinja::{path_loader, Environment};
|
||||
use minijinja_autoreload::AutoReloader;
|
||||
use std::path::PathBuf;
|
||||
use common::utils::template_engine::TemplateEngine;
|
||||
use std::sync::Arc;
|
||||
use surrealdb::engine::any::Any;
|
||||
|
||||
@@ -12,22 +11,13 @@ use surrealdb::engine::any::Any;
|
||||
pub struct HtmlState {
|
||||
pub db: Arc<SurrealDbClient>,
|
||||
pub openai_client: Arc<async_openai::Client<async_openai::config::OpenAIConfig>>,
|
||||
pub templates: Arc<AutoReloader>,
|
||||
pub templates: Arc<TemplateEngine>,
|
||||
pub session_store: Arc<SessionStore<SessionSurrealPool<Any>>>,
|
||||
}
|
||||
|
||||
impl HtmlState {
|
||||
pub async fn new(config: &AppConfig) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let reloader = AutoReloader::new(move |notifier| {
|
||||
let template_path = get_templates_dir();
|
||||
let mut env = Environment::new();
|
||||
env.set_loader(path_loader(&template_path));
|
||||
|
||||
notifier.set_fast_reload(true);
|
||||
notifier.watch_path(&template_path, true);
|
||||
minijinja_contrib::add_to_environment(&mut env);
|
||||
Ok(env)
|
||||
});
|
||||
let template_engine = create_template_engine!("templates");
|
||||
|
||||
let surreal_db_client = Arc::new(
|
||||
SurrealDbClient::new(
|
||||
@@ -48,7 +38,7 @@ impl HtmlState {
|
||||
|
||||
let app_state = HtmlState {
|
||||
db: surreal_db_client.clone(),
|
||||
templates: Arc::new(reloader),
|
||||
templates: Arc::new(template_engine),
|
||||
openai_client: openai_client.clone(),
|
||||
session_store,
|
||||
};
|
||||
@@ -56,23 +46,3 @@ impl HtmlState {
|
||||
Ok(app_state)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_workspace_root() -> PathBuf {
|
||||
// Starts from CARGO_MANIFEST_DIR (e.g., /project/crates/html-router/)
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
// Navigate up to /path/to/project/crates
|
||||
let crates_dir = manifest_dir
|
||||
.parent()
|
||||
.expect("Failed to find parent of manifest directory");
|
||||
|
||||
// Navigate up to workspace root
|
||||
crates_dir
|
||||
.parent()
|
||||
.expect("Failed to find workspace root")
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
pub fn get_templates_dir() -> PathBuf {
|
||||
get_workspace_root().join("templates")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::html_state::HtmlState;
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
@@ -6,19 +7,15 @@ use axum::{
|
||||
};
|
||||
use common::error::AppError;
|
||||
use minijinja::{context, Value};
|
||||
use minijinja_autoreload::AutoReloader;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use tracing::error;
|
||||
|
||||
use crate::html_state::HtmlState;
|
||||
|
||||
// Enum for template types
|
||||
#[derive(Clone)]
|
||||
pub enum TemplateKind {
|
||||
Full(String), // Full page template
|
||||
Partial(String, String), // Template name, block name
|
||||
Error(StatusCode), // Error template with status code
|
||||
Redirect(String), // Redirect
|
||||
Full(String),
|
||||
Partial(String, String),
|
||||
Error(StatusCode),
|
||||
Redirect(String),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -53,14 +50,12 @@ impl TemplateResponse {
|
||||
error => error,
|
||||
description => description
|
||||
};
|
||||
|
||||
Self {
|
||||
template_kind: TemplateKind::Error(status),
|
||||
context: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Convenience methods for common errors
|
||||
pub fn not_found() -> Self {
|
||||
Self::error(
|
||||
StatusCode::NOT_FOUND,
|
||||
@@ -118,25 +113,33 @@ struct TemplateStateWrapper {
|
||||
|
||||
impl IntoResponse for TemplateStateWrapper {
|
||||
fn into_response(self) -> Response {
|
||||
let templates = self.state.templates;
|
||||
let template_engine = &self.state.templates;
|
||||
|
||||
match &self.template_response.template_kind {
|
||||
TemplateKind::Full(name) => {
|
||||
render_template(name, self.template_response.context, templates)
|
||||
match template_engine.render(name, &self.template_response.context) {
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(e) => {
|
||||
error!("Failed to render template: {:?}", e);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateKind::Partial(name, block) => {
|
||||
render_block(name, block, self.template_response.context, templates)
|
||||
TemplateKind::Partial(template, block) => {
|
||||
match template_engine.render_block(template, block, &self.template_response.context)
|
||||
{
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(e) => {
|
||||
error!("Failed to render block: {:?}", e);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
TemplateKind::Error(status) => {
|
||||
let html = match try_render_template(
|
||||
"errors/error.html",
|
||||
self.template_response.context,
|
||||
templates,
|
||||
) {
|
||||
Ok(html_string) => Html(html_string),
|
||||
Err(_) => fallback_error(),
|
||||
};
|
||||
(*status, html).into_response()
|
||||
match template_engine.render("errors/error.html", &self.template_response.context) {
|
||||
Ok(html) => (*status, Html(html)).into_response(),
|
||||
Err(_) => (*status, fallback_error()).into_response(),
|
||||
}
|
||||
}
|
||||
TemplateKind::Redirect(path) => {
|
||||
(StatusCode::OK, [(axum_htmx::HX_REDIRECT, path.clone())], "").into_response()
|
||||
@@ -145,93 +148,11 @@ impl IntoResponse for TemplateStateWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions for rendering with error handling
|
||||
fn render_template(name: &str, context: Value, templates: Arc<AutoReloader>) -> Response {
|
||||
match try_render_template(name, context, templates.clone()) {
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(_) => fallback_error().into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_block(name: &str, block: &str, context: Value, templates: Arc<AutoReloader>) -> Response {
|
||||
match try_render_block(name, block, context, templates.clone()) {
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(_) => fallback_error().into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_render_template(
|
||||
template_name: &str,
|
||||
context: Value,
|
||||
templates: Arc<AutoReloader>,
|
||||
) -> Result<String, ()> {
|
||||
let env = templates.acquire_env().map_err(|e| {
|
||||
tracing::error!("Environment error: {:?}", e);
|
||||
()
|
||||
})?;
|
||||
|
||||
let tmpl = env.get_template(template_name).map_err(|e| {
|
||||
tracing::error!("Template error: {:?}", e);
|
||||
()
|
||||
})?;
|
||||
|
||||
tmpl.render(context).map_err(|e| {
|
||||
tracing::error!("Render error: {:?}", e);
|
||||
()
|
||||
})
|
||||
}
|
||||
|
||||
fn try_render_block(
|
||||
template_name: &str,
|
||||
block: &str,
|
||||
context: Value,
|
||||
templates: Arc<AutoReloader>,
|
||||
) -> Result<String, ()> {
|
||||
let env = templates.acquire_env().map_err(|e| {
|
||||
tracing::error!("Environment error: {:?}", e);
|
||||
()
|
||||
})?;
|
||||
|
||||
let tmpl = env.get_template(template_name).map_err(|e| {
|
||||
tracing::error!("Template error: {:?}", e);
|
||||
()
|
||||
})?;
|
||||
|
||||
let mut state = tmpl.eval_to_state(context).map_err(|e| {
|
||||
tracing::error!("Eval error: {:?}", e);
|
||||
()
|
||||
})?;
|
||||
|
||||
state.render_block(block).map_err(|e| {
|
||||
tracing::error!("Block render error: {:?}", e);
|
||||
()
|
||||
})
|
||||
}
|
||||
|
||||
fn fallback_error() -> Html<String> {
|
||||
Html(
|
||||
r#"
|
||||
<html>
|
||||
<body>
|
||||
<div class="container mx-auto p-4">
|
||||
<h1 class="text-4xl text-error">Error</h1>
|
||||
<p class="mt-4">Sorry, something went wrong displaying this page.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"#
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn with_template_response(
|
||||
State(state): State<HtmlState>,
|
||||
response: Response,
|
||||
) -> Response {
|
||||
// Clone the TemplateResponse from extensions
|
||||
let template_response = response.extensions().get::<TemplateResponse>().cloned();
|
||||
|
||||
if let Some(template_response) = template_response {
|
||||
if let Some(template_response) = response.extensions().get::<TemplateResponse>().cloned() {
|
||||
TemplateStateWrapper {
|
||||
state,
|
||||
template_response,
|
||||
@@ -242,40 +163,60 @@ pub async fn with_template_response(
|
||||
}
|
||||
}
|
||||
|
||||
// Define HtmlError
|
||||
#[derive(Debug)]
|
||||
pub enum HtmlError {
|
||||
AppError(AppError),
|
||||
TemplateError(String),
|
||||
}
|
||||
|
||||
// Conversion from AppError to HtmlError
|
||||
impl From<AppError> for HtmlError {
|
||||
fn from(err: AppError) -> Self {
|
||||
HtmlError::AppError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Conversion for database error to HtmlError
|
||||
impl From<surrealdb::Error> for HtmlError {
|
||||
fn from(err: surrealdb::Error) -> Self {
|
||||
HtmlError::AppError(AppError::from(err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<minijinja::Error> for HtmlError {
|
||||
fn from(err: minijinja::Error) -> Self {
|
||||
HtmlError::TemplateError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for HtmlError {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
HtmlError::AppError(err) => {
|
||||
let template_response = match err {
|
||||
AppError::NotFound(_) => TemplateResponse::not_found(),
|
||||
AppError::Auth(_) => TemplateResponse::unauthorized(),
|
||||
AppError::Validation(msg) => TemplateResponse::bad_request(&msg),
|
||||
_ => {
|
||||
tracing::error!("Internal error: {:?}", err);
|
||||
TemplateResponse::server_error()
|
||||
}
|
||||
};
|
||||
template_response.into_response()
|
||||
HtmlError::AppError(err) => match err {
|
||||
AppError::NotFound(_) => TemplateResponse::not_found().into_response(),
|
||||
AppError::Auth(_) => TemplateResponse::unauthorized().into_response(),
|
||||
AppError::Validation(msg) => TemplateResponse::bad_request(&msg).into_response(),
|
||||
_ => {
|
||||
error!("Internal error: {:?}", err);
|
||||
TemplateResponse::server_error().into_response()
|
||||
}
|
||||
},
|
||||
HtmlError::TemplateError(err) => {
|
||||
error!("Template error: {}", err);
|
||||
TemplateResponse::server_error().into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_error() -> String {
|
||||
r#"
|
||||
<html>
|
||||
<body>
|
||||
<div class="container mx-auto p-4">
|
||||
<h1 class="text-4xl text-error">Error</h1>
|
||||
<p class="mt-4">Sorry, something went wrong displaying this page.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"#
|
||||
.to_string()
|
||||
}
|
||||
|
||||
@@ -22,23 +22,24 @@ use futures::{
|
||||
Stream, StreamExt, TryStreamExt,
|
||||
};
|
||||
use json_stream_parser::JsonStreamParser;
|
||||
use minijinja::Value;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::from_str;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use tokio::sync::{mpsc::channel, Mutex};
|
||||
use tracing::{error, debug};
|
||||
use tracing::{debug, error};
|
||||
|
||||
use common::storage::{
|
||||
db::SurrealDbClient,
|
||||
types::{
|
||||
conversation::Conversation,
|
||||
message::{Message, MessageRole},
|
||||
user::User,
|
||||
system_settings::SystemSettings,
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{html_state::HtmlState, routes::render_template};
|
||||
use crate::html_state::HtmlState;
|
||||
|
||||
// Error handling function
|
||||
fn create_error_stream(
|
||||
@@ -272,17 +273,13 @@ pub async fn get_response_stream(
|
||||
}
|
||||
|
||||
// Render template with references
|
||||
match render_template(
|
||||
match state.templates.render(
|
||||
"chat/reference_list.html",
|
||||
ReferenceData { message },
|
||||
state.templates.clone(),
|
||||
&Value::from_serialize(ReferenceData { message }),
|
||||
) {
|
||||
Ok(html) => {
|
||||
// Extract the String from Html<String>
|
||||
let html_string = html.0;
|
||||
|
||||
// Return the rendered HTML
|
||||
Ok(Event::default().event("references").data(html_string))
|
||||
Ok(Event::default().event("references").data(html))
|
||||
}
|
||||
Err(_) => {
|
||||
// Handle template rendering error
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::response::Html;
|
||||
use minijinja_autoreload::AutoReloader;
|
||||
|
||||
use crate::middlewares::response_middleware::HtmlError;
|
||||
|
||||
pub mod account;
|
||||
pub mod admin;
|
||||
pub mod auth;
|
||||
@@ -14,20 +7,3 @@ pub mod index;
|
||||
pub mod ingestion;
|
||||
pub mod knowledge;
|
||||
pub mod search;
|
||||
|
||||
// Helper function for render_template
|
||||
pub fn render_template<T>(
|
||||
template_name: &str,
|
||||
context: T,
|
||||
templates: Arc<AutoReloader>,
|
||||
) -> Result<Html<String>, HtmlError>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let env = templates.acquire_env().unwrap();
|
||||
let tmpl = env.get_template(template_name).unwrap();
|
||||
let context = minijinja::Value::from_serialize(&context);
|
||||
let output = tmpl.render(context).unwrap();
|
||||
|
||||
Ok(Html(output))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user