refactor: extendable templates

refactor: simplification

refactor: simplification
This commit is contained in:
Per Stark
2026-01-13 15:57:12 +01:00
parent 037057d108
commit 354dc727c1
32 changed files with 711 additions and 579 deletions

View File

@@ -29,15 +29,17 @@ impl HtmlState {
config: AppConfig,
reranker_pool: Option<Arc<RerankerPool>>,
embedding_provider: Arc<EmbeddingProvider>,
template_engine: Option<Arc<TemplateEngine>>,
) -> Result<Self, Box<dyn std::error::Error>> {
let template_engine = create_template_engine!("templates");
debug!("Template engine created for html_router.");
let templates =
template_engine.unwrap_or_else(|| Arc::new(create_template_engine!("templates")));
debug!("Template engine configured for html_router.");
Ok(Self {
db,
openai_client,
session_store,
templates: Arc::new(template_engine),
templates,
config,
storage,
reranker_pool,

View File

@@ -0,0 +1,20 @@
{% extends 'admin/_layout.html' %}
{% block admin_navigation %}
<a href="/admin?section=overview"
class="nb-btn btn-sm px-4 {% if current_section == 'overview' %}nb-cta{% else %}btn-ghost{% endif %}">
Overview
</a>
<a href="/admin?section=models"
class="nb-btn btn-sm px-4 {% if current_section == 'models' %}nb-cta{% else %}btn-ghost{% endif %}">
Models
</a>
{% endblock %}
{% block admin_content %}
{% if current_section == 'models' %}
{% include 'admin/sections/models.html' %}
{% else %}
{% include 'admin/sections/overview.html' %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% extends 'body_base.html' %}
{% block title %}Minne - Admin{% endblock %}
{% block main %}
<div id="admin-shell" class="flex justify-center grow mt-2 sm:mt-4 pb-4">
<div class="container flex flex-col gap-4">
<section class="nb-panel p-4 sm:p-5 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<h1 class="text-xl font-extrabold tracking-tight">Admin Controls</h1>
</div>
<div class="text-xs opacity-60 sm:text-right">
Signed in as <span class="font-medium">{{ user.email }}</span>
</div>
</section>
<nav class="nb-panel p-2 flex flex-wrap gap-2 text-sm" hx-boost="true" hx-target="#admin-shell"
hx-select="#admin-shell" hx-swap="outerHTML" hx-push-url="true">
{% block admin_navigation %}
{% endblock %}
</nav>
<div id="admin-content" class="flex flex-col gap-4">
{% block admin_content %}
{% endblock %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,38 +1 @@
{% extends 'body_base.html' %}
{% block title %}Minne - Admin{% endblock %}
{% block main %}
<div id="admin-shell" class="flex justify-center grow mt-2 sm:mt-4 pb-4">
<div class="container flex flex-col gap-4">
<section class="nb-panel p-4 sm:p-5 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<h1 class="text-xl font-extrabold tracking-tight">Admin Controls</h1>
</div>
<div class="text-xs opacity-60 sm:text-right">
Signed in as <span class="font-medium">{{ user.email }}</span>
</div>
</section>
<nav class="nb-panel p-2 flex flex-wrap gap-2 text-sm" hx-boost="true" hx-target="#admin-shell"
hx-select="#admin-shell" hx-swap="outerHTML" hx-push-url="true">
<a href="/admin?section=overview"
class="nb-btn btn-sm px-4 {% if current_section == 'overview' %}nb-cta{% else %}btn-ghost{% endif %}">
Overview
</a>
<a href="/admin?section=models"
class="nb-btn btn-sm px-4 {% if current_section == 'models' %}nb-cta{% else %}btn-ghost{% endif %}">
Models
</a>
</nav>
<div id="admin-content" class="flex flex-col gap-4">
{% if current_section == 'models' %}
{% include 'admin/sections/models.html' %}
{% else %}
{% include 'admin/sections/overview.html' %}
{% endif %}
</div>
</div>
</div>
{% endblock %}
{% extends "admin/_base.html" %}

View File

@@ -0,0 +1,72 @@
{% extends "auth/_settings_layout.html" %}
{% block settings_header %}
<h1 class="text-xl font-extrabold tracking-tight">Account Settings</h1>
{% endblock %}
{% block settings_left_column %}
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Email</div>
<input type="email" name="email" value="{{ user.email }}" class="nb-input w-full" disabled />
</label>
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">API Key</div>
{% block api_key_section %}
{% if user.api_key %}
<div class="relative">
<input id="api_key_input" type="text" name="api_key" value="{{ user.api_key }}"
class="nb-input w-full pr-14" disabled />
<button type="button" id="copy_api_key_btn" onclick="copy_api_key()"
class="absolute inset-y-0 right-0 flex items-center px-2 nb-btn btn-sm" aria-label="Copy API key"
title="Copy API key">
{% include "icons/clipboard_icon.html" %}
</button>
</div>
<a href="https://www.icloud.com/shortcuts/66985f7b98a74aaeac6ba29c3f1f0960"
class="nb-btn nb-cta mt-2 w-full">Download iOS shortcut</a>
{% else %}
<button hx-post="/set-api-key" class="nb-btn nb-cta w-full" hx-swap="outerHTML">Create API-Key</button>
{% endif %}
{% endblock %}
</label>
<script>
function copy_api_key() {
const input = document.getElementById('api_key_input');
if (!input) return;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(input.value)
.then(() => show_toast('API key copied!', 'success'))
.catch(() => show_toast('Copy failed', 'error'));
} else {
show_toast('Copy not supported', 'info');
}
}
</script>
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Timezone</div>
{% block timezone_section %}
<select name="timezone" class="nb-select w-full" hx-patch="/update-timezone" hx-swap="outerHTML">
{% for tz in timezones %}
<option value="{{ tz }}" {% if tz==user.timezone %}selected{% endif %}>{{ tz }}</option>
{% endfor %}
</select>
{% endblock %}
</label>
{% endblock %}
{% block settings_right_column %}
<div>
{% block change_password_section %}
<button hx-get="/change-password" hx-swap="outerHTML" class="nb-btn w-full">Change Password</button>
{% endblock %}
</div>
<div>
<button hx-delete="/delete-account"
hx-confirm="This action will permanently delete your account and all data associated. Are you sure you want to continue?"
class="nb-btn btn-error w-full">Delete Account</button>
</div>
{% endblock %}

View File

@@ -0,0 +1,10 @@
{% extends "head_base.html" %}
{% block title %}Minne - Auth{% endblock %}
{% block body %}
<div class="min-h-[100dvh] flex flex-col items-center justify-center">
{% block auth_content %}
{% endblock %}
</div>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "body_base.html" %}
{% block title %}Minne - Account{% endblock %}
{% block main %}
<div class="flex justify-center grow mt-2 sm:mt-4 pb-4">
<div class="container">
<section class="mb-4">
<div class="nb-panel p-3 flex items-center justify-between">
{% block settings_header %}
{% endblock %}
</div>
</section>
<section class="grid grid-cols-1 lg:grid-cols-2 gap-4 space-y-2">
<!-- Left column -->
<div class="nb-panel p-4 space-y-2 flex flex-col">
{% block settings_left_column %}
{% endblock %}
</div>
<!-- Right column -->
<div class="nb-panel p-4 space-y-2">
{% block settings_right_column %}
{% endblock %}
</div>
</section>
<div id="account-result" class="mt-4"></div>
</div>
</div>
{% endblock %}

View File

@@ -1,88 +1 @@
{% extends "body_base.html" %}
{% block title %}Minne - Account{% endblock %}
{% block main %}
<div class="flex justify-center grow mt-2 sm:mt-4 pb-4">
<div class="container">
<section class="mb-4">
<div class="nb-panel p-3 flex items-center justify-between">
<h1 class="text-xl font-extrabold tracking-tight">Account Settings</h1>
</div>
</section>
<section class="grid grid-cols-1 lg:grid-cols-2 gap-4 space-y-2">
<!-- Left column -->
<div class="nb-panel p-4 space-y-2 flex flex-col">
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Email</div>
<input type="email" name="email" value="{{ user.email }}" class="nb-input w-full" disabled />
</label>
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">API Key</div>
{% block api_key_section %}
{% if user.api_key %}
<div class="relative">
<input id="api_key_input" type="text" name="api_key" value="{{ user.api_key }}"
class="nb-input w-full pr-14" disabled />
<button type="button" id="copy_api_key_btn" onclick="copy_api_key()"
class="absolute inset-y-0 right-0 flex items-center px-2 nb-btn btn-sm" aria-label="Copy API key"
title="Copy API key">
{% include "icons/clipboard_icon.html" %}
</button>
</div>
<a href="https://www.icloud.com/shortcuts/66985f7b98a74aaeac6ba29c3f1f0960"
class="nb-btn nb-cta mt-2 w-full">Download iOS shortcut</a>
{% else %}
<button hx-post="/set-api-key" class="nb-btn nb-cta w-full" hx-swap="outerHTML">Create API-Key</button>
{% endif %}
{% endblock %}
</label>
<script>
function copy_api_key() {
const input = document.getElementById('api_key_input');
if (!input) return;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(input.value)
.then(() => show_toast('API key copied!', 'success'))
.catch(() => show_toast('Copy failed', 'error'));
} else {
show_toast('Copy not supported', 'info');
}
}
</script>
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Timezone</div>
{% block timezone_section %}
<select name="timezone" class="nb-select w-full" hx-patch="/update-timezone" hx-swap="outerHTML">
{% for tz in timezones %}
<option value="{{ tz }}" {% if tz==user.timezone %}selected{% endif %}>{{ tz }}</option>
{% endfor %}
</select>
{% endblock %}
</label>
</div>
<!-- Right column -->
<div class="nb-panel p-4 space-y-2">
<div>
{% block change_password_section %}
<button hx-get="/change-password" hx-swap="outerHTML" class="nb-btn w-full">Change Password</button>
{% endblock %}
</div>
<div>
<button hx-delete="/delete-account"
hx-confirm="This action will permanently delete your account and all data associated. Are you sure you want to continue?"
class="nb-btn btn-error w-full">Delete Account</button>
</div>
</div>
</section>
<div id="account-result" class="mt-4"></div>
</div>
</div>
{% endblock %}
{% extends "auth/_account_settings_core.html" %}

View File

@@ -1,9 +1,7 @@
{% extends "head_base.html" %}
{% extends "auth/_layout.html" %}
{% block title %}Minne - Sign in{% endblock %}
{% block body %}
<div class="min-h-[100dvh] flex">
{% block auth_content %}
{% include "auth/signin_form.html" %}
</div>
{% endblock %}

View File

@@ -1,9 +1,8 @@
{% extends "head_base.html" %}
{% extends "auth/_layout.html" %}
{% block title %}Minne - Sign up{% endblock %}
{% block body %}
<div class="min-h-[100dvh] flex items-center">
{% block auth_content %}
<div class="container mx-auto px-4 sm:max-w-md">
<div class="nb-card p-5">
<div class="flex items-center justify-between mb-3">
@@ -46,10 +45,9 @@
</div>
</div>
</div>
</div>
<script>
<script>
// Detect timezone and set hidden input
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
document.getElementById("timezone").value = timezone;
</script>
</script>
{% endblock %}

View File

@@ -0,0 +1,78 @@
{% extends 'body_base.html' %}
{% block title %}Minne - Chat{% endblock %}
{% block main %}
<div class="flex grow relative justify-center mt-2 sm:mt-4">
<div class="container">
<section class="mb-3">
<div class="nb-panel p-3 flex items-center justify-between">
{% block chat_header_actions %}
{% endblock %}
</div>
</section>
<div id="chat-scroll-container" class="overflow-auto hide-scrollbar">
{% block chat_content %}
{% endblock %}
</div>
</div>
</div>
<script>
function doScrollChatToBottom() {
const mainScroll = document.querySelector('main');
if (mainScroll) mainScroll.scrollTop = mainScroll.scrollHeight;
const chatScroll = document.getElementById('chat-scroll-container');
if (chatScroll) chatScroll.scrollTop = chatScroll.scrollHeight;
const chatContainer = document.getElementById('chat_container');
if (chatContainer) chatContainer.scrollTop = chatContainer.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
}
function scrollChatToBottom() {
if (!window.location.pathname.startsWith('/chat')) return;
requestAnimationFrame(doScrollChatToBottom);
}
window.scrollChatToBottom = scrollChatToBottom;
// Delay initial scroll to avoid interfering with view transition
document.addEventListener('DOMContentLoaded', () => setTimeout(scrollChatToBottom, 350));
function handleChatSwap(e) {
if (!window.location.pathname.startsWith('/chat')) return;
// Full page swap: delay for view transition; partial swap: immediate
if (e.detail && e.detail.target && e.detail.target.tagName === 'BODY') {
setTimeout(scrollChatToBottom, 350);
} else {
scrollChatToBottom();
}
}
function cleanupChatListeners(e) {
if (e.detail && e.detail.target && e.detail.target.tagName === 'BODY') {
document.body.removeEventListener('htmx:afterSwap', window._chatEventHandlers.afterSwap);
document.body.removeEventListener('htmx:afterSettle', window._chatEventHandlers.afterSettle);
document.body.removeEventListener('htmx:beforeSwap', window._chatEventHandlers.beforeSwap);
delete window._chatEventHandlers;
window._chatListenersAttached = false;
}
}
window._chatEventHandlers = {
afterSwap: handleChatSwap,
afterSettle: handleChatSwap,
beforeSwap: cleanupChatListeners
};
if (!window._chatListenersAttached) {
document.body.addEventListener('htmx:afterSwap', window._chatEventHandlers.afterSwap);
document.body.addEventListener('htmx:afterSettle', window._chatEventHandlers.afterSettle);
document.body.addEventListener('htmx:beforeSwap', window._chatEventHandlers.beforeSwap);
window._chatListenersAttached = true;
}
</script>
{% endblock %}

View File

@@ -1,81 +1,14 @@
{% extends 'body_base.html' %}
{% extends "chat/_layout.html" %}
{% block title %}Minne - Chat{% endblock %}
{% block main %}
<div class="flex grow relative justify-center mt-2 sm:mt-4">
<div class="container">
<section class="mb-3">
<div class="nb-panel p-3 flex items-center justify-between">
{% block chat_header_actions %}
<h1 class="text-xl font-extrabold tracking-tight">Chat</h1>
<div class="text-xs opacity-70">Converse with your knowledge</div>
</div>
</section>
<div id="chat-scroll-container" class="overflow-auto hide-scrollbar">
{% endblock %}
{% block chat_content %}
{% include "chat/history.html" %}
</div>
</div>
</div>
<script>
function doScrollChatToBottom() {
const mainScroll = document.querySelector('main');
if (mainScroll) mainScroll.scrollTop = mainScroll.scrollHeight;
const chatScroll = document.getElementById('chat-scroll-container');
if (chatScroll) chatScroll.scrollTop = chatScroll.scrollHeight;
const chatContainer = document.getElementById('chat_container');
if (chatContainer) chatContainer.scrollTop = chatContainer.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
}
function scrollChatToBottom() {
if (!window.location.pathname.startsWith('/chat')) return;
requestAnimationFrame(doScrollChatToBottom);
}
window.scrollChatToBottom = scrollChatToBottom;
// Delay initial scroll to avoid interfering with view transition
document.addEventListener('DOMContentLoaded', () => setTimeout(scrollChatToBottom, 350));
function handleChatSwap(e) {
if (!window.location.pathname.startsWith('/chat')) return;
// Full page swap: delay for view transition; partial swap: immediate
if (e.detail && e.detail.target && e.detail.target.tagName === 'BODY') {
setTimeout(scrollChatToBottom, 350);
} else {
scrollChatToBottom();
}
}
function cleanupChatListeners(e) {
if (e.detail && e.detail.target && e.detail.target.tagName === 'BODY') {
document.body.removeEventListener('htmx:afterSwap', window._chatEventHandlers.afterSwap);
document.body.removeEventListener('htmx:afterSettle', window._chatEventHandlers.afterSettle);
document.body.removeEventListener('htmx:beforeSwap', window._chatEventHandlers.beforeSwap);
delete window._chatEventHandlers;
window._chatListenersAttached = false;
}
}
window._chatEventHandlers = {
afterSwap: handleChatSwap,
afterSettle: handleChatSwap,
beforeSwap: cleanupChatListeners
};
if (!window._chatListenersAttached) {
document.body.addEventListener('htmx:afterSwap', window._chatEventHandlers.afterSwap);
document.body.addEventListener('htmx:afterSettle', window._chatEventHandlers.afterSettle);
document.body.addEventListener('htmx:beforeSwap', window._chatEventHandlers.beforeSwap);
window._chatListenersAttached = true;
}
</script>
{% endblock %}
{% block overlay %}
{% include "chat/new_message_form.html" %}
{% include "chat/new_message_form.html" %}
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% macro icon(name) %}
{% if name == "home" %}
{% include "icons/home_icon.html" %}
{% elif name == "book" %}
{% include "icons/book_icon.html" %}
{% elif name == "document" %}
{% include "icons/document_icon.html" %}
{% elif name == "chat" %}
{% include "icons/chat_icon.html" %}
{% elif name == "search" %}
{% include "icons/search_icon.html" %}
{% elif name == "scratchpad" %}
{% include "icons/scratchpad_icon.html" %}
{% endif %}
{% endmacro %}

