diff --git a/CHANGELOG.md b/CHANGELOG.md index 622a86c..35c9781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ - Embeddings stored on own table - Refactored retrieval pipeline to use the new, faster and more accurate strategy. Read [blog post](https://blog.stark.pub/posts/eval-retrieval-refactor/) for more details. +## Version 0.2.7 (2025-12-04) +- Improved admin page, now only loads models when specifically requested. Groundwork for coming configuration features. +- Fix: timezone aware info in scratchpad + ## Version 0.2.6 (2025-10-29) - Added an opt-in FastEmbed-based reranking stage behind `reranking_enabled`. It improves retrieval accuracy by re-scoring hybrid results. - Fix: default name for relationships harmonized across application diff --git a/flake.nix b/flake.nix index 82292a2..4747963 100644 --- a/flake.nix +++ b/flake.nix @@ -26,6 +26,7 @@ src = ./.; filter = let extraPaths = [ + (toString ./Cargo.lock) (toString ./common/migrations) (toString ./common/schemas) (toString ./html-router/templates) diff --git a/html-router/src/routes/admin/handlers.rs b/html-router/src/routes/admin/handlers.rs index e3f2e0e..cd66f81 100644 --- a/html-router/src/routes/admin/handlers.rs +++ b/html-router/src/routes/admin/handlers.rs @@ -1,5 +1,9 @@ use async_openai::types::ListModelResponse; -use axum::{extract::State, response::IntoResponse, Form}; +use axum::{ + extract::{Query, State}, + response::IntoResponse, + Form, +}; use serde::{Deserialize, Serialize}; use common::{ @@ -31,44 +35,83 @@ use crate::{ pub struct AdminPanelData { user: User, settings: SystemSettings, - analytics: Analytics, - users: i64, + analytics: Option, + users: Option, default_query_prompt: String, default_image_prompt: String, conversation_archive: Vec, - available_models: ListModelResponse, + available_models: Option, + current_section: AdminSection, +} + +#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum AdminSection { + Overview, + Models, +} + +impl Default for AdminSection { + fn default() -> Self { + Self::Overview + } +} + +#[derive(Deserialize)] +pub struct AdminPanelQuery { + section: Option, } pub async fn show_admin_panel( State(state): State, RequireUser(user): RequireUser, + Query(query): Query, ) -> Result { - let ( - settings_res, - analytics_res, - user_count_res, - conversation_archive_res, - available_models_res, - ) = tokio::join!( + let section = match query.section.as_deref() { + Some("models") => AdminSection::Models, + _ => AdminSection::Overview, + }; + + let (settings, conversation_archive) = tokio::try_join!( SystemSettings::get_current(&state.db), - Analytics::get_current(&state.db), - Analytics::get_users_amount(&state.db), - User::get_user_conversations(&user.id, &state.db), - async { state.openai_client.models().list().await } - ); + User::get_user_conversations(&user.id, &state.db) + )?; + + let (analytics, users) = if section == AdminSection::Overview { + let (analytics, users) = tokio::try_join!( + Analytics::get_current(&state.db), + Analytics::get_users_amount(&state.db) + )?; + (Some(analytics), Some(users)) + } else { + (None, None) + }; + + let available_models = if section == AdminSection::Models { + Some( + state + .openai_client + .models() + .list() + .await + .map_err(|e| AppError::InternalError(e.to_string()))?, + ) + } else { + None + }; Ok(TemplateResponse::new_template( "admin/base.html", AdminPanelData { user, - settings: settings_res?, - analytics: analytics_res?, - available_models: available_models_res - .map_err(|e| AppError::InternalError(e.to_string()))?, - users: user_count_res?, + settings, + analytics, + available_models, + users, default_query_prompt: DEFAULT_QUERY_SYSTEM_PROMPT.to_string(), default_image_prompt: DEFAULT_IMAGE_PROCESSING_PROMPT.to_string(), - conversation_archive: conversation_archive_res?, + conversation_archive, + current_section: section, }, )) } @@ -115,7 +158,7 @@ pub async fn toggle_registration_status( SystemSettings::update(&state.db, new_settings.clone()).await?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/overview.html", "registration_status_input", RegistrationToggleData { settings: new_settings, @@ -242,7 +285,7 @@ pub async fn update_model_settings( .map_err(|_e| AppError::InternalError("Failed to get models".to_string()))?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/models.html", "model_settings_form", ModelSettingsData { settings: new_settings, @@ -307,7 +350,7 @@ pub async fn patch_query_prompt( SystemSettings::update(&state.db, new_settings.clone()).await?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/overview.html", "system_prompt_section", SystemPromptSectionData { settings: new_settings, @@ -366,7 +409,7 @@ pub async fn patch_ingestion_prompt( SystemSettings::update(&state.db, new_settings.clone()).await?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/overview.html", "system_prompt_section", SystemPromptSectionData { settings: new_settings, @@ -425,7 +468,7 @@ pub async fn patch_image_prompt( SystemSettings::update(&state.db, new_settings.clone()).await?; Ok(TemplateResponse::new_partial( - "admin/base.html", + "admin/sections/overview.html", "system_prompt_section", SystemPromptSectionData { settings: new_settings, diff --git a/html-router/templates/admin/base.html b/html-router/templates/admin/base.html index 0aed54e..119680b 100644 --- a/html-router/templates/admin/base.html +++ b/html-router/templates/admin/base.html @@ -3,190 +3,49 @@ {% block title %}Minne - Admin{% endblock %} {% block main %} -
-
-
-
-

Admin Dashboard

+
+
+
+
+

Admin Controls

+

+ Stay on top of analytics and manage AI integrations without waiting on long-running model calls. +

+
+
+ Signed in as {{ user.email }}
-
-
-
-
Page Loads
-
{{analytics.page_loads}}
-
Total page load events
-
-
-
Unique Visitors
-
{{analytics.visitors}}
-
Distinct users by fingerprint
-
-
-
Users
-
{{users}}
-
Registered accounts
-
-
-
+ -
- {% block system_prompt_section %} -
-
System Prompts
-
- - - -
-
- {% endblock %} - -
-
AI Models
- {% block model_settings_form %} -
- -
-
Query Model
- -

Current: {{settings.query_model}}

-
- - -
-
Processing Model
- -

Current: {{settings.processing_model}}

-
- - -
-
Image Processing Model
- -

Current: {{settings.image_processing_model}}

-
- - -
-
Voice Processing Model
- -

Current: {{settings.voice_processing_model}}

-
- - -
-
Embedding Model
- {% if settings.embedding_backend == "fastembed" or settings.embedding_backend == "hashed" %} - -

Model: {{settings.embedding_model}} - ({{settings.embedding_dimensions}} dims)

-

ℹ️ Embedding model is controlled by config when using {{settings.embedding_backend}} backend.

- {% else %} - -

Current: {{settings.embedding_model}} - ({{settings.embedding_dimensions}} dims)

- {% endif %} -
- - -
-
Embedding Dimensions
- {% if settings.embedding_backend == "fastembed" or settings.embedding_backend == "hashed" %} - -

ℹ️ Dimensions are fixed for {{settings.embedding_backend}} backend. Set EMBEDDING_BACKEND=openai to use OpenAI embeddings.

- {% else %} - - {% endif %} -
- - - {% if settings.embedding_backend != "fastembed" and settings.embedding_backend != "hashed" %} - - {% endif %} - -
- -
-
- - {% if settings.embedding_backend != "fastembed" and settings.embedding_backend != "hashed" %} - - {% endif %} - {% endblock %} -
- -
-
Registration
- -
-
-
+
+ {% if current_section == 'models' %} + {% include 'admin/sections/models.html' %} + {% else %} + {% include 'admin/sections/overview.html' %} + {% endif %} +
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/html-router/templates/admin/sections/models.html b/html-router/templates/admin/sections/models.html new file mode 100644 index 0000000..9ff3b42 --- /dev/null +++ b/html-router/templates/admin/sections/models.html @@ -0,0 +1,130 @@ +
+
+
+
AI Models
+

Model configuration

+

+ Choose which models power conversational search, ingestion analysis, and embeddings. Adjusting embeddings may trigger a full reprocess. +

+
+ + ← Back to Admin + +
+ + {% if available_models %} + {% block model_settings_form %} +
+
+
+
Query Model
+ +

Current: {{ settings.query_model }}

+
+ +
+
Processing Model
+ +

Current: {{ settings.processing_model }}

+
+
+ +
+
+
Image Processing Model
+ +

Current: {{ settings.image_processing_model }}

+
+ +
+
Voice Processing Model
+ +

Current: {{ settings.voice_processing_model }}

+
+
+ +
+
+
Embedding Model
+ +

Current: {{ settings.embedding_model }}

+
+ +
+
Embedding Dimensions
+ +

Changing dimensions will trigger a background re-embedding.

+
+
+ + + +
+ +
+
+ + + {% endblock %} + {% else %} +
+
Unable to load models
+

We could not reach the model provider. Check the API key and retry.

+
+ {% endif %} +
diff --git a/html-router/templates/admin/sections/overview.html b/html-router/templates/admin/sections/overview.html new file mode 100644 index 0000000..59a1a5c --- /dev/null +++ b/html-router/templates/admin/sections/overview.html @@ -0,0 +1,57 @@ +{% if analytics %} +
+
+
Page Loads
+
{{ analytics.page_loads }}
+
Total load events seen by Minne
+
+
+
Unique Visitors
+
{{ analytics.visitors }}
+
Distinct users by fingerprint
+
+
+
Users
+
{{ users or 0 }}
+
Registered accounts
+
+
+{% else %} +
+
Analytics unavailable
+

We could not fetch analytics for this view. Reload or check the monitoring pipeline.

+
+{% endif %} + +
+ {% block system_prompt_section %} +
+
+
+
System Prompts
+

Adjust the prompts that power retrieval, ingestion analysis, and image processing flows.

+
+ LLM +
+
+ + + +
+
+ {% endblock %} + +
+
Registration
+

Toggle whether new people can sign up without an invite.

+ +
+
+
diff --git a/main/Cargo.toml b/main/Cargo.toml index 3673897..e8b8da4 100644 --- a/main/Cargo.toml +++ b/main/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "main" -version = "0.2.6" +version = "0.2.7" edition = "2021" repository = "https://github.com/perstarkse/minne" license = "AGPL-3.0-or-later"