const path = require('path'); const { readJson, writeJson, defaultMemoryPath } = require('../../core/memory-store'); const DEFAULT_MEMORY_FILE = 'douyin-trend-memory.json'; function cleanList(values) { return [...new Set((values || []) .flatMap(value => Array.isArray(value) ? value : String(value || '').split(/[,,、\n\r]+|以及|与|和/)) .map(value => String(value || '').trim()) .filter(Boolean))]; } function appendNote(notes, note) { const text = String(note || '').trim(); if (!text) return (notes || []).slice(-20); const next = [...(notes || []).filter(Boolean), text]; return [...new Set(next)].slice(-20); } function defaultTrendMemory() { return { version: '0.3.12', preferredDirections: [], blockedDirections: [], focusModes: [], notes: [], updatedAt: '' }; } function trendMemoryPath(input = {}) { return path.resolve(input.memory || input.memoryPath || defaultMemoryPath(DEFAULT_MEMORY_FILE)); } function readTrendMemory(memoryPath) { return { ...defaultTrendMemory(), ...readJson(memoryPath, {}) }; } function extractAfter(text, pattern) { const match = String(text || '').match(pattern); return match ? match[1] : ''; } function inferPreferenceFromMessage(message) { const text = String(message || '').trim(); const preferred = cleanList([ extractAfter(text, /(?:保留|多给|继续给|更喜欢|适合)(.+?)(?:。|;|;|,但|但是|不要|少给|降权|$)/) ]); const blocked = cleanList([ extractAfter(text, /(?:不要|少给|降权|过滤|不适合|避开)(.+?)(?:。|;|;|,|$)/) ]); const focus = cleanList([ extractAfter(text, /(?:更偏|侧重|多看|下一版更偏)(.+?)(?:。|;|;|,|$)/) ]); return { preferred, blocked, focus }; } function updateTrendMemory(input = {}) { const memoryPath = trendMemoryPath(input); const memory = readTrendMemory(memoryPath); const inferred = inferPreferenceFromMessage(input.message || input.feedback || ''); const preferredDirections = cleanList([ ...memory.preferredDirections, ...inferred.preferred, ...(input.preferredDirections || []) ]); const blockedDirections = cleanList([ ...memory.blockedDirections, ...inferred.blocked, ...(input.blockedDirections || []) ]); const focusModes = cleanList([ ...memory.focusModes, ...inferred.focus, ...(input.focusModes || []) ]); const note = String(input.message || input.feedback || '').trim(); const notes = appendNote(memory.notes, note); const next = { ...memory, version: defaultTrendMemory().version, preferredDirections, blockedDirections, focusModes, notes, updatedAt: new Date().toISOString() }; writeJson(memoryPath, next); return { memoryPath, memory: next }; } function topicMatches(topic, terms) { const text = `${topic.title || ''} ${topic.angle || ''} ${topic.evidence || ''}`; const domainTerms = ['抖音', '视频', '评论', '口播', '开头', '脚本', '账号', '表达', '钩子', '转化', '选题', '流量']; const candidates = cleanList(terms).flatMap(term => { const trimmed = String(term || '').replace(/(方向|选题|内容|话术|场景|趋势)$/u, ''); return cleanList([ term, trimmed, ...domainTerms.filter(domainTerm => String(term || '').includes(domainTerm)) ]); }); return candidates.some(term => text.includes(term)); } function rankTopicsWithMemory(topics, memory) { const preferred = cleanList(memory.preferredDirections); const blocked = cleanList(memory.blockedDirections); return [...topics] .map((topic, originalIndex) => { let score = topics.length - originalIndex; if (topicMatches(topic, preferred)) score += 20; if (topicMatches(topic, blocked)) score -= 50; return { topic, score }; }) .sort((a, b) => b.score - a.score) .map(item => item.topic); } module.exports = { DEFAULT_MEMORY_FILE, trendMemoryPath, readTrendMemory, updateTrendMemory, rankTopicsWithMemory, cleanList };