|
|
|
|
|
|
|
|
|
|
|
|
|
|
const fs = require('fs'); |
|
|
const path = require('path'); |
|
|
|
|
|
|
|
|
const paths = { |
|
|
en: { |
|
|
json: path.resolve(__dirname, '../../../data/demo/public/InfoRadar-intro.json'), |
|
|
html: path.resolve(__dirname, '../content/home.en.html') |
|
|
}, |
|
|
zh: { |
|
|
json: path.resolve(__dirname, '../../../data/demo/public/CN/InfoRadar-介绍.json'), |
|
|
html: path.resolve(__dirname, '../content/home.zh.html') |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const TOKEN_SURPRISAL_MAX = 18; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calculateSurprisal(probability) { |
|
|
return -Math.log2(Math.max(probability, Number.EPSILON)); |
|
|
} |
|
|
|
|
|
|
|
|
const INTRO_RGB = '255, 71, 64'; |
|
|
|
|
|
|
|
|
const ALPHA_PRECISION = 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function escapeHtml(text) { |
|
|
return text |
|
|
.replace(/&/g, '&') |
|
|
.replace(/</g, '<') |
|
|
.replace(/>/g, '>') |
|
|
.replace(/"/g, '"') |
|
|
.replace(/'/g, '''); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 += '<br>'; |
|
|
} else if (part) { |
|
|
html += `<span class="intro-token" style="--a:${alpha}">${escapeHtml(part)}</span>`; |
|
|
} |
|
|
} |
|
|
} else { |
|
|
html += `<span class="intro-token" style="--a:${alpha}">${escapedText}</span>`; |
|
|
} |
|
|
} |
|
|
|
|
|
return html; |
|
|
} catch (error) { |
|
|
console.error(`Failed to generate HTML from JSON: ${jsonPath}`, error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function updateHTMLIntro(htmlPath, coloredHTML) { |
|
|
try { |
|
|
let html = fs.readFileSync(htmlPath, 'utf-8'); |
|
|
|
|
|
|
|
|
const regex = /(<div class="intro-brief"[^>]*>)([\s\S]*?)(<\/div>)/; |
|
|
|
|
|
if (!regex.test(html)) { |
|
|
console.error(`intro-brief not found in ${htmlPath}`); |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
const replacement = `<div class="intro-brief" style="--intro-rgb: ${INTRO_RGB}">\n ${coloredHTML}\n</div>`; |
|
|
html = html.replace(regex, replacement); |
|
|
|
|
|
fs.writeFileSync(htmlPath, html, 'utf-8'); |
|
|
console.log(`✓ Updated ${path.basename(htmlPath)}`); |
|
|
return true; |
|
|
} catch (error) { |
|
|
console.error(`Failed to update HTML file: ${htmlPath}`, error); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function main() { |
|
|
console.log('Generating colored intro HTML from JSON...\n'); |
|
|
|
|
|
let success = true; |
|
|
|
|
|
|
|
|
const enHTML = generateColoredHTML(paths.en.json); |
|
|
if (enHTML) { |
|
|
success = updateHTMLIntro(paths.en.html, enHTML) && success; |
|
|
} else { |
|
|
success = false; |
|
|
} |
|
|
|
|
|
|
|
|
const zhHTML = generateColoredHTML(paths.zh.json); |
|
|
if (zhHTML) { |
|
|
success = updateHTMLIntro(paths.zh.html, zhHTML) && success; |
|
|
} else { |
|
|
success = false; |
|
|
} |
|
|
|
|
|
if (success) { |
|
|
console.log('\n✓ All intro HTML files generated successfully'); |
|
|
} else { |
|
|
console.error('\n✗ Some generation failed'); |
|
|
process.exit(1); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
main(); |
|
|
|