View File

@@ -0,0 +1,14 @@
<nav class="sticky top-0 z-10 nb-panel nb-panel-canvas border-t-0">
<div class="container mx-auto navbar">
<div class="mr-2 flex-1">
{% block navbar_search %}
{% endblock %}
</div>
<div class="flex-none">
<ul class="menu menu-horizontal px-2 gap-2 items-center">
{% block navbar_actions %}
{% endblock %}
</ul>
</div>
</div>
</nav>

View File

@@ -0,0 +1,60 @@
<div class="drawer-side z-20">
<label for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<ul class="menu p-0 w-72 h-full nb-canvas text-base-content flex flex-col border-r-2 border-neutral">
<!-- === TOP FIXED SECTION === -->
<div class="px-2 mt-4 space-y-3">
{% block sidebar_nav_items %}
{% endblock %}
</div>
<!-- === MIDDLE SCROLLABLE SECTION === -->
<span class="px-4 py-2 nb-label">Recent Chats</span>
<div class="flex-1 overflow-y-auto space-y-1 custom-scrollbar">
{% if conversation_archive is defined and conversation_archive %}
{% for conversation in conversation_archive %}
<li id="conversation-{{ conversation.id }}">
{% if edit_conversation_id == conversation.id %}
<!-- Edit mode -->
<form hx-patch="/chat/{{ conversation.id }}/title" hx-target=".drawer-side" hx-swap="outerHTML"
class="flex items-center gap-1 px-2 py-2 max-w-72 relative">
<input type="text" name="title" value="{{ conversation.title }}" class="nb-input nb-input-sm max-w-52" />
<div class="flex gap-0.5 absolute right-2">
<button type="submit" class="btn btn-ghost btn-xs !p-0">{% include "icons/check_icon.html" %}</button>
<button type="button" hx-get="/chat/sidebar" hx-target=".drawer-side" hx-swap="outerHTML"
class="btn btn-ghost btn-xs !p-0">
{% include "icons/x_icon.html" %}
</button>
</div>
</form>
{% else %}
<!-- View mode -->
<div class="flex w-full pl-4 pr-2 py-2">
<a hx-boost="true" href="/chat/{{ conversation.id }}" class="flex-grow text-sm truncate">
<span>{{ conversation.title }}</span>
</a>
<div class="flex items-center gap-0.5 ml-2">
<button hx-get="/chat/{{ conversation.id }}/title" hx-target=".drawer-side" hx-swap="outerHTML"
class="btn btn-ghost btn-xs">
{% include "icons/edit_icon.html" %}
</button>
<button hx-delete="/chat/{{ conversation.id }}" hx-target=".drawer-side" hx-swap="outerHTML"
hx-confirm="Are you sure you want to delete this chat?" class="btn btn-ghost btn-xs">
{% include "icons/delete_icon.html" %}
</button>
</div>
</div>
{% endif %}
</li>
{% endfor %}
{% else %}
{% endif %}
</div>
<!-- === BOTTOM FIXED SECTION === -->
<div class="px-2 pb-4 space-y-3">
{% block sidebar_bottom_actions %}
{% endblock %}
</div>
</ul>
</div>

