mirror of
https://github.com/perstarkse/minne.git
synced 2026-04-23 09:18:36 +02:00
design: neobrutalist_theme into main
This commit is contained in:
@@ -1,207 +1,156 @@
|
||||
{% extends 'body_base.html' %}
|
||||
|
||||
{% block title %}Minne - Account{% endblock %}
|
||||
{% block title %}Minne - Admin{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<main class="container flex-grow flex flex-col mx-auto mt-4 space-y-6">
|
||||
<h1 class="text-2xl font-bold mb-2">Admin Dashboard</h1>
|
||||
<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">Admin Dashboard</h1>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="stats stats-vertical md:stats-horizontal shadow">
|
||||
<div class="stat">
|
||||
<div class="stat-title font-bold">Page loads</div>
|
||||
<div class="stat-value text-secondary">{{analytics.page_loads}}</div>
|
||||
<div class="stat-desc">Amount of page loads</div>
|
||||
</div>
|
||||
<section class="mb-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
<div class="nb-stat">
|
||||
<div class="text-xs opacity-70">Page Loads</div>
|
||||
<div class="text-3xl font-extrabold">{{analytics.page_loads}}</div>
|
||||
<div class="text-xs opacity-60">Total page load events</div>
|
||||
</div>
|
||||
<div class="nb-stat">
|
||||
<div class="text-xs opacity-70">Unique Visitors</div>
|
||||
<div class="text-3xl font-extrabold">{{analytics.visitors}}</div>
|
||||
<div class="text-xs opacity-60">Distinct users by fingerprint</div>
|
||||
</div>
|
||||
<div class="nb-stat">
|
||||
<div class="text-xs opacity-70">Users</div>
|
||||
<div class="text-3xl font-extrabold">{{users}}</div>
|
||||
<div class="text-xs opacity-60">Registered accounts</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title font-bold">Unique visitors</div>
|
||||
<div class="stat-value text-primary">{{analytics.visitors}}</div>
|
||||
<div class="stat-desc">Amount of unique visitors</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title font-bold">Users</div>
|
||||
<div class="stat-value text-accent">{{users}}</div>
|
||||
<div class="stat-desc">Amount of registered users</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings in Fieldset -->
|
||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
|
||||
{% block system_prompt_section %}
|
||||
<div id="system_prompt_section">
|
||||
<fieldset class="fieldset p-4 shadow rounded-box">
|
||||
<legend class="fieldset-legend">System Prompts</legend>
|
||||
<section class="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||
{% block system_prompt_section %}
|
||||
<div id="system_prompt_section" class="nb-panel p-4">
|
||||
<div class="text-sm font-semibold mb-3">System Prompts</div>
|
||||
<div class="flex gap-2 flex-col sm:flex-row">
|
||||
<button type="button" class="btn btn-primary btn-sm" hx-get="/edit-query-prompt" hx-target="#modal"
|
||||
hx-swap="innerHTML">
|
||||
Edit Query Prompt
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" hx-get="/edit-ingestion-prompt" hx-target="#modal"
|
||||
hx-swap="innerHTML">
|
||||
Edit Ingestion Prompt
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" hx-get="/edit-image-prompt" hx-target="#modal"
|
||||
hx-swap="innerHTML">
|
||||
Edit Image Prompt
|
||||
</button>
|
||||
<button type="button" class="nb-btn btn-sm" hx-get="/edit-query-prompt" hx-target="#modal" hx-swap="innerHTML">Edit Query Prompt</button>
|
||||
<button type="button" class="nb-btn btn-sm" hx-get="/edit-ingestion-prompt" hx-target="#modal" hx-swap="innerHTML">Edit Ingestion Prompt</button>
|
||||
<button type="button" class="nb-btn btn-sm" hx-get="/edit-image-prompt" hx-target="#modal" hx-swap="innerHTML">Edit Image Prompt</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<fieldset class="fieldset p-4 shadow rounded-box">
|
||||
<legend class="fieldset-legend">AI Models</legend>
|
||||
{% block model_settings_form %}
|
||||
<form hx-patch="/update-model-settings" hx-swap="outerHTML">
|
||||
<!-- Query Model -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text">Query Model</span>
|
||||
</label>
|
||||
<select name="query_model" class="select select-bordered w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.query_model==model.id %} selected {% endif %}>{{model.id}}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Current used:
|
||||
<span class="font-mono">{{settings.query_model}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Processing Model -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text">Processing Model</span>
|
||||
</label>
|
||||
<select name="processing_model" class="select select-bordered w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.processing_model==model.id %} selected {% endif %}>{{model.id}}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Current used:
|
||||
<span class="font-mono">{{settings.processing_model}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Image Processing Model -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text">Image Processing Model</span>
|
||||
</label>
|
||||
<select name="image_processing_model" class="select select-bordered w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.image_processing_model==model.id %} selected {% endif %}>
|
||||
{{model.id}}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Current used:
|
||||
<span class="font-mono">{{settings.image_processing_model}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Voice Processing Model -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text">Voice Processing Model</span>
|
||||
</label>
|
||||
<select name="voice_processing_model" class="select select-bordered w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.voice_processing_model==model.id %} selected {% endif %}>{{model.id}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Current used:
|
||||
<span class="font-mono">{{settings.voice_processing_model}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Embedding Model -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label">
|
||||
<span class="label-text">Embedding Model</span>
|
||||
</label>
|
||||
<select name="embedding_model" class="select select-bordered w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.embedding_model==model.id %} selected {% endif %}>{{model.id}}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
Current used:
|
||||
<span class="font-mono">{{settings.embedding_model}} ({{settings.embedding_dimensions}} dims)</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Embedding Dimensions (Always Visible) -->
|
||||
<div class="form-control mb-4">
|
||||
<label class="label" for="embedding_dimensions">
|
||||
<span class="label-text">Embedding Dimensions</span>
|
||||
</label>
|
||||
<input type="number" id="embedding_dimensions" name="embedding_dimensions" class="input input-bordered w-full"
|
||||
value="{{ settings.embedding_dimensions }}" required />
|
||||
</div>
|
||||
|
||||
<!-- Conditional Alert -->
|
||||
<div id="embedding-change-alert" role="alert" class="alert alert-warning mt-2 hidden">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||
</svg>
|
||||
<span><strong>Warning:</strong> Changing dimensions will require re-creating all embeddings. Make sure you
|
||||
look up what dimensions the model uses or use a model that allows specifying embedding dimensions</span>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-sm mt-4">Save Model Settings</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Use a self-executing function to avoid polluting the global scope
|
||||
// and to ensure it runs correctly after an HTMX swap.
|
||||
(() => {
|
||||
const dimensionInput = document.getElementById('embedding_dimensions');
|
||||
const alertElement = document.getElementById('embedding-change-alert');
|
||||
// The initial value is read directly from the template each time this script runs.
|
||||
const initialDimensions = '{{ settings.embedding_dimensions }}';
|
||||
|
||||
if (dimensionInput && alertElement) {
|
||||
// Use the 'input' event for immediate feedback as the user types.
|
||||
dimensionInput.addEventListener('input', (event) => {
|
||||
// Show alert if the current value is not the initial value. Hide it otherwise.
|
||||
if (event.target.value !== initialDimensions) {
|
||||
alertElement.classList.remove('hidden');
|
||||
} else {
|
||||
alertElement.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="fieldset p-4 shadow rounded-box">
|
||||
<legend class="fieldset-legend">Registration</legend>
|
||||
<label class="flex gap-4 text-center">
|
||||
{% block registration_status_input %}
|
||||
<form hx-patch="/toggle-registrations" hx-swap="outerHTML" hx-trigger="change">
|
||||
<input name="registration_open" type="checkbox" class="checkbox" {% if settings.registrations_enabled
|
||||
%}checked{% endif %} />
|
||||
<div class="nb-panel p-4">
|
||||
<div class="text-sm font-semibold mb-3">AI Models</div>
|
||||
{% block model_settings_form %}
|
||||
<form hx-patch="/update-model-settings" hx-swap="outerHTML" class="grid grid-cols-1 gap-4">
|
||||
<!-- Query Model -->
|
||||
<div>
|
||||
<div class="text-sm opacity-80 mb-1">Query Model</div>
|
||||
<select name="query_model" class="nb-select w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.query_model==model.id %} selected {% endif %}>{{model.id}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="text-xs opacity-70 mt-1">Current: <span class="font-mono">{{settings.query_model}}</span></p>
|
||||
</div>
|
||||
|
||||
<!-- Processing Model -->
|
||||
<div>
|
||||
<div class="text-sm opacity-80 mb-1">Processing Model</div>
|
||||
<select name="processing_model" class="nb-select w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.processing_model==model.id %} selected {% endif %}>{{model.id}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="text-xs opacity-70 mt-1">Current: <span class="font-mono">{{settings.processing_model}}</span></p>
|
||||
</div>
|
||||
|
||||
<!-- Image Processing Model -->
|
||||
<div>
|
||||
<div class="text-sm opacity-80 mb-1">Image Processing Model</div>
|
||||
<select name="image_processing_model" class="nb-select w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.image_processing_model==model.id %} selected {% endif %}>{{model.id}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="text-xs opacity-70 mt-1">Current: <span class="font-mono">{{settings.image_processing_model}}</span></p>
|
||||
</div>
|
||||
|
||||
<!-- Voice Processing Model -->
|
||||
<div>
|
||||
<div class="text-sm opacity-80 mb-1">Voice Processing Model</div>
|
||||
<select name="voice_processing_model" class="nb-select w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.voice_processing_model==model.id %} selected {% endif %}>{{model.id}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="text-xs opacity-70 mt-1">Current: <span class="font-mono">{{settings.voice_processing_model}}</span></p>
|
||||
</div>
|
||||
|
||||
<!-- Embedding Model -->
|
||||
<div>
|
||||
<div class="text-sm opacity-80 mb-1">Embedding Model</div>
|
||||
<select name="embedding_model" class="nb-select w-full">
|
||||
{% for model in available_models.data %}
|
||||
<option value="{{model.id}}" {% if settings.embedding_model==model.id %} selected {% endif %}>{{model.id}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="text-xs opacity-70 mt-1">Current: <span class="font-mono">{{settings.embedding_model}} ({{settings.embedding_dimensions}} dims)</span></p>
|
||||
</div>
|
||||
|
||||
<!-- Embedding Dimensions -->
|
||||
<div>
|
||||
<div class="text-sm opacity-80 mb-1" for="embedding_dimensions">Embedding Dimensions</div>
|
||||
<input type="number" id="embedding_dimensions" name="embedding_dimensions" class="nb-input w-full" value="{{ settings.embedding_dimensions }}" required />
|
||||
</div>
|
||||
|
||||
<!-- Alert -->
|
||||
<div id="embedding-change-alert" class="nb-panel p-3 bg-warning/20 hidden">
|
||||
<div class="text-sm"><strong>Warning:</strong> Changing dimensions will require re-creating all embeddings. Look up your model's required dimensions or use a model that allows specifying them.</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button type="submit" class="nb-btn nb-cta btn-sm">Save Model Settings</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
Enable Registrations
|
||||
</label>
|
||||
<div id="registration-status" class="text-sm mt-2"></div>
|
||||
</fieldset>
|
||||
|
||||
<script>
|
||||
// Rebind after HTMX swaps
|
||||
(() => {
|
||||
const dimensionInput = document.getElementById('embedding_dimensions');
|
||||
const alertElement = document.getElementById('embedding-change-alert');
|
||||
const initialDimensions = '{{ settings.embedding_dimensions }}';
|
||||
if (dimensionInput && alertElement) {
|
||||
dimensionInput.addEventListener('input', (event) => {
|
||||
if (String(event.target.value) !== String(initialDimensions)) {
|
||||
alertElement.classList.remove('hidden');
|
||||
} else {
|
||||
alertElement.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<div class="nb-panel p-4">
|
||||
<div class="text-sm font-semibold mb-3">Registration</div>
|
||||
<label class="flex items-center gap-3">
|
||||
{% block registration_status_input %}
|
||||
<form hx-patch="/toggle-registrations" hx-swap="outerHTML" hx-trigger="change">
|
||||
<input name="registration_open" type="checkbox" class="nb-checkbox" {% if settings.registrations_enabled %}checked{% endif %} />
|
||||
</form>
|
||||
{% endblock %}
|
||||
<span class="text-sm">Enable Registrations</span>
|
||||
</label>
|
||||
<div id="registration-status" class="text-xs opacity-70 mt-2"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,17 +7,17 @@ hx-swap="outerHTML"
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_content %}
|
||||
<h3 class="text-lg font-bold mb-4">Edit Image Processing Prompt</h3>
|
||||
<h3 class="text-xl font-extrabold tracking-tight mb-2">Edit Image Processing Prompt</h3>
|
||||
|
||||
<div class="form-control">
|
||||
<textarea name="image_processing_prompt" class="textarea textarea-bordered h-96 w-full font-mono text-sm">{{
|
||||
<textarea name="image_processing_prompt" class="nb-input h-96 w-full font-mono text-sm">{{
|
||||
settings.image_processing_prompt }}</textarea>
|
||||
<p class="text-xs text-gray-500 mt-1">System prompt used for processing images</p>
|
||||
<p class="text-xs opacity-70 mt-1">System prompt used for processing images</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block primary_actions %}
|
||||
<button type="button" class="btn btn-outline mr-2" id="reset_prompt_button">
|
||||
<button type="button" class="nb-btn mr-2" id="reset_prompt_button">
|
||||
Reset to Default
|
||||
</button>
|
||||
|
||||
@@ -29,10 +29,10 @@ hx-swap="outerHTML"
|
||||
});
|
||||
</script>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<button type="submit" class="nb-btn nb-cta">
|
||||
<span class="htmx-indicator hidden">
|
||||
<span class="loading loading-spinner loading-xs mr-2"></span>
|
||||
</span>
|
||||
Save Changes
|
||||
</button>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,17 +7,17 @@ hx-swap="outerHTML"
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_content %}
|
||||
<h3 class="text-lg font-bold mb-4">Edit Ingestion Prompt</h3>
|
||||
<h3 class="text-xl font-extrabold tracking-tight mb-2">Edit Ingestion Prompt</h3>
|
||||
|
||||
<div class="form-control">
|
||||
<textarea name="ingestion_system_prompt" class="textarea textarea-bordered h-96 w-full font-mono text-sm">{{
|
||||
<textarea name="ingestion_system_prompt" class="nb-input h-96 w-full font-mono text-sm">{{
|
||||
settings.ingestion_system_prompt }}</textarea>
|
||||
<p class="text-xs text-gray-500 mt-1">System prompt used for content processing and ingestion</p>
|
||||
<p class="text-xs opacity-70 mt-1">System prompt used for content processing and ingestion</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block primary_actions %}
|
||||
<button type="button" class="btn btn-outline mr-2" id="reset_prompt_button">
|
||||
<button type="button" class="nb-btn mr-2" id="reset_prompt_button">
|
||||
Reset to Default
|
||||
</button>
|
||||
|
||||
@@ -29,10 +29,10 @@ hx-swap="outerHTML"
|
||||
});
|
||||
</script>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<button type="submit" class="nb-btn nb-cta">
|
||||
<span class="htmx-indicator hidden">
|
||||
<span class="loading loading-spinner loading-xs mr-2"></span>
|
||||
</span>
|
||||
Save Changes
|
||||
</button>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -7,17 +7,17 @@ hx-swap="outerHTML"
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_content %}
|
||||
<h3 class="text-lg font-bold mb-4">Edit System Prompt</h3>
|
||||
<h3 class="text-xl font-extrabold tracking-tight mb-2">Edit System Prompt</h3>
|
||||
|
||||
<div class="form-control">
|
||||
<textarea name="query_system_prompt" class="textarea textarea-bordered h-96 w-full font-mono text-sm">{{
|
||||
<textarea name="query_system_prompt" class="nb-input h-96 w-full font-mono text-sm">{{
|
||||
settings.query_system_prompt }}</textarea>
|
||||
<p class="text-xs text-gray-500 mt-1">System prompt used for answering user queries</p>
|
||||
<p class="text-xs opacity-70 mt-1">System prompt used for answering user queries</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block primary_actions %}
|
||||
<button type="button" class="btn btn-outline mr-2" id="reset_prompt_button">
|
||||
<button type="button" class="nb-btn mr-2" id="reset_prompt_button">
|
||||
Reset to Default
|
||||
</button>
|
||||
|
||||
@@ -29,10 +29,10 @@ hx-swap="outerHTML"
|
||||
});
|
||||
</script>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<button type="submit" class="nb-btn nb-cta">
|
||||
<span class="htmx-indicator hidden">
|
||||
<span class="loading loading-spinner loading-xs mr-2"></span>
|
||||
</span>
|
||||
Save Changes
|
||||
</button>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,91 +3,86 @@
|
||||
{% block title %}Minne - Account{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<style>
|
||||
form.htmx-request {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
<main class="container flex-grow flex flex-col mx-auto mt-4 space-y-1">
|
||||
<h1 class="text-2xl font-bold mb-2">Account Settings</h1>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Email</span>
|
||||
</label>
|
||||
<input type="email" name="email" value="{{ user.email }}" class="input text-primary-content input-bordered w-full"
|
||||
disabled />
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">API key</span>
|
||||
</label>
|
||||
{% 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="input text-primary-content input-bordered w-full pr-12" disabled />
|
||||
<button type="button" id="copy_api_key_btn" onclick="copy_api_key()"
|
||||
class="absolute inset-y-0 cursor-pointer right-0 flex items-center pr-3" title="Copy API key">
|
||||
{% include "icons/clipboard_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
<a href="https://www.icloud.com/shortcuts/66985f7b98a74aaeac6ba29c3f1f0960"
|
||||
class="btn btn-accent mt-4 w-full">Download iOS shortcut</a>
|
||||
{% else %}
|
||||
<button hx-post="/set-api-key" class="btn btn-secondary w-full" hx-swap="outerHTML">
|
||||
Create API-Key
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<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">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>
|
||||
|
||||
<div class="form-control mt-4">
|
||||
<label class="label">
|
||||
<span class="label-text">Timezone</span>
|
||||
</label>
|
||||
{% block timezone_section %}
|
||||
<select name="timezone" class="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 %}
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div class="form-control mt-4 hidden">
|
||||
<button hx-post="/verify-email" class="btn btn-secondary w-full">
|
||||
Verify Email
|
||||
</button>
|
||||
<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 class="form-control mt-4">
|
||||
{% block change_password_section %}
|
||||
<button hx-get="/change-password" hx-swap="outerHTML" class="btn btn-primary w-full">
|
||||
Change Password
|
||||
</button>
|
||||
{% endblock %}
|
||||
</div>
|
||||
<div class="form-control mt-4">
|
||||
<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="btn btn-error w-full">
|
||||
Delete Account
|
||||
</button>
|
||||
</div>
|
||||
<div id="account-result" class="mt-4"></div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<form hx-patch="/change-password" class="flex flex-col gap-1">
|
||||
<input name="old_password" class="input w-full" type="password" placeholder="Enter old password"></input>
|
||||
<input name="new_password" class="input w-full" type="password" placeholder="Enter new password"></input>
|
||||
<button class="btn btn-primary w-full">Change Password</button>
|
||||
</form>
|
||||
<form hx-patch="/change-password" class="flex flex-col gap-3">
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Old Password</div>
|
||||
<input name="old_password" class="nb-input w-full" type="password" placeholder="Enter old password"></input>
|
||||
</label>
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">New Password</div>
|
||||
<input name="new_password" class="nb-input w-full" type="password" placeholder="Enter new password"></input>
|
||||
</label>
|
||||
<button class="nb-btn w-full">Change Password</button>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -1,52 +1,43 @@
|
||||
<style>
|
||||
form.htmx-request {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="flex justify-center grow container mx-auto px-4 sm:px-0 sm:max-w-md flex-col">
|
||||
<h1
|
||||
class="text-5xl sm:text-6xl py-4 pt-10 font-bold bg-linear-to-r from-primary to-secondary text-center text-transparent bg-clip-text">
|
||||
Minne
|
||||
</h1>
|
||||
<h2 class="text-2xl font-bold text-center mb-8">Login to your account</h2>
|
||||
|
||||
<form hx-post="/signin" hx-target="#login-result">
|
||||
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span>Email</span>
|
||||
<input name="email" type="email" placeholder="Email" class="input input-md w-full validator" required />
|
||||
<div class="validator-hint hidden">Enter valid email address</div>
|
||||
</label>
|
||||
<div class="container mx-auto px-4 sm:max-w-md flex-1 flex items-center justify-center">
|
||||
<div class="w-full nb-card p-5">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="brand-mark text-3xl font-extrabold tracking-tight">MINNE</div>
|
||||
<span class="nb-badge">Sign In</span>
|
||||
</div>
|
||||
<div class="u-hairline mb-3"></div>
|
||||
|
||||
<div class="form-control mt-4">
|
||||
<label class="floating-label">
|
||||
<span>Password</span>
|
||||
<input name="password" type="password" class="input validator w-full" required placeholder="Password"
|
||||
<form hx-post="/signin" hx-target="#login-result" class="flex flex-col gap-2">
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Email</div>
|
||||
<input name="email" type="email" placeholder="Email" class="nb-input w-full validator" required />
|
||||
<div class="validator-hint hidden text-xs opacity-70 mt-1">Enter valid email address</div>
|
||||
</label>
|
||||
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Password</div>
|
||||
<input name="password" type="password" class="nb-input w-full validator" required placeholder="Password"
|
||||
minlength="8" />
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-4">
|
||||
<label class="label cursor-pointer justify-start gap-4">
|
||||
<input type="checkbox" name="remember_me" class="checkbox " />
|
||||
<span class="label-text">Remember me</span>
|
||||
</label>
|
||||
|
||||
<div class="mt-1 text-error" id="login-result"></div>
|
||||
|
||||
<div class="form-control mt-1">
|
||||
<label class="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" name="remember_me" class="nb-checkbox" />
|
||||
<span class="label-text">Remember me</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-control mt-1">
|
||||
<button id="submit-btn" class="nb-btn nb-cta w-full">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="u-hairline my-3"></div>
|
||||
<div class="text-center text-sm">
|
||||
Don’t have an account?
|
||||
<a href="/signup" hx-boost="true" class="nb-link">Sign up</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-4" id="login-result"></div>
|
||||
|
||||
<div class="form-control mt-6">
|
||||
<button id="submit-btn" class="btn btn-primary w-full">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<div class="divider">OR</div>
|
||||
<div class="text-center text-sm">
|
||||
Don't have an account?
|
||||
<a href="/signup" hx-boost="true" class="link link-primary">Sign up</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,56 +3,48 @@
|
||||
{% block title %}Minne - Sign up{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<style>
|
||||
form.htmx-request {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
<div class="min-h-[100dvh] flex items-center">
|
||||
<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">
|
||||
<div class="text-3xl font-extrabold tracking-tight">MINNE</div>
|
||||
<span class="nb-badge">Sign Up</span>
|
||||
</div>
|
||||
<div class="u-hairline mb-3"></div>
|
||||
|
||||
<div class="min-h-[100dvh] container mx-auto px-4 sm:px-0 sm:max-w-md flex justify-center flex-col">
|
||||
<h1
|
||||
class="text-5xl sm:text-6xl py-4 pt-10 font-bold bg-linear-to-r from-primary to-secondary text-center text-transparent bg-clip-text">
|
||||
Minne
|
||||
</h1>
|
||||
<h2 class="text-2xl font-bold text-center mb-8">Create your account</h2>
|
||||
<form hx-post="/signup" hx-target="#signup-result" class="flex flex-col gap-4">
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Email</div>
|
||||
<input type="email" placeholder="Email" name="email" required class="nb-input w-full validator" />
|
||||
<div class="validator-hint hidden text-xs opacity-70 mt-1">Enter valid email address</div>
|
||||
</label>
|
||||
|
||||
<form hx-post="/signup" hx-target="#signup-result" class="">
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span>Email</span>
|
||||
<input type="email" placeholder="Email" name="email" required class="input input-md w-full validator" />
|
||||
<div class="validator-hint hidden">Enter valid email address</div>
|
||||
</label>
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Password</div>
|
||||
<input type="password" name="password" class="nb-input w-full validator" required placeholder="Password"
|
||||
minlength="8" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
|
||||
title="Must be more than 8 characters, including number, lowercase letter, uppercase letter" />
|
||||
<p class="validator-hint hidden text-xs opacity-70 mt-1">
|
||||
Must be more than 8 characters, including
|
||||
<br />At least one number
|
||||
<br />At least one lowercase letter
|
||||
<br />At least one uppercase letter
|
||||
</p>
|
||||
</label>
|
||||
|
||||
<div class="mt-2 text-error" id="signup-result"></div>
|
||||
<div class="form-control mt-1">
|
||||
<button id="submit-btn" class="nb-btn nb-cta w-full">Create Account</button>
|
||||
</div>
|
||||
<input type="hidden" name="timezone" id="timezone" />
|
||||
</form>
|
||||
|
||||
<div class="u-hairline my-3"></div>
|
||||
<div class="text-center text-sm">
|
||||
Already have an account?
|
||||
<a href="/signin" hx-boost="true" class="nb-link">Sign in</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-4">
|
||||
<label class="floating-label">
|
||||
<span>Password</span>
|
||||
<input type="password" name="password" class="input validator w-full" required placeholder="Password"
|
||||
minlength="8" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
|
||||
title="Must be more than 8 characters, including number, lowercase letter, uppercase letter" />
|
||||
<p class="validator-hint hidden">
|
||||
Must be more than 8 characters, including
|
||||
<br />At least one number
|
||||
<br />At least one lowercase letter
|
||||
<br />At least one uppercase letter
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-4 text-error" id="signup-result"></div>
|
||||
<div class="form-control mt-6">
|
||||
<button id="submit-btn" class="btn btn-primary w-full">
|
||||
Create Account
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" name="timezone" id="timezone" />
|
||||
</form>
|
||||
|
||||
<div class="divider">OR</div>
|
||||
|
||||
<div class="text-center text-sm">
|
||||
Already have an account?
|
||||
<a href="/signin" hx-boost="true" class="link link-primary">Sign in</a>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
@@ -60,4 +52,4 @@
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
document.getElementById("timezone").value = timezone;
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block body %}
|
||||
|
||||
<body class="bg-base-100 relative" hx-ext="head-support">
|
||||
<body class="relative" hx-ext="head-support">
|
||||
<div class="drawer lg:drawer-open">
|
||||
<input id="my-drawer" type="checkbox" class="drawer-toggle" />
|
||||
<!-- Page Content -->
|
||||
@@ -10,8 +10,9 @@
|
||||
<!-- Navbar -->
|
||||
{% include "navigation_bar.html" %}
|
||||
<!-- Main Content Area -->
|
||||
<main class="flex flex-1 overflow-y-auto">
|
||||
<main class="flex flex-col flex-1 overflow-y-auto">
|
||||
{% block main %}{% endblock %}
|
||||
<div class="p32 min-h-[10px]"></div>
|
||||
</main>
|
||||
</div>
|
||||
<!-- Sidebar -->
|
||||
@@ -21,25 +22,5 @@
|
||||
</div> <!-- End Drawer -->
|
||||
<div id="modal"></div>
|
||||
<div id="toast-container" class="fixed bottom-4 right-4 z-50 space-y-2"></div>
|
||||
<!-- Add CSS for custom scrollbar -->
|
||||
<style>
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.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 %}
|
||||
@@ -9,94 +9,40 @@
|
||||
{% block main %}
|
||||
<div class="flex grow relative justify-center mt-2 sm:mt-4">
|
||||
<div class="container">
|
||||
<div class="overflow-auto hide-scrollbar">
|
||||
<section class="mb-3">
|
||||
<div class="nb-panel p-3 flex items-center justify-between">
|
||||
<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">
|
||||
{% include "chat/history.html" %}
|
||||
{% include "chat/new_message_form.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.hide-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.markdown-content p {
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
.markdown-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-content ul,
|
||||
.markdown-content ol {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.75em;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.markdown-content li {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.markdown-content pre {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
padding: 0.5em;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.markdown-content code {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.markdown-content {
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.markdown-content table {
|
||||
border-collapse: collapse;
|
||||
margin: 0.75em 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.markdown-content th,
|
||||
.markdown-content td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 6px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown-content blockquote {
|
||||
border-left: 4px solid #ddd;
|
||||
padding-left: 10px;
|
||||
margin: 0.5em 0 0.5em 0.5em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.markdown-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function scrollChatToBottom() {
|
||||
const chatContainer = document.getElementById('chat_container');
|
||||
if (chatContainer) chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
requestAnimationFrame(() => {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
window.scrollChatToBottom = scrollChatToBottom;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', scrollChatToBottom);
|
||||
|
||||
document.body.addEventListener('htmx:afterSwap', scrollChatToBottom);
|
||||
document.body.addEventListener('htmx:afterSettle', scrollChatToBottom);
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="chat_container" class="pl-3 overflow-y-auto h-[calc(100vh-170px)] sm:h-[calc(100vh-190px)] hide-scrollbar">
|
||||
<div id="chat_container" class="px-3 pb-44 space-y-3">
|
||||
{% for message in history %}
|
||||
{% if message.role == "AI" %}
|
||||
<div class="chat chat-start">
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
{% include "chat/streaming_response.html" %}
|
||||
|
||||
<!-- OOB swap targeting the form element directly -->
|
||||
<form id="chat-form" hx-post="/chat/{{conversation.id}}" hx-target="#chat_container" hx-swap="beforeend"
|
||||
class="relative flex gap-2" hx-swap-oob="true">
|
||||
<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"
|
||||
id="chat-input"></textarea>
|
||||
class="nb-input h-24 pr-8 pl-2 pt-2 pb-2 flex-grow resize-none" id="chat-input"></textarea>
|
||||
<button type="submit" class="absolute p-2 cursor-pointer right-0.5 btn-ghost btn-sm top-1">
|
||||
{% include "icons/send_icon.html" %}
|
||||
</button>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<div class="absolute 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 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-6">{% include
|
||||
"icons/send_icon.html" %}
|
||||
</button>
|
||||
</form>
|
||||
<div class="fixed bottom-0 left-0 right-0 lg:left-72 z-20">
|
||||
<div class="mx-auto max-w-3xl px-4 pb-3">
|
||||
<div class="nb-panel p-2">
|
||||
<form hx-post="{% if conversation %} /chat/{{conversation.id}} {% else %} /chat {% endif %}"
|
||||
hx-target="#chat_container" hx-swap="beforeend" class="relative flex gap-2 items-end" id="chat-form">
|
||||
<textarea autofocus required name="content" placeholder="Type your message…" rows="3"
|
||||
class="nb-input flex-grow min-h-24 pr-10 pl-2 pt-2 pb-2 resize-none" id="chat-input"></textarea>
|
||||
<button type="submit" class="nb-btn nb-cta h-10 px-3">{% include "icons/send_icon.html" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -23,4 +24,4 @@
|
||||
document.getElementById('chat-input').value = ''; // Clear the textarea
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="relative my-2">
|
||||
<button id="references-toggle-{{message.id}}"
|
||||
class="text-xs text-blue-500 hover:text-blue-700 hover:underline focus:outline-none flex items-center"
|
||||
class="nb-btn btn-xs bg-base-100 hover:bg-base-200 flex items-center"
|
||||
onclick="toggleReferences('{{message.id}}')">
|
||||
References
|
||||
REFERENCES
|
||||
{% include "icons/chevron_icon.html" %}
|
||||
</button>
|
||||
<div id="references-content-{{message.id}}" class="hidden max-w-full mt-1">
|
||||
@@ -10,7 +10,7 @@
|
||||
{% for reference in message.references %}
|
||||
<div class="reference-badge-container" data-reference="{{reference}}" data-message-id="{{message.id}}"
|
||||
data-index="{{loop.index}}">
|
||||
<span class="badge badge-xs badge-neutral truncate max-w-[20ch] overflow-hidden text-left block cursor-pointer">
|
||||
<span class="nb-badge truncate max-w-[20ch] overflow-hidden text-left block cursor-pointer">
|
||||
{{reference}}
|
||||
</span>
|
||||
</div>
|
||||
@@ -80,7 +80,7 @@
|
||||
function createTooltip() {
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.id = tooltipId;
|
||||
tooltip.className = 'fixed z-[9999] bg-neutral-800 text-white p-3 rounded-md shadow-lg text-sm w-72 max-w-xs border border-neutral-700 hidden';
|
||||
tooltip.className = 'reference-tooltip hidden';
|
||||
tooltip.innerHTML = '<div class="animate-pulse">Loading...</div>';
|
||||
document.body.appendChild(tooltip);
|
||||
return tooltip;
|
||||
@@ -135,15 +135,3 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#references-toggle- {
|
||||
{
|
||||
message.id
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
@@ -25,7 +25,7 @@
|
||||
e.preventDefault();
|
||||
window.markdownBuffer[msgId] = (window.markdownBuffer[msgId] || '') + (e.detail.data || '');
|
||||
el.innerHTML = marked.parse(window.markdownBuffer[msgId].replace(/\\n/g, '\n'));
|
||||
if (typeof scrollChatToBottom === "function") scrollChatToBottom();
|
||||
if (typeof window.scrollChatToBottom === "function") window.scrollChatToBottom();
|
||||
});
|
||||
document.body.addEventListener('htmx:sseClose', function () {
|
||||
const msgId = '{{ user_message.id }}';
|
||||
@@ -33,7 +33,7 @@
|
||||
if (el && window.markdownBuffer[msgId]) {
|
||||
el.innerHTML = marked.parse(window.markdownBuffer[msgId].replace(/\\n/g, '\n'));
|
||||
delete window.markdownBuffer[msgId];
|
||||
if (typeof scrollChatToBottom === "function") scrollChatToBottom();
|
||||
if (typeof window.scrollChatToBottom === "function") window.scrollChatToBottom();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
{% 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">
|
||||
<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="flex items-center justify-between mb-4">
|
||||
<h2 class="text-2xl font-bold">Content</h2>
|
||||
<div class="nb-panel p-3 mb-4 flex items-center justify-between">
|
||||
<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">
|
||||
<div class="form-control">
|
||||
<select name="category" class="select select-bordered">
|
||||
<div>
|
||||
<select name="category" class="nb-select">
|
||||
<option value="">All Categories</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category }}" {% if selected_category==category %}selected{% endif %}>{{ category }}
|
||||
@@ -18,13 +18,11 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">Filter</button>
|
||||
<button type="submit" class="nb-btn btn-sm">Filter</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="text_content_cards">
|
||||
{% include "content/content_list.html" %}
|
||||
</div>
|
||||
{% include "content/content_list.html" %}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -1,19 +1,19 @@
|
||||
<div class="columns-1 md:columns-2 2xl:columns-3 gap-4" id="text_content_cards">
|
||||
<div class="nb-masonry w-full" id="text_content_cards">
|
||||
{% for text_content in text_contents %}
|
||||
<div class="card cursor-pointer mb-4 bg-base-100 shadow break-inside-avoid-column"
|
||||
<article class="nb-card cursor-pointer mx-auto mb-4 w-full max-w-[92vw] space-y-3 sm:max-w-none"
|
||||
hx-get="/content/{{ text_content.id }}/read" hx-target="#modal" hx-swap="innerHTML">
|
||||
{% if text_content.url_info %}
|
||||
<figure>
|
||||
<img src="/file/{{text_content.url_info.image_id}}" alt="website screenshot" />
|
||||
<figure class="-mx-4 -mt-4 border-b-2 border-neutral bg-base-200">
|
||||
<img class="w-full h-auto" src="/file/{{text_content.url_info.image_id}}" alt="website screenshot" />
|
||||
</figure>
|
||||
{% endif %}
|
||||
{% if text_content.file_info.mime_type == "image/png" or text_content.file_info.mime_type == "image/jpeg" %}
|
||||
<figure>
|
||||
<img src="/file/{{text_content.file_info.id}}" alt="{{text_content.file_info.file_name}}" />
|
||||
<figure class="-mx-4 -mt-4 border-b-2 border-neutral bg-base-200">
|
||||
<img class="w-full h-auto" src="/file/{{text_content.file_info.id}}" alt="{{text_content.file_info.file_name}}" />
|
||||
</figure>
|
||||
{% endif %}
|
||||
<div class="card-body max-w-[95vw]">
|
||||
<h2 class="card-title truncate">
|
||||
<div class="space-y-3 break-words">
|
||||
<h2 class="text-lg font-extrabold tracking-tight truncate">
|
||||
{% if text_content.url_info %}
|
||||
{{text_content.url_info.title}}
|
||||
{% elif text_content.file_info %}
|
||||
@@ -22,37 +22,36 @@
|
||||
{{text_content.text}}
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-xs opacity-60">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<p class="text-xs opacity-60 shrink-0">
|
||||
{{ text_content.created_at | datetimeformat(format="short", tz=user.timezone) }}
|
||||
</p>
|
||||
<div class="badge badge-soft badge-secondary mr-2">{{ text_content.category }}</div>
|
||||
<span class="nb-badge">{{ text_content.category }}</span>
|
||||
<div class="flex gap-2" hx-on:click="event.stopPropagation()">
|
||||
{% if text_content.url_info %}
|
||||
<button class="btn-btn-square btn-ghost btn-sm">
|
||||
<a href="{{text_content.url_info.url}}" target="_blank" rel="noopener noreferrer">
|
||||
{% include "icons/link_icon.html" %}
|
||||
</a>
|
||||
</button>
|
||||
<a href="{{text_content.url_info.url}}" target="_blank" rel="noopener noreferrer"
|
||||
class="nb-btn btn-square btn-sm" aria-label="Open source link">
|
||||
{% include "icons/link_icon.html" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<button hx-get="/content/{{ text_content.id }}/read" hx-target="#modal" hx-swap="innerHTML"
|
||||
class="btn btn-square btn-ghost btn-sm">
|
||||
class="nb-btn btn-square btn-sm" aria-label="Read content">
|
||||
{% include "icons/read_icon.html" %}
|
||||
</button>
|
||||
<button hx-get="/content/{{ text_content.id }}" hx-target="#modal" hx-swap="innerHTML"
|
||||
class="btn btn-square btn-ghost btn-sm">
|
||||
class="nb-btn btn-square btn-sm" aria-label="Edit content">
|
||||
{% include "icons/edit_icon.html" %}
|
||||
</button>
|
||||
<button hx-delete="/content/{{ text_content.id }}" hx-target="#text_content_cards" hx-swap="outerHTML"
|
||||
class="btn btn-square btn-ghost btn-sm">
|
||||
class="nb-btn btn-square btn-sm" aria-label="Delete content">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2">
|
||||
<p class="text-sm leading-relaxed">
|
||||
{{ text_content.instructions }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -8,34 +8,49 @@ flex flex-col min-h-[95%] w-11/12 max-w-[90ch] max-h-[95%]
|
||||
hx-patch="/content/{{text_content.id}}"
|
||||
hx-target="#main_section"
|
||||
hx-swap="outerHTML"
|
||||
class="flex flex-col flex-1 h-full"
|
||||
class="flex flex-col flex-1 h-full min-h-0"
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_content %}
|
||||
<h3 class="text-lg font-bold">Edit Content</h3>
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span class="label-text">Context</span>
|
||||
<input type="text" name="context" value="{{ text_content.context }}" class="w-full input input-bordered">
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span class="label-text">Category</span>
|
||||
<input type="text" name="category" value="{{ text_content.category }}" class="w-full input input-bordered">
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control flex-1 flex flex-col min-h-0">
|
||||
<label class="floating-label flex-1 flex flex-col min-h-0">
|
||||
<span class="label-text">Text</span>
|
||||
<textarea name="text" class="textarea textarea-bordered w-full flex-1 min-h-[200px] h-full resize-none">{{
|
||||
text_content.text }}</textarea>
|
||||
<h3 class="text-xl font-extrabold tracking-tight">Edit Content</h3>
|
||||
<div class="flex flex-col gap-3 flex-1 min-h-0">
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Context</div>
|
||||
<input type="text" name="context" value="{{ text_content.context }}" class="nb-input w-full">
|
||||
</label>
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Category</div>
|
||||
<input type="text" name="category" value="{{ text_content.category }}" class="nb-input w-full">
|
||||
</label>
|
||||
<label class="w-full flex-1 flex flex-col min-h-0">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Text</div>
|
||||
<textarea name="text" class="nb-input w-full flex-1 min-h-0 h-full resize-none overflow-y-auto">{{ text_content.text
|
||||
}}</textarea>
|
||||
</label>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
const form = document.getElementById('modal_form');
|
||||
if (!form) return;
|
||||
|
||||
if (document.getElementById('main_section')) {
|
||||
form.setAttribute('hx-target', '#main_section');
|
||||
form.setAttribute('hx-swap', 'outerHTML');
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.getElementById('latest_content_section')) {
|
||||
form.setAttribute('hx-target', '#latest_content_section');
|
||||
form.setAttribute('hx-swap', 'outerHTML');
|
||||
return;
|
||||
}
|
||||
|
||||
form.removeAttribute('hx-target');
|
||||
form.setAttribute('hx-swap', 'none');
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block primary_actions %}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Save Changes
|
||||
</button>
|
||||
{% endblock %}
|
||||
<button type="submit" class="nb-btn nb-cta">Save Changes</button>
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
{% block modal_content %}
|
||||
{% if text_content.url_info.image_id %}
|
||||
<img class="rounded-t-md overflow-clip" src="/file/{{text_content.url_info.image_id}}" alt="Screenshot of the site" />
|
||||
<img class="w-full border-b-2 border-neutral" src="/file/{{text_content.url_info.image_id}}" alt="Screenshot of the site" />
|
||||
{% endif %}
|
||||
{% if text_content.file_info.mime_type == "image/png" or text_content.file_info.mime_type == "image/jpeg" %}
|
||||
<figure>
|
||||
<img src="/file/{{text_content.file_info.id}}" alt="{{text_content.file_info.file_name}}" />
|
||||
</figure>
|
||||
{% endif %}
|
||||
<div id="reader-{{text_content.id}}" class="markdown-content prose" data-content="{{text_content.text | escape }}">
|
||||
<div id="reader-{{text_content.id}}" class="markdown-content prose-tufte" data-content="{{text_content.text | escape }}">
|
||||
{{text_content.text | escape }}
|
||||
</div>
|
||||
|
||||
@@ -39,4 +39,4 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block primary_actions %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,49 +1,63 @@
|
||||
{% block active_jobs_section %}
|
||||
<ul id="active_jobs_section" class="list">
|
||||
<div class="flex items-center gap-4">
|
||||
<li class="py-4 text-2xl font-bold tracking-wide">Active Tasks</li>
|
||||
<button class="cursor-pointer scale-75" hx-get="/active-jobs" hx-target="#active_jobs_section" hx-swap="outerHTML">
|
||||
<section id="active_jobs_section" class="nb-panel p-4 space-y-4 mt-6 sm:mt-8">
|
||||
<header class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h2 class="text-xl font-extrabold tracking-tight">Active Tasks</h2>
|
||||
<button class="nb-btn btn-square btn-sm" hx-get="/active-jobs" hx-target="#active_jobs_section" hx-swap="outerHTML"
|
||||
aria-label="Refresh active tasks">
|
||||
{% include "icons/refresh_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
{% for item in active_jobs %}
|
||||
<li class="list-row">
|
||||
<div class="bg-secondary rounded-box size-10 flex justify-center items-center text-secondary-content">
|
||||
{% if item.content.Url %}
|
||||
{% include "icons/link_icon.html" %}
|
||||
{% elif item.content.File %}
|
||||
{% include "icons/document_icon.html" %}
|
||||
{% else %}
|
||||
{% include "icons/bars_icon.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="[&:before]:content-['Status:_'] [&:before]:opacity-60">
|
||||
{% if item.status.name == "InProgress" %}
|
||||
In Progress, attempt {{item.status.attempts}}
|
||||
{% elif item.status.name == "Error" %}
|
||||
Error: {{item.status.message}}
|
||||
{% else %}
|
||||
{{item.status.name}}
|
||||
{% endif %}
|
||||
</header>
|
||||
{% if active_jobs %}
|
||||
<ul class="flex flex-col gap-3 list-none p-0 m-0">
|
||||
{% for item in active_jobs %}
|
||||
<li class="nb-panel p-3 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div class="size-10 shrink-0 flex items-center justify-center border-2 border-neutral bg-transparent">
|
||||
{% if item.content.Url %}
|
||||
{% include "icons/link_icon.html" %}
|
||||
{% elif item.content.File %}
|
||||
{% include "icons/document_icon.html" %}
|
||||
{% else %}
|
||||
{% include "icons/bars_icon.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="text-sm font-semibold">
|
||||
{% if item.status.name == "InProgress" %}
|
||||
In progress, attempt {{ item.status.attempts }}
|
||||
{% elif item.status.name == "Error" %}
|
||||
Error: {{ item.status.message }}
|
||||
{% else %}
|
||||
{{ item.status.name }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-xs font-semibold opacity-60">
|
||||
{{ item.created_at|datetimeformat(format="short", tz=user.timezone) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs font-semibold opacity-60">
|
||||
{{item.created_at|datetimeformat(format="short", tz=user.timezone)}} </div>
|
||||
</div>
|
||||
<p class="list-col-wrap text-xs [&:before]:content-['Content:_'] [&:before]:opacity-60">
|
||||
{% if item.content.Url %}
|
||||
{{item.content.Url.url}}
|
||||
{% elif item.content.File %}
|
||||
{{item.content.File.file_info.file_name}}
|
||||
{% else %}
|
||||
{{item.content.Text.text}}
|
||||
{% endif %}
|
||||
</p>
|
||||
<button hx-delete="/jobs/{{item.id}}" hx-target="#active_jobs_section" hx-swap="outerHTML"
|
||||
class="btn btn-square btn-ghost btn-sm">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="sm:flex-1 sm:text-right">
|
||||
<p class="text-xs opacity-80 leading-snug break-words">
|
||||
{% if item.content.Url %}
|
||||
{{ item.content.Url.url }}
|
||||
{% elif item.content.File %}
|
||||
{{ item.content.File.file_info.file_name }}
|
||||
{% else %}
|
||||
{{ item.content.Text.text }}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button hx-delete="/jobs/{{ item.id }}" hx-target="#active_jobs_section" hx-swap="outerHTML"
|
||||
class="nb-btn btn-square btn-sm" aria-label="Cancel task">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -7,8 +7,20 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="flex justify-center grow mt-2 sm:mt-4 pb-4">
|
||||
<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" %}
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
{% for task in tasks %}
|
||||
<li class="list-row" hx-ext="sse" sse-connect="/task/status-stream?task_id={{task.id}}" sse-close="close_stream">
|
||||
<div class="bg-secondary rounded-box size-10 flex justify-center items-center text-secondary-content"
|
||||
sse-swap="stop_loading" hx-swap="innerHTML">
|
||||
<span class="loading loading-spinner loading-xl"></span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex gap-1">
|
||||
<div sse-swap="status" hx-swap="innerHTML">
|
||||
Created
|
||||
</div>
|
||||
<div hx-get="/content/recent" hx-target="#latest_content_section" hx-swap="outerHTML"
|
||||
hx-trigger="sse:update_latest_content"></div>
|
||||
<li class="nb-panel p-3 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"
|
||||
hx-ext="sse" sse-connect="/task/status-stream?task_id={{task.id}}" sse-close="close_stream">
|
||||
<div class="flex items-center gap-3 flex-1 min-w-0">
|
||||
<div class="size-10 flex items-center justify-center border-2 border-neutral bg-transparent"
|
||||
sse-swap="stop_loading" hx-swap="innerHTML">
|
||||
<span class="loading loading-spinner loading-md"></span>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="text-sm font-semibold flex gap-2 items-center">
|
||||
<span sse-swap="status" hx-swap="innerHTML">Created</span>
|
||||
<div hx-get="/content/recent" hx-target="#latest_content_section" hx-swap="outerHTML"
|
||||
hx-trigger="sse:update_latest_content"></div>
|
||||
</div>
|
||||
<div class="text-xs font-semibold opacity-60">
|
||||
{{task.created_at|datetimeformat(format="short", tz=user.timezone)}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs font-semibold opacity-60">
|
||||
{{task.created_at|datetimeformat(format="short", tz=user.timezone)}} </div>
|
||||
</div>
|
||||
<p class="list-col-wrap text-xs [&:before]:content-['Content:_'] [&:before]:opacity-60">
|
||||
{% if task.content.Url %}
|
||||
{{task.content.Url.url}}
|
||||
{% elif task.content.File %}
|
||||
{{task.content.File.file_info.file_name}}
|
||||
{% else %}
|
||||
{{task.content.Text.text}}
|
||||
{% endif %}
|
||||
</p>
|
||||
<button hx-delete="/jobs/{{task.id}}" hx-target="#active_jobs_section" hx-swap="outerHTML"
|
||||
class="btn btn-square btn-ghost btn-sm">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
|
||||
<div class="sm:flex-1 sm:text-right">
|
||||
<p class="text-xs opacity-80 leading-snug break-words">
|
||||
{% if task.content.Url %}
|
||||
{{task.content.Url.url}}
|
||||
{% elif task.content.File %}
|
||||
{{task.content.File.file_info.file_name}}
|
||||
{% else %}
|
||||
{{task.content.Text.text}}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button hx-delete="/jobs/{{task.id}}" hx-target="#active_jobs_section" hx-swap="outerHTML"
|
||||
class="nb-btn btn-square btn-sm" aria-label="Cancel task">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
25
html-router/templates/dashboard/statistics.html
Normal file
25
html-router/templates/dashboard/statistics.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<section class="mb-4 sm:mt-4">
|
||||
<h2 class="text-2xl font-extrabold tracking-tight mb-3">Overview</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||
<div class="nb-stat">
|
||||
<div class="text-xs opacity-70">Total Documents</div>
|
||||
<div class="text-3xl font-extrabold">{{ stats.total_documents }}</div>
|
||||
<div class="text-xs opacity-60">+{{ stats.new_documents_week }} this week</div>
|
||||
</div>
|
||||
<div class="nb-stat">
|
||||
<div class="text-xs opacity-70">Text Chunks</div>
|
||||
<div class="text-3xl font-extrabold">{{ stats.total_text_chunks }}</div>
|
||||
<div class="text-xs opacity-60">+{{ stats.new_text_chunks_week }} this week</div>
|
||||
</div>
|
||||
<div class="nb-stat">
|
||||
<div class="text-xs opacity-70">Knowledge Entities</div>
|
||||
<div class="text-3xl font-extrabold">{{ stats.total_entities }}</div>
|
||||
<div class="text-xs opacity-60">+{{ stats.new_entities_week }} this week</div>
|
||||
</div>
|
||||
<div class="nb-stat">
|
||||
<div class="text-xs opacity-70">Conversations</div>
|
||||
<div class="text-3xl font-extrabold">{{ stats.total_conversations }}</div>
|
||||
<div class="text-xs opacity-60">+{{ stats.new_conversations_week }} this week</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -4,39 +4,37 @@ hx-post="/ingress-form"
|
||||
enctype="multipart/form-data"
|
||||
{% endblock %}
|
||||
{% block modal_content %}
|
||||
<h3 class="text-lg font-bold">Add new content</h3>
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span>Content</span>
|
||||
<textarea name="content" class="textarea input-bordered w-full"
|
||||
placeholder="Enter the content you want to ingest, it can be an URL or a text snippet">{{ content }}</textarea>
|
||||
<h3 class="text-xl font-extrabold tracking-tight">Add New Content</h3>
|
||||
<div class="flex flex-col gap-3">
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Content</div>
|
||||
<textarea name="content" class="nb-input w-full min-h-28"
|
||||
placeholder="Paste a URL or type/paste text to ingest…">{{ content }}</textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span>Context</span>
|
||||
<textarea name="context" class="textarea w-full"
|
||||
placeholder="Enter context for the AI here, help it understand what its seeing or how it should relate to the database">{{
|
||||
context }}</textarea>
|
||||
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Context</div>
|
||||
<textarea name="context" class="nb-input w-full min-h-24"
|
||||
placeholder="Optional: add context to guide how the content should be interpreted…">{{ context }}</textarea>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span>Category</span>
|
||||
<input type="text" name="category" class="input input-bordered validator w-full" value="{{ category }}"
|
||||
list="category-list" required />
|
||||
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Category</div>
|
||||
<input type="text" name="category" class="nb-input validator w-full" value="{{ category }}" list="category-list" required />
|
||||
<datalist id="category-list">
|
||||
{% for category in user_categories %}
|
||||
<option value="{{ category }}" />
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
<div class="validator-hint hidden">Category is required</div>
|
||||
<div class="validator-hint hidden text-xs opacity-70 mt-1">Category is required</div>
|
||||
</label>
|
||||
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Files</div>
|
||||
<input type="file" name="files" multiple class="file-input w-full rounded-none border-2 border-neutral" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label label-text">Files</label>
|
||||
<input type="file" name="files" multiple class="file-input file-input-bordered w-full" />
|
||||
</div>
|
||||
|
||||
<div id="error-message" class="text-error text-center {% if not error %}hidden{% endif %}">{{ error }}</div>
|
||||
<script>
|
||||
(function () {
|
||||
@@ -54,7 +52,7 @@ enctype="multipart/form-data"
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block primary_actions %}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Save Changes
|
||||
<button type="submit" class="nb-btn nb-cta">
|
||||
Add Content
|
||||
</button>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
{% 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 overflow-y-auto">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4">
|
||||
<h2 class="text-2xl font-bold">Knowledge Entities</h2>
|
||||
<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 flex flex-col sm:flex-row justify-between items-start sm:items-center">
|
||||
<h2 class="text-xl font-extrabold tracking-tight">Knowledge Entities</h2>
|
||||
<form hx-get="/knowledge" hx-target="#knowledge_pane" hx-push-url="true" hx-swap="outerHTML"
|
||||
class="flex items-center gap-4 mt-2 sm:mt-0">
|
||||
<div class="form-control">
|
||||
<select name="entity_type" class="select select-bordered">
|
||||
class="flex items-center gap-2 mt-2 sm:mt-0">
|
||||
<div>
|
||||
<select name="entity_type" class="nb-select">
|
||||
<option value="">All Types</option>
|
||||
{% for type in entity_types %}
|
||||
<option value="{{ type }}" {% if selected_entity_type==type %}selected{% endif %}>{{ type }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<select name="content_category" class="select select-bordered">
|
||||
<div>
|
||||
<select name="content_category" class="nb-select">
|
||||
<option value="">All Categories</option>
|
||||
{% for category in content_categories %}
|
||||
<option value="{{ category }}" {% if selected_content_category==category %}selected{% endif %}>{{ category
|
||||
@@ -26,12 +26,12 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">Filter</button>
|
||||
<button type="submit" class="nb-btn btn-sm">Filter</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h2 class="text-2xl font-bold mb-2 mt-10">Graph</h2>
|
||||
<div class="rounded-box overflow-clip mt-4 shadow p-2 mb-10">
|
||||
<div class="nb-card mt-4 p-2 mb-30">
|
||||
<div id="knowledge-graph" class="w-full" style="height: 640px;"
|
||||
data-entity-type="{{ selected_entity_type | default(value='') }}"
|
||||
data-content-category="{{ selected_content_category | default(value='') }}">
|
||||
|
||||
@@ -7,18 +7,18 @@ hx-swap="outerHTML"
|
||||
{% endblock %}
|
||||
|
||||
{% block modal_content %}
|
||||
<h3 class="text-lg font-bold">Edit Entity</h3>
|
||||
<h3 class="text-xl font-extrabold tracking-tight">Edit Entity</h3>
|
||||
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span class="label-text">Name</span>
|
||||
<input type="text" name="name" value="{{ entity.name }}" class="input input-bordered w-full">
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Name</div>
|
||||
<input type="text" name="name" value="{{ entity.name }}" class="nb-input w-full">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control relative" style="margin-top: -1.5rem;">
|
||||
<div class="absolute !left-3 !top-2.5 z-50 p-0.5 bg-white text-xs text-light">Type</div>
|
||||
<select name="entity_type" class="select w-full">
|
||||
<div class="absolute !left-3 !top-2.5 z-50 p-0.5 bg-base-100 text-xs">Type</div>
|
||||
<select name="entity_type" class="nb-select w-full">
|
||||
<option disabled>You must select a type</option>
|
||||
{% for et in entity_types %}
|
||||
<option value="{{ et }}" {% if entity.entity_type==et %}selected{% endif %}>{{ et }}</option>
|
||||
@@ -29,15 +29,13 @@ hx-swap="outerHTML"
|
||||
<input type="text" name="id" value="{{ entity.id }}" class="hidden">
|
||||
|
||||
<div class="form-control">
|
||||
<label class="floating-label">
|
||||
<span class="label-text">Description</span>
|
||||
<textarea name="description" class="w-full textarea textarea-bordered h-32">{{ entity.description }}</textarea>
|
||||
<label class="w-full">
|
||||
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Description</div>
|
||||
<textarea name="description" class="nb-input w-full h-32">{{ entity.description }}</textarea>
|
||||
</label>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block primary_actions %}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Save Changes
|
||||
</button>
|
||||
{% endblock %}
|
||||
<button type="submit" class="nb-btn nb-cta">Save Changes</button>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="grid md:grid-cols-2 2xl:grid-cols-3 gap-4" id="entity-list">
|
||||
<div class="grid md:grid-cols-2 2xl:grid-cols-3 gap-4 mt-6" id="entity-list">
|
||||
{% for entity in entities %}
|
||||
<div class="card min-w-72 bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
@@ -22,4 +22,4 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<div id="relationship_table_section"
|
||||
class="overflow-x-auto shadow rounded-box border border-base-content/5 bg-base-100 mb-10">
|
||||
<table class="table">
|
||||
<div id="relationship_table_section" class="overflow-x-auto nb-card mb-10">
|
||||
<table class="nb-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Origin</th>
|
||||
<th>Target</th>
|
||||
<th>Type</th>
|
||||
<th>Actions</th>
|
||||
<th class="text-left">Origin</th>
|
||||
<th class="text-left">Target</th>
|
||||
<th class="text-left">Type</th>
|
||||
<th class="text-left">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -31,9 +30,9 @@
|
||||
{{ relationship.out }}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ relationship.metadata.relationship_type }}</td>
|
||||
<td class="uppercase tracking-wide text-xs">{{ relationship.metadata.relationship_type }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline" hx-delete="/knowledge-relationship/{{ relationship.id }}"
|
||||
<button class="nb-btn btn-xs" hx-delete="/knowledge-relationship/{{ relationship.id }}"
|
||||
hx-target="#relationship_table_section" hx-swap="outerHTML">
|
||||
{% include "icons/delete_icon.html" %}
|
||||
</button>
|
||||
@@ -43,7 +42,7 @@
|
||||
<!-- New linking row -->
|
||||
<tr id="new_relationship">
|
||||
<td>
|
||||
<select name="in_" class="select select-bordered w-full new_relationship_input">
|
||||
<select name="in_" class="nb-select w-full new_relationship_input">
|
||||
<option disabled selected>Select Origin</option>
|
||||
{% for entity in entities %}
|
||||
<option value="{{ entity.id }}">
|
||||
@@ -53,7 +52,7 @@
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select name="out" class="select select-bordered w-full new_relationship_input">
|
||||
<select name="out" class="nb-select w-full new_relationship_input">
|
||||
<option disabled selected>Select Target</option>
|
||||
{% for entity in entities %}
|
||||
<option value="{{ entity.id }}">{{ entity.name }}</option>
|
||||
@@ -62,12 +61,11 @@
|
||||
</td>
|
||||
<td>
|
||||
<input id="relationship_type_input" name="relationship_type" type="text" placeholder="RelatedTo"
|
||||
class="input input-bordered w-full new_relationship_input" />
|
||||
class="nb-input w-full new_relationship_input" />
|
||||
</td>
|
||||
<td>
|
||||
<button id="save_relationship_button" type="button" class="btn btn-primary btn-sm"
|
||||
hx-post="/knowledge-relationship" hx-target="#relationship_table_section" hx-swap="outerHTML"
|
||||
hx-include=".new_relationship_input">
|
||||
<button id="save_relationship_button" type="button" class="nb-btn btn-sm" hx-post="/knowledge-relationship"
|
||||
hx-target="#relationship_table_section" hx-swap="outerHTML" hx-include=".new_relationship_input">
|
||||
Save
|
||||
</button>
|
||||
</td>
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
<dialog id="body_modal" class="modal">
|
||||
<div class="modal-box {% block modal_class %}{% endblock %} ">
|
||||
<div class="modal-box rounded-none border-2 border-neutral bg-base-100 shadow-[8px_8px_0_0_#000] {% block modal_class %}{% endblock %}">
|
||||
<form id="modal_form" {% block form_attributes %}{% endblock %}>
|
||||
<div class="flex flex-col flex-1 space-y-4">
|
||||
{% block modal_content %} <!-- Form fields go here in child templates -->
|
||||
{% endblock %}
|
||||
<div class="flex flex-col flex-1 gap-4">
|
||||
{% block modal_content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<div class="u-hairline mt-4 pt-3 flex justify-end gap-2">
|
||||
<!-- Close button (always visible) -->
|
||||
<button type="button" class="btn" onclick="document.getElementById('body_modal').close()">
|
||||
<button type="button" class="nb-btn" onclick="document.getElementById('body_modal').close()">
|
||||
Close
|
||||
</button>
|
||||
|
||||
<!-- Primary actions block -->
|
||||
{% block primary_actions %}
|
||||
<!-- Submit/Save buttons go here in child templates -->
|
||||
{% endblock %}
|
||||
{% block primary_actions %}{% endblock %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -38,4 +35,4 @@
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</dialog>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<nav class="bg-base-200 sticky top-0 z-10">
|
||||
<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">
|
||||
{% include "searchbar.html" %}
|
||||
@@ -12,4 +12,4 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
{% 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">
|
||||
<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 %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% if search_result is defined and search_result %}
|
||||
<ul class="list shadow">
|
||||
<ul class="nb-card p-0">
|
||||
{% for result in search_result %}
|
||||
<li class="list-row hover:bg-base-200/50 p-4">
|
||||
<div class="w-10 h-10 flex-shrink-0 mr-4 self-start mt-1">
|
||||
<li class="p-4 u-hairline hover:bg-base-200/40 flex gap-3">
|
||||
<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 result.url_info and result.url_info.url %}
|
||||
<div class="tooltip tooltip-right" data-tip="Web Link">
|
||||
{% include "icons/link_icon.html" %}
|
||||
@@ -17,10 +17,10 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex-grow min-w-0">
|
||||
<h3 class="text-lg font-semibold mb-1">
|
||||
<a hx-get="/content/{{ result.id }}/read" hx-target="#modal" hx-swap="innerHTML"
|
||||
class="link link-hover link-primary">
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-extrabold mb-1 leading-snug">
|
||||
<a hx-get="/content/{{ result.id }}/read" hx-target="#modal" hx-swap="innerHTML" class="nb-link">
|
||||
{% set title_text = result.highlighted_url_title
|
||||
| default(result.url_info.title if result.url_info else none, true)
|
||||
| default(result.highlighted_file_name, true)
|
||||
@@ -30,8 +30,7 @@
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<div class="markdown-content prose prose-sm text-sm text-base-content/80 mb-3 overflow-hidden line-clamp-6"
|
||||
data-content="{{result.highlighted_text | escape}}">
|
||||
<div class="markdown-content prose-tufte-compact text-base-content/80 mb-3 overflow-hidden line-clamp-6" data-content="{{result.highlighted_text | escape}}">
|
||||
{% if result.highlighted_text %}
|
||||
{{ result.highlighted_text | escape }}
|
||||
{% elif result.text %}
|
||||
@@ -41,43 +40,46 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-base-content/70 flex flex-wrap gap-x-4 gap-y-1 items-center">
|
||||
<span class="inline-flex items-center"><strong class="font-medium mr-1">Category:</strong>
|
||||
<span class="badge badge-soft badge-secondary badge-sm">{{ result.highlighted_category |
|
||||
default(result.category, true) |
|
||||
safe }}</span>
|
||||
<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">{{ result.highlighted_category | default(result.category, true) | safe }}</span>
|
||||
</span>
|
||||
|
||||
{% if result.highlighted_context or result.context %}
|
||||
<span class="inline-flex items-center"><strong class="font-medium mr-1">Context:</strong>
|
||||
<span class="badge badge-sm badge-outline">{{ result.highlighted_context | default(result.context, true) |
|
||||
safe }}</span>
|
||||
<span class="inline-flex items-center min-w-0">
|
||||
<span class="uppercase tracking-wide opacity-60 mr-2">Context</span>
|
||||
<span class="nb-badge">{{ result.highlighted_context | default(result.context, true) | safe }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if result.url_info and result.url_info.url %}
|
||||
<span class="inline-flex items-center min-w-0"><strong class="font-medium mr-1">Source:</strong>
|
||||
<a href="{{ result.url_info.url }}" target="_blank" class="link link-hover link-xs truncate"
|
||||
title="{{ result.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="{{ result.url_info.url }}" target="_blank" class="nb-link truncate" title="{{ result.url_info.url }}">
|
||||
{{ result.highlighted_url | default(result.url_info.url ) | safe }}
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="badge badge-ghost badge-sm">Score: {{ result.score }}</span>
|
||||
|
||||
<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>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
{% elif query_param is defined and query_param | trim != "" %}
|
||||
<div class="p-4 text-center text-base-content/70">
|
||||
<p class="text-xl font-semibold mb-2">No results found for "<strong>{{ query_param | escape }}</strong>".</p>
|
||||
<p class="text-sm">Try using different keywords or checking for typos.</p>
|
||||
</div>
|
||||
<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>
|
||||
{% else %}
|
||||
<div class="p-4 text-center text-base-content/70">
|
||||
<p class="text-lg font-medium">Enter a term above to search your knowledge base.</p>
|
||||
<p class="text-sm">Results will appear here.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<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 %}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<div class="flex items-center gap-2 min-w-[90px]">
|
||||
<form class="w-full" hx-boost="true" method="get" action="/search"
|
||||
<div class="flex items-center gap-2 min-w-[90px] w-full">
|
||||
<form class="w-full relative" hx-boost="true" method="get" action="/search"
|
||||
hx-trigger="keyup changed delay:500ms from:#search-input, search from:#search-input" hx-push-url="true">
|
||||
<input id="search-input" type="search" placeholder="Search for anything..."
|
||||
class="input input-sm input-bordered input-primary w-full" name="query" autocomplete="off"
|
||||
value="{{ query_param | default('', true) }}" />
|
||||
<input id="search-input" type="search" aria-label="Search" class=" nb-input w-full pl-9 ml-2" name="query"
|
||||
autocomplete="off" value="{{ query_param | default('', true) }}" />
|
||||
<button type="submit"
|
||||
class="absolute right-1 top-1/2 -translate-y-1/2 nb-btn btn-xs px-3 h-7 bg-base-100 hover:bg-base-200">
|
||||
Search
|
||||
</button>
|
||||
<span class="hidden md:inline absolute right-24 top-1/2 -translate-y-1/2 text-xs opacity-60">
|
||||
press <kbd class="kbd kbd-xs">Enter</kbd>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
@@ -15,11 +15,12 @@
|
||||
<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 bg-base-200 text-base-content flex flex-col">
|
||||
<!-- <a class="px-2 mt-4 text-center text-2xl text-primary font-bold" href="/" hx-boost="true">Minne</a> -->
|
||||
<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-14">
|
||||
<div class="px-2 mt-4 space-y-3">
|
||||
{% for url, name, label in [
|
||||
("/", "home", "Dashboard"),
|
||||
("/knowledge", "book", "Knowledge"),
|
||||
@@ -28,22 +29,22 @@
|
||||
("/search", "search", "Search")
|
||||
] %}
|
||||
<li>
|
||||
<a hx-boost="true" href="{{ url }}" class="flex items-center gap-3">
|
||||
<a hx-boost="true" href="{{ url }}" class="nb-btn w-full justify-start gap-3 bg-base-100 hover:bg-base-200">
|
||||
{{ icon(name) }}
|
||||
<span>{{ label }}</span>
|
||||
<span class="uppercase tracking-wide">{{ label }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li>
|
||||
<button class="btn btn-primary btn-outline w-full flex items-center gap-3 justify-start mt-2"
|
||||
hx-get="/ingress-form" hx-target="#modal" hx-swap="innerHTML">{% include "icons/send_icon.html" %} Add
|
||||
<button class="nb-btn nb-cta w-full flex items-center gap-3 justify-start mt-2" hx-get="/ingress-form"
|
||||
hx-target="#modal" hx-swap="innerHTML">{% include "icons/send_icon.html" %} Add
|
||||
Content</button>
|
||||
</li>
|
||||
<div class="divider "></div>
|
||||
<div class="u-hairline mt-4"></div>
|
||||
</div>
|
||||
|
||||
<!-- === MIDDLE SCROLLABLE SECTION === -->
|
||||
<span class="menu-title pb-4 ">Recent Chats</span>
|
||||
<span class="px-4 py-2 font-semibold tracking-wide">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 %}
|
||||
@@ -51,12 +52,12 @@
|
||||
{% 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>
|
||||
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">
|
||||
class="btn btn-ghost btn-xs !p-0">
|
||||
{% include "icons/x_icon.html" %}
|
||||
</button>
|
||||
</div>
|
||||
@@ -86,29 +87,30 @@
|
||||
</div>
|
||||
|
||||
<!-- === BOTTOM FIXED SECTION === -->
|
||||
<div class="px-2 pb-4">
|
||||
<div class="divider "></div>
|
||||
<div class="px-2 pb-4 space-y-3">
|
||||
<li>
|
||||
<a hx-boost="true" href="/account" class="flex btn btn-ghost justify-start items-center gap-3">
|
||||
<a hx-boost="true" href="/account"
|
||||
class="nb-btn w-full justify-start items-center gap-3 bg-base-100 hover:bg-base-200">
|
||||
{% include "icons/user_icon.html" %}
|
||||
<span>Account</span>
|
||||
<span class="uppercase tracking-wide">Account</span>
|
||||
</a>
|
||||
</li>
|
||||
{% if user.admin %}
|
||||
<li>
|
||||
<a hx-boost="true" href="/admin" class="flex btn btn-ghost justify-start items-center gap-3">
|
||||
<a hx-boost="true" href="/admin"
|
||||
class="nb-btn w-full justify-start items-center gap-3 bg-base-100 hover:bg-base-200">
|
||||
{% include "icons/wrench_screwdriver_icon.html" %}
|
||||
<span>Admin</span>
|
||||
<span class="uppercase tracking-wide">Admin</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a hx-boost="true" href="/signout"
|
||||
class="btn btn-error btn-outline w-full flex items-center gap-3 justify-start !mt-2">
|
||||
class="nb-btn w-full justify-start items-center gap-3 bg-base-100 hover:bg-base-200 border-error text-error">
|
||||
{% include "icons/logout_icon.html" %}
|
||||
<span>Logout</span>
|
||||
<span class="uppercase tracking-wide">Logout</span>
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user