/** * Rebuild the inlined running-status icon. * * Downloads the source animation (once), compresses it with ffmpeg to the * footprint the Chat running status paints, and regenerates * `src/client/icon-data.ts` as a base64 data URL — the browser half cannot * reference a package file, so the icon travels inside the client bundle. * * Requires ffmpeg on PATH. Usage: `pnpm icon:build`. */ import { execFileSync } from 'node:child_process' import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' /** Original animation, kept out of the bundle at 1.3 MiB. */ const SOURCE_URL = 'https://i0.hdslb.com/bfs/new_dyn/a5a0a0adb386867b3f5db5713576f9f5107716290.gif' /** * 64px covers the 32px painted icon at 2x; 128 colours hold the flat cartoon * fills; the source's 10fps is already the animation's own timing. */ const FILTER = 'fps=10,scale=64:64:flags=lanczos,split[a][b];' + '[a]palettegen=max_colors=128:stats_mode=diff[p];' + '[b][p]paletteuse=dither=bayer:bayer_scale=3:diff_mode=rectangle' const root = join(dirname(fileURLToPath(import.meta.url)), '..') const assetsDir = join(root, 'assets') const sourcePath = join(assetsDir, 'prts-neuron.source.gif') const iconPath = join(assetsDir, 'prts-neuron.gif') const modulePath = join(root, 'src', 'client', 'icon-data.ts') mkdirSync(assetsDir, { recursive: true }) if (!existsSync(sourcePath)) { const response = await fetch(SOURCE_URL) if (!response.ok) throw new Error(`icon source download failed: ${response.status}`) writeFileSync(sourcePath, Buffer.from(await response.arrayBuffer())) } execFileSync('ffmpeg', ['-y', '-v', 'error', '-i', sourcePath, '-vf', FILTER, '-loop', '0', iconPath]) const base64 = readFileSync(iconPath).toString('base64') const dataUrl = `data:image/gif;base64,${base64}` const chunks = dataUrl.match(/.{1,100}/g) ?? [] const literal = chunks.map((chunk, index) => `${index === 0 ? " '" : " + '"}${chunk}'`).join('\n') writeFileSync(modulePath, `/** * The running-status icon, inlined as a data URL. * * Generated by \`pnpm icon:build\` from \`assets/prts-neuron.gif\` — do not edit by * hand. The browser half can neither reach a package file nor rely on the * hotlink-protected source image, so the compressed animation ships inside the * client bundle: 64x64, 128 colours, 10fps, ~44 KiB of GIF, ~1.3% of the * original download. */ export const ICON_DATA_URL = ${literal} `) console.log(`icon: ${(dataUrl.length / 1024).toFixed(1)} KiB data URL -> ${modulePath}`)