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