content_generator.js 18.9 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
/**
 * WeChatAI Content Generation Workflow
 * 内容生成工作流 - 读取选题、匹配模板、生成文章
 */

const fs = require('fs');
const path = require('path');

// 配置
const CONFIG = {
  baseDir: '/Users/yibo/Documents/LemonAI/lemonboss/data/workspace/wechatai',
  templatesDir: '/Users/yibo/Documents/LemonAI/lemonboss/data/workspace/wechatai/config/templates',
  outputDir: '/Users/yibo/Documents/LemonAI/lemonboss/data/workspace/wechatai/articles',
  hotspotsDir: '/Users/yibo/Documents/LemonAI/lemonboss/data/workspace/wechatai/hotspots',
};

// 模板配置
const TEMPLATE_CONFIG = {
  morning_report: {
    file: 'morning_report.md',
    minWords: 300,
    maxWords: 500,
    description: 'AI早报'
  },
  hotspot_analysis: {
    file: 'hotspot_analysis.md',
    minWords: 1000,
    maxWords: 1500,
    description: '热点解读'
  },
  deep_dive: {
    file: 'deep_dive.md',
    minWords: 2000,
    maxWords: 3000,
    description: '深度专题'
  },
  weekly_digest: {
    file: 'weekly_digest.md',
    minWords: 1500,
    maxWords: 2000,
    description: '周盘点'
  }
};

/**
 * 读取精选选题
 * @param {string} date - 日期 YYYY-MM-DD
 * @returns {Array} 选题列表
 */
function readCuratedTopics(date) {
  const filePath = path.join(CONFIG.hotspotsDir, date, 'curated_topics.json');
  if (!fs.existsSync(filePath)) {
    console.error(`[ERROR] 选题文件不存在: ${filePath}`);
    return [];
  }
  try {
    const content = fs.readFileSync(filePath, 'utf-8');
    return JSON.parse(content);
  } catch (error) {
    console.error(`[ERROR] 解析选题文件失败: ${error.message}`);
    return [];
  }
}

/**
 * 读取模板
 * @param {string} contentType - 内容类型
 * @returns {string} 模板内容
 */
function readTemplate(contentType) {
  const config = TEMPLATE_CONFIG[contentType];
  if (!config) {
    console.error(`[ERROR] 未知的模板类型: ${contentType}`);
    return null;
  }
  const templatePath = path.join(CONFIG.templatesDir, config.file);
  if (!fs.existsSync(templatePath)) {
    console.error(`[ERROR] 模板文件不存在: ${templatePath}`);
    return null;
  }
  return fs.readFileSync(templatePath, 'utf-8');
}

/**
 * 生成文章大纲
 * @param {Object} topic - 选题对象
 * @returns {Object} 大纲对象
 */
function generateOutline(topic) {
  const outlines = {
    morning_report: {
      sections: ['今日亮点', '要闻速览', '一句话总结'],
      keyPoints: [
        { title: '亮点事件', angle: '最有价值的AI进展' },
        { title: '行业动态', angle: '公司和产品更新' },
        { title: '技术突破', angle: '研究论文和开源项目' }
      ]
    },
    hotspot_analysis: {
      sections: ['背景速览', '核心解读', '影响与展望', '小结'],
      keyPoints: [
        { title: '事件背景', angle: '时间线和来龙去脉' },
        { title: '技术细节', angle: '核心功能和原理' },
        { title: '市场影响', angle: '对行业和用户的影响' }
      ]
    },
    deep_dive: {
      sections: ['背景铺垫', '技术分析', '竞争格局', '应用案例', '趋势预测', '总结'],
      keyPoints: [
        { title: '发展脉络', angle: '技术演进历程' },
        { title: '核心原理', angle: '深入技术细节' },
        { title: '对比分析', angle: '与竞品的优劣势' },
        { title: '实际应用', angle: '落地场景和案例' },
        { title: '未来趋势', angle: '发展方向预测' }
      ]
    },
    weekly_digest: {
      sections: ['本周Top5', '分类速览', '下周预告'],
      keyPoints: [
        { title: '重大发布', angle: '本周最重要的5个事件' },
        { title: '模型进展', angle: '大模型更新和性能提升' },
        { title: '商业动态', angle: '投融资和产品发布' },
        { title: '下周看点', angle: '预告值得关注的事件' }
      ]
    }
  };
  return outlines[topic.content_type] || outlines.hotspot_analysis;
}

