release: 1.0.5

This commit is contained in:
Per Stark
2026-06-24 22:02:31 +02:00
parent ba3fd6ed46
commit d273390de8
118 changed files with 989 additions and 690 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "html-router"
version = "0.1.0"
edition = "2021"
edition = "2024"
license = "AGPL-3.0-or-later"
[lints]
+1 -1
View File
@@ -6,8 +6,8 @@ use common::{create_template_engine, storage::db::ProvidesDb, utils::config::App
use retrieval_pipeline::reranking::RerankerPool;
use std::collections::HashMap;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
atomic::{AtomicUsize, Ordering},
};
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
+2 -2
View File
@@ -13,14 +13,14 @@ pub mod router_factory;
pub mod routes;
pub mod utils;
use axum::{extract::FromRef, Router};
use axum::{Router, extract::FromRef};
use axum_session::{Session, SessionStore};
use axum_session_auth::AuthSession;
use axum_session_surreal::SessionSurrealPool;
use common::storage::types::user::User;
use html_state::HtmlState;
use router_factory::RouterFactory;
use surrealdb::{engine::any::Any, Surreal};
use surrealdb::{Surreal, engine::any::Any};
pub type AuthSessionType = AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>;
pub type SessionType = Session<SessionSurrealPool<Any>>;
@@ -2,13 +2,13 @@ use std::collections::HashMap;
use std::sync::Arc;
use axum::{
Extension,
extract::{Request, State},
http::{HeaderName, StatusCode},
middleware::Next,
response::{Html, IntoResponse, Redirect, Response},
Extension,
};
use axum_htmx::{HxRequest, HX_TRIGGER};
use axum_htmx::{HX_TRIGGER, HxRequest};
use common::{
error::AppError,
utils::template_engine::{ProvidesTemplateEngine, Value},
@@ -18,7 +18,7 @@ use serde::Serialize;
use serde_json::json;
use tracing::error;
use crate::{html_state::HtmlState, AuthSessionType};
use crate::{AuthSessionType, html_state::HtmlState};
use common::storage::types::{
conversation::{Conversation, SidebarConversation},
user::{Theme, User},
@@ -175,10 +175,10 @@ const HTMX_HEADERS_TO_FORWARD: &[&str] = &["HX-Push", "HX-Trigger", "HX-Redirect
fn forward_headers(from: &axum::http::HeaderMap, to: &mut axum::http::HeaderMap) {
for &header_name in HTMX_HEADERS_TO_FORWARD {
if let Ok(name) = HeaderName::from_bytes(header_name.as_bytes()) {
if let Some(value) = from.get(&name) {
to.insert(name.clone(), value.clone());
}
if let Ok(name) = HeaderName::from_bytes(header_name.as_bytes())
&& let Some(value) = from.get(&name)
{
to.insert(name.clone(), value.clone());
}
}
}
@@ -219,13 +219,13 @@ where
let mut current_user = None;
{
if let Some(auth) = req.extensions().get::<AuthSessionType>() {
if let Some(user) = &auth.current_user {
is_authenticated = true;
user_theme = user.theme.as_str();
initial_theme = user.theme.initial_theme();
current_user = Some(TemplateUser::from(user));
}
if let Some(auth) = req.extensions().get::<AuthSessionType>()
&& let Some(user) = &auth.current_user
{
is_authenticated = true;
user_theme = user.theme.as_str();
initial_theme = user.theme.initial_theme();
current_user = Some(TemplateUser::from(user));
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
use axum::{extract::FromRef, middleware::from_fn_with_state, Router};
use axum::{Router, extract::FromRef, middleware::from_fn_with_state};
use axum_session::SessionLayer;
use axum_session_auth::{AuthConfig, AuthSessionLayer};
use axum_session_surreal::SessionSurrealPool;
use common::storage::types::user::User;
use surrealdb::{engine::any::Any, Surreal};
use surrealdb::{Surreal, engine::any::Any};
use crate::{
html_state::HtmlState,
+2 -2
View File
@@ -1,13 +1,13 @@
use axum::{extract::State, Form};
use axum::{Form, extract::State};
use chrono_tz::TZ_VARIANTS;
use serde::{Deserialize, Serialize};
use crate::{
AuthSessionType,
middlewares::{
auth_middleware::RequireUser,
response_middleware::{TemplateResponse, TemplateResult},
},
AuthSessionType,
};
use common::storage::types::user::{Theme, User};
+1 -1
View File
@@ -1,8 +1,8 @@
mod handlers;
use axum::{
Router,
extract::FromRef,
routing::{delete, get, patch, post},
Router,
};
use crate::html_state::HtmlState;
+3 -3
View File
@@ -1,7 +1,7 @@
use async_openai::types::models::ListModelResponse;
use axum::{
extract::{Query, State},
Form,
extract::{Query, State},
};
use serde::{Deserialize, Serialize};
@@ -18,8 +18,8 @@ use common::{
utils::{
config::AppConfig,
embedding::{
fastembed_model_dimension, is_valid_fastembed_model_code,
list_fastembed_embedding_models, EmbeddingBackend, FastEmbedModelOption,
EmbeddingBackend, FastEmbedModelOption, fastembed_model_dimension,
is_valid_fastembed_model_code, list_fastembed_embedding_models,
},
},
};
+1 -1
View File
@@ -1,9 +1,9 @@
mod handlers;
use axum::{
Router,
extract::FromRef,
middleware::from_fn,
routing::{get, patch},
Router,
};
use handlers::{
patch_image_prompt, patch_ingestion_prompt, patch_query_prompt, show_admin_panel,
+1 -1
View File
@@ -2,7 +2,7 @@ pub mod signin;
pub mod signout;
pub mod signup;
use axum::{extract::FromRef, routing::get, Router};
use axum::{Router, extract::FromRef, routing::get};
use signin::{authenticate_user, show_signin_form};
use signout::sign_out_user;
use signup::{process_signup_and_show_verification, show_signup_form};
+2 -2
View File
@@ -1,11 +1,11 @@
use axum::{extract::State, Form};
use axum::{Form, extract::State};
use axum_htmx::HxBoosted;
use serde::{Deserialize, Serialize};
use crate::{
AuthSessionType,
html_state::HtmlState,
middlewares::response_middleware::{TemplateResponse, TemplateResult},
AuthSessionType,
};
use common::storage::types::user::User;
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::{
middlewares::response_middleware::{TemplateResponse, TemplateResult},
AuthSessionType,
middlewares::response_middleware::{TemplateResponse, TemplateResult},
};
pub async fn sign_out_user(auth: AuthSessionType) -> TemplateResult {
+2 -2
View File
@@ -1,4 +1,4 @@
use axum::{extract::State, Form};
use axum::{Form, extract::State};
use axum_htmx::HxBoosted;
use serde::{Deserialize, Serialize};
@@ -8,9 +8,9 @@ use common::{
};
use crate::{
AuthSessionType,
html_state::HtmlState,
middlewares::response_middleware::{TemplateResponse, TemplateResult},
AuthSessionType,
};
#[derive(Deserialize, Serialize)]
+3 -3
View File
@@ -1,7 +1,7 @@
use axum::{
Form,
extract::{Path, State},
http::HeaderValue,
Form,
};
use serde::{Deserialize, Serialize};
@@ -18,8 +18,8 @@ use crate::{
middlewares::{
auth_middleware::RequireUser,
response_middleware::{
template_as_response, template_with_headers, ResponseResult, TemplateResponse,
TemplateResult,
ResponseResult, TemplateResponse, TemplateResult, template_as_response,
template_with_headers,
},
},
};
@@ -6,24 +6,24 @@ use async_stream::stream;
use axum::{
extract::{Query, State},
response::{
sse::{Event, KeepAlive, KeepAliveStream},
Sse,
sse::{Event, KeepAlive, KeepAliveStream},
},
};
use futures::{
stream::{self, once},
Stream, StreamExt, TryStreamExt,
stream::{self, once},
};
use json_stream_parser::JsonStreamParser;
use minijinja::Value;
use retrieval_pipeline::answer_retrieval::{
chunks_to_chat_context, create_chat_request, create_user_message_with_history,
LLMResponseFormat,
LLMResponseFormat, chunks_to_chat_context, create_chat_request,
create_user_message_with_history,
};
use serde::{Deserialize, Serialize};
use serde_json::from_str;
use tokio::sync::mpsc::channel;
use tokio::sync::Mutex;
use tokio::sync::mpsc::channel;
use tracing::{debug, error, info};
use common::storage::{
@@ -66,7 +66,7 @@ async fn get_message_and_user(
Ok(None) => {
return Err(sse_with_keep_alive(create_error_stream(
"Message not found: the specified message does not exist",
)))
)));
}
Err(e) => {
error!("Database error retrieving message {}: {:?}", message_id, e);
@@ -233,12 +233,12 @@ pub async fn get_response_stream(
fn build_chat_event_stream(
state: HtmlState,
openai_stream: impl Stream<
Item = Result<
async_openai::types::chat::CreateChatCompletionStreamResponse,
async_openai::error::OpenAIError,
>,
> + Send
+ 'static,
Item = Result<
async_openai::types::chat::CreateChatCompletionStreamResponse,
async_openai::error::OpenAIError,
>,
> + Send
+ 'static,
user_message: &Message,
user_id: String,
allowed_reference_ids: Vec<String>,
@@ -502,17 +502,17 @@ impl StreamParserState {
let json = self.parser.result();
if let Some(obj) = json.as_object() {
if let Some(answer) = obj.get("answer") {
self.in_answer_field = true;
if let Some(obj) = json.as_object()
&& let Some(answer) = obj.get("answer")
{
self.in_answer_field = true;
let current_content = answer.as_str().unwrap_or_default().to_string();
let current_content = answer.as_str().unwrap_or_default().to_string();
if current_content.len() > self.last_answer_content.len() {
let new_content = current_content[self.last_answer_content.len()..].to_string();
self.last_answer_content = current_content;
return new_content;
}
if current_content.len() > self.last_answer_content.len() {
let new_content = current_content[self.last_answer_content.len()..].to_string();
self.last_answer_content = current_content;
return new_content;
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ mod message_response_stream;
mod reference_validation;
mod references;
use axum::{extract::FromRef, routing::get, Router};
use axum::{Router, extract::FromRef, routing::get};
pub use chat_handlers::{
delete_conversation, new_chat_user_message, new_user_message, patch_conversation_title,
reload_sidebar, show_chat_base as show_base, show_conversation_editing_title,
@@ -6,7 +6,7 @@ use common::{
error::AppError,
storage::{
db::SurrealDbClient,
types::{knowledge_entity::KnowledgeEntity, text_chunk::TextChunk, StoredObject},
types::{StoredObject, knowledge_entity::KnowledgeEntity, text_chunk::TextChunk},
},
};
use retrieval_pipeline::RetrievalOutput;
@@ -448,10 +448,12 @@ mod tests {
assert_eq!(result.valid_refs, vec![first.id, second.id]);
assert_eq!(result.invalid_refs.len(), 2);
assert!(result
.invalid_refs
.iter()
.all(|entry| entry.reason == InvalidReferenceReason::Duplicate));
assert!(
result
.invalid_refs
.iter()
.all(|entry| entry.reason == InvalidReferenceReason::Duplicate)
);
}
#[tokio::test]
+1 -1
View File
@@ -17,7 +17,7 @@ use crate::{
},
};
use super::reference_validation::{normalize_reference, ReferenceLookupTarget};
use super::reference_validation::{ReferenceLookupTarget, normalize_reference};
#[derive(Serialize)]
struct ReferenceTooltipData {
+2 -2
View File
@@ -1,6 +1,6 @@
use axum::{
extract::{Path, Query, State},
Form,
extract::{Path, Query, State},
};
use axum_htmx::{HxBoosted, HxRequest, HxTarget};
use serde::{Deserialize, Serialize};
@@ -13,7 +13,7 @@ use crate::{
auth_middleware::RequireUser,
response_middleware::{TemplateResponse, TemplateResult},
},
utils::pagination::{paginate_items, Pagination},
utils::pagination::{Pagination, paginate_items},
utils::text_content_preview::truncate_text_contents,
};
use url::form_urlencoded;
+1 -1
View File
@@ -1,6 +1,6 @@
mod handlers;
use axum::{extract::FromRef, routing::get, Router};
use axum::{Router, extract::FromRef, routing::get};
use handlers::{
delete_text_content, patch_text_content, show_content_page, show_content_read_modal,
show_recent_content, show_text_content_edit_form,
+2 -2
View File
@@ -1,7 +1,7 @@
use axum::{
body::Body,
extract::{Path, State},
http::{header, HeaderMap, HeaderValue, StatusCode},
http::{HeaderMap, HeaderValue, StatusCode, header},
response::IntoResponse,
};
use chrono::{DateTime, Utc};
@@ -13,7 +13,7 @@ use crate::{
middlewares::{
auth_middleware::RequireUser,
response_middleware::{
template_as_response, ResponseResult, TemplateResponse, TemplateResult,
ResponseResult, TemplateResponse, TemplateResult, template_as_response,
},
},
utils::text_content_preview::truncate_text_contents,
+1 -1
View File
@@ -1,9 +1,9 @@
pub mod handlers;
use axum::{
Router,
extract::FromRef,
routing::{delete, get},
Router,
};
use handlers::{
delete_job, delete_text_content, index_handler, serve_file, show_active_jobs, show_task_archive,
+3 -3
View File
@@ -4,12 +4,12 @@ use axum::{
extract::{Query, State},
http::StatusCode,
response::{
sse::{Event, KeepAlive, KeepAliveStream},
Sse,
sse::{Event, KeepAlive, KeepAliveStream},
},
};
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
use futures::{future::try_join_all, stream, Stream, StreamExt, TryFutureExt};
use futures::{Stream, StreamExt, TryFutureExt, future::try_join_all, stream};
use minijinja::context;
use serde::{Deserialize, Serialize};
use tempfile::NamedTempFile;
@@ -24,7 +24,7 @@ use common::{
ingestion_task::{IngestionTask, TaskState},
user::User,
},
utils::ingest_limits::{validate_ingest_input, IngestValidationError},
utils::ingest_limits::{IngestValidationError, validate_ingest_input},
};
use crate::{
+1 -1
View File
@@ -1,6 +1,6 @@
mod handlers;
use axum::{extract::DefaultBodyLimit, extract::FromRef, routing::get, Router};
use axum::{Router, extract::DefaultBodyLimit, extract::FromRef, routing::get};
use handlers::{get_task_updates_stream, hide_ingest_form, process_ingest_form, show_ingest_form};
use crate::html_state::HtmlState;
+11 -12
View File
@@ -3,15 +3,15 @@ use std::collections::{HashMap, HashSet};
use std::fmt;
use axum::{
Form, Json,
extract::{Path, Query, State},
http::HeaderValue,
response::{IntoResponse, Response},
Form, Json,
};
use axum_htmx::{HxBoosted, HxRequest, HX_TRIGGER};
use axum_htmx::{HX_TRIGGER, HxBoosted, HxRequest};
use serde::{
de::{self, Deserializer, MapAccess, Visitor},
Deserialize, Serialize,
de::{self, Deserializer, MapAccess, Visitor},
};
use common::{
@@ -27,7 +27,7 @@ use common::{
utils::embedding::EmbeddingProvider,
};
use retrieval_pipeline::{
normalize_fts_terms, reciprocal_rank_fusion, RetrievalTuning, RrfConfig, Scored,
RetrievalTuning, RrfConfig, Scored, normalize_fts_terms, reciprocal_rank_fusion,
};
use tracing::debug;
use uuid::Uuid;
@@ -37,10 +37,10 @@ use crate::{
middlewares::{
auth_middleware::RequireUser,
response_middleware::{
template_with_headers, ResponseResult, TemplateResponse, TemplateResult,
ResponseResult, TemplateResponse, TemplateResult, template_with_headers,
},
},
utils::pagination::{paginate_items, paginate_slice, Pagination},
utils::pagination::{Pagination, paginate_items, paginate_slice},
};
use url::form_urlencoded;
@@ -950,12 +950,11 @@ fn normalize_filter(input: Option<String>) -> Option<String> {
fn trim_matching_quotes(value: &str) -> &str {
let bytes = value.as_bytes();
if let (Some(&first), Some(&last)) = (bytes.first(), bytes.last()) {
if bytes.len() >= 2
&& ((first == b'"' && last == b'"') || (first == b'\'' && last == b'\''))
{
return &value[1..value.len().saturating_sub(1)];
}
if let (Some(&first), Some(&last)) = (bytes.first(), bytes.last())
&& bytes.len() >= 2
&& ((first == b'"' && last == b'"') || (first == b'\'' && last == b'\''))
{
return &value[1..value.len().saturating_sub(1)];
}
value
}
+1 -1
View File
@@ -1,9 +1,9 @@
mod handlers;
use axum::{
Router,
extract::FromRef,
routing::{delete, get, post},
Router,
};
use handlers::{
create_knowledge_entity, delete_knowledge_entity, delete_knowledge_relationship,
@@ -1,10 +1,10 @@
use axum::{
Form,
extract::{Path, Query, State},
http::{HeaderValue, StatusCode},
response::{IntoResponse, Response},
Form,
};
use axum_htmx::{HxBoosted, HxRequest, HX_TRIGGER};
use axum_htmx::{HX_TRIGGER, HxBoosted, HxRequest};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
@@ -12,7 +12,7 @@ use crate::html_state::HtmlState;
use crate::middlewares::{
auth_middleware::RequireUser,
response_middleware::{
template_with_headers, ResponseResult, TemplateResponse, TemplateResult,
ResponseResult, TemplateResponse, TemplateResult, template_with_headers,
},
};
use common::storage::types::{
+1 -1
View File
@@ -1,8 +1,8 @@
mod handlers;
use axum::{
Router,
extract::FromRef,
routing::{delete, get, patch, post},
Router,
};
use crate::html_state::HtmlState;
+2 -2
View File
@@ -4,9 +4,9 @@ use axum::extract::{Query, State};
use axum_htmx::{HxBoosted, HxRequest};
use common::storage::types::{text_content::TextContent, user::User};
use retrieval_pipeline::{
retrieve, RetrievalConfig, RetrievalOutput, RetrievedChunk, RetrievedEntity,
RetrievalConfig, RetrievalOutput, RetrievedChunk, RetrievedEntity, retrieve,
};
use serde::{de, Deserialize, Deserializer, Serialize};
use serde::{Deserialize, Deserializer, Serialize, de};
use std::{fmt, str::FromStr};
use crate::{
+2 -2
View File
@@ -1,8 +1,8 @@
mod handlers;
use axum::{extract::FromRef, routing::get, Router};
use axum::{Router, extract::FromRef, routing::get};
#[allow(clippy::module_name_repetitions)]
pub use handlers::{search_result_handler as result_handler, SearchParams as SearchQueryParams};
pub use handlers::{SearchParams as SearchQueryParams, search_result_handler as result_handler};
use crate::html_state::HtmlState;
+3 -3
View File
@@ -3,10 +3,10 @@
use std::sync::Arc;
use axum::{
body::{to_bytes, Body},
http::{header, Request, StatusCode},
response::Response,
Router,
body::{Body, to_bytes},
http::{Request, StatusCode, header},
response::Response,
};
use common::{
storage::{db::SurrealDbClient, store::StorageManager, types::user::User},
+1 -1
View File
@@ -9,7 +9,7 @@
use std::fs;
use std::path::{Path, PathBuf};
use minijinja::{path_loader, Environment};
use minijinja::{Environment, path_loader};
fn templates_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("templates")