wip: plotly & additional documentation

This commit is contained in:
Per Stark
2025-02-04 12:54:55 +01:00
parent fd19195148
commit 711e54aea1
69 changed files with 618 additions and 156 deletions

125
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"] }

43
assets/font/README.md Normal file
View File

@@ -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

148
assets/font/css/satoshi.css Normal file
View File

@@ -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.

File diff suppressed because one or more lines are too long

View File

@@ -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(

View File

@@ -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())
}

View File

@@ -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())
}

View File

@@ -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())
}

View File

@@ -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())
}

View File

@@ -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;

View File

@@ -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())
}

View File

@@ -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 hasslefree experience by signing up for the readytouse 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>&lt;your_url&gt;: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 %}

View File

@@ -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>

View File

@@ -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 %}

View File

@@ -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">

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>