import { PrismaClient } from "@prisma/client"; import { PrismaMariaDb } from "@prisma/adapter-mariadb"; import { Octokit } from "octokit"; 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 octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); const openai = new OpenAI({ apiKey: process.env.DEEPSEEK_API_KEY, baseURL: "https://api.deepseek.com/v1" }); const REVIEW_PROMPT = `你是AI技术资深评测师。请对以下AI开源项目进行五维评测,每个维度1-5分。 评测维度: - capability:场景与能力(功能覆盖度、场景适配性、输出质量) - devExp:开发体验(安装便捷度、文档质量、API设计、调试友好度) - costLicense:成本与许可(开源协议友好度、部署成本、资源消耗) - community:社区健康(Stars趋势、贡献者数量、Issue响应速度、更新频率) - performance:性能稳定(推理速度、并发能力、稳定性、可扩展性) 项目信息: 名称:{name} 描述:{description} GitHub Stars:{stars} README摘要:{readme} 返回纯JSON: { "capability": 4.5, "devExp": 4.0, "costLicense": 5.0, "community": 4.5, "performance": 4.0, "overall": 4.4, "reviews": { "capability": {"score": 4.5, "summary": "一句话总结", "detail": "详细分析"}, "devExp": {"score": 4.0, "summary": "一句话总结", "detail": "详细分析"}, "costLicense": {"score": 5.0, "summary": "一句话总结", "detail": "详细分析"}, "community": {"score": 4.5, "summary": "一句话总结", "detail": "详细分析"}, "performance": {"score": 4.0, "summary": "一句话总结", "detail": "详细分析"} }, "tags": ["标签1", "标签2"] }`; 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) { try { return JSON.parse(braceMatch[0]); } catch {} } return JSON.parse(jsonStr); } function extractOwnerRepo(sourceUrl) { const match = sourceUrl.match(/github\.com\/([^/]+)\/([^/]+)/); if (!match) return null; return { owner: match[1], repo: match[2].replace(/\.git$/, "") }; } async function getReadme(owner, repo) { try { const { data } = await octokit.rest.repos.getReadme({ owner, repo }); const content = Buffer.from(data.content, "base64").toString("utf-8"); return content.substring(0, 3000); } catch { return "(无法获取README)"; } } async function main() { const startTime = new Date(); const now = new Date(); const cnOffset = 8 * 60 * 60 * 1000; const cnTime = new Date(now.getTime() + cnOffset); const today = cnTime.toISOString().split("T")[0]; console.log(`🔥 [Task6] ${today} 评测最热工具...`); const unreviewed = await prisma.skill.findMany({ where: { status: "published", sourceUrl: { contains: "github.com" }, skillReview: null, }, orderBy: [{ githubStars: "desc" }, { viewCount: "desc" }], take: 2, select: { id: true, name: true, description: true, sourceUrl: true, githubStars: true }, }); if (unreviewed.length === 0) { console.log(" 所有热门技能已有评测,跳过"); await prisma.taskLog.create({ data: { taskKey: "task6-review-hot", taskName: "评测热门技能", status: "completed", startedAt: startTime, finishedAt: new Date(), duration: new Date().getTime() - startTime.getTime(), result: { reviewed: 0, message: "所有热门技能已有评测" }, }, }); await prisma.$disconnect(); return; } console.log(` 待评测: ${unreviewed.map((s) => s.name).join(", ")}`); let reviewed = 0; let failed = 0; const errors = []; for (const skill of unreviewed) { console.log(`\n 📦 评测: ${skill.name} (${skill.githubStars}⭐)`); const parsed = extractOwnerRepo(skill.sourceUrl); let readme = "(无README)"; let stars = skill.githubStars; if (parsed) { readme = await getReadme(parsed.owner, parsed.repo); try { const { data } = await octokit.rest.repos.get({ owner: parsed.owner, repo: parsed.repo }); stars = data.stargazers_count; } catch {} } const prompt = REVIEW_PROMPT .replace("{name}", skill.name) .replace("{description}", skill.description || "") .replace("{stars}", String(stars)) .replace("{readme}", readme.substring(0, 2000)); try { const completion = await openai.chat.completions.create({ model: "deepseek-v4-pro", messages: [{ role: "user", content: prompt }], temperature: 0.3, }); const content = completion.choices[0]?.message?.content; if (!content) throw new Error("DeepSeek返回为空"); const parsed_review = extractJSON(content); const scores = [parsed_review.capability, parsed_review.devExp, parsed_review.costLicense, parsed_review.community, parsed_review.performance].filter((s) => typeof s === "number"); const overall = parsed_review.overall || (scores.length > 0 ? Math.round((scores.reduce((a, b) => a + b, 0) / scores.length) * 10) / 10 : 3.0); await prisma.skillReview.create({ data: { skillId: skill.id, capability: parsed_review.capability || 3.0, devExp: parsed_review.devExp || 3.0, costLicense: parsed_review.costLicense || 3.0, community: parsed_review.community || 3.0, performance: parsed_review.performance || 3.0, overall, reviews: parsed_review.reviews || {}, tags: parsed_review.tags || [], status: "PUBLISHED", reviewedAt: new Date(), }, }); await prisma.skill.update({ where: { id: skill.id }, data: { lastReviewedAt: new Date() }, }); reviewed++; console.log(` ✅ 评测完成: overall=${overall}`); } catch (e) { failed++; errors.push(`${skill.name}: ${e.message}`); console.error(` ❌ 评测失败: ${e.message}`); } } const endTime = new Date(); const duration = endTime.getTime() - startTime.getTime(); const result = { total: unreviewed.length, reviewed, failed, duration: `${(duration / 1000).toFixed(1)}s`, }; console.log(`\n🏁 [Task6] 完成: 评测${reviewed}个, 失败${failed}个`); await prisma.taskLog.create({ data: { taskKey: "task6-review-hot", taskName: "评测热门技能", status: "completed", startedAt: startTime, finishedAt: endTime, duration, result, error: errors.length > 0 ? errors.slice(0, 5).join("\n") : null, }, }); await prisma.$disconnect(); } main().catch(async (e) => { console.error("❌ 任务六失败:", e.message); if (prisma) { try { await prisma.taskLog.create({ data: { taskKey: "task6-review-hot", taskName: "评测热门技能", status: "failed", startedAt: new Date(), finishedAt: new Date(), duration: 0, error: e.message, }, }); } catch {} await prisma.$disconnect(); } process.exit(1); });