Files
minne/html-router/templates/chat/reference_list.html
T
Per Stark 7b850769c9 fix: html-router modals and add insta snapshot tests.
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.
2026-06-03 20:20:43 +02:00

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>