138 lines
4.6 KiB
JavaScript
138 lines
4.6 KiB
JavaScript
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()); |