content-plan.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. const fs = require('fs');
  2. const path = require('path');
  3. const DEFAULT_VOC_ISSUES = [
  4. {
  5. issue: '第一次了解怕踩雷',
  6. insight: '用户不是不想选,而是不知道怎么判断才稳。',
  7. title: '第一次了解我们,按这个顺序看不容易踩雷',
  8. hook: '第一次接触别急着下决定,先看这 3 点最稳。',
  9. script: '第一次了解我们,如果你怕选错、怕花冤枉钱,可以先按这个顺序判断。第一步先看核心产品或服务能不能解决你最关心的问题;第二步看真实用户的反馈和案例;第三步再对比价格和适配度。这样你不是盲选,而是在判断到底值不值。',
  10. shots: ['产品/服务全貌', '核心卖点特写', '真实案例展示', '用户使用场景'],
  11. commentPrompt: '第一次了解你最担心什么?评论区告诉我,我帮你判断。',
  12. metric: '评论里“怎么选/第一次/适不适合/怎么对比”的提问是否增加'
  13. },
  14. {
  15. issue: '觉得价格贵',
  16. insight: '用户真正想知道的是贵在哪里、值不值、怎么选更划算。',
  17. title: '为什么我们不是最便宜的,但很多老用户还会复购',
  18. hook: '如果你只看价格,会觉得我们不便宜,但这几个地方才是成本。',
  19. script: '很多用户第一次看价格会犹豫,所以今天直接讲清楚。我们贵的地方不是噱头,而是这几项投入、这套服务的稳定性,还有长期使用的保障。第一次不建议盲选,可以先从入门方案开始,既能体验核心价值,也不会一下子超预算。',
  20. shots: ['成本构成展示', '服务/工艺过程', '前后效果对比', '不同方案价格说明'],
  21. commentPrompt: '想看入门方案怎么选,还是进阶方案怎么搭配?',
  22. metric: '评论里“贵/值不值”下降,“方案/怎么选”上升'
  23. },
  24. {
  25. issue: '担心买了不适合自己',
  26. insight: '适配焦虑要用“适合谁、不适合谁”来降低。',
  27. title: '这个适合谁、不适合谁,我直接说清楚',
  28. hook: '先说不适合什么人,再说适合什么人。',
  29. script: '我们不想只讲好的一面,所以今天直接说清楚。我们适合这类需求、这种预算、这种使用场景的人;如果你的情况是另外几种,可能要先看看其他选择。讲清楚不适合谁,反而能减少你拿到手之后的落差。',
  30. shots: ['不同人群标签', '适配场景对比', '真实使用反馈', '正面说明'],
  31. commentPrompt: '你的需求和预算大概是什么?我帮你判断适不适合。',
  32. metric: '评论里“适不适合/像我这种/能不能用”的互动是否增加'
  33. },
  34. {
  35. issue: '不知道核心差异是什么',
  36. insight: '卖点要从“我们很好”讲到“为什么和别人不一样”。',
  37. title: '别人也在做,我们到底不一样在哪',
  38. hook: '不是所有卖点都重要,但这一个差异值得你了解。',
  39. script: '很多同行都说自己好,但用户真正想知道的是差异在哪。我们最核心的不是参数堆叠,而是这个做法、这套体验和这类长期结果。第一次了解,你可以不用全看完,但一定要看这一点,因为它最能代表我们想做的事。',
  40. shots: ['核心差异演示', '关键细节特写', '用户真实反应', '对比说明'],
  41. commentPrompt: '你选这类产品或服务时,最看重哪个点?',
  42. metric: '收藏、转发、评论里“想了解/值得/怎么联系”的信号是否增加'
  43. },
  44. {
  45. issue: '担心售后服务跟不上',
  46. insight: '服务焦虑要用流程透明和响应承诺来降低。',
  47. title: '买之前先知道:出问题找谁、多久响应',
  48. hook: '我们最怕的是你遇到问题不知道该找谁。',
  49. script: '售后和服务是很多人下单前最担心的,所以我们把流程提前说清楚。咨询、售后、退换、维护分别走哪个入口,多久能响应,都有明确说明。你不用猜流程,遇到问题直接按指引找对应的人,我们也会持续优化响应速度。',
  50. shots: ['服务流程图', '响应承诺说明', '售后入口', '真实处理记录'],
  51. commentPrompt: '你最在意响应速度、解决能力,还是后续保障?',
  52. metric: '评论里“没人管/服务慢”下降,“流程/保障”正向反馈增加'
  53. },
  54. {
  55. issue: '内容看着种草,实际怕落差',
  56. insight: '内容要降低夸张表达,主动讲适合谁、不适合谁。',
  57. title: '不想只拍好看的,今天把真实情况说清楚',
  58. hook: '先说缺点,再说适合什么人。',
  59. script: '我们不想只拍滤镜很好看的内容,所以今天直接说清楚。我们适合这类需求、愿意接受这些前提的人;如果你期待的是另外几种效果,可能要提前了解清楚。讲清楚真实情况,反而能减少你接触之后的落差。',
  60. shots: ['真实场景', '无滤镜实拍', '真实使用过程', '正面说明'],
  61. commentPrompt: '你接触之前最想先知道优点还是缺点?',
  62. metric: '评论里“真实/避坑/适合谁”的互动是否增加'
  63. },
  64. {
  65. issue: '不知道为什么值得专门选你',
  66. insight: '要把特色从“好”讲到“为什么值得选”。',
  67. title: '如果你只选一次,建议把这件事想明白',
  68. hook: '不是所有理由都重要,但这一个值得你了解一次。',
  69. script: '很多同行都说自己好,但用户真正想知道为什么值得专门选你。我们最核心的不是名头,而是这个方法、这份长期结果和这类真实场景。第一次了解,你可以不用全部记住,但一定要看这一点,因为它最能代表我们的价值。',
  70. shots: ['核心价值演示', '长期结果展示', '用户真实反馈', '场景氛围'],
  71. commentPrompt: '你会为了哪种结果专门选一家?',
  72. metric: '收藏、转发、评论里“想要/值得/怎么联系”的信号是否增加'
  73. }
  74. ];
  75. function splitList(value) {
  76. if (!value) return [];
  77. if (Array.isArray(value)) return value.flatMap(splitList);
  78. return String(value).split(/[;;\n\r]+/).map(item => item.trim()).filter(Boolean);
  79. }
  80. function readTextIfExists(filePath) {
  81. if (!filePath) return '';
  82. const absolute = path.resolve(filePath);
  83. if (!fs.existsSync(absolute)) return '';
  84. return fs.readFileSync(absolute, 'utf8').replace(/^\uFEFF/, '');
  85. }
  86. function collectIssues(input = {}) {
  87. const explicit = splitList(input.issues || input.problems || input.vocIssues || input.contentDirections || input.directions || input.evidence || input.comments);
  88. if (explicit.length) return explicit.slice(0, 12);
  89. const text = String(input.reportText || input.evidenceText || input.context || input.background || '').trim() || readTextIfExists(input.report || input.reportPath);
  90. const lines = splitList(text).filter(line => /价格|贵|踩雷|服务|售后|怎么选|适合|不适合|落差|案例|对比|值不值|效果|质量|体验|方案|差异|卖点|为什么|预算/.test(line));
  91. return lines.length ? lines.slice(0, 12) : DEFAULT_VOC_ISSUES.map(item => item.issue);
  92. }
  93. function inferTemplate(issue, index, input = {}) {
  94. const text = String(issue || '');
  95. const matched = DEFAULT_VOC_ISSUES.find(item =>
  96. text.includes(item.issue) ||
  97. item.issue.includes(text) ||
  98. (/踩雷|第一次|怎么选|不会选/.test(text) && item.issue.includes('踩雷')) ||
  99. (/贵|价格|性价比|值不值|预算/.test(text) && item.issue.includes('价格')) ||
  100. (/适合|不适合|适配/.test(text) && item.issue.includes('适合')) ||
  101. (/差异|卖点|特色|不一样|区别/.test(text) && item.issue.includes('差异')) ||
  102. (/服务|售后|没人管|响应|态度/.test(text) && item.issue.includes('服务')) ||
  103. (/落差|种草|滤镜|网红/.test(text) && item.issue.includes('落差')) ||
  104. (/为什么|值得|理由/.test(text) && item.issue.includes('值得'))
  105. );
  106. return matched || DEFAULT_VOC_ISSUES[index % DEFAULT_VOC_ISSUES.length];
  107. }
  108. function buildContentPlan(input = {}) {
  109. const brand = String(input.project || input.brand || input.store || '你的品牌').trim();
  110. const industry = String(input.industry || input.category || '通用行业').trim();
  111. const platform = String(input.platform || '抖音/小红书').trim();
  112. const days = Math.max(1, Math.min(Number(input.days || 7), 15));
  113. const issues = collectIssues(input);
  114. const plan = Array.from({ length: days }, (_, index) => {
  115. const sourceIssue = issues[index % issues.length];
  116. const template = inferTemplate(sourceIssue, index, input);
  117. return {
  118. day: index + 1,
  119. userIssue: sourceIssue || template.issue,
  120. insight: template.insight,
  121. title: template.title,
  122. hook: template.hook,
  123. script: template.script,
  124. shots: template.shots,
  125. commentPrompt: template.commentPrompt,
  126. validationMetric: template.metric,
  127. platform
  128. };
  129. });
  130. const assistantMessage = [
  131. '# 7 天 VOC 内容选题与口播脚本',
  132. '',
  133. '## 你这轮能拿到什么',
  134. `- 一份面向 ${brand} 的 7 天内容选题表。`,
  135. '- 每条内容都绑定一个真实用户问题,不做空泛爆款标题。',
  136. '- 每条都包含前 3 秒钩子、口播脚本、镜头建议、评论区引导和验证指标。',
  137. '',
  138. '## 业务口径',
  139. `- 品牌:${brand}`,
  140. `- 行业/品类:${industry}`,
  141. `- 发布平台:${platform}`,
  142. `- 内容周期:${days} 天`,
  143. '',
  144. '## 7 天内容表',
  145. ...plan.map(item => [
  146. '',
  147. `### Day ${item.day}:${item.title}`,
  148. `- 回应的用户问题:${item.userIssue}`,
  149. `- 洞察判断:${item.insight}`,
  150. `- 前 3 秒钩子:${item.hook}`,
  151. `- 口播脚本:${item.script}`,
  152. `- 镜头建议:${item.shots.join(' -> ')}`,
  153. `- 评论区引导:${item.commentPrompt}`,
  154. `- 验证指标:${item.validationMetric}`
  155. ].join('\n')),
  156. '',
  157. '## 拍摄提醒',
  158. '- 先拍真实场景、真实产品、真实服务流程,不要只拍滤镜氛围。',
  159. '- 每条内容只解决一个用户问题,避免一条视频讲太多。',
  160. '- 发完后把评论里的新问题继续放回 VOC 问题池,下一轮再优化。',
  161. '',
  162. '## 下一步建议',
  163. '1. 先选 2 条最容易拍的内容今天开拍。',
  164. '2. 发布 7 天后,把评论反馈告诉我,我会判断哪些选题有效、哪些要降权。',
  165. '3. 如果已经有趋势报告或问题池,可以把 Top 3 问题传进来,我会重排内容顺序。'
  166. ].join('\n');
  167. return {
  168. status: 'ok',
  169. assistantMessage,
  170. summary: {
  171. brand,
  172. industry,
  173. platform,
  174. days,
  175. topicCount: plan.length,
  176. sourceIssueCount: issues.length
  177. },
  178. data: {
  179. brand,
  180. industry,
  181. platform,
  182. issues,
  183. plan
  184. },
  185. files: [],
  186. nextActions: [
  187. '选择 2 条最容易拍的内容先执行',
  188. '发布后把评论反馈放回 VOC 问题池',
  189. '7 天后反馈有效/无效,我会更新内容方向'
  190. ],
  191. warnings: [],
  192. errors: []
  193. };
  194. }
  195. module.exports = {
  196. buildContentPlan
  197. };