mirror of
https://github.com/perstarkse/minne.git
synced 2026-04-21 00:11:26 +02:00
feat: scratchpad
additional improvements changelog fix: wording
This commit is contained in:
5
html-router/templates/icons/pencil_icon.html
Normal file
5
html-router/templates/icons/pencil_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"
|
||||
width="20" height="20" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 376 B |
5
html-router/templates/icons/scratchpad_icon.html
Normal file
5
html-router/templates/icons/scratchpad_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"
|
||||
width="20" height="20" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 376 B |
113
html-router/templates/scratchpad/base.html
Normal file
113
html-router/templates/scratchpad/base.html
Normal file
@@ -0,0 +1,113 @@
|
||||
{% 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 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">
|
||||
<input type="text" name="title" placeholder="Enter scratchpad title..." class="nb-input nb-input-sm" required>
|
||||
<button type="submit" class="nb-btn nb-cta">
|
||||
{% include "icons/scratchpad_icon.html" %} Create
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block 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">
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<h3 class="font-semibold text-lg truncate flex-1">{{ scratchpad.title }}</h3>
|
||||
<div class="flex gap-1 ml-2">
|
||||
<button hx-get="/scratchpad/{{ scratchpad.id }}/modal" hx-target="#modal" hx-swap="innerHTML"
|
||||
class="nb-btn nb-btn-sm btn-ghost" title="Edit scratchpad">
|
||||
{% include "icons/pencil_icon.html" %}
|
||||
</button>
|
||||
<form hx-post="/scratchpad/{{ scratchpad.id }}/archive" hx-target="#main_section" hx-swap="outerHTML"
|
||||
class="inline-flex">
|
||||
<button type="submit" class="nb-btn nb-btn-sm btn-ghost text-warning" title="Archive scratchpad">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-base-content/70 mb-2">
|
||||
{{ scratchpad.content[:100] }}{% if scratchpad.content|length > 100 %}...{% endif %}
|
||||
</div>
|
||||
<div class="text-xs text-base-content/50">
|
||||
Last saved: {{ scratchpad.last_saved_at }}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-span-full nb-panel p-8 text-center">
|
||||
<h3 class="text-lg font-semibold mt-2 mb-2">No scratchpads yet</h3>
|
||||
<p class="text-base-content/70 mb-4">Create your first scratchpad to start jotting down ideas</p>
|
||||
<form hx-post="/scratchpad" hx-target="#main_section" hx-swap="outerHTML"
|
||||
class="inline-flex gap-2">
|
||||
<input type="text" name="title" placeholder="My first scratchpad..." class="nb-input" required>
|
||||
<button type="submit" class="nb-btn nb-cta">
|
||||
{% include "icons/scratchpad_icon.html" %} Create Scratchpad
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% if archived_scratchpads %}
|
||||
<div class="mt-6">
|
||||
<details class="nb-panel p-3 space-y-4">
|
||||
<summary class="flex items-center justify-between gap-2 text-sm font-semibold cursor-pointer">
|
||||
<span>Archived Scratchpads</span>
|
||||
<span class="nb-badge">{{ archived_scratchpads|length }}</span>
|
||||
</summary>
|
||||
|
||||
<div class="text-sm text-base-content/60">Archived scratchpads were ingested into your knowledge base. You can
|
||||
restore them if you want to keep editing.</div>
|
||||
|
||||
<div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for scratchpad in archived_scratchpads %}
|
||||
<div class="nb-card p-3 space-y-3">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h4 class="font-semibold text-base truncate" title="{{ scratchpad.title }}">{{ scratchpad.title }}</h4>
|
||||
<div class="text-xs text-base-content/50">Archived {{ scratchpad.archived_at }}</div>
|
||||
{% if scratchpad.ingested_at %}
|
||||
<div class="text-xs text-base-content/40">Ingestion started {{ scratchpad.ingested_at }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-shrink-0 flex-wrap justify-end">
|
||||
<form hx-post="/scratchpad/{{ scratchpad.id }}/restore" hx-target="#main_section" hx-swap="outerHTML"
|
||||
class="inline-flex">
|
||||
<button type="submit" class="nb-btn nb-btn-sm">
|
||||
Restore
|
||||
</button>
|
||||
</form>
|
||||
<form hx-delete="/scratchpad/{{ scratchpad.id }}" hx-target="#main_section" hx-swap="outerHTML"
|
||||
hx-confirm="Permanently delete this scratchpad?" class="inline-flex">
|
||||
<button type="submit" class="nb-btn nb-btn-sm btn-ghost text-error" title="Delete permanently">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</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 %}
|
||||
286
html-router/templates/scratchpad/editor_modal.html
Normal file
286
html-router/templates/scratchpad/editor_modal.html
Normal file
@@ -0,0 +1,286 @@
|
||||
{% extends "modal_base.html" %}
|
||||
|
||||
{% block modal_class %}w-11/12 max-w-[90ch] max-h-[95%] overflow-y-auto{% endblock %}
|
||||
|
||||
{% block form_attributes %}{% endblock %}
|
||||
|
||||
{% block modal_content %}
|
||||
<h3 class="text-xl font-extrabold tracking-tight">
|
||||
<div class="flex items-center gap-2" id="title-container">
|
||||
<span class="font-semibold text-lg flex-1 truncate" id="title-display">{{ scratchpad.title }}</span>
|
||||
<button type="button" onclick="editTitle()" class="nb-btn nb-btn-sm btn-ghost">
|
||||
{% include "icons/edit_icon.html" %} Edit title
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Hidden title form -->
|
||||
<form id="title-form" hx-patch="/scratchpad/{{ scratchpad.id }}/title" hx-target="#body_modal" hx-swap="outerHTML"
|
||||
class="hidden flex items-center gap-2">
|
||||
<input type="text" name="title" value="{{ scratchpad.title }}"
|
||||
class="nb-input nb-input-sm font-semibold text-lg flex-1" id="title-input">
|
||||
<button type="submit" class="nb-btn nb-btn-sm">{% include "icons/check_icon.html" %}</button>
|
||||
<button type="button" onclick="cancelEditTitle()" class="nb-btn nb-btn-sm btn-ghost">{% include "icons/x_icon.html" %}</button>
|
||||
</form>
|
||||
</h3>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="text-xs text-base-content/50 flex items-center gap-2">
|
||||
<span>Last saved: <span id="last-saved">{{ scratchpad.last_saved_at }}</span></span>
|
||||
<span id="save-status"
|
||||
class="inline-flex items-center gap-1 text-success opacity-0 transition-opacity duration-300 pointer-events-none">
|
||||
{% include "icons/check_icon.html" %} <span class="uppercase tracking-wider text-[0.7em]">Saved</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form id="auto-save-form"
|
||||
hx-patch="/scratchpad/{{ scratchpad.id }}/auto-save"
|
||||
hx-trigger="keyup changed delay:2s, focusout"
|
||||
hx-indicator="#save-indicator"
|
||||
hx-swap="none"
|
||||
class="flex flex-col gap-2">
|
||||
<label class="w-full">
|
||||
<textarea name="content" id="scratchpad-content"
|
||||
class="nb-input w-full min-h-[60vh] resize-none font-mono text-sm"
|
||||
placeholder="Start typing your thoughts... (Tab to indent, Shift+Tab to outdent)"
|
||||
autofocus>{{ scratchpad.content }}</textarea>
|
||||
</label>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div id="save-indicator" class="htmx-indicator text-sm text-base-content/50 hidden">
|
||||
{% include "icons/refresh_icon.html" %} Saving...
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-base-content/50">
|
||||
<span id="char-count">{{ scratchpad.content|length }}</span> characters
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="action-row" class="flex gap-2 justify-between items-center">
|
||||
<form hx-post="/scratchpad/{{ scratchpad.id }}/ingest"
|
||||
hx-target="#main_section"
|
||||
hx-swap="outerHTML"
|
||||
hx-on::after-request="if(event.detail.successful) document.getElementById('body_modal').close()"
|
||||
class="inline flex flex-col gap-3"
|
||||
id="ingest-form">
|
||||
<button type="button" class="nb-btn nb-cta" onclick="toggleIngestConfirmation(true)"
|
||||
data-role="ingest-trigger">
|
||||
{% include "icons/send_icon.html" %} Ingest as Content
|
||||
</button>
|
||||
<div id="ingest-warning"
|
||||
class="nb-card bg-warning/10 border border-warning text-warning-content text-sm leading-relaxed flex flex-col gap-2 p-3 hidden">
|
||||
<div>
|
||||
<strong class="font-semibold text-warning">Before you ingest</strong>
|
||||
<p>
|
||||
This will archive the scratchpad right away. After ingestion finishes you can review the content from the
|
||||
<a href="/content" class="nb-link">Content</a> page, and archived scratchpads remain available below with a restore option.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="submit" class="nb-btn nb-btn-sm nb-cta">
|
||||
Confirm ingest
|
||||
</button>
|
||||
<button type="button" class="nb-btn nb-btn-sm btn-ghost" onclick="toggleIngestConfirmation(false)">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form id="archive-form" hx-post="/scratchpad/{{ scratchpad.id }}/archive" hx-target="#main_section"
|
||||
hx-swap="outerHTML" hx-on::after-request="if(event.detail.successful) document.getElementById('body_modal').close()"
|
||||
class="inline">
|
||||
<button type="submit" class="nb-btn nb-btn-ghost text-warning">
|
||||
{% include "icons/delete_icon.html" %} Archive
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Title editing functions
|
||||
function editTitle() {
|
||||
const titleContainer = document.getElementById('title-container');
|
||||
const titleForm = document.getElementById('title-form');
|
||||
const titleInput = document.getElementById('title-input');
|
||||
if (!titleContainer || !titleForm) return;
|
||||
|
||||
titleContainer.classList.add('hidden');
|
||||
titleForm.classList.remove('hidden');
|
||||
|
||||
if (titleInput) {
|
||||
titleInput.focus();
|
||||
titleInput.select();
|
||||
}
|
||||
}
|
||||
|
||||
function cancelEditTitle() {
|
||||
const titleContainer = document.getElementById('title-container');
|
||||
const titleForm = document.getElementById('title-form');
|
||||
if (!titleContainer || !titleForm) return;
|
||||
|
||||
titleContainer.classList.remove('hidden');
|
||||
titleForm.classList.add('hidden');
|
||||
}
|
||||
|
||||
(function initScratchpadModal() {
|
||||
const modal = document.getElementById('body_modal');
|
||||
if (!modal) return;
|
||||
|
||||
const textarea = modal.querySelector('#scratchpad-content');
|
||||
const charCount = modal.querySelector('#char-count');
|
||||
const lastSaved = modal.querySelector('#last-saved');
|
||||
const saveStatus = modal.querySelector('#save-status');
|
||||
const autoSaveForm = modal.querySelector('#auto-save-form');
|
||||
const ingestWarning = modal.querySelector('#ingest-warning');
|
||||
const ingestForm = modal.querySelector('#ingest-form');
|
||||
const actionRow = modal.querySelector('#action-row');
|
||||
let saveStatusTimeout;
|
||||
|
||||
const updateCharCount = () => {
|
||||
if (!textarea || !charCount) return;
|
||||
charCount.textContent = textarea.value.length;
|
||||
};
|
||||
|
||||
const autoResize = () => {
|
||||
if (!textarea) return;
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
};
|
||||
|
||||
if (textarea) {
|
||||
textarea.addEventListener('input', () => {
|
||||
updateCharCount();
|
||||
autoResize();
|
||||
});
|
||||
|
||||
// Tab support - insert 4 spaces or handle outdenting
|
||||
textarea.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const value = textarea.value;
|
||||
|
||||
if (e.shiftKey) {
|
||||
// Shift+Tab: Outdent - remove up to 4 spaces from start of current line
|
||||
const lineStart = value.lastIndexOf('\n', start - 1) + 1;
|
||||
const currentLine = value.substring(lineStart, start);
|
||||
const leadingSpaces = currentLine.match(/^ */)?.[0]?.length || 0;
|
||||
const spacesToRemove = Math.min(4, leadingSpaces);
|
||||
|
||||
if (spacesToRemove > 0) {
|
||||
textarea.value = value.substring(0, lineStart) +
|
||||
currentLine.substring(spacesToRemove) +
|
||||
value.substring(start);
|
||||
|
||||
// Adjust cursor position
|
||||
textarea.selectionStart = textarea.selectionEnd = start - spacesToRemove;
|
||||
}
|
||||
} else {
|
||||
// Tab: Indent - insert 4 spaces at cursor position
|
||||
textarea.value = value.substring(0, start) + ' ' + value.substring(end);
|
||||
|
||||
// Restore cursor position after inserted spaces
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 4;
|
||||
}
|
||||
|
||||
// Trigger input event to update character count and auto-resize
|
||||
textarea.dispatchEvent(new Event('input'));
|
||||
}
|
||||
});
|
||||
|
||||
updateCharCount();
|
||||
autoResize();
|
||||
}
|
||||
|
||||
if (autoSaveForm) {
|
||||
autoSaveForm.addEventListener('htmx:beforeRequest', (evt) => {
|
||||
if (evt.detail.elt !== autoSaveForm) return;
|
||||
if (saveStatus) {
|
||||
saveStatus.classList.add('opacity-0');
|
||||
saveStatus.classList.remove('opacity-100');
|
||||
}
|
||||
});
|
||||
|
||||
autoSaveForm.addEventListener('htmx:afterRequest', (evt) => {
|
||||
if (evt.detail.elt !== autoSaveForm) return;
|
||||
if (!evt.detail.successful) return;
|
||||
|
||||
const xhr = evt.detail.xhr;
|
||||
if (xhr && xhr.responseText) {
|
||||
try {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
if (data.last_saved_at_display && lastSaved) {
|
||||
lastSaved.textContent = data.last_saved_at_display;
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore JSON parse errors
|
||||
}
|
||||
}
|
||||
|
||||
if (saveStatus) {
|
||||
if (saveStatusTimeout) {
|
||||
clearTimeout(saveStatusTimeout);
|
||||
}
|
||||
saveStatus.classList.remove('opacity-0');
|
||||
saveStatus.classList.add('opacity-100');
|
||||
saveStatusTimeout = setTimeout(() => {
|
||||
saveStatus.classList.add('opacity-0');
|
||||
saveStatus.classList.remove('opacity-100');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (ingestForm) {
|
||||
ingestForm.addEventListener('htmx:afterRequest', (evt) => {
|
||||
if (evt.detail.elt !== ingestForm) return;
|
||||
toggleIngestConfirmation(false);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
function toggleIngestConfirmation(show) {
|
||||
const modal = document.getElementById('body_modal');
|
||||
if (!modal) return;
|
||||
|
||||
const warning = modal.querySelector('#ingest-warning');
|
||||
const actionRow = modal.querySelector('#action-row');
|
||||
const ingestForm = modal.querySelector('#ingest-form');
|
||||
const archiveForm = modal.querySelector('#archive-form');
|
||||
const ingestButton = modal.querySelector('[data-role="ingest-trigger"]');
|
||||
const confirmButton = warning ? warning.querySelector('button[type="submit"]') : null;
|
||||
if (!warning || !ingestButton || !actionRow || !ingestForm) return;
|
||||
|
||||
if (show) {
|
||||
warning.classList.remove('hidden');
|
||||
ingestButton.classList.add('hidden');
|
||||
actionRow.classList.add('flex-col', 'items-stretch');
|
||||
actionRow.classList.remove('items-center', 'justify-between');
|
||||
ingestForm.classList.add('w-full');
|
||||
if (archiveForm) {
|
||||
archiveForm.classList.add('w-full');
|
||||
}
|
||||
if (confirmButton) {
|
||||
confirmButton.focus();
|
||||
}
|
||||
} else {
|
||||
warning.classList.add('hidden');
|
||||
ingestButton.classList.remove('hidden');
|
||||
actionRow.classList.remove('flex-col', 'items-stretch');
|
||||
actionRow.classList.add('items-center', 'justify-between');
|
||||
ingestForm.classList.remove('w-full');
|
||||
if (archiveForm) {
|
||||
archiveForm.classList.remove('w-full');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block primary_actions %}
|
||||
<!-- No additional actions needed -->
|
||||
{% endblock %}
|
||||
@@ -3,131 +3,134 @@
|
||||
{% for result in search_result %}
|
||||
<li class="p-4 u-hairline hover:bg-base-200/40 flex gap-3">
|
||||
{% if result.result_type == "text_content" %}
|
||||
{% set tc = result.text_content %}
|
||||
<div class="w-10 h-10 flex-shrink-0 self-start mt-1 grid place-items-center border-2 border-neutral bg-base-100 shadow-[4px_4px_0_0_#000]">
|
||||
{% if tc.url_info and tc.url_info.url %}
|
||||
<div class="tooltip tooltip-right" data-tip="Web Link">
|
||||
{% include "icons/link_icon.html" %}
|
||||
</div>
|
||||
{% elif tc.file_info and tc.file_info.file_name %}
|
||||
<div class="tooltip tooltip-right" data-tip="File Document">
|
||||
{% include "icons/document_icon.html" %}
|
||||
</div>
|
||||
{% set tc = result.text_content %}
|
||||
<div
|
||||
class="w-10 h-10 flex-shrink-0 self-start mt-1 grid place-items-center border-2 border-neutral bg-base-100 shadow-[4px_4px_0_0_#000]">
|
||||
{% if tc.url_info and tc.url_info.url %}
|
||||
<div class="tooltip tooltip-right" data-tip="Web Link">
|
||||
{% include "icons/link_icon.html" %}
|
||||
</div>
|
||||
{% elif tc.file_info and tc.file_info.file_name %}
|
||||
<div class="tooltip tooltip-right" data-tip="File Document">
|
||||
{% include "icons/document_icon.html" %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="tooltip tooltip-right" data-tip="Text Content">
|
||||
{% include "icons/bars_icon.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-extrabold mb-1 leading-snug">
|
||||
<a hx-get="/content/{{ tc.id }}/read" hx-target="#modal" hx-swap="innerHTML" class="nb-link">
|
||||
{% set title_text = tc.highlighted_url_title
|
||||
| default(tc.url_info.title if tc.url_info else none, true)
|
||||
| default(tc.highlighted_file_name, true)
|
||||
| default(tc.file_info.file_name if tc.file_info else none, true)
|
||||
| default("Text snippet: " ~ (tc.id | string)[-8:], true) %}
|
||||
{{ title_text }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<div class="markdown-content prose-tufte-compact text-base-content/80 mb-3 overflow-hidden line-clamp-6"
|
||||
data-content="{{tc.highlighted_text | escape}}">
|
||||
{% if tc.highlighted_text %}
|
||||
{{ tc.highlighted_text | escape }}
|
||||
{% elif tc.text %}
|
||||
{{ tc.text | escape }}
|
||||
{% else %}
|
||||
<div class="tooltip tooltip-right" data-tip="Text Content">
|
||||
{% include "icons/bars_icon.html" %}
|
||||
</div>
|
||||
<span class="italic opacity-60">No text preview available.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-extrabold mb-1 leading-snug">
|
||||
<a hx-get="/content/{{ tc.id }}/read" hx-target="#modal" hx-swap="innerHTML" class="nb-link">
|
||||
{% set title_text = tc.highlighted_url_title
|
||||
| default(tc.url_info.title if tc.url_info else none, true)
|
||||
| default(tc.highlighted_file_name, true)
|
||||
| default(tc.file_info.file_name if tc.file_info else none, true)
|
||||
| default("Text snippet: " ~ (tc.id | string)[-8:], true) %}
|
||||
{{ title_text | safe }}
|
||||
<div class="text-xs flex flex-wrap gap-x-4 gap-y-2 items-center">
|
||||
<span class="inline-flex items-center">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Category</span>
|
||||
<span class="nb-badge">{{ tc.highlighted_category | default(tc.category, true) | safe }}</span>
|
||||
</span>
|
||||
|
||||
{% if tc.highlighted_context or tc.context %}
|
||||
<span class="inline-flex items-center min-w-0">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Context</span>
|
||||
<span class="nb-badge">{{ tc.highlighted_context | default(tc.context, true) | safe }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if tc.url_info and tc.url_info.url %}
|
||||
<span class="inline-flex items-center min-w-0">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Source</span>
|
||||
<a href="{{ tc.url_info.url }}" target="_blank" class="nb-link truncate" title="{{ tc.url_info.url }}">
|
||||
{{ tc.highlighted_url | default(tc.url_info.url ) | safe }}
|
||||
</a>
|
||||
</h3>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="markdown-content prose-tufte-compact text-base-content/80 mb-3 overflow-hidden line-clamp-6" data-content="{{tc.highlighted_text | escape}}">
|
||||
{% if tc.highlighted_text %}
|
||||
{{ tc.highlighted_text | escape }}
|
||||
{% elif tc.text %}
|
||||
{{ tc.text | escape }}
|
||||
{% else %}
|
||||
<span class="italic opacity-60">No text preview available.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="text-xs flex flex-wrap gap-x-4 gap-y-2 items-center">
|
||||
<span class="inline-flex items-center">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Category</span>
|
||||
<span class="nb-badge">{{ tc.highlighted_category | default(tc.category, true) | safe }}</span>
|
||||
</span>
|
||||
|
||||
{% if tc.highlighted_context or tc.context %}
|
||||
<span class="inline-flex items-center min-w-0">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Context</span>
|
||||
<span class="nb-badge">{{ tc.highlighted_context | default(tc.context, true) | safe }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if tc.url_info and tc.url_info.url %}
|
||||
<span class="inline-flex items-center min-w-0">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Source</span>
|
||||
<a href="{{ tc.url_info.url }}" target="_blank" class="nb-link truncate" title="{{ tc.url_info.url }}">
|
||||
{{ tc.highlighted_url | default(tc.url_info.url ) | safe }}
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="inline-flex items-center">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Score</span>
|
||||
<span class="nb-badge">{{ result.score }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="inline-flex items-center">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Score</span>
|
||||
<span class="nb-badge">{{ result.score }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% elif result.result_type == "knowledge_entity" %}
|
||||
{% set entity = result.knowledge_entity %}
|
||||
<div class="w-10 h-10 flex-shrink-0 self-start mt-1 grid place-items-center border-2 border-neutral bg-base-100 shadow-[4px_4px_0_0_#000]">
|
||||
<div class="tooltip tooltip-right" data-tip="Knowledge Entity">
|
||||
{% include "icons/book_icon.html" %}
|
||||
</div>
|
||||
{% set entity = result.knowledge_entity %}
|
||||
<div
|
||||
class="w-10 h-10 flex-shrink-0 self-start mt-1 grid place-items-center border-2 border-neutral bg-base-100 shadow-[4px_4px_0_0_#000]">
|
||||
<div class="tooltip tooltip-right" data-tip="Knowledge Entity">
|
||||
{% include "icons/book_icon.html" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-extrabold mb-1 leading-snug">
|
||||
<a hx-get="/knowledge-entity/{{ entity.id }}" hx-target="#modal" hx-swap="innerHTML" class="nb-link">
|
||||
{% set entity_title = entity.highlighted_name | default(entity.name, true) %}
|
||||
{{ entity_title }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<div class="prose prose-tufte-compact text-base-content/80 mb-3 overflow-hidden line-clamp-6">
|
||||
{% if entity.highlighted_description %}
|
||||
{{ entity.highlighted_description }}
|
||||
{% elif entity.description %}
|
||||
{{ entity.description | escape }}
|
||||
{% else %}
|
||||
<span class="italic opacity-60">No description available.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-extrabold mb-1 leading-snug">
|
||||
<a hx-get="/knowledge-entity/{{ entity.id }}" hx-target="#modal" hx-swap="innerHTML" class="nb-link">
|
||||
{% set entity_title = entity.highlighted_name | default(entity.name, true) %}
|
||||
{{ entity_title | safe }}
|
||||
</a>
|
||||
</h3>
|
||||
<div class="text-xs flex flex-wrap gap-x-4 gap-y-2 items-center">
|
||||
<span class="inline-flex items-center">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Entity Type</span>
|
||||
<span class="nb-badge">{{ entity.entity_type }}</span>
|
||||
</span>
|
||||
|
||||
<div class="prose prose-tufte-compact text-base-content/80 mb-3 overflow-hidden line-clamp-6">
|
||||
{% if entity.highlighted_description %}
|
||||
{{ entity.highlighted_description | safe }}
|
||||
{% elif entity.description %}
|
||||
{{ entity.description | escape }}
|
||||
{% else %}
|
||||
<span class="italic opacity-60">No description available.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if entity.source_id %}
|
||||
<span class="inline-flex items-center min-w-0">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Source ID</span>
|
||||
<span class="nb-badge truncate max-w-xs" title="{{ entity.source_id }}">{{ entity.source_id }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="text-xs flex flex-wrap gap-x-4 gap-y-2 items-center">
|
||||
<span class="inline-flex items-center">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Entity Type</span>
|
||||
<span class="nb-badge">{{ entity.entity_type }}</span>
|
||||
</span>
|
||||
|
||||
{% if entity.source_id %}
|
||||
<span class="inline-flex items-center min-w-0">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Source ID</span>
|
||||
<span class="nb-badge truncate max-w-xs" title="{{ entity.source_id }}">{{ entity.source_id }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="inline-flex items-center">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Score</span>
|
||||
<span class="nb-badge">{{ result.score }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="inline-flex items-center">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Score</span>
|
||||
<span class="nb-badge">{{ result.score }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
{% elif query_param is defined and query_param | trim != "" %}
|
||||
<div class="nb-panel p-5 text-center">
|
||||
<p class="text-xl font-extrabold mb-2">No results for “{{ query_param | escape }}”.</p>
|
||||
<p class="text-sm opacity-70">Try different keywords or check for typos.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="nb-panel p-5 text-center">
|
||||
<p class="text-lg font-semibold">Enter a term above to search your knowledge base.</p>
|
||||
<p class="text-sm opacity-70">Results will appear here.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -9,6 +9,8 @@
|
||||
{% include "icons/chat_icon.html" %}
|
||||
{% elif name == "search" %}
|
||||
{% include "icons/search_icon.html" %}
|
||||
{% elif name == "scratchpad" %}
|
||||
{% include "icons/scratchpad_icon.html" %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -26,7 +28,8 @@
|
||||
("/knowledge", "book", "Knowledge"),
|
||||
("/content", "document", "Content"),
|
||||
("/chat", "chat", "Chat"),
|
||||
("/search", "search", "Search")
|
||||
("/search", "search", "Search"),
|
||||
("/scratchpad", "scratchpad", "Scratchpad")
|
||||
] %}
|
||||
<li>
|
||||
<a hx-boost="true" href="{{ url }}" class="nb-btn w-full justify-start gap-3 bg-base-100 hover:bg-base-200">
|
||||
|
||||
Reference in New Issue
Block a user