mirror of
https://github.com/perstarkse/minne.git
synced 2026-04-24 17:58:31 +02:00
multi chat and history, oob swap
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
<div id="chat_container">
|
||||
<div id="chat_container" class="pl-3 overflow-y-auto h-[calc(100vh-160px)] pb-32">
|
||||
{% for message in history %}
|
||||
{% if message.role == "AI" %}
|
||||
<div class="chat chat-start">
|
||||
<div class="chat-header">{{ message.role }}</div>
|
||||
<div class="chat-bubble">
|
||||
{{ message.content }}
|
||||
<div>
|
||||
<div class="chat-bubble">
|
||||
{{ message.content }}
|
||||
</div>
|
||||
{% if message.references %}
|
||||
{% include "chat/reference_list.html" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="chat chat-end">
|
||||
<div class="chat-header">{{ message.role }}</div>
|
||||
<div class="chat-bubble">
|
||||
{{ message.content }}
|
||||
</div>
|
||||
@@ -19,21 +22,20 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Scroll to latest message after HTMX swap
|
||||
document.body.addEventListener('htmx:afterSwap', function (evt) {
|
||||
const chatContainer = document.getElementById('chat_container');
|
||||
if (chatContainer) {
|
||||
setTimeout(() => {
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
|
||||
// Also scroll when page loads
|
||||
window.addEventListener('load', function () {
|
||||
const chatContainer = document.getElementById('chat_container');
|
||||
if (chatContainer) {
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#chat_container {
|
||||
max-height: 70vh;
|
||||
/* Adjust as needed */
|
||||
overflow-y: auto;
|
||||
/* Enable scrolling */
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
29
templates/chat/new_chat_first_response.html
Normal file
29
templates/chat/new_chat_first_response.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% 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>
|
||||
<button type="submit" class="absolute p-2 cursor-pointer right-0.5 btn-ghost btn-sm top-1">
|
||||
{% include "icons/send_icon.html" %}
|
||||
</button>
|
||||
<label for="my-drawer-2" class="absolute cursor-pointer top-9 right-0.5 p-2 drawer-button xl:hidden z-20 ">
|
||||
{% include "icons/hamburger_icon.html" %}
|
||||
</label>
|
||||
</form>
|
||||
<script>
|
||||
document.getElementById('chat-input').addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
htmx.trigger('#chat-form', 'submit');
|
||||
}
|
||||
});
|
||||
// Clear textarea after successful submission
|
||||
document.getElementById('chat-form').addEventListener('htmx:afterRequest', function (e) {
|
||||
if (e.detail.successful) { // Check if the request was successful
|
||||
document.getElementById('chat-input').value = ''; // Clear the textarea
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="fixed w-full mx-auto max-w-3xl p-4 pb-0 sm:pb-4 left-0 right-0 bottom-0">
|
||||
<form hx-post="/chat/{{conversation.id}}" hx-target="#chat_container" hx-swap="beforeend" class="relative flex gap-2"
|
||||
id="chat-form">
|
||||
<div class="fixed w-full mx-auto max-w-3xl p-4 pb-0 sm:pb-4 left-0 right-0 bottom-0 bg-base-100 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"
|
||||
id="chat-input"></textarea>
|
||||
|
||||
@@ -5,21 +5,19 @@
|
||||
{% include "icons/chevron_icon.html" %}
|
||||
</button>
|
||||
<div id="references-content-{{user_message_id}}" class="hidden max-w-full mt-1">
|
||||
<div class="flex flex-wrap">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{% for reference in references %}
|
||||
<span class="badge badge-sm text-xs truncate max-w-[20ch] overflow-hidden text-left block tooltip"
|
||||
hx-get="/knowledge/{{reference}}" hx-trigger="mouseenter once delay:500ms"
|
||||
hx-target="#tooltip-content-{{loop.index}}-{{user_message_id}}" hx-swap="innerHTML">
|
||||
{{reference}}
|
||||
<div id="tooltip-content-{{loop.index}}-{{user_message_id}}" class="tooltip-content">
|
||||
<!-- Loading indicator while content is fetched -->
|
||||
<div class="animate-pulse text-gray-400 text-xs">Loading...</div>
|
||||
</div>
|
||||
</span>
|
||||
<div class="reference-badge-container" data-reference="{{reference}}" data-message-id="{{user_message_id}}"
|
||||
data-index="{{loop.index}}">
|
||||
<span class="badge badge-xs badge-neutral truncate max-w-[20ch] overflow-hidden text-left block cursor-pointer">
|
||||
{{reference}}
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('references-toggle-{{user_message_id}}').addEventListener('click', function () {
|
||||
const content = document.getElementById('references-content-{{user_message_id}}');
|
||||
@@ -27,4 +25,66 @@
|
||||
content.classList.toggle('hidden');
|
||||
icon.classList.toggle('rotate-180');
|
||||
});
|
||||
|
||||
// Initialize portal tooltips
|
||||
document.querySelectorAll('.reference-badge-container').forEach(container => {
|
||||
const reference = container.dataset.reference;
|
||||
const messageId = container.dataset.messageId;
|
||||
const index = container.dataset.index;
|
||||
let tooltipId = `tooltip-${messageId}-${index}`;
|
||||
let tooltipContent = null;
|
||||
let tooltipTimeout;
|
||||
|
||||
// Create tooltip element (initially hidden)
|
||||
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.innerHTML = '<div class="animate-pulse">Loading...</div>';
|
||||
document.body.appendChild(tooltip);
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
container.addEventListener('mouseenter', function () {
|
||||
// Clear any existing timeout
|
||||
if (tooltipTimeout) clearTimeout(tooltipTimeout);
|
||||
|
||||
// Get or create tooltip
|
||||
let tooltip = document.getElementById(tooltipId);
|
||||
if (!tooltip) tooltip = createTooltip();
|
||||
|
||||
// Position tooltip
|
||||
const rect = container.getBoundingClientRect();
|
||||
tooltip.style.top = `${rect.bottom + window.scrollY + 5}px`;
|
||||
tooltip.style.left = `${rect.left + window.scrollX}px`;
|
||||
|
||||
// Adjust position if it would overflow viewport
|
||||
const tooltipRect = tooltip.getBoundingClientRect();
|
||||
if (rect.left + tooltipRect.width > window.innerWidth - 20) {
|
||||
tooltip.style.left = `${window.innerWidth - tooltipRect.width - 20 + window.scrollX}px`;
|
||||
}
|
||||
|
||||
// Show tooltip
|
||||
tooltip.classList.remove('hidden');
|
||||
|
||||
// Load content if needed
|
||||
if (!tooltipContent) {
|
||||
fetch(`/knowledge/${encodeURIComponent(reference)}`)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
tooltipContent = html;
|
||||
if (document.getElementById(tooltipId)) {
|
||||
document.getElementById(tooltipId).innerHTML = html;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
container.addEventListener('mouseleave', function () {
|
||||
tooltipTimeout = setTimeout(() => {
|
||||
const tooltip = document.getElementById(tooltipId);
|
||||
if (tooltip) tooltip.classList.add('hidden');
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
3
templates/chat/reference_tooltip.html
Normal file
3
templates/chat/reference_tooltip.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div>{{entity.name}}</div>
|
||||
<div>{{entity.description}}</div>
|
||||
<div>{{entity.updated_at|datetimeformat(format="short", tz=user.timezone)}} </div>
|
||||
@@ -19,7 +19,8 @@
|
||||
</div>
|
||||
</div>{% endif %}
|
||||
<div class="mt-4">
|
||||
<form hx-post="/chat" hx-target="body" hx-swap="outerHTML" method="POST" class="flex items-center space-x-4">
|
||||
<form hx-post="/initialized-chat" hx-target="body" hx-swap="outerHTML" method="POST"
|
||||
class="flex items-center space-x-4">
|
||||
<input type="hidden" name="user_query" value="{{ user_query }}">
|
||||
<input type="hidden" name="llm_response" value="{{ answer_content }}">
|
||||
<input type="hidden" name="references" value="{{ answer_references }}">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<nav class="navbar bg-base-200 !p-0">
|
||||
<div class="container flex mx-auto">
|
||||
<div class="flex-1 flex items-center">
|
||||
<a class="text-2xl text-primary font-bold" href="/" hx-boost="true">Minne</a>
|
||||
<a class="text-2xl p-2 text-primary font-bold" href="/" hx-boost="true">Minne</a>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
|
||||
Reference in New Issue
Block a user