/** Chat area DOM helpers. All functions receive the messages container element. */
const TOOL_ICONS = {
web_search: '🔍',
filesystem: '📁',
http_request: '🌐',
code_exec: '⚙️',
terminal: '💻',
ssh_exec: '🖧',
};
function esc(str) {
return String(str ?? '')
.replace(/&/g, '&').replace(/</g, '<')
.replace(/>/g, '>').replace(/"/g, '"');
}
function timeLabel(iso) {
if (!iso) return '';
const d = new Date(iso);
if (isNaN(d)) return '';
const diff = Date.now() - d;
if (diff < 60_000) return 'just now';
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
if (diff < 86_400_000) return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
return d.toLocaleDateString();
}
export function appendMessage(el, role, content) {
const wrap = document.createElement('div');
wrap.className = `msg ${role}`;
const bubble = document.createElement('div');
bubble.className = 'bubble';
bubble.textContent = content;
const time = document.createElement('div');
time.className = 'msg-time';
time.textContent = timeLabel(new Date().toISOString());
wrap.append(bubble, time);
el.appendChild(wrap);
return bubble;
}
export function appendToolCall(el, event) {
const icon = TOOL_ICONS[event.tool] ?? '🔧';
const card = document.createElement('div');
card.className = `tool-card${event.success ? '' : ' error'}`;
card.innerHTML = `
<div class="tool-header">
<span class="tool-icon">${icon}</span>
<span>${esc(event.tool)}</span>
</div>
<div class="tool-result">${esc(event.result)}</div>`;
el.appendChild(card);
}
export function appendTypingIndicator(el) {
removeTypingIndicator(el);
const div = document.createElement('div');
div.className = 'typing';
div.id = 'typing-indicator';
div.innerHTML = '<span></span><span></span><span></span>';
el.appendChild(div);
}
export function removeTypingIndicator(el) {
el.querySelector('#typing-indicator')?.remove();
}
export function appendError(el, message) {
const div = document.createElement('div');
div.className = 'msg-error';
div.textContent = `Error: ${message}`;
el.appendChild(div);
}
export function showEmptyState(el) {
el.innerHTML = `
<div class="empty-chat">
<div class="icon">💬</div>
<p>Start a new conversation</p>
</div>`;
}
export function scrollToBottom(el) {
el.scrollTop = el.scrollHeight;
}