preference-memory.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. const path = require('path');
  2. const { readJson, writeJson, defaultMemoryPath } = require('../../core/memory-store');
  3. const DEFAULT_MEMORY_FILE = 'douyin-trend-memory.json';
  4. function cleanList(values) {
  5. return [...new Set((values || [])
  6. .flatMap(value => Array.isArray(value) ? value : String(value || '').split(/[,,、\n\r]+|以及|与|和/))
  7. .map(value => String(value || '').trim())
  8. .filter(Boolean))];
  9. }
  10. function appendNote(notes, note) {
  11. const text = String(note || '').trim();
  12. if (!text) return (notes || []).slice(-20);
  13. const next = [...(notes || []).filter(Boolean), text];
  14. return [...new Set(next)].slice(-20);
  15. }
  16. function defaultTrendMemory() {
  17. return {
  18. version: '0.3.12',
  19. preferredDirections: [],
  20. blockedDirections: [],
  21. focusModes: [],
  22. notes: [],
  23. updatedAt: ''
  24. };
  25. }
  26. function trendMemoryPath(input = {}) {
  27. return path.resolve(input.memory || input.memoryPath || defaultMemoryPath(DEFAULT_MEMORY_FILE));
  28. }
  29. function readTrendMemory(memoryPath) {
  30. return {
  31. ...defaultTrendMemory(),
  32. ...readJson(memoryPath, {})
  33. };
  34. }
  35. function extractAfter(text, pattern) {
  36. const match = String(text || '').match(pattern);
  37. return match ? match[1] : '';
  38. }
  39. function inferPreferenceFromMessage(message) {
  40. const text = String(message || '').trim();
  41. const preferred = cleanList([
  42. extractAfter(text, /(?:保留|多给|继续给|更喜欢|适合)(.+?)(?:。|;|;|,但|但是|不要|少给|降权|$)/)
  43. ]);
  44. const blocked = cleanList([
  45. extractAfter(text, /(?:不要|少给|降权|过滤|不适合|避开)(.+?)(?:。|;|;|,|$)/)
  46. ]);
  47. const focus = cleanList([
  48. extractAfter(text, /(?:更偏|侧重|多看|下一版更偏)(.+?)(?:。|;|;|,|$)/)
  49. ]);
  50. return { preferred, blocked, focus };
  51. }
  52. function updateTrendMemory(input = {}) {
  53. const memoryPath = trendMemoryPath(input);
  54. const memory = readTrendMemory(memoryPath);
  55. const inferred = inferPreferenceFromMessage(input.message || input.feedback || '');
  56. const preferredDirections = cleanList([
  57. ...memory.preferredDirections,
  58. ...inferred.preferred,
  59. ...(input.preferredDirections || [])
  60. ]);
  61. const blockedDirections = cleanList([
  62. ...memory.blockedDirections,
  63. ...inferred.blocked,
  64. ...(input.blockedDirections || [])
  65. ]);
  66. const focusModes = cleanList([
  67. ...memory.focusModes,
  68. ...inferred.focus,
  69. ...(input.focusModes || [])
  70. ]);
  71. const note = String(input.message || input.feedback || '').trim();
  72. const notes = appendNote(memory.notes, note);
  73. const next = {
  74. ...memory,
  75. version: defaultTrendMemory().version,
  76. preferredDirections,
  77. blockedDirections,
  78. focusModes,
  79. notes,
  80. updatedAt: new Date().toISOString()
  81. };
  82. writeJson(memoryPath, next);
  83. return { memoryPath, memory: next };
  84. }
  85. function topicMatches(topic, terms) {
  86. const text = `${topic.title || ''} ${topic.angle || ''} ${topic.evidence || ''}`;
  87. const domainTerms = ['抖音', '视频', '评论', '口播', '开头', '脚本', '账号', '表达', '钩子', '转化', '选题', '流量'];
  88. const candidates = cleanList(terms).flatMap(term => {
  89. const trimmed = String(term || '').replace(/(方向|选题|内容|话术|场景|趋势)$/u, '');
  90. return cleanList([
  91. term,
  92. trimmed,
  93. ...domainTerms.filter(domainTerm => String(term || '').includes(domainTerm))
  94. ]);
  95. });
  96. return candidates.some(term => text.includes(term));
  97. }
  98. function rankTopicsWithMemory(topics, memory) {
  99. const preferred = cleanList(memory.preferredDirections);
  100. const blocked = cleanList(memory.blockedDirections);
  101. return [...topics]
  102. .map((topic, originalIndex) => {
  103. let score = topics.length - originalIndex;
  104. if (topicMatches(topic, preferred)) score += 20;
  105. if (topicMatches(topic, blocked)) score -= 50;
  106. return { topic, score };
  107. })
  108. .sort((a, b) => b.score - a.score)
  109. .map(item => item.topic);
  110. }
  111. module.exports = {
  112. DEFAULT_MEMORY_FILE,
  113. trendMemoryPath,
  114. readTrendMemory,
  115. updateTrendMemory,
  116. rankTopicsWithMemory,
  117. cleanList
  118. };