โ€”

๐Ÿ‘‹ Welcome to ViewLLM

Browse, search, and preview HTML reports from AI coding agents. Select a file from the sidebar to get started.

๐Ÿ“‚ File tree ๐Ÿ” Search ๐Ÿ”„ Unread tracking ๐ŸŽจ Themes ๐Ÿ“ฑ Mobile ready

โš™๏ธ Settings

๐Ÿ“„ HTML
๐Ÿ“ Markdown
Text preview
Thumbnail preview

๐Ÿ“„ ${file.path}

This is a mock preview of ${file.path}.

Generated at ${new Date(file.mtime).toLocaleString()}

Share

Submit to AI Directories

Futurepedia AI Tool Hunt Awesome AI Tools ToolPilot AI Infinity

Nebula AI Tool Base

`; } else { content = `# ${file.path}\n\nThis is a **Markdown** preview for *${file.path}*.\n\n- Item 1\n- Item 2\n- Item 3\n\n> Generated at ${new Date(file.mtime).toLocaleString()}`; } const blob = new Blob([content], { type: 'text/html' }); previewIframe.src = URL.createObjectURL(blob); // update hash history.replaceState(null, '', '#' + encodeURIComponent(file.path)); // re-render to update unread dots renderRecent(); renderTree(); // close mobile sidebar closeSidebar(); } // ----- SIDEBAR TOGGLE (mobile) ----- function openSidebar() { sidebar.classList.add('open'); sidebarOverlay.classList.add('open'); } function closeSidebar() { sidebar.classList.remove('open'); sidebarOverlay.classList.remove('open'); } mobileBtn.addEventListener('click', openSidebar); sidebarOverlay.addEventListener('click', closeSidebar); // ----- SETTINGS TOGGLE ----- function openSettings() { settingsOverlay.classList.add('open'); settingsPanel.classList.add('open'); } function closeSettings() { settingsOverlay.classList.remove('open'); settingsPanel.classList.remove('open'); } settingsToggle.addEventListener('click', openSettings); settingsClose.addEventListener('click', closeSettings); settingsOverlay.addEventListener('click', closeSettings); // ----- SETTINGS BINDINGS ----- themeSelect.addEventListener('change', () => { settings.theme = themeSelect.value; document.documentElement.setAttribute('data-theme', settings.theme); saveSettings(); }); showHtmlCb.addEventListener('change', () => { settings.showHtml = showHtmlCb.checked; saveSettings(); refreshAll(); }); showMdCb.addEventListener('change', () => { settings.showMd = showMdCb.checked; saveSettings(); refreshAll(); }); textPreviewCb.addEventListener('change', () => { settings.textPreview = textPreviewCb.checked; saveSettings(); }); thumbPreviewCb.addEventListener('change', () => { settings.thumbPreview = thumbPreviewCb.checked; saveSettings(); }); recentCountInput.addEventListener('change', () => { const val = parseInt(recentCountInput.value); if (val > 0 && val <= 100) { settings.recentCount = val; saveSettings(); refreshRecent(); } }); function renderExcludes() { excludeList.innerHTML = ''; settings.excludes.forEach(ex => { const tag = document.createElement('span'); tag.className = 'exclude-tag'; tag.innerHTML = `${ex} `; tag.querySelector('button').addEventListener('click', () => { settings.excludes = settings.excludes.filter(e => e !== ex); saveSettings(); renderExcludes(); refreshAll(); }); excludeList.appendChild(tag); }); } addExcludeBtn.addEventListener('click', () => { const val = excludeInput.value.trim(); if (val && !settings.excludes.includes(val)) { settings.excludes.push(val); saveSettings(); renderExcludes(); refreshAll(); excludeInput.value = ''; } }); excludeInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') addExcludeBtn.click(); }); // ----- SEARCH ----- searchInput.addEventListener('input', () => { searchQuery = searchInput.value.trim(); renderTree(); renderRecent(); }); // ----- MARK ALL READ ----- markAllReadBtn.addEventListener('click', markAllAsRead); // ----- PULL TO REFRESH (touch) ----- let touchStartY = 0; let touchDelta = 0; const pullIndicator = document.getElementById('pullIndicator'); const mainEl = document.getElementById('mainContent'); mainEl.addEventListener('touchstart', (e) => { if (!currentFile) return; if (previewIframe.contentWindow) { // only if iframe is at top touchStartY = e.touches[0].clientY; touchDelta = 0; } }, { passive: true }); mainEl.addEventListener('touchmove', (e) => { if (!currentFile) return; const y = e.touches[0].clientY; touchDelta = y - touchStartY; if (touchDelta > 0) { pullIndicator.classList.add('active'); if (touchDelta > 60) { pullIndicator.textContent = i18n[currentLang].pullRefresh.replace('โ†“', '๐Ÿ”„'); } else { pullIndicator.textContent = i18n[currentLang].pullRefresh; } } }, { passive: true }); mainEl.addEventListener('touchend', () => { if (!currentFile) return; pullIndicator.classList.remove('active'); if (touchDelta > 60) { // refresh iframe if (currentFile) openFile(currentFile); } touchDelta = 0; }, { passive: true }); // ----- REFRESH HELPERS ----- function refreshAll() { renderTree(); renderRecent(); } function refreshRecent() { fetchRecent(settings.recentCount).then(files => { recentFiles = files; renderRecent(); }); } // ----- POLLING (simulate real-time updates) ----- function startPolling() { // tree every 5s, recent every 2s setInterval(() => { fetchTree().then(tree => { mockData.tree = tree; renderTree(); }); }, 5000); setInterval(() => { fetchRecent(settings.recentCount).then(files => { recentFiles = files; renderRecent(); }); }, 2000); } // ----- INIT ----- function init() { loadSettings(); applyLanguage('en'); loadReadMap(); // initial data fetchTree().then(tree => { mockData.tree = tree; renderTree(); }); fetchRecent(settings.recentCount).then(files => { recentFiles = files; renderRecent(); }); // check hash if (window.location.hash) { const path = decodeURIComponent(window.location.hash.slice(1)); const found = mockData.allFiles.find(f => f.path === path); if (found) openFile(found); } startPolling(); } init(); })();

Share

Submit to AI Directories

Futurepedia AI Tool Hunt Awesome AI Tools ToolPilot AI Infinity

Nebula AI Tool Base