const fs = require('fs'); const path = require('path'); const { marked } = require('marked'); // ─── Config ──────────────────────────────────────────────── const LOG_DIR = path.join(__dirname, 'llm_log'); const OUTPUT_FILE = path.join(__dirname, 'index.html'); // ─── Marked Config ───────────────────────────────────────── const headingIds = new Map(); const renderer = new marked.Renderer(); renderer.code = function (code, lang) { const validLang = lang || 'text'; const highlighted = highlightCode(code, validLang); const id = 'highlight-' + Math.random().toString(36).slice(2, 8); return `
${highlighted}
`; }; renderer.codespan = function (code) { return `${code}`; }; renderer.tablerow = function (content) { return `${content}\n`; }; renderer.tablecell = function (content, flags) { const type = flags.header ? 'th' : 'td'; const cls = flags.header ? 'px-4 py-3 text-left font-semibold bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700' : 'px-4 py-3 border border-gray-200 dark:border-gray-700'; const tag = flags.align ? `<${type} class="${cls}" align="${flags.align}">` : `<${type} class="${cls}">`; return tag + content + `\n`; }; renderer.table = function (header, body) { if (body) body = `${body}`; return `
${header}${body}
\n`; }; marked.setOptions({ renderer, gfm: true }); function highlightCode(code, lang) { const escaped = escapeHtml(code.trim()); if (!lang) return escaped; try { const hl = require('highlight.js'); const result = hl.getLanguage(lang) ? hl.highlight(code.trim(), { language: lang }) : hl.highlightAuto(code.trim()); return result.value; } catch { return escaped; } } function escapeHtml(str) { return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); } // ─── Read Log Files ──────────────────────────────────────── function readLogFile(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); return content .split(/\r?\n/) .map(line => line.trim()) .filter(line => line.length > 0); } // ─── Inject Heading IDs into Markdown ────────────────────── function injectHeadingIds(markdown, prefix) { const toc = []; const lines = markdown.split('\n'); const headingRegex = /^(#{1,6})\s+(.+)$/; let headingIndex = 0; for (let i = 0; i < lines.length; i++) { const match = lines[i].match(headingRegex); if (match) { const level = match[1].length; const text = match[2].trim().replace(/[#!*_~]/g, ''); const headingId = `${prefix}-h${headingIndex++}`; lines[i] = lines[i] + ` {#${headingId}}`; toc.push({ level, text, id: headingId }); } } return { markdown: lines.join('\n'), toc }; } // ─── Build Pages ─────────────────────────────────────────── async function build() { const logFile = path.join(LOG_DIR, 'log.txt'); const logDir = LOG_DIR; let mdFiles = []; // Try reading log.txt first if (fs.existsSync(logFile)) { const entries = readLogFile(logFile); mdFiles = entries .filter(entry => entry.endsWith('.md')) .map(entry => path.resolve(path.dirname(logFile), '..', entry)); } // Fallback: scan llm_log directory for .md files if (mdFiles.length === 0 && fs.existsSync(logDir) && fs.statSync(logDir).isDirectory()) { mdFiles = fs.readdirSync(logDir) .filter(f => f.endsWith('.md')) .map(f => path.join(logDir, f)) .sort(); } if (mdFiles.length === 0) { console.error('No .md files found in llm_log/ or llm_log/log.txt'); process.exit(1); } console.log(`Found ${mdFiles.length} markdown file(s)`); const pages = []; for (const filePath of mdFiles) { try { const raw = fs.readFileSync(filePath, 'utf-8'); if (!raw.trim()) continue; const prefix = path.basename(filePath, '.md'); const { markdown, toc } = injectHeadingIds(raw, prefix); const html = marked.parse(markdown); pages.push({ title: prefix, html, toc, filePath }); } catch (err) { console.warn(`Failed to process ${filePath}: ${err.message}`); } } if (pages.length === 0) { console.error('No valid markdown content found.'); process.exit(1); } // ─── Generate HTML ─────────────────────────────────────── const tocLinks = pages.map((p, i) => `
  • ${escapeHtml(p.title)} ${p.toc.length > 0 ? `${p.toc.length}` : ''} ${p.toc.length > 0 ? `` : ''}
  • `).join(''); const pageBlocks = pages.map((p, i) => `

    ${escapeHtml(p.title)}

    ${path.basename(p.filePath)}

    ${p.html}
    `).join(''); const html = ` LLM Log Viewer `; fs.writeFileSync(OUTPUT_FILE, html, 'utf-8'); console.log(`\nBuild complete: ${OUTPUT_FILE}`); console.log(` - ${pages.length} page(s) rendered`); } build().catch(err => { console.error(err); process.exit(1); });