/** * 构建时脚本:从JSON生成带颜色的HTML */ const fs = require('fs'); const path = require('path'); // 文件路径配置 const paths = { en: { json: path.resolve(__dirname, '../../../data/demo/public/InfoHighlight-intro.json'), html: path.resolve(__dirname, '../content/home.en.html') }, zh: { json: path.resolve(__dirname, '../../../data/demo/public/CN/InfoHighlight-介绍.json'), html: path.resolve(__dirname, '../content/home.zh.html') } }; // ========================================== // 颜色计算逻辑(从 SurprisalColorConfig.ts 复制) // ========================================== const TOKEN_SURPRISAL_MAX = 18; /** * 计算 surprisal(信息量) */ function calculateSurprisal(probability) { return -Math.log2(Math.max(probability, Number.EPSILON)); } /** RGB 部分,通过 CSS 变量复用 */ const INTRO_RGB = '255, 71, 64'; /** alpha 小数位数 */ const ALPHA_PRECISION = 2; /** * 根据 surprisal 计算 alpha(0–0.7),保留指定位数 */ function getTokenAlpha(surprisal) { const normalizedValue = surprisal < 0 ? 0 : surprisal >= TOKEN_SURPRISAL_MAX ? 1 : surprisal / TOKEN_SURPRISAL_MAX; const alpha = Math.max(0, Math.min(1, normalizedValue)) * 0.7; return alpha.toFixed(ALPHA_PRECISION); } // ========================================== // HTML 生成逻辑 // ========================================== /** * 转义HTML特殊字符 */ function escapeHtml(text) { return text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * 从JSON生成带颜色的HTML(使用 CSS 变量 --intro-rgb,span 仅写 alpha) */ function generateColoredHTML(jsonPath) { try { const content = fs.readFileSync(jsonPath, 'utf-8'); const data = JSON.parse(content); let html = ''; for (const token of data.result.bpe_strings) { const text = token.raw; const prob = token.real_topk[1]; const surprisal = calculateSurprisal(prob); const alpha = getTokenAlpha(surprisal); const escapedText = escapeHtml(text); if (text.includes('\n')) { const parts = text.split(/(\n)/); for (const part of parts) { if (part === '\n') { html += '
'; } else if (part) { html += `${escapeHtml(part)}`; } } } else { html += `${escapedText}`; } } return html; } catch (error) { console.error(`Failed to generate HTML from JSON: ${jsonPath}`, error); return null; } } /** * 计算替换后的 HTML;若与原文一致则无需写入。 * @returns {{ ok: true, changed: boolean, nextHtml: string } | { ok: false }} */ function buildIntroHtml(htmlPath, coloredHTML) { try { const original = fs.readFileSync(htmlPath, 'utf-8'); // 匹配
之间的内容 const regex = /(
]*>)([\s\S]*?)(<\/div>)/; if (!regex.test(original)) { console.error(`intro-brief not found in ${htmlPath}`); return { ok: false }; } // 替换为带颜色的HTML,容器上定义 CSS 变量供 span 复用 const replacement = `
\n ${coloredHTML}\n
`; const nextHtml = original.replace(regex, replacement); const changed = nextHtml !== original; return { ok: true, changed, nextHtml }; } catch (error) { console.error(`Failed to read/update HTML file: ${htmlPath}`, error); return { ok: false }; } } /** * 主函数 */ function main() { const enHTML = generateColoredHTML(paths.en.json); if (!enHTML) { console.error('\n✗ Some generation failed'); process.exit(1); } const en = buildIntroHtml(paths.en.html, enHTML); if (!en.ok) { console.error('\n✗ Some generation failed'); process.exit(1); } const zhHTML = generateColoredHTML(paths.zh.json); if (!zhHTML) { console.error('\n✗ Some generation failed'); process.exit(1); } const zh = buildIntroHtml(paths.zh.html, zhHTML); if (!zh.ok) { console.error('\n✗ Some generation failed'); process.exit(1); } if (!en.changed && !zh.changed) { return; } console.log('Generating colored intro HTML from JSON...\n'); if (en.changed) { fs.writeFileSync(paths.en.html, en.nextHtml, 'utf-8'); console.log(`✓ Updated ${path.basename(paths.en.html)}`); } if (zh.changed) { fs.writeFileSync(paths.zh.html, zh.nextHtml, 'utf-8'); console.log(`✓ Updated ${path.basename(paths.zh.html)}`); } console.log('\n✓ All intro HTML files generated successfully'); } // 执行 main();