View File

@@ -0,0 +1,17 @@
{% extends 'body_base.html' %}
{% block title %}Minne - Content{% endblock %}
{% block main %}
<main id="main_section" class="flex justify-center grow mt-2 sm:mt-4 gap-6 mb-10 w-full">
<div class="container">
<div class="nb-panel p-3 mb-4 flex items-center justify-between">
{% block content_header %}
{% endblock %}
</div>
{% block content_list %}
{% endblock %}
</div>
</main>
{% endblock %}

View File

@@ -1,11 +1,6 @@
{% extends 'body_base.html' %}
{% extends 'content/_layout.html' %}
{% block title %}Minne - Content{% endblock %}
{% block main %}
<main id="main_section" class="flex justify-center grow mt-2 sm:mt-4 gap-6 mb-10 w-full">
<div class="container">
<div class="nb-panel p-3 mb-4 flex items-center justify-between">
{% block content_header %}
<h2 class="text-xl font-extrabold tracking-tight">Content</h2>
<form hx-get="/content" hx-target="#main_section" hx-swap="outerHTML" hx-push-url="true"
class="flex items-center gap-2 mt-2 sm:mt-0">
@@ -21,9 +16,8 @@
</div>
<button type="submit" class="nb-btn btn-sm">Filter</button>
</form>
</div>
{% include "content/content_list.html" %}
</div>
</main>
{% endblock %}
{% block content_list %}
{% include "content/content_list.html" %}
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends "dashboard/_layout.html" %}
{% block dashboard_header %}
<h1 class="text-xl font-extrabold tracking-tight">Dashboard</h1>
<button class="nb-btn nb-cta" hx-get="/ingress-form" hx-target="#modal" hx-swap="innerHTML">
{% include "icons/send_icon.html" %}
<span class="ml-2">Add Content</span>
</button>
{% endblock %}
{% block dashboard_widgets %}
{% include "dashboard/statistics.html" %}
{% include "dashboard/recent_content.html" %}
{% include "dashboard/active_jobs.html" %}
{% endblock %}

