Files

88 lines
3.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 画个标 · 海报 AI 设计器:DeepSeek 输出设计决策 JSON,系统校验后映射为元素方案
* 复用 HGB_AI.chat;白名单:版式模板(poster-generator.LAYOUTS)+ 配色(poster-generator.PALETTES)
* AI 输出 JSON:{ overallConcept, plans:[{ title, subtitle, paletteId, layoutId, concept }] }
* AI 失败由调用方降级为内置引擎(defaultDecision 随机组合),保证页面不空白。
*/
const HGB_PosterAI = (function () {
'use strict';
function buildPrompt(opt) {
const layoutList = HGB_PosterGenerator.LAYOUTS.map(l => `${l.id}:${l.name}`).join('\n');
const paletteList = HGB_PosterGenerator.PALETTES.map(p => `${p.id}:${p.name}(背景 ${p.bg})`).join('\n');
const sys = `你是顶级的海报设计师。请基于用户提供的主题信息,从白名单中选择版式与配色,设计多套专业海报方案。
设计原则:
1. 标题(title)精炼有力、突出主题;副标题(subtitle)补充说明(可空)。
2. 版式 layoutId 与配色 paletteId 必须从白名单中选择,不得编造。
3. 多套方案尽量使用不同的版式与配色,呈现不同设计方向。
4. 每套方案给出贴切的中文方案名(name)与设计理念(concept)。
只输出 JSON,不要输出任何其他文字。JSON 结构:
{ "overallConcept": "整体理念", "plans": [ { "name": "方案名", "title": "主标题", "subtitle": "副标题(可空)", "paletteId": "indigo", "layoutId": "centered", "concept": "设计理念" } ] }
其中 plans 数组包含 ${opt.count || 5} 套方案。`;
const user = `【主题信息】
- 行业/主题:${opt.industry || '通用'}
- 主标题:${opt.title || '(由你拟定)'}
- 副标题:${opt.sub || '(无)'}
- 描述:${opt.desc || '(无)'}
【版式白名单】
${layoutList}
【配色白名单】
${paletteList}
请设计 ${opt.count || 5} 套海报方案。`;
return [
{ role: 'system', content: sys },
{ role: 'user', content: user }
];
}
function extractJson(content) {
let t = String(content || '').trim();
t = t.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/, '');
const s = t.indexOf('{'), e = t.lastIndexOf('}');
if (s < 0 || e < s) throw new Error('AI 输出不是有效 JSON');
return JSON.parse(t.slice(s, e + 1));
}
/** 校验并规范化 AI 输出 */
function validate(data, opt) {
if (!data || !Array.isArray(data.plans) || !data.plans.length) throw new Error('AI 未返回方案');
for (const p of data.plans) {
if (!HGB_PosterGenerator.PALETTES.some(x => x.id === p.paletteId)) p._paletteInvalid = true;
if (!HGB_PosterGenerator.LAYOUTS.some(x => x.id === p.layoutId)) p._layoutInvalid = true;
}
return data;
}
/** 决策 → 元素方案(非法字段回退默认,不随机) */
function mapToPlan(spec, p, idx) {
const decision = HGB_PosterGenerator.defaultDecision(p.title, p.subtitle);
if (!p._paletteInvalid) decision.paletteId = p.paletteId;
if (!p._layoutInvalid) decision.layoutId = p.layoutId;
const elements = HGB_PosterGenerator.generate(spec, decision);
return {
id: 'poster_' + Date.now().toString(36) + idx.toString(36),
name: (p.name || '').trim() || ('方案 ' + (idx + 1)),
concept: (p.concept || '').trim(),
elements,
specId: spec.id,
ai: true
};
}
async function generatePostersAI(opt) {
const spec = HGB_PosterStore.spec();
const messages = buildPrompt(opt);
const content = await HGB_AI.chat(messages, { json: true, maxTokens: 4096 });
const data = validate(extractJson(content), opt);
const plans = data.plans.map((p, i) => mapToPlan(spec, p, i));
return { plans, overallConcept: (data.overallConcept || '').trim(), raw: content };
}
return { generatePostersAI, buildPrompt, extractJson, validate, mapToPlan };
})();