/**
 * 使用模板生成文章
 * @param {Object} topic - 选题对象
 * @param {string} template - 模板内容
 * @returns {string} 生成的文章
 */
function generateArticle(topic, template) {
  const now = new Date();
  const dateStr = now.toISOString().split('T')[0];
  const outline = generateOutline(topic);

  // 基础变量替换
  let article = template
    .replace(/\{\{TITLE\}\}/g, topic.title)
    .replace(/\{\{DATE\}\}/g, dateStr)
    .replace(/\{\{SOURCE\}\}/g, topic.source || 'WeChatAI')
    .replace(/\{\{SOURCES\}\}/g, topic.sources?.join(', ') || topic.source || '综合整理')
    .replace(/\{\{TAGS\}\}/g, topic.tags?.join(', ') || 'AI, 人工智能')
    .replace(/\{\{SUMMARY\}\}/g, topic.summary || '');

  // 根据内容类型生成不同内容
  switch (topic.content_type) {
    case 'morning_report':
      article = generateMorningReportContent(article, topic);
      break;
    case 'hotspot_analysis':
      article = generateHotspotAnalysisContent(article, topic);
      break;
    case 'deep_dive':
      article = generateDeepDiveContent(article, topic, outline);
      break;
    case 'weekly_digest':
      article = generateWeeklyDigestContent(article, topic);
      break;
  }

  return article;
}

/**
 * 生成早报内容
 */
function generateMorningReportContent(article, topic) {
  // 生成开头导语
  const opening = `早上好!这里是 WeChatAI 早报。

今天是 ${new Date().toLocaleDateString('zh-CN')},AI 领域又发生了哪些新鲜事?让我们一起来看看。`;

  // 生成新闻条目(模拟数据,实际应从 topic 数据中提取)
  const newsItems = topic.news_items || [
    { title: 'AI 领域最新动态', summary: '详细内容待填充...' },
    { title: '技术突破', summary: '详细内容待填充...' },
    { title: '产品发布', summary: '详细内容待填充...' }
  ];

  let newsContent = '';
  newsItems.forEach((news, index) => {
    newsContent += `### ${index + 1}. ${news.title}\n${news.summary}\n\n`;
  });

  const summary = 'AI 行业持续高速发展,新技术和新产品不断涌现。保持关注,把握先机!';

  return article
    .replace(/\{\{SUMMARY\}\}/g, opening)
    .replace(/\{\{HIGHLIGHT_ITEM\}\}/g, newsItems[0]?.title || '今日AI要闻')
    .replace(/\{\{NEWS_1_TITLE\}\}/g, newsItems[0]?.title || '')
    .replace(/\{\{NEWS_1_SUMMARY\}\}/g, newsItems[0]?.summary || '')
    .replace(/\{\{NEWS_2_TITLE\}\}/g, newsItems[1]?.title || '')
    .replace(/\{\{NEWS_2_SUMMARY\}\}/g, newsItems[1]?.summary || '')
    .replace(/\{\{NEWS_3_TITLE\}\}/g, newsItems[2]?.title || '')
    .replace(/\{\{NEWS_3_SUMMARY\}\}/g, newsItems[2]?.summary || '')
    .replace(/\{\{NEWS_4_TITLE\}\}/g, newsItems[3]?.title || '')
    .replace(/\{\{NEWS_4_SUMMARY\}\}/g, newsItems[3]?.summary || '')
    .replace(/\{\{NEWS_5_TITLE\}\}/g, newsItems[4]?.title || '')
    .replace(/\{\{NEWS_5_SUMMARY\}\}/g, newsItems[4]?.summary || '')
    .replace(/\{\{ONE_SENTENCE_SUMMARY\}\}/g, summary)
    .replace(/\{\{SOURCE_COUNT\}\}/g, String(newsItems.length));
}

/**
 * 生成热点解读内容
 */
