D122: xhs widget system - natural language intent mapper + 8 content types

This commit is contained in:
bingshuo 2026-06-02 15:22:45 +08:00
parent 7b682fd4ad
commit dd73c2174c

View File

@ -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,
}
}