llm-semantic-visualizer / src /ui /Components.js
hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
6.77 kB
import { STATE } from '../core/State.js';
import { updateThreadColor, updateThreadVisibility } from '../visualizer/ThreadManager.js';
import { updateHUD } from './HUD.js';
export function updateThreadListUI() {
const ul = document.getElementById('thread-list-ul');
if (!ul) return;
ul.innerHTML = '';
STATE.threads.forEach((t, index) => {
const li = document.createElement('li');
li.className = 'list-item';
if (t.isGhost) {
li.style.borderColor = 'var(--color-brand-secondary)';
li.style.boxShadow = '0 0 8px oklch(from var(--color-brand-secondary) l c h / 0.2)';
}
const isFocused = STATE.focusedThreads.has(index);
const isHidden = STATE.hiddenThreads.has(index);
const hex = '#' + t.color.getHexString();
li.innerHTML = `
<div style="display:flex; align-items:center; gap:8px; overflow:hidden;">
<input type="color" class="thread-color-picker" data-index="${index}" value="${hex}" style="width:16px; height:16px; border:none; padding:0; background:none; cursor:pointer; flex-shrink:0;">
<span style="font-weight:700; color:${hex}; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:110px; font-size: var(--font-size-small);">${t.label} <span style="font-size:9px; opacity:0.5;">ID: ${t.tokenID || 0}</span></span>
</div>
<div style="display:flex; gap:6px; align-items:center; flex-shrink:0;">
<span id="thread-val-${index}" style="font-family: var(--font-mono); font-size: var(--font-size-small); color: var(--color-neutral-400); width:35px; text-align:right; font-weight: 500;">0.00</span>
<button class="btn-focus btn btn-secondary" data-index="${index}" style="
background: ${isFocused ? 'var(--color-semantic-success)' : 'oklch(from var(--color-neutral-50) l c h / 0.04)'};
color: ${isFocused ? 'var(--color-neutral-950)' : 'var(--color-semantic-success)'};
border-color: ${isFocused ? 'var(--color-semantic-success)' : 'oklch(from var(--color-neutral-50) l c h / 0.08)'};
font-size:10px; width:22px; height:22px; min-height:22px; display:flex; align-items:center; justify-content:center; padding:0;
" title="Focus">🎯</button>
<button class="btn-hide btn btn-secondary" data-index="${index}" style="
background: ${isHidden ? 'var(--color-semantic-error)' : 'oklch(from var(--color-neutral-50) l c h / 0.04)'};
color: ${isHidden ? 'var(--color-neutral-50)' : 'var(--color-neutral-400)'};
border-color: ${isHidden ? 'var(--color-semantic-error)' : 'oklch(from var(--color-neutral-50) l c h / 0.08)'};
font-size:10px; width:22px; height:22px; min-height:22px; display:flex; align-items:center; justify-content:center; padding:0;
" title="Toggle Visibility">👁️</button>
</div>
`;
ul.appendChild(li);
});
// Attach color picker event listeners
ul.querySelectorAll('.thread-color-picker').forEach(input => {
input.oninput = (e) => {
const idx = parseInt(e.target.dataset.index);
const hex = e.target.value;
const thread = STATE.threads[idx];
if (thread) {
updateThreadColor(thread, hex);
}
};
});
// Focus state button listener
ul.querySelectorAll('.btn-focus').forEach(btn => {
btn.onclick = (e) => {
const idx = parseInt(e.currentTarget.dataset.index);
if (STATE.focusedThreads.has(idx)) {
STATE.focusedThreads.delete(idx);
} else {
STATE.focusedThreads.add(idx);
}
updateThreadListUI();
updateThreadVisibility();
};
});
// Visibility state button listener
ul.querySelectorAll('.btn-hide').forEach(btn => {
btn.onclick = (e) => {
const idx = parseInt(e.currentTarget.dataset.index);
if (STATE.hiddenThreads.has(idx)) {
STATE.hiddenThreads.delete(idx);
} else {
STATE.hiddenThreads.add(idx);
}
updateThreadListUI();
updateThreadVisibility();
};
});
}
export function displayHunterResults(matches) {
const container = document.getElementById('hunter-results');
if (!container) return;
container.innerHTML = '';
if (matches.length === 0) {
container.innerHTML = '<li class="list-item" style="color: var(--color-neutral-400); justify-content: center; font-size: var(--font-size-small);">No matches found.</li>';
return;
}
matches.forEach(m => {
const li = document.createElement('li');
li.className = 'list-item';
li.style.cssText = `
cursor: pointer; font-family: var(--font-mono); font-size: var(--font-size-small);
`;
li.innerHTML = `
<span style="font-weight: 700; color: var(--color-brand-primary);">DIM ${m.index}</span>
<span style="color: var(--color-neutral-50); font-weight: 600;">${m.value.toFixed(3)}</span>
`;
li.onclick = () => {
STATE.currentSelectedIndex = m.index;
updateHUD();
};
li.onmouseover = () => {
li.style.backgroundColor = 'oklch(from var(--color-brand-primary) l c h / 0.1)';
li.style.borderColor = 'var(--color-brand-primary)';
};
li.onmouseout = () => {
li.style.backgroundColor = 'oklch(from var(--color-neutral-50) l c h / 0.02)';
li.style.borderColor = 'oklch(from var(--color-neutral-50) l c h / 0.04)';
};
container.appendChild(li);
});
}
export function renderDemoBadge(containerTarget, customMessage) {
const el = typeof containerTarget === 'string' ? document.querySelector(containerTarget) : containerTarget;
if (!el) return;
const msg = customMessage || 'DEMO MODE (READ-ONLY) — This action is disabled in the public demo to ensure stability.';
const badgeHtml = `
<div style="margin-top: 12px; padding: 10px 14px; background: oklch(from var(--color-brand-primary) l c h / 0.08); border: 1px solid oklch(from var(--color-brand-primary) l c h / 0.3); border-radius: var(--border-radius-medium, 6px); color: var(--color-neutral-100); font-size: 0.75rem; line-height: 1.4; display: flex; align-items: center; gap: 8px;">
<span style="font-size: 1rem; flex-shrink: 0;">🔒</span>
<span>${msg}</span>
</div>
`;
el.insertAdjacentHTML('beforeend', badgeHtml);
}