function generateHotspotAnalysisContent(article, topic) {
  // 开头三句话
  const opening = `${topic.title}是近期AI领域最受关注的热点事件。

它的发布不仅标志着技术的重大突破,更可能对整个行业格局产生深远影响。

本文将带你快速了解事件背景、深入解读核心看点、并展望其未来影响。`;

  // 背景
  const background = `${topic.title}于近期正式发布,引发了业界广泛关注。

从技术层面看,这一进展代表了AI领域的最新突破。

从市场反应来看,各方评价普遍积极。`;

  // 核心解读
  const keyPoint1 = `${topic.title}在**技术架构**上实现了重要创新。

相比前代产品,在性能和效率方面都有显著提升。

这种改进使其在实际应用场景中更具竞争力。`;

  const keyPoint2 = `从**商业角度**分析,这一发布将加剧市场竞争。

主要厂商纷纷跟进,行业格局可能因此重塑。

对于用户而言,这意味着将有更多优质选择。`;

  const keyPoint3 = `长期来看,这项技术将**推动整个行业**向前发展。

新的应用场景将不断涌现,商业价值也将持续释放。

我们可以期待更多基于此技术的创新产品出现。`;

  // 影响与展望
  const impact = `${topic.title}的发布是一个重要里程碑。

短期内,我们将看到更多基于该技术的产品和应用落地。

中长期来看,它可能重新定义行业标准和用户期望。

建议读者持续关注后续发展,把握机遇。`;

  // 小结
  const conclusion = `${topic.title}代表了AI领域的最新进展,值得关注。

无论是技术从业者还是普通用户,都将从中受益。

我们将继续跟进报道,敬请期待。`;

  return article
    .replace(/\{\{OPENING_PARAGRAPH\}\}/g, opening)
    .replace(/\{\{BACKGROUND_CONTENT\}\}/g, background)
    .replace(/\{\{KEY_POINT_1_TITLE\}\}/g, '技术突破')
    .replace(/\{\{KEY_POINT_1_CONTENT\}\}/g, keyPoint1)
    .replace(/\{\{KEY_POINT_2_TITLE\}\}/g, '商业影响')
    .replace(/\{\{KEY_POINT_2_CONTENT\}\}/g, keyPoint2)
    .replace(/\{\{KEY_POINT_3_TITLE\}\}/g, '未来展望')
    .replace(/\{\{KEY_POINT_3_CONTENT\}\}/g, keyPoint3)
    .replace(/\{\{IMPACT_CONTENT\}\}/g, impact)
    .replace(/\{\{CONCLUSION\}\}/g, conclusion);
}

/**
 * 生成深度专题内容
 */
function generateDeepDiveContent(article, topic, outline) {
  const opening = `${topic.title}是近年来AI领域最重要的技术突破之一。

它不仅改变了我们对AI能力的认知,更开启了全新的应用可能。

本文将从技术原理、发展历程、应用案例和未来趋势四个维度,为你全面解析这一革命性进展。`;

  const section1 = `${topic.title}的诞生并非偶然,而是多年技术积累的结果。

从最初的构想到最终落地,经历了多次技术迭代和突破。

关键里程碑包括基础架构的确立、核心算法的优化、以及大规模验证的成功。`;

  const section2 = `在技术层面,${topic.title}采用了创新的架构设计。

其核心优势在于**高效的信息处理能力**和**优秀的泛化性能**。

具体来说,它通过改进的注意力机制和优化的训练策略,实现了性能的质的飞跃。`;

  const section3 = `当前市场上,${topic.title}面临着激烈的竞争。

主要竞品包括来自各大科技巨头的同类产品。

但凭借独特的技术优势和先发制人的策略,${topic.title}在市场上占据了有利地位。`;

  const section4 = `${topic.title}的实际应用已经覆盖多个领域。

在**内容创作**方面,它能够辅助生成高质量的文本、图像和视频。

在**代码开发**领域,它可以显著提高开发效率,降低错误率。

在**数据分析**场景,它展现了强大的洞察力和决策支持能力。`;

  const section5 = `展望未来,${topic.title}将继续演进和完善。

预计下一代产品将在**多模态理解**和**推理能力**方面取得突破。

同时,随着成本的降低和易用性的提升,它将在更多行业和场景中得到应用。

我们可以期待一个AI能力更加普惠的未来。`;

  const discussion = `你觉得${topic.title}最令人兴奋的应用场景是什么?

欢迎在评论区分享你的想法!`;

  return article
    .replace(/\{\{OPENING_PARAGRAPH\}\}/g, opening)
    .replace(/\{\{SECTION_1_TITLE\}\}/g, '发展历程')
    .replace(/\{\{SECTION_1_CONTENT\}\}/g, section1)
    .replace(/\{\{SECTION_2_TITLE\}\}/g, '技术解析')
    .replace(/\{\{SECTION_2_CONTENT\}\}/g, section2)
    .replace(/\{\{SECTION_3_TITLE\}\}/g, '竞争格局')
    .replace(/\{\{SECTION_3_CONTENT\}\}/g, section3)
    .replace(/\{\{SECTION_4_TITLE\}\}/g, '应用案例')
    .replace(/\{\{SECTION_4_CONTENT\}\}/g, section4)
    .replace(/\{\{SECTION_5_TITLE\}\}/g, '未来趋势')
    .replace(/\{\{SECTION_5_CONTENT\}\}/g, section5)
    .replace(/\{\{DISCUSSION_QUESTION\}\}/g, discussion);
}