View File

@@ -0,0 +1,21 @@
{% extends "body_base.html" %}
{% block title %}Minne - Dashboard{% endblock %}
{% block main %}
<div class="flex justify-center grow mt-2 sm:mt-4 pb-4 w-full">
<div class="container">
<section class="mb-4">
{% block dashboard_alerts %}
{% endblock %}
<div class="nb-panel p-3 flex items-center justify-between">
{% block dashboard_header %}
{% endblock %}
</div>
</section>
{% block dashboard_widgets %}
{% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -1,26 +1 @@
{% extends "body_base.html" %}
{% block title %}Minne - Dashboard{% endblock %}
{% block main %}
<div class="flex justify-center grow mt-2 sm:mt-4 pb-4 w-full">
<div class="container">
<section class="mb-4">
<div class="nb-panel p-3 flex items-center justify-between">
<h1 class="text-xl font-extrabold tracking-tight">Dashboard</h1>
<button class="nb-btn nb-cta" hx-get="/ingress-form" hx-target="#modal" hx-swap="innerHTML">
{% include "icons/send_icon.html" %}
<span class="ml-2">Add Content</span>
</button>
</div>
</section>
{% include "dashboard/statistics.html" %}
{% include "dashboard/recent_content.html" %}
{% include "dashboard/active_jobs.html" %}
</div>
</div>
{% endblock %}
{% extends "dashboard/_base.html" %}

View File

@@ -0,0 +1,9 @@
{% extends 'body_base.html' %}
{% block main %}
<main class="container justify-center flex-grow flex mx-auto mt-4">
<div class="flex flex-col space-y-4 text-center justify-center">
{% block error_content %}
{% endblock %}
</div>
</main>
{% endblock %}

View File

@@ -1,13 +1,10 @@
{% extends 'body_base.html' %}
{% block main %}
<main class="container justify-center flex-grow flex mx-auto mt-4">
<div class="flex flex-col space-y-4 text-center justify-center">
{% extends "errors/_layout.html" %}
{% block error_content %}
<h1 class="text-2xl font-bold text-error">
{{ status_code }}
</h1>
<p class="text-2xl my-4">{{ title }}</p>
<p class="text-base-content/60">{{ description }}</p>
<a href="/" class="btn btn-primary mt-8">Go Home</a>
</div>
</main>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'body_base.html' %}
{% block title %}Minne - Knowledge{% endblock %}
{% block main %}
<div id="knowledge_pane" class="flex justify-center grow mt-2 sm:mt-4 gap-6">
<div class="container">
<div class="nb-panel p-3 mb-4 space-y-3 sm:space-y-0 sm:flex sm:flex-row sm:justify-between sm:items-center">
{% block knowledge_header %}
{% endblock %}
</div>
{% block knowledge_content %}
{% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -1,11 +1,6 @@
{% extends 'body_base.html' %}
{% extends 'knowledge/_layout.html' %}
{% block title %}Minne - Knowledge{% endblock %}
{% block main %}
<div id="knowledge_pane" class="flex justify-center grow mt-2 sm:mt-4 gap-6">
<div class="container">
<div class="nb-panel p-3 mb-4 space-y-3 sm:space-y-0 sm:flex sm:flex-row sm:justify-between sm:items-center">
{% block knowledge_header %}
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3">
<h2 class="text-xl font-extrabold tracking-tight">Knowledge Entities</h2>
<button type="button" class="nb-btn nb-cta btn-sm mr-2" hx-get="/knowledge-entity/new" hx-target="#modal"
@@ -35,8 +30,9 @@
</div>
<button type="submit" class="nb-btn btn-sm">Filter</button>
</form>
</div>
{% endblock %}
{% block knowledge_content %}
<h2 class="text-2xl font-bold mb-2 mt-10 ">Graph</h2>
<div class="nb-card mt-4 p-2">
<div id="knowledge-graph" class="w-full" style="height: 640px;"
@@ -47,6 +43,4 @@
{% include "knowledge/entity_list.html" %}
<h2 class="text-2xl font-bold mb-2 mt-2">Relationships</h2>
{% include "knowledge/relationship_table.html" %}
</div>
</div>
{% endblock %}

View File

@@ -1,15 +1,12 @@
<nav class="sticky top-0 z-10 nb-panel nb-panel-canvas border-t-0" style="view-transition-name: navbar; contain: layout;">
<div class="container mx-auto navbar">
<div class="mr-2 flex-1">
{% extends "components/_navbar_layout.html" %}
{% block navbar_search %}
{% include "searchbar.html" %}
</div>
<div class="flex-none">
<ul class="menu menu-horizontal px-2 gap-2 items-center">
{% endblock %}
{% block navbar_actions %}
<label for="my-drawer" aria-label="open sidebar" class="hover:cursor-pointer lg:hidden">
{% include "icons/hamburger_icon.html" %}
</label>
{% include "theme_toggle.html" %}
</ul>
</div>
</div>
</nav>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends 'body_base.html' %}
{% block title %}Minne - Scratchpad{% endblock %}
{% block main %}
<main id="main_section" class="flex justify-center grow mt-2 sm:mt-4 gap-6 mb-10 w-full">
<div class="container">
{% block scratchpad_header %}
{% endblock %}
{% block scratchpad_content %}
{% endblock %}
{% block scratchpad_archived %}
{% endblock %}
</div>
</main>
{% if new_scratchpad %}
<div hx-swap-oob="innerHTML:#modal">
<div hx-get="/scratchpad/{{ new_scratchpad.id }}/modal" hx-trigger="load" hx-target="#modal" hx-swap="innerHTML"></div>
</div>
{% endif %}
{% endblock %}

