mirror of
https://github.com/perstarkse/minne.git
synced 2026-04-10 03:13:37 +02:00
feat: site wide sidebar
This commit is contained in:
144
html-router/assets/htmx-head-ext.js
Normal file
144
html-router/assets/htmx-head-ext.js
Normal file
@@ -0,0 +1,144 @@
|
||||
//==========================================================
|
||||
// head-support.js
|
||||
//
|
||||
// An extension to add head tag merging.
|
||||
//==========================================================
|
||||
(function(){
|
||||
|
||||
var api = null;
|
||||
|
||||
function log() {
|
||||
//console.log(arguments);
|
||||
}
|
||||
|
||||
function mergeHead(newContent, defaultMergeStrategy) {
|
||||
|
||||
if (newContent && newContent.indexOf('<head') > -1) {
|
||||
const htmlDoc = document.createElement("html");
|
||||
// remove svgs to avoid conflicts
|
||||
var contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
|
||||
// extract head tag
|
||||
var headTag = contentWithSvgsRemoved.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);
|
||||
|
||||
// if the head tag exists...
|
||||
if (headTag) {
|
||||
|
||||
var added = []
|
||||
var removed = []
|
||||
var preserved = []
|
||||
var nodesToAppend = []
|
||||
|
||||
htmlDoc.innerHTML = headTag;
|
||||
var newHeadTag = htmlDoc.querySelector("head");
|
||||
var currentHead = document.head;
|
||||
|
||||
if (newHeadTag == null) {
|
||||
return;
|
||||
} else {
|
||||
// put all new head elements into a Map, by their outerHTML
|
||||
var srcToNewHeadNodes = new Map();
|
||||
for (const newHeadChild of newHeadTag.children) {
|
||||
srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// determine merge strategy
|
||||
var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy;
|
||||
|
||||
// get the current head
|
||||
for (const currentHeadElt of currentHead.children) {
|
||||
|
||||
// If the current head element is in the map
|
||||
var inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
|
||||
var isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval";
|
||||
var isPreserved = api.getAttributeValue(currentHeadElt, "hx-preserve") === "true";
|
||||
if (inNewContent || isPreserved) {
|
||||
if (isReAppended) {
|
||||
// remove the current version and let the new version replace it and re-execute
|
||||
removed.push(currentHeadElt);
|
||||
} else {
|
||||
// this element already exists and should not be re-appended, so remove it from
|
||||
// the new content map, preserving it in the DOM
|
||||
srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
|
||||
preserved.push(currentHeadElt);
|
||||
}
|
||||
} else {
|
||||
if (mergeStrategy === "append") {
|
||||
// we are appending and this existing element is not new content
|
||||
// so if and only if it is marked for re-append do we do anything
|
||||
if (isReAppended) {
|
||||
removed.push(currentHeadElt);
|
||||
nodesToAppend.push(currentHeadElt);
|
||||
}
|
||||
} else {
|
||||
// if this is a merge, we remove this content since it is not in the new head
|
||||
if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: currentHeadElt}) !== false) {
|
||||
removed.push(currentHeadElt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Push the tremaining new head elements in the Map into the
|
||||
// nodes to append to the head tag
|
||||
nodesToAppend.push(...srcToNewHeadNodes.values());
|
||||
log("to append: ", nodesToAppend);
|
||||
|
||||
for (const newNode of nodesToAppend) {
|
||||
log("adding: ", newNode);
|
||||
var newElt = document.createRange().createContextualFragment(newNode.outerHTML);
|
||||
log(newElt);
|
||||
if (api.triggerEvent(document.body, "htmx:addingHeadElement", {headElement: newElt}) !== false) {
|
||||
currentHead.appendChild(newElt);
|
||||
added.push(newElt);
|
||||
}
|
||||
}
|
||||
|
||||
// remove all removed elements, after we have appended the new elements to avoid
|
||||
// additional network requests for things like style sheets
|
||||
for (const removedElement of removed) {
|
||||
if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: removedElement}) !== false) {
|
||||
currentHead.removeChild(removedElement);
|
||||
}
|
||||
}
|
||||
|
||||
api.triggerEvent(document.body, "htmx:afterHeadMerge", {added: added, kept: preserved, removed: removed});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension("head-support", {
|
||||
init: function(apiRef) {
|
||||
// store a reference to the internal API.
|
||||
api = apiRef;
|
||||
|
||||
htmx.on('htmx:afterSwap', function(evt){
|
||||
let xhr = evt.detail.xhr;
|
||||
if (xhr) {
|
||||
var serverResponse = xhr.response;
|
||||
if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
|
||||
mergeHead(serverResponse, evt.detail.boosted ? "merge" : "append");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
htmx.on('htmx:historyRestore', function(evt){
|
||||
if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
|
||||
if (evt.detail.cacheMiss) {
|
||||
mergeHead(evt.detail.serverResponse, "merge");
|
||||
} else {
|
||||
mergeHead(evt.detail.item.head, "merge");
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
htmx.on('htmx:historyItemCreated', function(evt){
|
||||
var historyItem = evt.detail.item;
|
||||
historyItem.head = document.head.outerHTML;
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
})()
|
||||
6
html-router/assets/marked.min.js
vendored
Normal file
6
html-router/assets/marked.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -252,7 +252,7 @@ pub async fn show_conversation_editing_title(
|
||||
}
|
||||
|
||||
Ok(TemplateResponse::new_template(
|
||||
"chat/drawer.html",
|
||||
"sidebar.html",
|
||||
DrawerContext {
|
||||
user,
|
||||
conversation_archive,
|
||||
@@ -273,7 +273,7 @@ pub async fn patch_conversation_title(
|
||||
let updated_conversations = User::get_user_conversations(&user.id, &state.db).await?;
|
||||
|
||||
Ok(TemplateResponse::new_template(
|
||||
"chat/drawer.html",
|
||||
"sidebar.html",
|
||||
DrawerContext {
|
||||
user,
|
||||
conversation_archive: updated_conversations,
|
||||
@@ -306,7 +306,7 @@ pub async fn delete_conversation(
|
||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
||||
|
||||
Ok(TemplateResponse::new_template(
|
||||
"chat/drawer.html",
|
||||
"sidebar.html",
|
||||
DrawerContext {
|
||||
user,
|
||||
conversation_archive,
|
||||
@@ -322,7 +322,7 @@ pub async fn reload_sidebar(
|
||||
let conversation_archive = User::get_user_conversations(&user.id, &state.db).await?;
|
||||
|
||||
Ok(TemplateResponse::new_template(
|
||||
"chat/drawer.html",
|
||||
"sidebar.html",
|
||||
DrawerContext {
|
||||
user,
|
||||
conversation_archive,
|
||||
|
||||
@@ -1,16 +1,45 @@
|
||||
{% extends "head_base.html" %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<body>
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<!-- Navbar -->
|
||||
{% include "navigation_bar.html" %}
|
||||
<body class="bg-base-100" hx-ext="head-support">
|
||||
<div class="drawer lg:drawer-open">
|
||||
<input id="my-drawer" type="checkbox" class="drawer-toggle" />
|
||||
<!-- Page Content -->
|
||||
<div class="drawer-content flex flex-col h-screen">
|
||||
<!-- Navbar -->
|
||||
{% include "navigation_bar.html" %}
|
||||
<!-- Main Content Area -->
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
{% block main %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
<!-- Sidebar -->
|
||||
{% if user %}
|
||||
{% include "sidebar.html" %}
|
||||
{% endif %}
|
||||
</div> <!-- End Drawer -->
|
||||
<div id="modal"></div>
|
||||
<div id="toast-container"></div>
|
||||
<!-- Optional: Add CSS for custom scrollbar -->
|
||||
<style>
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
<!-- Main Content -->
|
||||
{% block main %}{% endblock %}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
<div id="modal"></div>
|
||||
<div id="toast-container" class="toast toast-bottom toast-end"></div>
|
||||
</div>
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.custom-scrollbar {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
{% endblock %}
|
||||
@@ -1,42 +1,18 @@
|
||||
{% extends 'body_base.html' %}
|
||||
{% block title %} Minne Chat {% endblock %}
|
||||
{% block title %}Minne Chat{% endblock %}
|
||||
{% block head %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="/assets/marked.min.js"></script>
|
||||
<script src="/assets/htmx-ext-sse.js" defer></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="drawer xl:drawer-open h-[calc(100vh-65px)] overflow-auto">
|
||||
<input id="my-drawer-2" type="checkbox" class="drawer-toggle" />
|
||||
<!-- Drawer Content -->
|
||||
<div class="drawer-content flex justify-center ">
|
||||
<main class="flex justify-center grow mt-2 sm:mt-4 gap-6 mb-10 max-w-3xl w-full absolute left-0 right-0 mx-auto">
|
||||
<div class="relative w-full">
|
||||
{% include "chat/history.html" %}
|
||||
{% include "chat/new_message_form.html" %}
|
||||
</div>
|
||||
</main>
|
||||
<div class="flex justify-center mt-2 sm:mt-4 mb-10">
|
||||
<div class="max-w-3xl w-full overflow-auto hide-scrollbar">
|
||||
{% include "chat/history.html" %}
|
||||
{% include "chat/new_message_form.html" %}
|
||||
</div>
|
||||
<!-- Drawer Sidebar -->
|
||||
{% include "chat/drawer.html" %}
|
||||
</div>
|
||||
<style>
|
||||
/* Custom styles to override DaisyUI defaults */
|
||||
.drawer-content {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.drawer-side {
|
||||
z-index: 20;
|
||||
/* Ensure drawer is above content */
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.drawer-open .drawer-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
@@ -111,10 +87,12 @@
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
marked.setOptions({
|
||||
breaks: true, gfm: true, headerIds: false, mangle: false,
|
||||
smartLists: true, smartypants: true, xhtml: false
|
||||
});
|
||||
function initialize() {
|
||||
marked.setOptions({
|
||||
breaks: true, gfm: true, headerIds: false, mangle: false,
|
||||
smartLists: true, smartypants: true, xhtml: false
|
||||
});
|
||||
}
|
||||
|
||||
// Render static markdown (any .markdown-content[data-content])
|
||||
function renderStaticMarkdown() {
|
||||
@@ -125,11 +103,14 @@
|
||||
}
|
||||
|
||||
function scrollChatToBottom() {
|
||||
console.log("scrolling chat");
|
||||
const chatContainer = document.getElementById('chat_container');
|
||||
console.log(chatContainer);
|
||||
if (chatContainer) chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
|
||||
function processChatUi() {
|
||||
initialize();
|
||||
renderStaticMarkdown();
|
||||
scrollChatToBottom();
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<div class="drawer-side z-50 max-h-[calc(100vh-65px)]">
|
||||
<label for="my-drawer-2" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<ul class="menu bg-base-200 text-base-content w-72 ">
|
||||
<!-- Sidebar content here -->
|
||||
<li class=" mt-4 cursor-pointer "><a href=" /chat" hx-boost="true" class="flex justify-between">Create new
|
||||
chat<span>{% include
|
||||
"icons/edit_icon.html" %}
|
||||
</span></a></li>
|
||||
<div class="divider"></div>
|
||||
{% for conversation in conversation_archive %}
|
||||
{% if edit_conversation_id == conversation.id %}
|
||||
<!-- Render the editable title form variant -->
|
||||
<li class="align-center" id="conversation-{{ conversation.id }}">
|
||||
<form hx-patch="/chat/{{ conversation.id }}/title" hx-target=".drawer-side" hx-swap="outerHTML"
|
||||
class="flex items-center gap-2">
|
||||
<input type="text" name="title" value="{{ conversation.title }}" class="input input-sm" />
|
||||
<button type="submit">{% include "icons/check_icon.html" %}
|
||||
</button>
|
||||
<button type="button" hx-get="/chat/sidebar" hx-target=".drawer-side" hx-swap="outerHTML">{% include
|
||||
"icons/x_icon.html" %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<!-- Render the normal view mode conversation item -->
|
||||
<li class="align-center" id="conversation-{{ conversation.id }}">
|
||||
<div class="justify-between">
|
||||
<a href="/chat/{{ conversation.id }}" hx-boost="true">
|
||||
{{ conversation.title }} - {{ conversation.created_at | datetimeformat(format="short", tz=user.timezone) }}
|
||||
</a>
|
||||
<div class="flex gap-0.5">
|
||||
<button hx-get="/chat/{{ conversation.id }}/title" hx-target=".drawer-side" hx-swap="outerHTML">
|
||||
{% include "icons/edit_icon.html" %}
|
||||
</button>
|
||||
<button hx-delete="/chat/{{ conversation.id }}" hx-target=".drawer-side" hx-swap="outerHTML">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="chat_container" class="pl-3 overflow-y-auto h-[calc(100vh-155px)] hide-scrollbar">
|
||||
<div id="chat_container" class="pl-3 overflow-y-auto h-[calc(100vh-170px)] sm:h-[calc(100vh-190px)] hide-scrollbar">
|
||||
{% for message in history %}
|
||||
{% if message.role == "AI" %}
|
||||
<div class="chat chat-start">
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<div class="fixed w-full mx-auto max-w-3xl p-0 pb-0 sm:pb-4 left-0 right-0 bottom-0 bg-base-100 z-10">
|
||||
<div class="fixed w-full mx-auto max-w-3xl p-0 pb-0 sm:pb-4 left-0 right-0 bottom-0 z-10">
|
||||
<form hx-post="{% if conversation %} /chat/{{conversation.id}} {% else %} /chat {% endif %}"
|
||||
hx-target="#chat_container" hx-swap="beforeend" class="relative flex gap-2" id="chat-form">
|
||||
<textarea autofocus required name="content" placeholder="Type your message..." rows="2"
|
||||
class="textarea textarea-ghost rounded-2xl rounded-b-none h-24 sm:rounded-b-2xl pr-8 bg-base-200 flex-grow resize-none"
|
||||
class="textarea textarea-ghost rounded-2xl rounded-b-none h-24 sm:rounded-b-2xl pr-8 bg-base-200 flex-grow resize-none focus:outline-none focus:bg-base-200"
|
||||
id="chat-input"></textarea>
|
||||
<button type="submit" class="absolute p-2 cursor-pointer right-0.5 btn-ghost btn-sm top-1">{% include
|
||||
<button type="submit" class="absolute p-2 cursor-pointer right-0.5 btn-ghost btn-sm top-6">{% include
|
||||
"icons/send_icon.html" %}
|
||||
</button>
|
||||
<label for="my-drawer-2" class="absolute cursor-pointer top-9 right-0.5 p-2 drawer-button xl:hidden z-20 ">
|
||||
{% include "icons/hamburger_icon.html" %}
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
{% block main %}
|
||||
<main id="main_section" class="flex justify-center grow mt-2 sm:mt-4 gap-6 mb-10">
|
||||
<div class="container">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4">
|
||||
<h2 class="text-2xl font-bold">Text Contents</h2>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-2xl font-bold">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">
|
||||
<div class="form-control">
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<script src="/assets/htmx.min.js" defer></script>
|
||||
<script src="/assets/theme-toggle.js" defer></script>
|
||||
<script src="/assets/toast.js" defer></script>
|
||||
<script src="/assets/htmx-head-ext.js" defer></script>
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="icon" href="/assets/icon/favicon.ico">
|
||||
|
||||
5
html-router/templates/icons/book_icon.html
Normal file
5
html-router/templates/icons/book_icon.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 446 B |
5
html-router/templates/icons/home_icon.html
Normal file
5
html-router/templates/icons/home_icon.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 438 B |
5
html-router/templates/icons/logout_icon.html
Normal file
5
html-router/templates/icons/logout_icon.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15m-3 0-3-3m0 0 3-3m-3 3H15" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 353 B |
5
html-router/templates/icons/user_icon.html
Normal file
5
html-router/templates/icons/user_icon.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 355 B |
@@ -1,17 +1,22 @@
|
||||
<nav class="navbar bg-base-200 !p-0">
|
||||
<div class="container flex mx-auto">
|
||||
<div class="flex-1 flex items-center">
|
||||
<nav class="bg-base-200 sticky top-0 z-10">
|
||||
<div class="container mx-auto navbar">
|
||||
<div class="flex-none lg:hidden">
|
||||
<label for="my-drawer" aria-label="open sidebar" class="hover:cursor-pointer ">
|
||||
{% include "icons/hamburger_icon.html" %}
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<a class="text-2xl p-2 text-primary font-bold" href="/" hx-boost="true">Minne</a>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
<div class="flex-none">
|
||||
<ul class="menu menu-horizontal px-1 items-center">
|
||||
{% include "theme_toggle.html" %}
|
||||
<li><a hx-boost="true" class="" href="/documentation">Docs</a></li>
|
||||
<li><a hx-boost="true" href="/documentation">Docs</a></li>
|
||||
{% if user %}
|
||||
<li>
|
||||
<details>
|
||||
<summary>Account</summary>
|
||||
<ul class="bg-base-100 rounded-t-none p-2 z-50">
|
||||
<ul class="bg-base-100 rounded-t-none p-2 z-50 shadow w-40">
|
||||
<li><a hx-boost="true" href="/account">Account</a></li>
|
||||
{% if user.admin %}
|
||||
<li><a hx-boost="true" href="/admin">Admin</a></li>
|
||||
@@ -21,7 +26,7 @@
|
||||
</details>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a hx-boost="true" class="" href="/signin">Sign in</a></li>
|
||||
<li><a hx-boost="true" href="/signin">Sign in</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
100
html-router/templates/sidebar.html
Normal file
100
html-router/templates/sidebar.html
Normal file
@@ -0,0 +1,100 @@
|
||||
{% 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" %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
<div class="drawer-side z-20">
|
||||
<label for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
|
||||
<ul class="menu p-0 w-64 h-full bg-base-200 text-base-content flex flex-col">
|
||||
|
||||
<!-- === TOP FIXED SECTION === -->
|
||||
<div class="divider mt-14"></div>
|
||||
<div class="px-2">
|
||||
{% for url, name, label in [
|
||||
("/", "home", "Dashboard"),
|
||||
("/knowledge", "book", "Knowledge"),
|
||||
("/content", "document", "Content"),
|
||||
("/chat", "chat", "Chat")
|
||||
] %}
|
||||
<li>
|
||||
<a hx-boost="true" href="{{ url }}" class="flex items-center gap-3">
|
||||
{{ icon(name) }}
|
||||
<span>{{ label }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<div class="divider "></div>
|
||||
</div>
|
||||
|
||||
<!-- === MIDDLE SCROLLABLE SECTION === -->
|
||||
<span class="menu-title pb-4 ">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">
|
||||
<input type="text" name="title" value="{{ conversation.title }}" class="input input-sm flex-grow" />
|
||||
<div class="flex gap-0.5">
|
||||
<button type="submit" class="btn btn-ghost btn-xs">{% 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">
|
||||
{% include "icons/x_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<!-- View mode -->
|
||||
<div class="flex w-full px-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 %}
|
||||
<li><span class="text-xs italic text-base-content/60 px-4">No recent chats</span></li>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- === BOTTOM FIXED SECTION === -->
|
||||
<div class="px-2 pb-4">
|
||||
<div class="divider "></div>
|
||||
<li>
|
||||
<a hx-boost="true" href="/account" class="flex items-center gap-3">
|
||||
{% include "icons/user_icon.html" %}
|
||||
<span>Account</span>
|
||||
</a>
|
||||
</li>
|
||||
<form action="/signout" method="get" class="w-full block">
|
||||
<li>
|
||||
<button type="submit" class="btn btn-error btn-outline w-full flex items-center gap-3 justify-start !mt-1">
|
||||
{% include "icons/logout_icon.html" %}
|
||||
<span>Logout</span>
|
||||
</button>
|
||||
</li>
|
||||
</form>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
Reference in New Issue
Block a user