/**
 * 生成周盘点内容
 */
function generateWeeklyDigestContent(article, topic) {
  const opening = `本周AI领域精彩纷呈,多个重磅消息值得关注。

从大模型更新到产品发布,从技术突破到商业动态,信息量巨大。

本文为你梳理本周最值得关注的5大事件,以及各类别的重要进展。`;

  const weekRange = `${new Date().toLocaleDateString('zh-CN')} 所在周`;

  // 模拟本周Top5
  const topItems = topic.top_items || [
    { title: '大模型重要更新', summary: '性能大幅提升,引发行业热议。' },
    { title: '新产品发布', summary: '创新功能惊艳亮相,用户体验升级。' },
    { title: '开源项目亮点', summary: '社区贡献显著,生态系统进一步完善。' },
    { title: '研究论文突破', summary: '理论基础得到加强,应用前景广阔。' },
    { title: '商业合作动态', summary: '产业链整合加速,市场格局生变。' }
  ];

  const modelSummary = '本周大模型领域进展显著。主要厂商发布了重要更新,性能指标全面提升。开源社区也有诸多贡献,生态日益繁荣。';

  const fundingSummary = '资本市场对AI领域持续看好。本周有多起融资事件,涉及大模型、应用层和基础设施等多个方向。';

  const productSummary = '产品层面,多个创新功能上线。用户体验得到显著改善,应用场景进一步拓展。';

  const policySummary = '政策层面,各国继续加强对AI的监管和规范。行业自律也在加强,可持续发展成为共识。';

  const nextWeekPreview = '下周值得关注的是几家主要厂商的发布会,以及多个重要学术会议。预计将有更多技术和产品动态发布。';

  return article
    .replace(/\{\{OPENING_PARAGRAPH\}\}/g, opening)
    .replace(/\{\{WEEK_RANGE\}\}/g, weekRange)
    .replace(/\{\{TOP_1_TITLE\}\}/g, topItems[0]?.title || '')
    .replace(/\{\{TOP_1_SUMMARY\}\}/g, topItems[0]?.summary || '')
    .replace(/\{\{TOP_2_TITLE\}\}/g, topItems[1]?.title || '')
    .replace(/\{\{TOP_2_SUMMARY\}\}/g, topItems[1]?.summary || '')
    .replace(/\{\{TOP_3_TITLE\}\}/g, topItems[2]?.title || '')
    .replace(/\{\{TOP_3_SUMMARY\}\}/g, topItems[2]?.summary || '')
    .replace(/\{\{TOP_4_TITLE\}\}/g, topItems[3]?.title || '')
    .replace(/\{\{TOP_4_SUMMARY\}\}/g, topItems[3]?.summary || '')
    .replace(/\{\{TOP_5_TITLE\}\}/g, topItems[4]?.title || '')
    .replace(/\{\{TOP_5_SUMMARY\}\}/g, topItems[4]?.summary || '')
    .replace(/\{\{MODEL_SUMMARY\}\}/g, modelSummary)
    .replace(/\{\{FUNDING_SUMMARY\}\}/g, fundingSummary)
    .replace(/\{\{PRODUCT_SUMMARY\}\}/g, productSummary)
    .replace(/\{\{POLICY_SUMMARY\}\}/g, policySummary)
    .replace(/\{\{NEXT_WEEK_PREVIEW\}\}/g, nextWeekPreview);
}

/**
 * 质量自检
 * @param {string} article - 文章内容
 * @param {string} contentType - 内容类型
 * @returns {Object} 检查结果
 */
