import { Context, h } from 'koishi' import path from 'path' import { pathToFileURL } from 'url' import AnsiToHtml from 'ansi-to-html' import { escapeHtml } from './utils' // 渲染终端输出为图片 export async function renderTerminalImage(ctx: Context, workingDir: string, command: string, output: string): Promise { if (!ctx.puppeteer) { throw new Error('Puppeteer plugin is not available') } const ansiStrip = (text: string) => text.replace(/\x1B\[[0-9;]*[A-Za-z]/g, '') const normalizeTabs = (text: string) => text.replace(/\t/g, ' ') const displayOutputRaw = normalizeTabs(output || '(no output)') const displayOutput = displayOutputRaw.replace(/^\s+/, '') const lines = displayOutput.split(/\r?\n/) const commandLineLength = ansiStrip(`${workingDir}$ ${command}`).length const visibleLineLengths = lines.map(line => ansiStrip(line).length) const maxLineLength = Math.max(commandLineLength, ...visibleLineLengths) || commandLineLength const charWidth = 7.1 // refined average width for JetBrains Mono 13px const horizontalBuffer = 56 // padding + borders + margin buffer const containerWidth = Math.max(600, Math.min(1600, Math.ceil(maxLineLength * charWidth + horizontalBuffer))) const ansi = new AnsiToHtml({ fg: '#cccccc', bg: '#1e1e1e', newline: true, escapeXML: true, stream: false, }) const coloredOutputHtml = ansi.toHtml(displayOutput) const fontPath = pathToFileURL(path.resolve(__dirname, '../fonts/JetBrainsMono-Regular.ttf')).href const html = `
Terminal
${escapeHtml(workingDir)}$
${escapeHtml(command)}
${coloredOutputHtml}
` const page = await ctx.puppeteer.page() try { await page.setContent(html) await page.waitForNetworkIdle({ timeout: 5000 }) const element = await page.$('.terminal') const screenshot = await element.screenshot({ type: 'png' }) as Buffer return h.image(screenshot, 'image/png') } finally { await page.close() } }