2026-05-25: 等级体系升级(20级)+论坛二级分类+会员空间
This commit is contained in:
+70
-31
@@ -3,6 +3,7 @@ import { PrismaMariaDb } from "@prisma/adapter-mariadb";
|
||||
import { Octokit } from "octokit";
|
||||
import OpenAI from "openai";
|
||||
import "dotenv/config";
|
||||
import { withRetry } from "./lib/retry.mjs";
|
||||
|
||||
const base = process.env.DATABASE_URL || "mysql://localhost:3306/zhuiguang_ai?charset=utf8mb4";
|
||||
const sep = base.includes("?") ? "&" : "?";
|
||||
@@ -165,13 +166,17 @@ function genSlug(name) {
|
||||
|
||||
async function searchRepos(query, minStars, max) {
|
||||
const q = `${query} stars:>${minStars} pushed:>2024-01-01`;
|
||||
const { data } = await octokit.rest.search.repos({ q, sort: "stars", order: "desc", per_page: max });
|
||||
const { data } = await withRetry(async () => {
|
||||
return await octokit.rest.search.repos({ q, sort: "stars", order: "desc", per_page: max });
|
||||
}, { maxRetries: 3, label: `GitHub Search "${query}"` });
|
||||
return data.items;
|
||||
}
|
||||
|
||||
async function getReadme(owner, repo) {
|
||||
try {
|
||||
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
|
||||
const { data } = await withRetry(async () => {
|
||||
return await octokit.rest.repos.getReadme({ owner, repo });
|
||||
}, { maxRetries: 3, label: `GitHub README ${owner}/${repo}` });
|
||||
return Buffer.from(data.content, "base64").toString("utf-8");
|
||||
} catch { return ""; }
|
||||
}
|
||||
@@ -181,9 +186,11 @@ async function genReview(repoName, desc, stars, license, readme) {
|
||||
.replace("{repoName}", repoName).replace("{description}", desc || "暂无描述")
|
||||
.replace("{stars}", stars.toString()).replace("{license}", license || "未知")
|
||||
.replace("{readmeSummary}", readme.substring(0, 1500));
|
||||
const { choices } = await openai.chat.completions.create({
|
||||
model: "deepseek-v4-pro", messages: [{ role: "user", content: prompt }], temperature: 0.3,
|
||||
});
|
||||
const { choices } = await withRetry(async () => {
|
||||
return await openai.chat.completions.create({
|
||||
model: "deepseek-v4-pro", messages: [{ role: "user", content: prompt }], temperature: 0.3,
|
||||
});
|
||||
}, { maxRetries: 3, label: "DeepSeek Review API" });
|
||||
const content = choices[0]?.message?.content;
|
||||
if (!content) throw new Error("Empty response");
|
||||
let review = extractJSON(content);
|
||||
@@ -199,15 +206,16 @@ async function genReview(repoName, desc, stars, license, readme) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`🚀 [Daily Discover] ${new Date().toISOString().split("T")[0]} 开始...\n`);
|
||||
const DAILY_LIMIT = 10;
|
||||
const startTime = new Date();
|
||||
console.log(`🚀 [Task4] ${new Date().toISOString().split("T")[0]} 发现新AI技能...\n`);
|
||||
const DAILY_LIMIT = 30;
|
||||
let discovered = 0, skipped = 0, reviewed = 0, failed = 0;
|
||||
|
||||
for (const query of SEARCH_QUERIES) {
|
||||
if (discovered >= DAILY_LIMIT) break;
|
||||
console.log(`🔍 "${query}"`);
|
||||
let repos;
|
||||
try { repos = await searchRepos(query, 500, 5); } catch (e) { console.error(` ❌ 搜索失败: ${e.message}`); failed++; continue; }
|
||||
try { repos = await searchRepos(query, 1000, 5); } catch (e) { console.error(` ❌ 搜索失败: ${e.message}`); failed++; continue; }
|
||||
|
||||
for (const repo of repos) {
|
||||
if (discovered >= DAILY_LIMIT) break;
|
||||
@@ -225,36 +233,31 @@ async function main() {
|
||||
const [owner, name] = repo.full_name.split("/");
|
||||
const readme = await getReadme(owner, name);
|
||||
let reviewData = null;
|
||||
try { reviewData = await genReview(repo.full_name, repo.description || "", repo.stargazers_count, repo.license?.spdx_id || "Unknown", readme); console.log(` ✅ 评测: ${reviewData.overall}`); reviewed++; }
|
||||
catch (e) { console.log(` ⚠️ 评测失败: ${e.message}`); }
|
||||
try { reviewData = await genReview(repo.full_name, repo.description || "", repo.stargazers_count, repo.license?.spdx_id || "Unknown", readme); console.log(` ✅ 预评测: ${reviewData.overall}`); reviewed++; }
|
||||
catch (e) { console.log(` ⚠️ 预评测失败: ${e.message}`); }
|
||||
|
||||
const categoryId = await guessCategory(repo.full_name, repo.description || "", reviewData?.tags || []);
|
||||
const skill = await prisma.skill.create({
|
||||
const pendingSkill = await prisma.pendingSkill.create({
|
||||
data: {
|
||||
name: repo.full_name.substring(0, 100), slug, categoryId,
|
||||
description: repo.description || "", sourceUrl: repo.html_url, sourceType: "github",
|
||||
rating: reviewData?.overall || 0,
|
||||
features: { forks: repo.forks_count, language: repo.language, license: repo.license?.spdx_id || null, topics: repo.topics || [] },
|
||||
tags: reviewData?.tags || [], status: "draft",
|
||||
lastReviewedAt: reviewData ? new Date() : null,
|
||||
githubStarsHistory: [{ date: new Date().toISOString().split("T")[0], stars: repo.stargazers_count }],
|
||||
features: {
|
||||
forks: repo.forks_count,
|
||||
language: repo.language,
|
||||
license: repo.license?.spdx_id || null,
|
||||
topics: repo.topics || [],
|
||||
stars: repo.stargazers_count,
|
||||
reviewData: reviewData || null,
|
||||
},
|
||||
tags: reviewData?.tags || [],
|
||||
status: "pending",
|
||||
},
|
||||
});
|
||||
console.log(` ✅ Skill #${skill.id}`);
|
||||
|
||||
if (reviewData) {
|
||||
await prisma.skillReview.create({
|
||||
data: {
|
||||
skillId: skill.id, capability: reviewData.capability.score, devExp: reviewData.devExp.score,
|
||||
costLicense: reviewData.costLicense.score, community: reviewData.community.score,
|
||||
performance: reviewData.performance.score, overall: reviewData.overall,
|
||||
reviews: reviewData, status: "DRAFT",
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log(` ✅ PendingSkill #${pendingSkill.id}`);
|
||||
|
||||
await prisma.reviewGenerationLog.create({
|
||||
data: { skillId: skill.id, status: "SUCCESS", prompt: `daily-discover: ${repo.full_name}`, response: reviewData ? JSON.stringify(reviewData) : null },
|
||||
data: { skillId: pendingSkill.id, status: "SUCCESS", prompt: `task4-discover: ${repo.full_name}`, response: reviewData ? JSON.stringify(reviewData) : null },
|
||||
});
|
||||
discovered++;
|
||||
} catch (e) {
|
||||
@@ -262,14 +265,50 @@ async function main() {
|
||||
failed++;
|
||||
try {
|
||||
await prisma.reviewGenerationLog.create({
|
||||
data: { skillId: 0, status: "FAILED", prompt: `daily-discover: ${repo.full_name}`, error: e.message?.substring(0, 500) || "Unknown" },
|
||||
data: { skillId: 0, status: "FAILED", prompt: `task4-discover: ${repo.full_name}`, error: e.message?.substring(0, 500) || "Unknown" },
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n🏁 [Daily Discover] 完成: 发现${discovered} / 跳过${skipped} / 评测${reviewed} / 失败${failed}`);
|
||||
const endTime = new Date();
|
||||
const duration = endTime.getTime() - startTime.getTime();
|
||||
const result = { discovered, skipped, reviewed, failed, duration: `${(duration / 1000).toFixed(1)}s` };
|
||||
|
||||
console.log(`\n🏁 [Task4] 完成: 发现${discovered} / 跳过${skipped} / 评测${reviewed} / 失败${failed}`);
|
||||
|
||||
await prisma.taskLog.create({
|
||||
data: {
|
||||
taskKey: "task4-discover-skills",
|
||||
taskName: "发现AI技能",
|
||||
status: "completed",
|
||||
startedAt: startTime,
|
||||
finishedAt: endTime,
|
||||
duration,
|
||||
result,
|
||||
error: failed > discovered * 2 ? `失败率过高: ${failed}/${discovered}` : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error("💥 异常:", e); process.exit(1); }).finally(() => prisma.$disconnect());
|
||||
main().catch(async (e) => {
|
||||
console.error("💥 异常:", e);
|
||||
if (prisma) {
|
||||
try {
|
||||
await prisma.taskLog.create({
|
||||
data: {
|
||||
taskKey: "task4-discover-skills",
|
||||
taskName: "发现AI技能",
|
||||
status: "failed",
|
||||
startedAt: new Date(),
|
||||
finishedAt: new Date(),
|
||||
duration: 0,
|
||||
error: e.message,
|
||||
},
|
||||
});
|
||||
} catch {}
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
process.exit(1);
|
||||
}).finally(() => prisma.$disconnect());
|
||||
|
||||
Reference in New Issue
Block a user