function qualityCheck(article, contentType) {
  const config = TEMPLATE_CONFIG[contentType];
  const checks = {
    wordCount: { passed: false, value: 0, required: `${config.minWords}-${config.maxWords}` },
    hasOpening3Sentences: { passed: false, note: '开头3句话检查' },
    hasBoldText: { passed: false, note: '加粗标记检查' },
    paragraphLength: { passed: false, note: '段落长度检查' },
    formatCheck: { passed: true, note: '格式规范检查' }
  };

  // 字数检查
  const chineseChars = article.match(/[\u4e00-\u9fa5]/g) || [];
  const wordCount = chineseChars.length;
  checks.wordCount.value = wordCount;
  checks.wordCount.passed = wordCount >= config.minWords && wordCount <= config.maxWords;

  // 开头3句话检查(检查是否有「这是/什么/本文」等关键词)
  const openingPattern = /这是|什么|本文|为什么|重要|讲什么/;
  checks.hasOpening3Sentences.passed = openingPattern.test(article.substring(0, 500));

  // 加粗检查
  checks.hasBoldText.passed = /\*\*.+?\*\*/.test(article);

  // 段落长度检查(每段不超过150字)
  const paragraphs = article.split('\n\n');
  const longParagraphs = paragraphs.filter(p => p.length > 150);
  checks.paragraphLength.passed = longParagraphs.length <= 3;

  const allPassed = Object.values(checks).every(c => c.passed);

  return {
    passed: allPassed,
    contentType,
    wordCount,
    checks,
    issues: allPassed ? [] : Object.entries(checks)
      .filter(([_, c]) => !c.passed)
      .map(([name, _]) => name)
  };
}

/**
 * 保存文章
 * @param {string} topicId - 选题ID
 * @param {string} date - 日期
 * @param {string} content - 文章内容
 */
function saveArticle(topicId, date, content) {
  const dir = path.join(CONFIG.outputDir, date);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
  const filePath = path.join(dir, `${topicId}.md`);
  fs.writeFileSync(filePath, content, 'utf-8');
  console.log(`[INFO] 文章已保存: ${filePath}`);
  return filePath;
}

/**
 * 主流程:生成文章
 * @param {string} date - 日期
 * @param {string} topicId - 特定选题ID(可选)
 */
async function generate(date, topicId = null) {
  console.log(`[INFO] 开始生成文章: date=${date}, topicId=${topicId || 'all'}`);

  const topics = readCuratedTopics(date);
  if (topics.length === 0) {
    console.log('[WARN] 没有找到选题');
    return [];
  }

  const results = [];

  for (const topic of topics) {
    if (topicId && topic.topic_id !== topicId) continue;

    console.log(`[INFO] 处理选题: ${topic.topic_id} - ${topic.title}`);

    // 读取模板
    const template = readTemplate(topic.content_type);
    if (!template) {
      console.error(`[ERROR] 无法获取模板: ${topic.content_type}`);
      continue;
    }

    // 生成文章
    const article = generateArticle(topic, template);

    // 质量检查
    const check = qualityCheck(article, topic.content_type);
    console.log(`[INFO] 质量检查: ${check.passed ? '通过' : '未通过'} (${check.wordCount}字)`);
    if (!check.passed) {
      console.log(`[WARN] 检查未通过项: ${check.issues.join(', ')}`);
    }

    // 保存文章
    const filePath = saveArticle(topic.topic_id, date, article);

    results.push({
      topicId: topic.topic_id,
      title: topic.title,
      contentType: topic.content_type,
      wordCount: check.wordCount,
      filePath,
      qualityCheck: check
    });
  }

  console.log(`[INFO] 文章生成完成: ${results.length} 篇`);
  return results;
}

// CLI 入口
if (require.main === module) {
  const args = process.argv.slice(2);
  const date = args[0] || new Date().toISOString().split('T')[0];
  const topicId = args[1] || null;

  generate(date, topicId).then(results => {
    console.log('\n=== 生成结果 ===');
    results.forEach(r => {
      console.log(`✓ ${r.topicId}: ${r.title} (${r.wordCount}字)`);
    });
    process.exit(0);
  }).catch(err => {
    console.error('[ERROR] 生成失败:', err);
    process.exit(1);
  });
}

module.exports = {
  generate,
  readCuratedTopics,
  readTemplate,
  generateOutline,
  generateArticle,
  qualityCheck,
  TEMPLATE_CONFIG
};