88 lines
3.8 KiB
JavaScript
88 lines
3.8 KiB
JavaScript
/**
|
||
* 画个标 · 海报 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 };
|
||
})();
|