/** * ═══════════════════════════════════════════════════ * 铸渊封面工作室 · 生成引擎 · v2.0 * ═══════════════════════════════════════════════════ * * 动态模板加载。新增模板只需: * 1. 创建 templates/xxx.js * 2. 在 templates/registry.js 注册 * 3. 此文件零改动 */ import { renderToImage, renderCarousel } from './renderer.js' import { TEMPLATE_REGISTRY, listTemplates, getTemplate, getDefaultTemplate, prepareRenderData, } from './templates/registry.js' import { analyzeText, recommendScheme, recommendLayout, } from './config.js' /* ═══════════════════════════════════════════════════ * 神笔马良 · 自动分析 + 生成 * ═══════════════════════════════════════════════════ */ /** * @param {string} text - 内容(自由文本) * @param {object} options * @param {string} options.templateId - 模板ID(默认自动选择) * @param {string} options.presetId - 预设风格ID * @param {string} options.title - 标题(不传则从文本提取) * @param {string} options.layout - 布局 * @param {string} options.output - 输出文件名 * @returns {Promise<{files: string[], analysis: object}>} */ export async function fromText(text, options = {}) { // 分析内容 const analysis = analyzeText(text) // 选择模板 const template = options.templateId ? getTemplate(options.templateId) : getDefaultTemplate() if (!template) { throw new Error('没有可用的模板') } // 提取内容 const lines = text.split('\n').filter(Boolean) const title = options.title || lines[0] || '' const bodyLines = options.title ? lines : lines.slice(1) const body = bodyLines.join('\n') // 自动选择布局 const layout = options.layout || recommendLayout(analysis) || 'default' // 自动选择预设 let presetId = options.presetId if (!presetId && template.presets.length > 0) { const scheme = recommendScheme(analysis) const presetMap = { cream: 'warm', rose: 'rose', green: 'green', night: 'dark', } presetId = presetMap[scheme] || template.presets[0].id } // 准备渲染数据 const renderData = prepareRenderData(template.id, presetId, { title, body, subtitle: options.subtitle || '', tag: options.tag || '', layout, }) if (!renderData) { throw new Error(`模板 "${template.id}" 不存在`) } // 渲染 const html = template.render(renderData) const baseName = options.output || `cover_${Date.now()}` const file = await renderToImage(html, { ...template.sizes, name: baseName, format: 'png', }) return { files: [file], analysis: { ...analysis, templateId: template.id, templateName: template.name, presetId: renderData.presetId, layout: renderData.layout, sizes: template.sizes, }, } } /* ═══════════════════════════════════════════════════ * 精确生成 · 指定所有参数 * ═══════════════════════════════════════════════════ */ export async function generate(opts = {}) { // 如果给的是自由文本,走 fromText if (opts.text) { return fromText(opts.text, { templateId: opts.templateId, presetId: opts.presetId, title: opts.title, subtitle: opts.subtitle, tag: opts.tag, layout: opts.layout, output: opts.output, }) } const { templateId = '', presetId = '', title = '', body = '', subtitle = '', tag = '', layout = 'default', output = '', width, height, } = opts const template = templateId ? getTemplate(templateId) : getDefaultTemplate() if (!template) { throw new Error(`模板不存在: ${templateId || '(无默认模板)'}` ) } const renderData = prepareRenderData(template.id, presetId, { title, body, subtitle, tag, layout, }) const sizes = width && height ? { width, height } : template.sizes const html = template.render({ ...renderData, sizes }) const baseName = output || `cover_${Date.now()}` const file = await renderToImage(html, { ...sizes, name: baseName }) return { files: [file], templateId: template.id, templateName: template.name, presetId: renderData.presetId, layout: renderData.layout, } } /* ═══════════════════════════════════════════════════ * 导出模板列表(供 API 使用) * ═══════════════════════════════════════════════════ */ export { listTemplates, getTemplate, getDefaultTemplate } /* ═══════════════════════════════════════════════════ * CLI 入口 * ═══════════════════════════════════════════════════ */ async function cli() { const args = process.argv.slice(2) function getArg(key) { const idx = args.indexOf(`--${key}`) if (idx === -1) return undefined const values = [] for (let i = idx + 1; i < args.length; i++) { if (args[i].startsWith('--')) break values.push(args[i]) } return values.length === 1 ? values[0] : values.join(' ') } const text = getArg('text') || getArg('t') if (text) { console.log('🎨 铸渊正在理解你的内容...') const result = await fromText(text, { templateId: getArg('template'), presetId: getArg('preset'), title: getArg('title'), output: getArg('output'), }) console.log('✅ 生成完成!') console.log(` 📐 模板: ${result.analysis.templateName}`) console.log(` 🎨 预设: ${result.analysis.presetId}`) console.log(` 📐 布局: ${result.analysis.layout}`) result.files.forEach(f => console.log(` 📄 ${f}`)) return } // 帮助 console.log(` 铸渊封面工作室 v2.0 · 神笔马良 🎨 自动模式(推荐): node generate.js --text "你的内容" --template xiaohongshu --preset tech 📋 可用模板: ${listTemplates().map(t => `· ${t.icon} ${t.name} (${t.id}) — ${t.description}`).join('\n ')} 🎨 可用预设: ${Object.values(TEMPLATE_REGISTRY).flatMap(t => t.presets.map(p => `· ${p.name} (${p.id}) — 来自 ${t.name}`) ).join('\n ')} 示例: node generate.js --text "零基础手搓AI封面生成器\n- 不需要GPU\n- 一行命令部署\n- 语言驱动排版" --template xiaohongshu --preset tech `) } if (process.argv[1] && process.argv[1].includes('generate.js')) { cli().catch(err => { console.error('❌ 生成失败:', err.message) process.exit(1) }) }