feat: release build bundles assets

This commit is contained in:
Per Stark
2025-03-28 22:24:09 +01:00
parent c8a97d9b52
commit b2759bfa73
43 changed files with 6193 additions and 277 deletions

66
Cargo.lock generated
View File

@@ -1046,6 +1046,10 @@ dependencies = [
"futures",
"mime",
"mime_guess",
"minijinja",
"minijinja-autoreload",
"minijinja-contrib",
"minijinja-embed",
"reqwest",
"serde",
"serde_json",
@@ -2014,6 +2018,7 @@ dependencies = [
"common",
"composite-retrieval",
"futures",
"include_dir",
"json-stream-parser",
"minijinja",
"minijinja-autoreload",
@@ -2027,6 +2032,7 @@ dependencies = [
"thiserror",
"tokio",
"tower-http",
"tower-serve-static",
"tracing",
]
@@ -2368,6 +2374,25 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "include_dir"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
dependencies = [
"include_dir_macros",
]
[[package]]
name = "include_dir_macros"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -3421,6 +3446,26 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
@@ -5284,6 +5329,27 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-serve-static"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "148022c3d604d85a3b558ef7154aa90aaec5f9506beae64f6ad4e856306d287f"
dependencies = [
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"include_dir",
"mime",
"mime_guess",
"percent-encoding",
"pin-project",
"tokio",
"tokio-util",
"tower-service",
]
[[package]]
name = "tower-service"
version = "0.3.3"

View File

@@ -4,7 +4,7 @@ use super::types::{analytics::Analytics, system_settings::SystemSettings, Stored
use axum_session::{SessionConfig, SessionError, SessionStore};
use axum_session_surreal::SessionSurrealPool;
use futures::Stream;
use std::ops::Deref;
use std::{ops::Deref, sync::Arc};
use surrealdb::{
engine::any::{connect, Any},
opt::auth::Root,
@@ -15,6 +15,9 @@ use surrealdb::{
pub struct SurrealDbClient {
pub client: Surreal<Any>,
}
pub trait ProvidesDb {
fn db(&self) -> &Arc<SurrealDbClient>;
}
impl SurrealDbClient {
/// # Initialize a new datbase client

View File

@@ -4,6 +4,10 @@ pub use minijinja_contrib;
pub use minijinja_embed;
use std::sync::Arc;
pub trait ProvidesTemplateEngine {
fn template_engine(&self) -> &Arc<TemplateEngine>;
}
#[derive(Clone)]
pub enum TemplateEngine {
// Use AutoReload for debug builds (debug_assertions is true)

View File

@@ -30,6 +30,8 @@ plotly = "0.12.1"
surrealdb = "2.0.4"
tower-http = { version = "0.6.2", features = ["fs"] }
chrono-tz = "0.10.1"
tower-serve-static = "0.1.1"
include_dir = "0.7.4"
common = { path = "../common" }
composite-retrieval = { path = "../composite-retrieval" }

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 252 KiB

After

Width:  |  Height:  |  Size: 252 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 790 B

After

Width:  |  Height:  |  Size: 790 B

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,43 @@
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 common::utils::template_engine::TemplateEngine;
use common::utils::template_engine::{ProvidesTemplateEngine, TemplateEngine};
use common::{create_template_engine, storage::db::ProvidesDb};
use std::sync::Arc;
use surrealdb::engine::any::Any;
use tracing::debug;
use crate::{OpenAIClientType, SessionStoreType};
#[derive(Clone)]
pub struct HtmlState {
pub db: Arc<SurrealDbClient>,
pub openai_client: Arc<async_openai::Client<async_openai::config::OpenAIConfig>>,
pub openai_client: Arc<OpenAIClientType>,
pub templates: Arc<TemplateEngine>,
pub session_store: Arc<SessionStore<SessionSurrealPool<Any>>>,
pub session_store: Arc<SessionStoreType>,
}
impl HtmlState {
pub async fn new(config: &AppConfig) -> Result<Self, Box<dyn std::error::Error>> {
pub fn new_with_resources(
db: Arc<SurrealDbClient>,
openai_client: Arc<OpenAIClientType>,
session_store: Arc<SessionStoreType>,
) -> Result<Self, Box<dyn std::error::Error>> {
let template_engine = create_template_engine!("templates");
debug!("Template engine created for html_router.");
let surreal_db_client = Arc::new(
SurrealDbClient::new(
&config.surrealdb_address,
&config.surrealdb_username,
&config.surrealdb_password,
&config.surrealdb_namespace,
&config.surrealdb_database,
)
.await?,
);
surreal_db_client.ensure_initialized().await?;
let openai_client = Arc::new(async_openai::Client::new());
let session_store = Arc::new(surreal_db_client.create_session_store().await?);
let app_state = HtmlState {
db: surreal_db_client.clone(),
templates: Arc::new(template_engine),
openai_client: openai_client.clone(),
Ok(Self {
db,
openai_client,
session_store,
};
Ok(app_state)
templates: Arc::new(template_engine),
})
}
}
impl ProvidesDb for HtmlState {
fn db(&self) -> &Arc<SurrealDbClient> {
&self.db
}
}
impl ProvidesTemplateEngine for HtmlState {
fn template_engine(&self) -> &Arc<TemplateEngine> {
&self.templates
}
}

View File

@@ -4,7 +4,7 @@ pub mod router_factory;
pub mod routes;
use axum::{extract::FromRef, Router};
use axum_session::Session;
use axum_session::{Session, SessionStore};
use axum_session_auth::AuthSession;
use axum_session_surreal::SessionSurrealPool;
use common::storage::types::user::User;
@@ -14,6 +14,8 @@ use surrealdb::{engine::any::Any, Surreal};
pub type AuthSessionType = AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>;
pub type SessionType = Session<SessionSurrealPool<Any>>;
pub type SessionStoreType = SessionStore<SessionSurrealPool<Any>>;
pub type OpenAIClientType = async_openai::Client<async_openai::config::OpenAIConfig>;
/// Html routes
pub fn html_routes<S>(app_state: &HtmlState) -> Router<S>

View File

@@ -3,31 +3,28 @@ use axum::{
middleware::Next,
response::Response,
};
use axum_session_surreal::SessionSurrealPool;
use surrealdb::engine::any::Any;
use common::storage::types::analytics::Analytics;
use common::storage::{db::ProvidesDb, types::analytics::Analytics};
use crate::html_state::HtmlState;
use crate::SessionType;
pub async fn analytics_middleware(
State(state): State<HtmlState>,
session: axum_session::Session<SessionSurrealPool<Any>>,
/// Middleware to count unique visitors and page loads
pub async fn analytics_middleware<S>(
State(state): State<S>,
session: SessionType,
request: Request,
next: Next,
) -> Response {
// Get the path from the request
) -> Response
where
S: ProvidesDb + Clone + Send + Sync + 'static,
{
let path = request.uri().path();
// Only count if it's a main page request (not assets or other resources)
if !path.starts_with("/assets") && !path.starts_with("/_next") && !path.contains('.') {
if !path.starts_with("/assets") && !path.contains('.') {
if !session.get::<bool>("counted_visitor").unwrap_or(false) {
let _ = Analytics::increment_visitors(&state.db).await;
let _ = Analytics::increment_visitors(state.db()).await;
session.set("counted_visitor", true);
}
let _ = Analytics::increment_page_loads(&state.db).await;
let _ = Analytics::increment_page_loads(state.db()).await;
}
next.run(request).await
}

View File

@@ -1,11 +1,10 @@
use crate::html_state::HtmlState;
use axum::{
extract::State,
http::StatusCode,
response::{Html, IntoResponse, Response},
Extension,
};
use common::error::AppError;
use common::{error::AppError, utils::template_engine::ProvidesTemplateEngine};
use minijinja::{context, Value};
use serde::Serialize;
use tracing::error;
@@ -106,58 +105,45 @@ impl IntoResponse for TemplateResponse {
}
}
struct TemplateStateWrapper {
state: HtmlState,
template_response: TemplateResponse,
}
pub async fn with_template_response<S>(State(state): State<S>, response: Response) -> Response
where
S: ProvidesTemplateEngine + Clone + Send + Sync + 'static,
{
if let Some(template_response) = response.extensions().get::<TemplateResponse>().cloned() {
let template_engine = state.template_engine();
impl IntoResponse for TemplateStateWrapper {
fn into_response(self) -> Response {
let template_engine = &self.state.templates;
match &self.template_response.template_kind {
match &template_response.template_kind {
TemplateKind::Full(name) => {
match template_engine.render(name, &self.template_response.context) {
match template_engine.render(name, &template_response.context) {
Ok(html) => Html(html).into_response(),
Err(e) => {
error!("Failed to render template: {:?}", e);
error!("Failed to render template '{}': {:?}", name, e);
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
}
}
}
TemplateKind::Partial(template, block) => {
match template_engine.render_block(template, block, &self.template_response.context)
{
match template_engine.render_block(template, block, &template_response.context) {
Ok(html) => Html(html).into_response(),
Err(e) => {
error!("Failed to render block: {:?}", e);
error!("Failed to render block '{}/{}': {:?}", template, block, e);
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
}
}
}
TemplateKind::Error(status) => {
match template_engine.render("errors/error.html", &self.template_response.context) {
match template_engine.render("errors/error.html", &template_response.context) {
Ok(html) => (*status, Html(html)).into_response(),
Err(_) => (*status, fallback_error()).into_response(),
Err(e) => {
error!("Failed to render error template: {:?}", e);
(*status, fallback_error()).into_response()
}
}
}
TemplateKind::Redirect(path) => {
(StatusCode::OK, [(axum_htmx::HX_REDIRECT, path.clone())], "").into_response()
}
}
}
}
pub async fn with_template_response(
State(state): State<HtmlState>,
response: Response,
) -> Response {
if let Some(template_response) = response.extensions().get::<TemplateResponse>().cloned() {
TemplateStateWrapper {
state,
template_response,
}
.into_response()
} else {
response
}

View File

@@ -8,7 +8,6 @@ use axum_session_auth::{AuthConfig, AuthSessionLayer};
use axum_session_surreal::SessionSurrealPool;
use common::storage::types::user::User;
use surrealdb::{engine::any::Any, Surreal};
use tower_http::services::ServeDir;
use crate::{
html_state::HtmlState,
@@ -18,6 +17,27 @@ use crate::{
},
};
#[macro_export]
macro_rules! create_asset_service {
// Takes the relative path to the asset directory
($relative_path:expr) => {{
#[cfg(debug_assertions)]
{
let crate_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let assets_path = crate_dir.join($relative_path);
tracing::debug!("Assets: Serving from filesystem: {:?}", assets_path);
tower_http::services::ServeDir::new(assets_path)
}
#[cfg(not(debug_assertions))]
{
tracing::debug!("Assets: Serving embedded directory");
static ASSETS_DIR: include_dir::Dir<'static> =
include_dir::include_dir!("$CARGO_MANIFEST_DIR/assets");
tower_serve_static::ServeDir::new(&ASSETS_DIR)
}
}};
}
pub struct RouterFactory<S> {
app_state: HtmlState,
public_routers: Vec<Router<S>>,
@@ -108,27 +128,35 @@ where
}
// Add public assets to public router
if let Some(assets) = self.public_assets_config {
public_router =
public_router.nest_service(&assets.path, ServeDir::new(assets.directory));
if let Some(assets_config) = self.public_assets_config {
// Call the macro using the stored relative directory path
let asset_service = create_asset_service!(&assets_config.directory);
// Nest the resulting service under the stored URL path
public_router = public_router.nest_service(&assets_config.path, asset_service);
}
// Start with an empty protected router
let mut protected_router = Router::new();
// Merge all protected routers
// Check if there are any protected routers
let has_protected_routes =
!self.protected_routers.is_empty() || !self.nested_protected_routes.is_empty();
// Merge root-level protected routers
for router in self.protected_routers {
protected_router = protected_router.merge(router);
}
// Add nested protected routes
// Nest protected routers
for (path, router) in self.nested_protected_routes {
protected_router = protected_router.nest(&path, router);
}
// Apply auth middleware to all protected routes
let protected_router =
protected_router.route_layer(from_fn_with_state(self.app_state.clone(), require_auth));
// Apply auth middleware
if has_protected_routes {
protected_router = protected_router
.route_layer(from_fn_with_state(self.app_state.clone(), require_auth));
}
// Combine public and protected routes
let mut router = Router::new().merge(public_router).merge(protected_router);
@@ -142,11 +170,11 @@ where
router
.layer(from_fn_with_state(
self.app_state.clone(),
analytics_middleware,
analytics_middleware::<HtmlState>,
))
.layer(map_response_with_state(
self.app_state.clone(),
with_template_response,
with_template_response::<HtmlState>,
))
.layer(
AuthSessionLayer::<User, String, SessionSurrealPool<Any>, Surreal<Any>>::new(Some(

View File

@@ -1,20 +0,0 @@
{% extends "body_base.html" %}
{% block main %}
<main class="flex justify-center grow mt-2 sm:mt-4 pb-10">
<div class="container">
<div class="grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-4">
<!-- Documentation Menu -->
<aside class="bg-base-200 rounded-lg p-4">
{% include "documentation/menu.html" %}
</aside>
<!-- Main Content -->
<article
class="prose prose-sm md:prose-base prose-h1:mb-2 prose-h2:my-2 prose-p:my-2 prose-ul:my-2 prose-pre:my-2 flex mx-auto justify-center flex-col">
{% block article %}
{% endblock %}
</article>
</div>
</div>
</main>
{% endblock %}

View File

@@ -1,58 +0,0 @@
{% extends 'documentation/base.html' %}
{% block article %}
<h1>Get Started with Minne</h1>
<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 class="overflow-x-auto text-sm">
<code class="break-words whitespace-pre-wrap">
OPENAI_API_KEY: your_api_key
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. A minimum of 1 core and 256MB of RAM is recommended.
</li>
<li>
<strong>Worker:</strong> Handles content parsing and creation of database entities. It's recommended to allocate at
least two cores and 1024 MB RAM. It will run on less but might run into constraints depending on the content being
parsed.
</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,26 +0,0 @@
{% extends "documentation/base.html" %}
{% block article %}
<h1 class="text-3xl mb-2">Documentation</h1>
<p>
Personalised Knowledge Management (PKM) is a system designed to help individuals organise, store, and retrieve
information effectively. It empowers users to create a personalised workflow for managing knowledge, enabling
better decision-making and productivity.
</p>
<p>
This documentation will guide you through the core concepts, tools, and best practices for building and
maintaining your own PKM system.
</p>
<div class="card bg-base-200 rounded-lg shadow-md">
<div class="card-body">
<h3 class="card-title not-prose">Getting Started</h3>
<p>
To begin, explore the sections in the navigation menu. Each section provides detailed insights into
different
aspects of PKM, from foundational principles to advanced techniques.
</p>
<div class="card-actions">
<a href="/documentation/quick-start" class="btn btn-primary text-primary-content" hx-boost="true">Learn More</a>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,28 +0,0 @@
<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>
<ul>
<li><a hx-boost="true" href="/documentation/submenu1">What is PKM?</a></li>
<li><a hx-boost="true" href="/documentation/submenu2">Benefits of PKM</a></li>
</ul>
</details>
</li>
<li>
<details>
<summary>Tools & Techniques</summary>
<ul>
<li><a hx-boost="true" href="/documentation/tools">Tools for PKM</a></li>
<li><a hx-boost="true" href="/documentation/techniques">Effective Techniques</a></li>
</ul>
</details>
</li>
<li><a hx-boost="true" href="/documentation/faq">FAQ</a></li>
</ul>

View File

@@ -1,20 +0,0 @@
{% extends 'documentation/base.html' %}
{% block article %}
<h1>Mobile Friendly Ingression: How to Submit Content from iOS to Minne</h1>
<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,27 +0,0 @@
{% extends 'documentation/base.html' %}
{% block article %}
<h1>Privacy Policy</h1>
<p>We value your privacy and are committed to protecting your personal information. This policy
outlines how we handle your data and ensures transparency in our practices.</p>
<h2>Data Collection</h2>
<p>We only collect data that is necessary for the functionality of our services. Any data you upload to
our site remains your property and will not be shared with third parties unless required by law.</p>
<h2>Cookies</h2>
<p>We do not use cookies for tracking or analytics. The cookies we employ are strictly for session
management, ensuring a smooth and secure user experience.</p>
<h2>No Unnecessary Data Extraction</h2>
<p>We believe that unnecessary data extraction is unethical and a poor practice. We only collect the
minimum amount of data required to provide our services effectively, ensuring your privacy is respected at all
times.</p>
<h2>Your Rights</h2>
<p>You have the right to access, modify, or delete your data at any time. If you have any concerns
about how your data is handled, please contact us.</p>
<h2>Changes to This Policy</h2>
<p>We may update this privacy policy from time to time. Any changes will be posted on this page, and we
encourage you to review it periodically.</p>
{% endblock %}

View File

@@ -21,8 +21,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Get config
let config = get_config()?;
// Set up server components
let html_state = HtmlState::new(&config).await?;
// Set up router states
let db = Arc::new(
SurrealDbClient::new(
&config.surrealdb_address,
&config.surrealdb_username,
&config.surrealdb_password,
&config.surrealdb_namespace,
&config.surrealdb_database,
)
.await?,
);
// Ensure db is initialized
db.ensure_initialized().await?;
let session_store = Arc::new(db.create_session_store().await?);
let openai_client = Arc::new(async_openai::Client::new());
let html_state = HtmlState::new_with_resources(db, openai_client, session_store)?;
let api_state = ApiState {
db: html_state.db.clone(),
};

View File

@@ -1,6 +1,8 @@
use std::sync::Arc;
use api_router::{api_routes_v1, api_state::ApiState};
use axum::{extract::FromRef, Router};
use common::utils::config::get_config;
use common::{storage::db::SurrealDbClient, utils::config::get_config};
use html_router::{html_routes, html_state::HtmlState};
use tracing::info;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
@@ -18,7 +20,25 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = get_config()?;
// Set up router states
let html_state = HtmlState::new(&config).await?;
let db = Arc::new(
SurrealDbClient::new(
&config.surrealdb_address,
&config.surrealdb_username,
&config.surrealdb_password,
&config.surrealdb_namespace,
&config.surrealdb_database,
)
.await?,
);
// Ensure db is initialized
db.ensure_initialized().await?;
let session_store = Arc::new(db.create_session_store().await?);
let openai_client = Arc::new(async_openai::Client::new());
let html_state = HtmlState::new_with_resources(db, openai_client, session_store)?;
let api_state = ApiState {
db: html_state.db.clone(),
};

View File

@@ -2,7 +2,7 @@
\[\] configs primarily get envs
\[\] filtering on categories
\[\] integrate assets folder in release build
\[\] integrate templates in release build
\[x] integrate templates in release build
\[\] markdown rendering in client
\[\] openai api key in config
\[\] three js graph explorer