mirror of
https://github.com/perstarkse/minne.git
synced 2026-06-12 17:24:26 +02:00
7b850769c9
Avoid nested forms in the scratchpad editor, centralize modal lifecycle in modal.js, return HTMX partials from archive, and add template compile plus layout snapshots.
155 lines
5.7 KiB
HTML
155 lines
5.7 KiB
HTML
<div class="relative my-2">
|
|
<button id="references-toggle-{{message.id}}" data-message-id="{{message.id}}"
|
|
class="references-toggle nb-btn btn-xs bg-base-100 hover:bg-base-200 flex items-center">
|
|
REFERENCES
|
|
{% include "icons/chevron_icon.html" %}
|
|
</button>
|
|
<div id="references-content-{{message.id}}" class="hidden max-w-full mt-1">
|
|
<div id="references-list-{{message.id}}" class="flex flex-wrap gap-1">
|
|
{% for reference in message.references %}
|
|
<div class="reference-badge-container" data-reference="{{reference}}" data-message-id="{{message.id}}"
|
|
data-index="{{loop.index}}">
|
|
<span class="nb-badge truncate max-w-[20ch] overflow-hidden text-left block cursor-pointer">
|
|
{{reference}}
|
|
</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Register global handlers once; this fragment is re-injected per SSE response.
|
|
if (!window.__referencesInit) {
|
|
window.__referencesInit = true;
|
|
|
|
document.body.addEventListener('click', function (e) {
|
|
const btn = e.target.closest('.references-toggle');
|
|
if (btn) toggleReferences(btn.dataset.messageId);
|
|
});
|
|
|
|
document.addEventListener('DOMContentLoaded', initializeReferenceTooltips);
|
|
document.body.addEventListener('htmx:afterSwap', initializeReferenceTooltips);
|
|
}
|
|
|
|
function toggleReferences(messageId) {
|
|
const refsContent = document.getElementById('references-content-' + messageId);
|
|
const refsList = document.getElementById('references-list-' + messageId);
|
|
const toggleBtn = document.getElementById('references-toggle-' + messageId);
|
|
|
|
// Toggle visibility
|
|
if (refsContent.classList.contains('hidden')) {
|
|
refsContent.classList.remove('hidden');
|
|
|
|
// Wait for DOM update then scroll to make visible
|
|
setTimeout(() => {
|
|
refsList.scrollIntoView({behavior: 'smooth', block: 'nearest'});
|
|
|
|
// Also ensure chat container updates its scroll position
|
|
const chatContainer = document.getElementById('chat_container');
|
|
if (chatContainer) {
|
|
const refPosition = refsList.getBoundingClientRect().bottom;
|
|
const containerBottom = chatContainer.getBoundingClientRect().bottom;
|
|
|
|
if (refPosition > containerBottom) {
|
|
chatContainer.scrollTop += (refPosition - containerBottom + 20);
|
|
}
|
|
}
|
|
}, 50);
|
|
} else {
|
|
refsContent.classList.add('hidden');
|
|
}
|
|
|
|
// Rotate chevron icon (assuming it's the first SVG in the button)
|
|
const chevron = toggleBtn.querySelector('svg');
|
|
if (chevron) {
|
|
chevron.style.transform = refsContent.classList.contains('hidden') ?
|
|
'rotate(0deg)' : 'rotate(180deg)';
|
|
}
|
|
}
|
|
|
|
function initializeReferenceTooltips() {
|
|
document.querySelectorAll('.reference-badge-container').forEach(container => {
|
|
if (container.dataset.initialized === 'true') return;
|
|
|
|
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 = 'reference-tooltip 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(`/chat/reference/${encodeURIComponent(reference)}`)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error(`reference lookup failed with status ${response.status}`);
|
|
}
|
|
return response.text();
|
|
})
|
|
.then(html => {
|
|
tooltipContent = html;
|
|
if (document.getElementById(tooltipId)) {
|
|
document.getElementById(tooltipId).innerHTML = html;
|
|
}
|
|
})
|
|
.catch(() => {
|
|
tooltipContent = '<div class="text-xs opacity-70">Reference unavailable.</div>';
|
|
if (document.getElementById(tooltipId)) {
|
|
document.getElementById(tooltipId).innerHTML = tooltipContent;
|
|
}
|
|
});
|
|
} else if (tooltip) {
|
|
// Set content if already loaded
|
|
tooltip.innerHTML = tooltipContent;
|
|
}
|
|
});
|
|
|
|
container.addEventListener('mouseleave', function () {
|
|
tooltipTimeout = setTimeout(() => {
|
|
const tooltip = document.getElementById(tooltipId);
|
|
if (tooltip) tooltip.classList.add('hidden');
|
|
}, 200);
|
|
});
|
|
|
|
container.dataset.initialized = 'true';
|
|
});
|
|
}
|
|
|
|
// Initialize any badges present in this freshly injected fragment (idempotent).
|
|
initializeReferenceTooltips();
|
|
</script>
|