mirror of
https://github.com/perstarkse/minne.git
synced 2026-05-22 07:37:34 +02:00
wip: plotly & additional documentation
This commit is contained in:
Generated
+125
@@ -661,6 +661,15 @@ version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "0.15.1"
|
||||
@@ -1403,6 +1412,12 @@ dependencies = [
|
||||
"dtoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35"
|
||||
|
||||
[[package]]
|
||||
name = "earcutr"
|
||||
version = "0.4.3"
|
||||
@@ -1471,6 +1486,16 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
@@ -2036,6 +2061,15 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
@@ -3353,6 +3387,36 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "plotly"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0746e9faf2b051db76470fd428cbc0db792db05346dedaae4a75b16d7be503b5"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"erased-serde",
|
||||
"once_cell",
|
||||
"plotly_derive",
|
||||
"rand",
|
||||
"rinja",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"serde_with",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotly_derive"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d683930282f098b9f524e2596e3e63483507ac499231c96127fcb166bc05d26"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.6.2"
|
||||
@@ -3826,6 +3890,49 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rinja"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dc4940d00595430b3d7d5a01f6222b5e5b51395d1120bdb28d854bb8abb17a5"
|
||||
dependencies = [
|
||||
"humansize",
|
||||
"itoa",
|
||||
"percent-encoding",
|
||||
"rinja_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rinja_derive"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d9ed0146aef6e2825f1b1515f074510549efba38d71f4554eec32eb36ba18b"
|
||||
dependencies = [
|
||||
"basic-toml",
|
||||
"memchr",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rinja_parser",
|
||||
"rustc-hash 2.0.0",
|
||||
"serde",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rinja_parser"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93f9a866e2e00a7a1fb27e46e9e324a6f7c0e7edc4543cae1d38f4e4a100c610"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"nom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rkyv"
|
||||
version = "0.7.45"
|
||||
@@ -4275,6 +4382,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
@@ -5264,6 +5382,12 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
@@ -5925,6 +6049,7 @@ dependencies = [
|
||||
"minijinja-autoreload",
|
||||
"minijinja-contrib",
|
||||
"mockall",
|
||||
"plotly",
|
||||
"reqwest",
|
||||
"scraper",
|
||||
"serde",
|
||||
|
||||
@@ -23,6 +23,7 @@ minijinja = { version = "2.5.0", features = ["loader", "multi_template"] }
|
||||
minijinja-autoreload = "2.5.0"
|
||||
minijinja-contrib = { version = "2.6.0", features = ["datetime", "timezone"] }
|
||||
mockall = "0.13.0"
|
||||
plotly = "0.12.1"
|
||||
reqwest = {version = "0.12.12", features = ["charset", "json"]}
|
||||
scraper = "0.22.0"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Installing Webfonts
|
||||
Follow these simple Steps.
|
||||
|
||||
## 1.
|
||||
Put `satoshi/` Folder into a Folder called `fonts/`.
|
||||
|
||||
## 2.
|
||||
Put `satoshi.css` into your `css/` Folder.
|
||||
|
||||
## 3. (Optional)
|
||||
You may adapt the `url('path')` in `satoshi.css` depends on your Website Filesystem.
|
||||
|
||||
## 4.
|
||||
Import `satoshi.css` at the top of you main Stylesheet.
|
||||
|
||||
```
|
||||
@import url('satoshi.css');
|
||||
```
|
||||
|
||||
## 5.
|
||||
You are now ready to use the following Rules in your CSS to specify each Font Style:
|
||||
```
|
||||
font-family: Satoshi-Light;
|
||||
font-family: Satoshi-LightItalic;
|
||||
font-family: Satoshi-Regular;
|
||||
font-family: Satoshi-Italic;
|
||||
font-family: Satoshi-Medium;
|
||||
font-family: Satoshi-MediumItalic;
|
||||
font-family: Satoshi-Bold;
|
||||
font-family: Satoshi-BoldItalic;
|
||||
font-family: Satoshi-Black;
|
||||
font-family: Satoshi-BlackItalic;
|
||||
font-family: Satoshi-Variable;
|
||||
font-family: Satoshi-VariableItalic;
|
||||
|
||||
```
|
||||
## 6. (Optional)
|
||||
Use `font-variation-settings` rule to controll axes of variable fonts:
|
||||
wght 900.0
|
||||
|
||||
Available axes:
|
||||
'wght' (range from 300.0 to 900.0
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* @license
|
||||
*
|
||||
* Font Family: Satoshi
|
||||
* Designed by: Deni Anggara
|
||||
* URL: https://www.fontshare.com/fonts/satoshi
|
||||
* © 2025 Indian Type Foundry
|
||||
*
|
||||
* Satoshi Light
|
||||
* Satoshi LightItalic
|
||||
* Satoshi Regular
|
||||
* Satoshi Italic
|
||||
* Satoshi Medium
|
||||
* Satoshi MediumItalic
|
||||
* Satoshi Bold
|
||||
* Satoshi BoldItalic
|
||||
* Satoshi Black
|
||||
* Satoshi BlackItalic
|
||||
* Satoshi Variable (Variable font)
|
||||
* Satoshi VariableItalic (Variable font)
|
||||
|
||||
*
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Satoshi-Light';
|
||||
src: url('../fonts/Satoshi-Light.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-Light.woff') format('woff'),
|
||||
url('../fonts/Satoshi-Light.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Satoshi-LightItalic';
|
||||
src: url('../fonts/Satoshi-LightItalic.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-LightItalic.woff') format('woff'),
|
||||
url('../fonts/Satoshi-LightItalic.ttf') format('truetype');
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Satoshi-Regular';
|
||||
src: url('../fonts/Satoshi-Regular.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-Regular.woff') format('woff'),
|
||||
url('../fonts/Satoshi-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Satoshi-Italic';
|
||||
src: url('../fonts/Satoshi-Italic.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-Italic.woff') format('woff'),
|
||||
url('../fonts/Satoshi-Italic.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Satoshi-Medium';
|
||||
src: url('../fonts/Satoshi-Medium.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-Medium.woff') format('woff'),
|
||||
url('../fonts/Satoshi-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Satoshi-MediumItalic';
|
||||
src: url('../fonts/Satoshi-MediumItalic.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-MediumItalic.woff') format('woff'),
|
||||
url('../fonts/Satoshi-MediumItalic.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Satoshi-Bold';
|
||||
src: url('../fonts/Satoshi-Bold.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-Bold.woff') format('woff'),
|
||||
url('../fonts/Satoshi-Bold.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Satoshi-BoldItalic';
|
||||
src: url('../fonts/Satoshi-BoldItalic.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-BoldItalic.woff') format('woff'),
|
||||
url('../fonts/Satoshi-BoldItalic.ttf') format('truetype');
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Satoshi-Black';
|
||||
src: url('../fonts/Satoshi-Black.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-Black.woff') format('woff'),
|
||||
url('../fonts/Satoshi-Black.ttf') format('truetype');
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Satoshi-BlackItalic';
|
||||
src: url('../fonts/Satoshi-BlackItalic.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-BlackItalic.woff') format('woff'),
|
||||
url('../fonts/Satoshi-BlackItalic.ttf') format('truetype');
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
}
|
||||
/**
|
||||
* This is a variable font
|
||||
* You can control variable axes as shown below:
|
||||
* font-variation-settings: wght 900.0;
|
||||
*
|
||||
* available axes:
|
||||
'wght' (range from 300.0 to 900.0
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Satoshi-Variable';
|
||||
src: url('../fonts/Satoshi-Variable.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-Variable.woff') format('woff'),
|
||||
url('../fonts/Satoshi-Variable.ttf') format('truetype');
|
||||
font-weight: 300 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
/**
|
||||
* This is a variable font
|
||||
* You can control variable axes as shown below:
|
||||
* font-variation-settings: wght 900.0;
|
||||
*
|
||||
* available axes:
|
||||
'wght' (range from 300.0 to 900.0
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Satoshi-VariableItalic';
|
||||
src: url('../fonts/Satoshi-VariableItalic.woff2') format('woff2'),
|
||||
url('../fonts/Satoshi-VariableItalic.woff') format('woff'),
|
||||
url('../fonts/Satoshi-VariableItalic.ttf') format('truetype');
|
||||
font-weight: 300 900;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
File diff suppressed because one or more lines are too long
+7
-3
@@ -29,12 +29,14 @@ use zettle_db::{
|
||||
html::{
|
||||
account::{delete_account, set_api_key, show_account_page, update_timezone},
|
||||
admin_panel::{show_admin_panel, toggle_registration_status},
|
||||
documentation::index::show_documentation_index,
|
||||
documentation::{
|
||||
show_documentation_index, show_get_started, show_mobile_friendly,
|
||||
show_privacy_policy,
|
||||
},
|
||||
gdpr::{accept_gdpr, deny_gdpr},
|
||||
index::{delete_job, delete_text_content, index_handler},
|
||||
ingress_form::{hide_ingress_form, process_ingress_form, show_ingress_form},
|
||||
knowledge::entities::show_knowledge_page,
|
||||
privacy_policy::show_privacy_policy,
|
||||
knowledge::show_knowledge_page,
|
||||
search_result::search_result_handler,
|
||||
signin::{authenticate_user, show_signin_form},
|
||||
signout::sign_out_user,
|
||||
@@ -184,6 +186,8 @@ fn html_routes(
|
||||
)
|
||||
.route("/documentation", get(show_documentation_index))
|
||||
.route("/documentation/privacy-policy", get(show_privacy_policy))
|
||||
.route("/documentation/get-started", get(show_get_started))
|
||||
.route("/documentation/mobile-friendly", get(show_mobile_friendly))
|
||||
.nest_service("/assets", ServeDir::new("assets/"))
|
||||
.layer(from_fn_with_state(app_state.clone(), analytics_middleware))
|
||||
.layer(
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
use axum::{extract::State, response::IntoResponse};
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
|
||||
use crate::{
|
||||
error::HtmlError,
|
||||
page_data,
|
||||
server::{routes::html::render_template, AppState},
|
||||
storage::types::user::User,
|
||||
};
|
||||
|
||||
page_data!(IndexData, "documentation/index.html", {
|
||||
user: Option<User>
|
||||
});
|
||||
|
||||
pub async fn show_documentation_index(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let output = render_template(
|
||||
IndexData::template_name(),
|
||||
IndexData {
|
||||
user: auth.current_user,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
@@ -1 +1,79 @@
|
||||
pub mod index;
|
||||
use axum::{extract::State, response::IntoResponse};
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
|
||||
use crate::{
|
||||
error::HtmlError,
|
||||
page_data,
|
||||
server::{routes::html::render_template, AppState},
|
||||
storage::types::user::User,
|
||||
};
|
||||
|
||||
page_data!(DocumentationData, "do_not_use_this", {
|
||||
user: Option<User>,
|
||||
current_path: String
|
||||
});
|
||||
|
||||
pub async fn show_privacy_policy(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let output = render_template(
|
||||
"documentation/privacy.html",
|
||||
DocumentationData {
|
||||
user: auth.current_user,
|
||||
current_path: "/privacy_policy".to_string(),
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
pub async fn show_get_started(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let output = render_template(
|
||||
"documentation/get_started.html",
|
||||
DocumentationData {
|
||||
user: auth.current_user,
|
||||
current_path: "/get-started".to_string(),
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
pub async fn show_mobile_friendly(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let output = render_template(
|
||||
"documentation/mobile_friendly.html",
|
||||
DocumentationData {
|
||||
user: auth.current_user,
|
||||
current_path: "/mobile-friendly".to_string(),
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
pub async fn show_documentation_index(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let output = render_template(
|
||||
"documentation/index.html",
|
||||
DocumentationData {
|
||||
user: auth.current_user,
|
||||
current_path: "/index".to_string(),
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::{IntoResponse, Redirect},
|
||||
};
|
||||
use axum_session::Session;
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use tokio::join;
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
error::{AppError, HtmlError},
|
||||
page_data,
|
||||
server::{
|
||||
routes::html::{render_block, render_template},
|
||||
AppState,
|
||||
},
|
||||
storage::{
|
||||
db::{delete_item, get_item},
|
||||
types::{
|
||||
file_info::FileInfo, job::Job, knowledge_entity::KnowledgeEntity,
|
||||
knowledge_relationship::KnowledgeRelationship, text_chunk::TextChunk,
|
||||
text_content::TextContent, user::User,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
page_data!(KnowledgeBaseData, "knowledge/base.html", {
|
||||
entities: Vec<KnowledgeEntity>,
|
||||
relationships: Vec<KnowledgeRelationship>,
|
||||
user: User
|
||||
});
|
||||
|
||||
pub async fn show_knowledge_page(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
// Early return if the user is not authenticated
|
||||
let user = match auth.current_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/signin").into_response()),
|
||||
};
|
||||
|
||||
let entities = User::get_knowledge_entities(&user.id, &state.surreal_db_client)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
info!("Got entities ok");
|
||||
|
||||
let relationships = User::get_knowledge_relationships(&user.id, &state.surreal_db_client)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
let output = render_template(
|
||||
KnowledgeBaseData::template_name(),
|
||||
KnowledgeBaseData {
|
||||
entities,
|
||||
relationships,
|
||||
user,
|
||||
},
|
||||
state.templates,
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
@@ -1 +1,112 @@
|
||||
pub mod entities;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::{IntoResponse, Redirect},
|
||||
};
|
||||
use axum_session::Session;
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use plotly::{Configuration, Layout, Plot, Scatter};
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use tokio::join;
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
error::{AppError, HtmlError},
|
||||
page_data,
|
||||
server::{
|
||||
routes::html::{render_block, render_template},
|
||||
AppState,
|
||||
},
|
||||
storage::{
|
||||
db::{delete_item, get_item},
|
||||
types::{
|
||||
file_info::FileInfo, job::Job, knowledge_entity::KnowledgeEntity,
|
||||
knowledge_relationship::KnowledgeRelationship, text_chunk::TextChunk,
|
||||
text_content::TextContent, user::User,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
page_data!(KnowledgeBaseData, "knowledge/base.html", {
|
||||
entities: Vec<KnowledgeEntity>,
|
||||
relationships: Vec<KnowledgeRelationship>,
|
||||
user: User,
|
||||
plot_html: String
|
||||
});
|
||||
|
||||
pub async fn show_knowledge_page(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
// Early return if the user is not authenticated
|
||||
let user = match auth.current_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/signin").into_response()),
|
||||
};
|
||||
|
||||
let entities = User::get_knowledge_entities(&user.id, &state.surreal_db_client)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
info!("Got entities ok");
|
||||
|
||||
let relationships = User::get_knowledge_relationships(&user.id, &state.surreal_db_client)
|
||||
.await
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
// In your handler function
|
||||
let mut plot = Plot::new();
|
||||
|
||||
// Create node positions (you might want to use a proper layout algorithm)
|
||||
let node_x: Vec<f64> = entities.iter().enumerate().map(|(i, _)| i as f64).collect();
|
||||
let node_y: Vec<f64> = vec![0.0; entities.len()];
|
||||
let node_text: Vec<String> = entities.iter().map(|e| e.description.clone()).collect();
|
||||
|
||||
// Add nodes
|
||||
let nodes = Scatter::new(node_x.clone(), node_y.clone())
|
||||
.mode(plotly::common::Mode::Markers)
|
||||
.text_array(node_text)
|
||||
.name("Entities")
|
||||
.hover_template("%{text}");
|
||||
|
||||
// Add edges
|
||||
let mut edge_x = Vec::new();
|
||||
let mut edge_y = Vec::new();
|
||||
for rel in &relationships {
|
||||
let from_idx = entities.iter().position(|e| e.id == rel.out).unwrap_or(0);
|
||||
let to_idx = entities.iter().position(|e| e.id == rel.in_).unwrap_or(0);
|
||||
|
||||
edge_x.extend_from_slice(&[from_idx as f64, to_idx as f64, std::f64::NAN]);
|
||||
edge_y.extend_from_slice(&[0.0, 0.0, std::f64::NAN]);
|
||||
}
|
||||
|
||||
let edges = Scatter::new(edge_x, edge_y)
|
||||
.mode(plotly::common::Mode::Lines)
|
||||
.name("Relationships");
|
||||
|
||||
plot.add_trace(edges);
|
||||
plot.add_trace(nodes);
|
||||
|
||||
let layout = Layout::new()
|
||||
.title("Knowledge Graph")
|
||||
.show_legend(false)
|
||||
.height(600);
|
||||
|
||||
plot.set_layout(layout);
|
||||
|
||||
// Convert to HTML
|
||||
let html = plot.to_html();
|
||||
|
||||
let output = render_template(
|
||||
KnowledgeBaseData::template_name(),
|
||||
KnowledgeBaseData {
|
||||
entities,
|
||||
relationships,
|
||||
user,
|
||||
plot_html: html,
|
||||
},
|
||||
state.templates,
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ pub mod gdpr;
|
||||
pub mod index;
|
||||
pub mod ingress_form;
|
||||
pub mod knowledge;
|
||||
pub mod privacy_policy;
|
||||
pub mod search_result;
|
||||
pub mod signin;
|
||||
pub mod signout;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
use axum::{extract::State, response::IntoResponse};
|
||||
use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
|
||||
use crate::{
|
||||
error::HtmlError,
|
||||
page_data,
|
||||
server::{routes::html::render_template, AppState},
|
||||
storage::types::user::User,
|
||||
};
|
||||
|
||||
page_data!(PrivacyPolicyData, "documentation/privacy.html", {
|
||||
user: Option<User>
|
||||
});
|
||||
|
||||
pub async fn show_privacy_policy(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let output = render_template(
|
||||
PrivacyPolicyData::template_name(),
|
||||
PrivacyPolicyData {
|
||||
user: auth.current_user,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{% extends 'documentation/base.html' %}
|
||||
{% block article %}
|
||||
<h2>Get Started with Minne</h2>
|
||||
<p>Minne offers two installation options to suit your needs:</p>
|
||||
<ol>
|
||||
<li>
|
||||
<strong>Hosted Version:</strong> Enjoy a hassle‐free experience by signing up for the ready‐to‐use service.
|
||||
Simply navigate to <code>/signup</code> to create an account.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Self-Hosted:</strong> Gain full control by running Minne on your own infrastructure. Visit
|
||||
<a href="https://github.com/perstarkse/minne">GitHub</a> to download the latest release. After extracting the
|
||||
release, open the <code>config.yaml</code> file and set the following configurations:
|
||||
</li>
|
||||
</ol>
|
||||
<pre><code>OPENAI_API_KEY: your_api_key
|
||||
SMTP_EMAIL_RELAYER: your_email_relayer
|
||||
SMTP_USERNAME: your_smtp_username
|
||||
SMTP_PASSWORD: your_smtp_password
|
||||
DB_ADDRESS: your_db_address
|
||||
DB_USER: your_db_user
|
||||
DB_PASSWORD: your_db_password</code></pre>
|
||||
<p>The database settings relate to a running instance of SurrealDB. You can opt for their cloud solution or run your
|
||||
own instance.</p>
|
||||
|
||||
<p>Once your configuration is complete, start both the server and the worker. They can be hosted on separate
|
||||
machines, with different resource requirements:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Server:</strong> Lightweight, using roughly 50MB of RAM. A minimum of 1 core and 256MB of RAM is
|
||||
recommended.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Worker:</strong> Handles content parsing, typically consuming about 60MB of RAM—occasionally peaking up
|
||||
to 1GB. We recommend allocating at least 2 cores and 1024MB of RAM.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>After launching the services, navigate to <code><your_url>:3000/signup</code> to register. The first
|
||||
account created will automatically receive admin permissions, allowing you to later disable further registrations
|
||||
via the <code>/admin</code> page if desired.</p>
|
||||
|
||||
<p>From the homepage (<code>/</code>), you can:</p>
|
||||
<ul>
|
||||
<li>Submit content, including files, videos, and URLs for ingestion.</li>
|
||||
<li>Monitor job statuses and manage your existing content.</li>
|
||||
<li>Search your content or start a chat conversation for assistance.</li>
|
||||
</ul>
|
||||
|
||||
<p>Visit the <code>/knowledge</code> page to view your content organized by different sections. This page also
|
||||
provides a visual demonstration of the graph database structure, enhancing your understanding of content
|
||||
relationships.</p>
|
||||
|
||||
<p>This streamlined setup ensures intuitive onboarding while offering robust customization options. Whether you are
|
||||
a novice or an advanced user, Minne is designed to deliver a smooth experience and reliable performance.</p>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,11 @@
|
||||
<ul class="menu bg-base-200 rounded-box w-full">
|
||||
<li><a hx-boost="true" href="/documentation">Start</a></li>
|
||||
<li><a hx-boost="true" class="" href="/documentation/privacy-policy">Privacy Policy</a></li>
|
||||
<ul class="menu bg-base-200 rounded-box w-full ">
|
||||
<li><a hx-boost="true" class="{{'menu-active' if current_path=='/index' }}" href="/documentation">Start</a></li>
|
||||
<li><a hx-boost="true" class="{{'menu-active' if current_path=='/get-started' }}"
|
||||
href="/documentation/get-started">Get Started</a></li>
|
||||
<li><a hx-boost="true" class="{{'menu-active' if current_path=='/mobile-friendly' }}"
|
||||
href="/documentation/mobile-friendly">Mobile friendly</a></li>
|
||||
<li><a hx-boost="true" class="{{'menu-active' if current_path=='/privacy-policy' }}"
|
||||
href="/documentation/privacy-policy">Privacy Policy</a></li>
|
||||
<li>
|
||||
<details open>
|
||||
<summary>Core Concepts</summary>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{% extends 'documentation/base.html' %}
|
||||
{% block article %}
|
||||
<header>
|
||||
<h2>Mobile Friendly Ingression: How to Submit Content from iOS to Minne</h2>
|
||||
</header>
|
||||
<p>Minne is built with simplicity in mind. Whether you wish to save a file, capture a thought, or share a page,
|
||||
submitting content is effortless. Our server provides API access that enables users to perform actions using a
|
||||
personalized API key.</p>
|
||||
|
||||
<p>An iOS shortcut has been developed to streamline the process of sending content. To begin, navigate to
|
||||
<code>/account</code> and generate an API key. Once created, you will see an option to download the iOS shortcut.
|
||||
</p>
|
||||
|
||||
<p>After downloading the shortcut, update the "Get response from URL" authentication headers with your API key. If
|
||||
you are self-hosting, ensure the URL is adjusted accordingly.</p>
|
||||
|
||||
<p>The shortcut integrates seamlessly with iOS. When you "share with Minne," you will be prompted to provide
|
||||
instructions to the AI and to either choose an existing category or create a new one for your submission.</p>
|
||||
|
||||
<p>While an Android solution is in the works, for now you can add the web app to your home screen as a Progressive
|
||||
Web App (PWA) for a similar mobile-friendly experience.</p>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,6 @@
|
||||
{% block active_jobs_section %}
|
||||
<ul id="active_jobs_section" class="list ">
|
||||
<li class="py-4 text-xs opacity-60 tracking-wide">Active Jobs</li>
|
||||
<li class="py-4 text-center text-xs opacity-60 tracking-wide">Active Jobs</li>
|
||||
{% for item in active_jobs %}
|
||||
<li class="list-row">
|
||||
<div class="bg-secondary rounded-box size-10 flex justify-center items-center text-secondary-content">
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<div class="shadow mt-4 rounded-box">
|
||||
<div class="flex gap-4">
|
||||
<button class="btn btn-primary" hx-get="/ingress-form" hx-swap="outerHTML">Add Content</button>
|
||||
<a class="btn btn-secondary" href="/knowledge" hx-boost="true">View Knowledge</a>
|
||||
</div>
|
||||
<div class="flex gap-4 flex-col">
|
||||
<a class="btn btn-secondary" href="/knowledge" hx-boost="true">View Knowledge</a>
|
||||
<button class="btn btn-primary" hx-get="/ingress-form" hx-swap="outerHTML">Add Content</button>
|
||||
</div>
|
||||
@@ -1,6 +1,6 @@
|
||||
{% block latest_content_section %}
|
||||
<ul id="latest_content_section" class="list">
|
||||
<li class="py-4 text-xs opacity-60 tracking-wide">Recently added content</li>
|
||||
<li class="py-4 text-center text-xs opacity-60 tracking-wide">Recently added content</li>
|
||||
{% for item in latest_text_contents %}
|
||||
<li class="list-row">
|
||||
<div class="bg-accent rounded-box size-10 flex justify-center items-center text-accent-content">
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<div class="py-4 shadow rounded-box">
|
||||
<h2>
|
||||
Search your content
|
||||
</h2>
|
||||
<input type="text" placeholder="Search your knowledge base" class="input input-bordered w-full" name="query"
|
||||
hx-get="/search" hx-target="#search-results" />
|
||||
<div id="search-results" class="mt-4">
|
||||
<!-- Results will be populated here by HTMX -->
|
||||
</div>
|
||||
<h2>
|
||||
Search your content
|
||||
</h2>
|
||||
<input type="text" placeholder="Search your knowledge base" class="input input-bordered w-full" name="query"
|
||||
hx-get="/search" hx-target="#search-results" />
|
||||
<div id="search-results" class="mt-4">
|
||||
<!-- Results will be populated here by HTMX -->
|
||||
</div>
|
||||
@@ -34,8 +34,8 @@
|
||||
</div>
|
||||
<div id="error-message" class="text-error text-center {% if not error %}hidden{% endif %}">{{ error }}</div>
|
||||
<div class="form-control mt-6 flex flex-col sm:flex-row gap-1">
|
||||
<button type="submit" class="btn btn-primary w-full sm:w-fit">Submit</button>
|
||||
<button hx-get="/hide-ingress-form" hx-target="#ingress-form" hx-swap="outerHTML"
|
||||
class="btn btn-outline w-full sm:w-fit">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary w-full sm:w-fit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -2,12 +2,12 @@
|
||||
{% block main %}
|
||||
<main class="flex justify-center grow mt-2 sm:mt-4 gap-6">
|
||||
<div class="container">
|
||||
|
||||
{{plot_html|safe}}
|
||||
<h2>Entities</h2>
|
||||
|
||||
{% for entity in entities %}
|
||||
<p>{{entity.description}} /p>
|
||||
{% endfor %}
|
||||
<p>{{entity.id}} - {{entity.description}} </p>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<h2 class="mt-10">Relationships</h2>
|
||||
|
||||
Reference in New Issue
Block a user