import ConfigControl from "../config/configControl.js"; import puppeteer from 'puppeteer'; import fs from 'fs'; import path from 'path'; //渲染器 class Renderer { constructor() { this.browser = null; this.config = null; this.isInitialized = false; } async init() { try { this.config = await ConfigControl.get('ai'); this.browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); this.isInitialized = true; } catch (error) { logger.error(`[crystelf-renderer] 初始化失败: ${error.message}`); } } /** * 渲染代码为图片 * @param code 代码 * @param language 语言 * @returns {Promise} */ async renderCode(code, language = 'text') { if (!this.isInitialized) { await this.init(); } try { const page = await this.browser.newPage(); const codeConfig = this.config?.codeRenderer || {}; const html = this.generateCodeHTML(code, language, codeConfig); await page.setContent(html); await page.setViewport({ width: 800, height: 600 }); const tempDir = path.join(process.cwd(), 'temp', 'html'); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } const filename = `code_${Date.now()}.png`; const filepath = path.join(tempDir, filename); await page.screenshot({ path: filepath, fullPage: true, type: 'png' }); await page.close(); logger.info(`[crystelf-ai] 代码渲染完成: ${filepath}`); return filepath; } catch (error) { logger.error(`[crystelf-ai] 代码渲染失败: ${error.message}`); return null; } } /** * 渲染md为图片 * @param markdown * @returns {Promise} */ async renderMarkdown(markdown) { if (!this.isInitialized) { await this.init(); } try { const page = await this.browser.newPage(); const markdownConfig = this.config?.markdownRenderer || {}; const html = this.generateMarkdownHTML(markdown, markdownConfig); await page.setContent(html); await page.setViewport({ width: 800, height: 600 }); const tempDir = path.join(process.cwd(), 'temp', 'html'); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } const filename = `markdown_${Date.now()}.png`; const filepath = path.join(tempDir, filename); await page.screenshot({ path: filepath, fullPage: true, type: 'png' }); await page.close(); logger.info(`[crystelf-ai] Markdown渲染完成: ${filepath}`); return filepath; } catch (error) { logger.error(`[crystelf-ai] Markdown渲染失败: ${error.message}`); return null; } } /** * 生成代码html * @param code 代码内容 * @param language 语言 * @param config 配置 * @returns {string} */ generateCodeHTML(code, language, config) { const theme = config.theme || 'github'; const fontSize = config.fontSize || 14; const lineNumbers = config.lineNumbers !== false; const backgroundColor = config.backgroundColor || '#f6f8fa'; return ` Code Render
${this.escapeHtml(code)}
`; } /** * 生成Markdown HTML * @param {string} markdown Markdown内容 * @param {Object} config 配置 * @returns {string} HTML内容 */ generateMarkdownHTML(markdown, config) { const theme = config.theme || 'light'; const fontSize = config.fontSize || 14; const codeTheme = config.codeTheme || 'github'; return ` Markdown Render
`; } escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, (m) => map[m]); } async close() { if (this.browser) { await this.browser.close(); this.browser = null; this.isInitialized = false; } } } export default new Renderer();