175 lines
5.9 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.

// 小红书封面意图映射引擎 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,
}
}