diff --git a/image-generator/xiaohongshu-widgets/intent-mapper.js b/image-generator/xiaohongshu-widgets/intent-mapper.js new file mode 100644 index 0000000..b624537 --- /dev/null +++ b/image-generator/xiaohongshu-widgets/intent-mapper.js @@ -0,0 +1,174 @@ +// 小红书封面意图映射引擎 v1.0 +// 自然语言 → widget组合 + 随机化策略 +// 国作登字-2026-A-00037559 + +import { WIDGETS, randomVariant } from './widgets.js' + +// 内容类型 → 预设widget组合 +const CONTENT_PRESETS = { + tutorial: { + name: '教程攻略', + widgets: ['badge','title','gradient-stripe','bullet-list','info-tag-row','highlight-box'], + decorChance: 0.6, + layout: 'card', + }, + review: { + name: '测评推荐', + widgets: ['sticker-badge','title','photo-frame','bullet-list','mood-indicator'], + decorChance: 0.5, + layout: 'card', + }, + quote: { + name: '金句语录', + widgets: ['title','scribble-underline','sticky-note','highlight-box'], + decorChance: 0.3, + layout: 'card', + }, + recipe: { + name: '美食食谱', + widgets: ['sticker-badge','title','photo-frame','info-tag-row','bullet-list'], + decorChance: 0.7, + layout: 'card', + }, + travel: { + name: '旅游攻略', + widgets: ['title','photo-frame','info-tag-row','bullet-list','mood-indicator'], + decorChance: 0.8, + layout: 'card', + }, + lifestyle: { + name: '生活分享', + widgets: ['sticker-badge','title','photo-frame','mood-indicator','sticky-note'], + decorChance: 0.8, + layout: 'card', + }, + study: { + name: '学习笔记', + widgets: ['title','gradient-stripe','bullet-list','highlight-box','info-tag-row'], + decorChance: 0.4, + layout: 'card', + }, + general: { + name: '通用模板', + widgets: ['title','bullet-list','highlight-box'], + decorChance: 0.5, + layout: 'card', + }, +} + +// 情绪 → 配色和装饰偏好 +const MOOD_MAP = { + warm: { presetScheme: 'warm', decorPrefs: ['dot-grid:warm','gradient-stripe:warm-sunset','sticker-badge:hot'] }, + elegant: { presetScheme: 'green', decorPrefs: ['dot-grid:mixed','gradient-stripe:forest','sticker-badge:free'] }, + cool: { presetScheme: 'tech', decorPrefs: ['dot-grid:cool','gradient-stripe:cool-ocean','sticker-badge:top'] }, + sweet: { presetScheme: 'rose', decorPrefs: ['dot-grid:mixed','gradient-stripe:rose-gold','sticker-badge:new'] }, + neutral: { presetScheme: 'minimal', decorPrefs: ['dot-grid:large','gradient-stripe:warm-sunset','sticker-badge:hand'] }, +} + +// 自然语言关键词 → 内容类型 +const KEYWORD_MAP = { + tutorial: ['教程','攻略','指南','步骤','方法','如何','怎么','手把手','入门'], + review: ['测评','评测','推荐','值得买','避坑','踩雷','拔草','种草','开箱'], + quote: ['金句','语录','名言','说','一句话','文案','摘抄'], + recipe: ['食谱','美食','做饭','料理','烘焙','家常菜','甜品','饮料'], + travel: ['旅游','旅行','攻略','打卡','拍照','景点','路线','酒店'], + lifestyle: ['日常','生活','穿搭','护肤','化妆','好物','分享','记录'], + study: ['学习','笔记','读书','复习','考试','备考','打卡','计划'], +} + +// 关键词 → widget覆盖 +const WIDGET_OVERRIDES = { + '打卡': { add: ['mood-indicator'], remove: [] }, + '对比': { add: ['split-layout'], remove: ['bullet-list'] }, + '好物': { add: ['photo-frame','sticker-badge','info-tag-row'], remove: [] }, + '合集': { add: ['card-grid','photo-frame'], remove: ['bullet-list'] }, + '读书': { add: ['highlight-box','sticky-note'], remove: ['photo-frame'] }, + '快乐': { add: ['mood-indicator:happy','sticker-badge:hand'], remove: [] }, + '干货': { add: ['bullet-list:star','highlight-box:green-marker'], remove: ['photo-frame'] }, + '心情': { add: ['mood-indicator','sticky-note'], remove: ['info-tag-row'] }, +} + +// 解析自然语言 → 结构化意图 +export function parseIntent(text) { + if (!text) return CONTENT_PRESETS.general + + // 检测内容类型 + let contentType = 'general' + let maxScore = 0 + for (const [type, keywords] of Object.entries(KEYWORD_MAP)) { + let score = 0 + for (const kw of keywords) { + if (text.includes(kw)) score++ + } + if (score > maxScore) { + maxScore = score + contentType = type + } + } + + // 检测情绪 + let mood = 'neutral' + if (/温馨|温暖|治愈|温柔|舒服/i.test(text)) mood = 'warm' + else if (/高级|文艺|优雅|质感|简约/i.test(text)) mood = 'elegant' + else if (/酷|科技|未来|赛博|暗黑/i.test(text)) mood = 'cool' + else if (/可爱|甜|少女|粉|软/i.test(text)) mood = 'sweet' + + return { contentType, mood } +} + +// 根据意图生成widget组合 +export function buildComposition(intent, options = {}) { + const { contentType, mood } = intent + const preset = { ...CONTENT_PRESETS[contentType] || CONTENT_PRESETS.general } + + // 复制widget列表 + let widgets = [...preset.widgets] + + // 应用情绪装饰偏好 + const moodPrefs = MOOD_MAP[mood] || MOOD_MAP.neutral + + // 应用关键词覆盖 + const text = options.text || '' + for (const [kw, override] of Object.entries(WIDGET_OVERRIDES)) { + if (text.includes(kw)) { + widgets = widgets.filter(w => !override.remove.includes(w)) + for (const add of override.add) { + if (!widgets.includes(add)) widgets.push(add) + } + } + } + + // 随机化:根据decorChance概率添加装饰widget + const decorWidgets = ['dot-grid','gradient-stripe','sticker-badge','hand-drawn-circle','corner-fold'] + if (Math.random() < preset.decorChance) { + const decor = decorWidgets[Math.floor(Math.random() * decorWidgets.length)] + if (!widgets.includes(decor)) { + // 装饰widget放在前面或最后 + widgets.unshift(decor) + } + } + + // 随机决定是否加照片框 + if (options.hasImage && !widgets.includes('photo-frame')) { + widgets.splice(2, 0, 'photo-frame') + } + + // 构建组件栈 + const stack = widgets.map(widgetId => { + const variant = options.specificVariants?.[widgetId] || randomVariant(widgetId) + return { + component: widgetId, + variant: variant?.id || 'default', + params: variant?.css ? { ...variant } : {}, + } + }) + + return { + contentType, + mood, + presetName: preset.name, + scheme: moodPrefs.presetScheme, + layout: preset.layout, + stack, + } +}