guanghulab/image-studio/generate.js

244 lines
7.3 KiB
JavaScript
Raw Normal View History

/**
*
* 铸渊封面工作室 · 生成引擎 · 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)
})
}