Files
zhuiguang-ai/scripts/generate-review-content.mjs
T

138 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import OpenAI from "openai";
import "dotenv/config";
const base = process.env.DATABASE_URL || "mysql://localhost:3306/zhuiguang_ai?charset=utf8mb4";
const sep = base.includes("?") ? "&" : "?";
const connectionString = `${base}${sep}connection_limit=20&pool_timeout=30`;
const adapter = new PrismaMariaDb(connectionString);
const prisma = new PrismaClient({ adapter });
const openai = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/v1",
});
function extractJSON(str) {
const trimmed = str.trim();
const codeBlockMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
let jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : trimmed;
const braceMatch = jsonStr.match(/\{[\s\S]*\}/);
if (braceMatch) jsonStr = braceMatch[0];
return JSON.parse(jsonStr);
}
const LONG_ARTICLE_PROMPT = `你是AI工具评测专家。基于以下评测数据,撰写一篇800-1200字的深度评测文章。
项目:{name}
简介:{desc}
综合评分:{overall}/5
五维评分:能力{cap}/5 | 开发体验{dev}/5 | 成本许可{cost}/5 | 社区{comm}/5 | 性能{perf}/5
要求:
1. 标题醒目,20字以内
2. 开头用一句话概括项目核心价值
3. 分5个段落分别深入分析五个维度
4. 每段包含具体使用场景和优缺点
5. 结尾给出适用人群和推荐指数
6. 语言专业但易懂,避免空洞套话
返回纯文本,不要JSON格式。`;
const VIDEO_SCRIPT_PROMPT = `为GitHub开源AI项目{name}(⭐{stars} stars)创作一个60秒短视频脚本。
项目简介:{desc}
综合评分:{overall}/5
要求5个镜头:
- 镜头1:开场hook(5秒)
- 镜头2:项目定位(10秒)
- 镜头3:核心功能展示(20秒)
- 镜头4:使用场景(15秒)
- 镜头5:CTA号召(10秒)
每个镜头包含:[画面描述] + 旁白文案
返回纯文本格式。`;
async function generateLongArticle(review, skill) {
const prompt = LONG_ARTICLE_PROMPT
.replace("{name}", skill.name)
.replace("{desc}", skill.description || "暂无描述")
.replace("{overall}", review.overall.toString())
.replace("{cap}", review.capability.toString())
.replace("{dev}", review.devExp.toString())
.replace("{cost}", review.costLicense.toString())
.replace("{comm}", review.community.toString())
.replace("{perf}", review.performance.toString());
const { choices } = await openai.chat.completions.create({
model: "deepseek-v4-pro",
messages: [{ role: "user", content: prompt }],
temperature: 0.7,
});
return choices[0]?.message?.content || "";
}
async function generateVideoScript(review, skill) {
const reviewsData = review.reviews || {};
const prompt = VIDEO_SCRIPT_PROMPT
.replace("{name}", skill.name)
.replace("{desc}", skill.description || "暂无描述")
.replace("{overall}", review.overall.toString())
.replace("{stars}", String(reviewsData.stars || "N/A"));
const { choices } = await openai.chat.completions.create({
model: "deepseek-v4-pro",
messages: [{ role: "user", content: prompt }],
temperature: 0.7,
});
return choices[0]?.message?.content || "";
}
async function main() {
console.log("📝 批量生成评测内容\n");
const reviews = await prisma.skillReview.findMany({
where: { status: "PUBLISHED", longArticle: null },
include: { skill: { select: { id: true, name: true, description: true } } },
});
console.log(`找到 ${reviews.length} 个需要生成内容的评测\n`);
for (let i = 0; i < reviews.length; i++) {
const r = reviews[i];
console.log(`\n[${i + 1}/${reviews.length}] ${r.skill.name} (overall: ${r.overall})`);
try {
console.log(" 生成长文...");
const article = await generateLongArticle(r, r.skill);
console.log(` ✅ 长文: ${article.length}字`);
console.log(" 生成视频脚本...");
const script = await generateVideoScript(r, r.skill);
console.log(` ✅ 脚本: ${script.length}字`);
await prisma.skillReview.update({
where: { id: r.id },
data: {
longArticle: article,
videoScript: script,
},
});
console.log(" 💾 已保存");
if (i < reviews.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
} catch (e) {
console.error(` ❌ 失败: ${e.message}`);
}
}
console.log("\n🏁 内容生成完成");
}
main()
.catch((e) => { console.error("💥", e.message); process.exit(1); })
.finally(() => prisma.$disconnect());