View File

@@ -1,11 +1,6 @@
{% extends 'body_base.html' %}
{% extends 'scratchpad/_layout.html' %}
{% block title %}Minne - Scratchpad{% endblock %}
{% block main %}
<main id="main_section" class="flex justify-center grow mt-2 sm:mt-4 gap-6 mb-10 w-full">
<div class="container">
{% block header %}
{% block scratchpad_header %}
<div class="nb-panel p-3 mb-4 flex items-center justify-between">
<h2 class="text-xl font-extrabold tracking-tight">Scratchpads</h2>
<form hx-post="/scratchpad" hx-target="#main_section" hx-swap="outerHTML" class="flex gap-2">
@@ -15,9 +10,9 @@
</button>
</form>
</div>
{% endblock %}
{% endblock %}
{% block content %}
{% block scratchpad_content %}
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{% for scratchpad in scratchpads %}
<div class="nb-card p-4 hover:nb-shadow-hover transition-all">
@@ -57,8 +52,9 @@
</div>
{% endfor %}
</div>
{% endblock %}
{% endblock %}
{% block scratchpad_archived %}
{% if archived_scratchpads %}
<div class="mt-6">
<details class="nb-panel p-3 space-y-4">
@@ -102,12 +98,4 @@
</details>
</div>
{% endif %}
</div>
</main>
{% if new_scratchpad %}
<div hx-swap-oob="innerHTML:#modal">
<div hx-get="/scratchpad/{{ new_scratchpad.id }}/modal" hx-trigger="load" hx-target="#modal" hx-swap="innerHTML"></div>
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "body_base.html" %}
{% block title %}Minne - Search{% endblock %}
{% block main %}
<div class="flex justify-center grow mt-2 sm:mt-4">
<div class="container">
<section class="mb-4">
<div class="nb-panel p-3 flex items-center justify-between">
{% block search_header %}
{% endblock %}
</div>
</section>
{% block search_results %}
{% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -1,17 +1,10 @@
{% extends "body_base.html" %}
{% extends "search/_layout.html" %}
{% block title %}Minne - Search{% endblock %}
{% block main %}
<div class="flex justify-center grow mt-2 sm:mt-4">
<div class="container">
<section class="mb-4">
<div class="nb-panel p-3 flex items-center justify-between">
{% block search_header %}
<h1 class="text-xl font-extrabold tracking-tight">Search</h1>
<div class="text-xs opacity-70">Find documents, entities, and snippets</div>
</div>
</section>
{% include "search/response.html" %}
</div>
</div>
{% endblock %}
{% block search_results %}
{% include "search/response.html" %}
{% endblock %}

View File

@@ -1,28 +1,7 @@
{% macro icon(name) %}
{% if name == "home" %}
{% include "icons/home_icon.html" %}
{% elif name == "book" %}
{% include "icons/book_icon.html" %}
{% elif name == "document" %}
{% include "icons/document_icon.html" %}
{% elif name == "chat" %}
{% include "icons/chat_icon.html" %}
{% elif name == "search" %}
{% include "icons/search_icon.html" %}
{% elif name == "scratchpad" %}
{% include "icons/scratchpad_icon.html" %}
{% endif %}
{% endmacro %}
{% extends "components/_sidebar_layout.html" %}
{% from "components/_icon_macro.html" import icon %}
<div class="drawer-side z-20" style="view-transition-name: sidebar; contain: layout;">
<label for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<ul class="menu p-0 w-72 h-full nb-canvas text-base-content flex flex-col border-r-2 border-neutral">
<!-- <a class="px-4 py-4 text-2xl font-extrabold tracking-tight text-primary border-b-2 border-neutral bg-base-100 nb-shadow" -->
<!-- href="/" hx-boost="true">Minne</a> -->
<!-- === TOP FIXED SECTION === -->
<div class="px-2 mt-4 space-y-3">
{% block sidebar_nav_items %}
{% for url, name, label in [
("/", "home", "Dashboard"),
("/knowledge", "book", "Knowledge"),
@@ -44,53 +23,9 @@
Content</button>
</li>
<div class="u-hairline mt-4"></div>
</div>
{% endblock %}
<!-- === MIDDLE SCROLLABLE SECTION === -->
<span class="px-4 py-2 nb-label">Recent Chats</span>
<div class="flex-1 overflow-y-auto space-y-1 custom-scrollbar">
{% if conversation_archive is defined and conversation_archive %}
{% for conversation in conversation_archive %}
<li id="conversation-{{ conversation.id }}">
{% if edit_conversation_id == conversation.id %}
<!-- Edit mode -->
<form hx-patch="/chat/{{ conversation.id }}/title" hx-target=".drawer-side" hx-swap="outerHTML"
class="flex items-center gap-1 px-2 py-2 max-w-72 relative">
<input type="text" name="title" value="{{ conversation.title }}" class="nb-input nb-input-sm max-w-52" />
<div class="flex gap-0.5 absolute right-2">
<button type="submit" class="btn btn-ghost btn-xs !p-0">{% include "icons/check_icon.html" %}</button>
<button type="button" hx-get="/chat/sidebar" hx-target=".drawer-side" hx-swap="outerHTML"
class="btn btn-ghost btn-xs !p-0">
{% include "icons/x_icon.html" %}
</button>
</div>
</form>
{% else %}
<!-- View mode -->
<div class="flex w-full pl-4 pr-2 py-2">
<a hx-boost="true" href="/chat/{{ conversation.id }}" class="flex-grow text-sm truncate">
<span>{{ conversation.title }}</span>
</a>
<div class="flex items-center gap-0.5 ml-2">
<button hx-get="/chat/{{ conversation.id }}/title" hx-target=".drawer-side" hx-swap="outerHTML"
class="btn btn-ghost btn-xs">
{% include "icons/edit_icon.html" %}
</button>
<button hx-delete="/chat/{{ conversation.id }}" hx-target=".drawer-side" hx-swap="outerHTML"
hx-confirm="Are you sure you want to delete this chat?" class="btn btn-ghost btn-xs">
{% include "icons/delete_icon.html" %}
</button>
</div>
</div>
{% endif %}
</li>
{% endfor %}
{% else %}
{% endif %}
</div>
<!-- === BOTTOM FIXED SECTION === -->
<div class="px-2 pb-4 space-y-3">
{% block sidebar_bottom_actions %}
<li>
<a hx-boost="true" href="/account"
class="nb-btn w-full justify-start items-center gap-3 bg-base-100 hover:bg-base-200">
@@ -114,6 +49,4 @@
<span class="uppercase tracking-wide">Logout</span>
</a>
</li>
</div>
</ul>
</div>
{% endblock %}

View File

@@ -116,6 +116,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
config.clone(),
reranker_pool.clone(),
embedding_provider.clone(),
None,
)
.await?;
@@ -284,6 +285,7 @@ mod tests {
config.clone(),
None,
embedding_provider,
None,
)
.await
.expect("failed to build html state");

View File

@@ -71,6 +71,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
config.clone(),
reranker_pool,
embedding_provider,
None,
)
.await?;