feat: 市场调研落地——趋势AI人话解读(plainExplain/takeAway,采集自动生成+SSR+小程序卡)+ 首页今日知识卡
This commit is contained in:
@@ -14,6 +14,9 @@ const TrendSchema = new Schema({
|
||||
},
|
||||
tags: [String],
|
||||
hot: { type: Boolean, default: false },
|
||||
// 人话解读(AI 生成,治"看不懂AI新闻"的焦虑)
|
||||
plainExplain: { type: String, default: '' }, // 一句话说清这是什么
|
||||
takeAway: { type: String, default: '' }, // 对普通人意味着什么
|
||||
picked: { type: Boolean, default: false },
|
||||
newsDate: { type: Date, default: Date.now, index: true },
|
||||
viewCount: { type: Number, default: 0 },
|
||||
|
||||
@@ -66,7 +66,7 @@ router.get('/sitemap.xml', async (req, res) => {
|
||||
|
||||
|
||||
// ===== SSR 趋势页(SEO/GEO:真实内容直出,搜索引擎可收录) =====
|
||||
const TREND_PAGE_CSS = 'body{font-family:-apple-system,"PingFang SC",sans-serif;color:#1f2333;max-width:720px;margin:0 auto;padding:24px 18px;line-height:1.7}h1{font-size:22px}.meta{color:#9aa0b4;font-size:12px;margin:4px 0 20px}a{color:#5b5ff8;text-decoration:none}.item{padding:14px 0;border-bottom:1px solid #eef0f6}.item h2{font-size:16px;margin:0 0 6px}.item p{margin:0;font-size:13px;color:#6b7080}.item .m{font-size:11px;color:#a6abbb;margin-top:4px}.tag{display:inline-block;background:rgba(91,95,248,.1);color:#5b5ff8;border-radius:99px;padding:1px 8px;font-size:11px;margin-right:6px}.foot{margin-top:28px;color:#b6bac9;font-size:12px}'
|
||||
const TREND_PAGE_CSS = 'body{font-family:-apple-system,"PingFang SC",sans-serif;color:#1f2333;max-width:720px;margin:0 auto;padding:24px 18px;line-height:1.7}h1{font-size:22px}.meta{color:#9aa0b4;font-size:12px;margin:4px 0 20px}a{color:#5b5ff8;text-decoration:none}.item{padding:14px 0;border-bottom:1px solid #eef0f6}.item h2{font-size:16px;margin:0 0 6px}.item p{margin:0;font-size:13px;color:#6b7080}.item .m{font-size:11px;color:#a6abbb;margin-top:4px}.tag{display:inline-block;background:rgba(91,95,248,.1);color:#5b5ff8;border-radius:99px;padding:1px 8px;font-size:11px;margin-right:6px}.foot{margin-top:28px;color:#b6bac9;font-size:12px}.plain{background:#f5f5ff;border-left:3px solid #5b5ff8;padding:8px 12px;margin:8px 0;font-size:14px;color:#3a3f55}.plainbox{background:#f5f5ff;border-left:3px solid #5b5ff8;padding:10px 14px;border-radius:0 8px 8px 0;margin:14px 0}.plainbox p{margin:4px 0;font-size:14px;color:#3a3f55}'
|
||||
|
||||
router.get('/trends', async (req, res) => {
|
||||
const o = origin(req)
|
||||
@@ -76,6 +76,7 @@ router.get('/trends', async (req, res) => {
|
||||
<div class="item">
|
||||
<h2><a href="${o}/trends/${t._id}">${esc(t.title)}</a></h2>
|
||||
${t.summary ? `<p>${esc(String(t.summary).slice(0, 120))}</p>` : ''}
|
||||
${t.plainExplain ? `<p class="plain"><b>人话:</b>${esc(t.plainExplain)}${t.takeAway ? ` <b>对你:</b>${esc(t.takeAway)}` : ''}</p>` : ''}
|
||||
<div class="m"><span class="tag">${esc(t.category || '资讯')}</span>${esc(t.source || '')} · ${(t.newsDate || t.createdAt || '').toString().slice(0, 10)}</div>
|
||||
</div>`).join('')
|
||||
res.type('html').send(`<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
@@ -104,6 +105,7 @@ router.get('/trends/:id', async (req, res) => {
|
||||
<h1>${esc(t.title)}</h1>
|
||||
<div class="meta">${esc(t.source || '')} · ${(t.newsDate || t.createdAt || '').toString().slice(0, 10)}</div>
|
||||
${t.summary ? `<p>${esc(t.summary)}</p>` : ''}
|
||||
${t.plainExplain ? `<div class="plainbox"><p><b>💡 一句话说清:</b>${esc(t.plainExplain)}</p>${t.takeAway ? `<p><b>🎯 对你意味着:</b>${esc(t.takeAway)}</p>` : ''}</div>` : ''}
|
||||
${t.body ? `<div>${esc(t.body).split('\n').map((p) => `<p>${p}</p>`).join('')}</div>` : ''}
|
||||
${t.sourceUrl ? `<p><a href="${esc(t.sourceUrl)}" rel="noopener nofollow" target="_blank">阅读原文 →</a></p>` : ''}
|
||||
<div class="foot"><a href="/trends">← 更多 AI 趋势</a> · © 宇之然</div>
|
||||
|
||||
@@ -189,6 +189,33 @@ async function callAI(messages, maxTokens = 1024) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/** 人话解读:每条趋势生成"一句话说清 + 对普通人意味着什么"(治 AI 焦虑的核心价值层) */
|
||||
async function explainItems(items) {
|
||||
const targets = items.filter(i => i.title && !i.plainExplain).slice(0, 14);
|
||||
if (targets.length === 0) return items;
|
||||
console.log(` → 生成人话解读 ${targets.length} 条...`);
|
||||
for (const item of targets) {
|
||||
const prompt = `你是"维度精灵",帮普通上班族看懂 AI 新闻。基于下面这条资讯,输出 JSON(只输出 JSON):
|
||||
{"plainExplain":"用一句大白话说清这条资讯讲的是什么,30字内,不用术语","takeAway":"这对普通人意味着什么/该注意什么,一句行动向的话,30字内"}
|
||||
|
||||
资讯标题:${item.title}
|
||||
${item.summary ? '摘要:' + String(item.summary).slice(0, 200) : ''}`;
|
||||
const out = await callAI([{ role: 'user', content: prompt }], 300);
|
||||
const m = out.match(/\{[\s\S]*?\}/);
|
||||
if (m) {
|
||||
try {
|
||||
const j = JSON.parse(m[0]);
|
||||
if (j.plainExplain) item.plainExplain = String(j.plainExplain).slice(0, 60);
|
||||
if (j.takeAway) item.takeAway = String(j.takeAway).slice(0, 60);
|
||||
} catch (e) { /* 解析失败留空,前端回退只显示标题 */ }
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
}
|
||||
const ok = items.filter(i => i.plainExplain).length;
|
||||
console.log(` ✓ 人话解读完成 ${ok}/${targets.length}`);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** 翻译英文内容到中文 */
|
||||
async function translateItems(items) {
|
||||
const needTranslate = items.filter(i => !hasChinese(i.title));
|
||||
@@ -342,6 +369,9 @@ async function main() {
|
||||
console.log(` ✓ 中文保底补充: ${need} 条`);
|
||||
}
|
||||
|
||||
// AI 人话解读(失败不阻断采集,字段留空前端自动回退)
|
||||
try { await explainItems(items); } catch (e) { console.log(' ⚠ 人话解读失败:', e.message); }
|
||||
|
||||
// 全量刷新:清空旧数据(避免残留英文 / 测试数据)
|
||||
await Trend.deleteMany({});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user