const Utils = { escapeHtml(str) { if (str === null || str === undefined) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }, formatDuration(ms) { if (!ms || ms < 0) return '—'; if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; const m = Math.floor(ms / 60000); const s = Math.floor((ms % 60000) / 1000); return `${m}m ${s}s`; }, formatCost(usd) { if (!usd || usd === 0) return '$0.0000'; return `$${Number(usd).toFixed(4)}`; }, truncate(str, max = 80) { if (!str) return ''; return str.length > max ? str.slice(0, max) + '…' : str; }, refreshIcons(container) { if (!window.lucide) return; const target = container || document; const pending = target.querySelectorAll('i[data-lucide]'); if (pending.length === 0) return; lucide.createIcons(); }, formatFileSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; }, initDropzone(dropzoneId, fileInputId, fileListId) { const zone = document.getElementById(dropzoneId); const input = document.getElementById(fileInputId); const list = document.getElementById(fileListId); if (!zone || !input || !list) return null; const state = { files: [] }; function render() { list.innerHTML = state.files.map((f, i) => `