v0.11.1: 权限体系重构 + AI管线优化 + 内容整理

This commit is contained in:
sgp
2026-05-22 18:36:41 +08:00
parent 7955d11c52
commit b33fe1e8e1
397 changed files with 30940 additions and 161 deletions
+373
View File
@@ -0,0 +1,373 @@
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 || undefined,
});
const openai = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/v1",
});
const CATEGORY_KEYWORDS = {
"AI Agent": ["agent", "autonomous", "workflow", "tool call"],
"代码生成": ["code", "programming", "developer", "copilot"],
"测试": ["test", "testing", "qa", "quality"],
"文档": ["documentation", "docs", "readme"],
"安全": ["security", "vulnerability", "safety", "alignment"],
"DevOps与部署": ["deployment", "deploy", "devops", "ci/cd", "docker", "kubernetes"],
"RAG": ["rag", "retrieval", "vector", "embedding"],
"AI/ML工程": ["training", "fine-tun", "model", "machine learning", "ml"],
"多模态": ["multimodal", "vision", "image", "video", "audio"],
"Prompt工程": ["prompt", "prompting", "instruction"],
};
const NON_AI_KEYWORDS = [
"book", "guide", "tutorial", "awesome", "interview",
"notes", "handbook", "cheatsheet", "cheat-sheet", "roadmap",
"everything you need to know", "curated list", "collection",
];
const AI_TOPICS = [
"ai", "artificial-intelligence", "machine-learning", "deep-learning",
"llm", "large-language-model", "nlp", "natural-language-processing",
"agent", "rag", "gpt", "transformer", "generative-ai", "chatgpt",
"langchain", "prompt-engineering", "multimodal", "text-generation",
"vector-database", "embedding", "model-training", "fine-tuning",
];
function isValidAIProject(repo) {
const name = (repo.full_name || "").toLowerCase();
const desc = (repo.description || "").toLowerCase();
const text = `${name} ${desc}`;
for (const kw of NON_AI_KEYWORDS) {
if (text.includes(kw)) {
console.log(
` 🚫 过滤(非AI): ${repo.full_name} (命中关键词: "${kw}")`
);
return false;
}
}
const language = (repo.language || "").toLowerCase();
if (["markdown", "html", "tex", "latex"].includes(language) && repo.stargazers_count < 5000) {
console.log(
` 🚫 过滤(文档): ${repo.full_name} (语言: ${repo.language}, ⭐${repo.stargazers_count})`
);
return false;
}
const topics = (repo.topics || []).map((t) => t.toLowerCase());
if (topics.length > 0) {
const hasAITopic = topics.some((t) =>
AI_TOPICS.some((ai) => t.includes(ai))
);
if (!hasAITopic) {
console.log(
` 🚫 过滤(无AI主题): ${repo.full_name} (topics: ${topics.join(", ").substring(0, 100)})`
);
return false;
}
}
return true;
}
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 {}
}
const arrayMatch = jsonStr.match(/\[[\s\S]*\]/);
if (arrayMatch) {
try { return JSON.parse(arrayMatch[0]); } catch {}
}
return JSON.parse(jsonStr);
}
async function guessCategory(name, desc, tags) {
const categories = await prisma.skillCategory.findMany({
select: { id: true, name: true },
orderBy: { sortOrder: "asc" },
});
if (categories.length === 0) {
throw new Error("数据库中没有技能分类,请先创建分类");
}
const text = `${name} ${desc} ${tags.join(" ")}`.toLowerCase();
for (const [categoryName, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
for (const kw of keywords) {
if (text.includes(kw)) {
const match = categories.find((c) => c.name === categoryName);
if (match) return match.id;
}
}
}
return categories[0].id;
}
const REVIEW_PROMPT = `你是一个GitHub开源项目评测专家。请基于以下项目信息进行五维深度评测,并以JSON格式返回。务必只返回JSON,不要包含任何其他文字或Markdown格式。
项目信息:
- 仓库全名:{repoName}
- 简介:{description}
- GitHub Stars:{stars}
- 开源许可证:{license}
- README摘要(前1500字):{readmeSummary}
请严格按照以下JSON结构输出:
{
"capability": { "score": 4.5, "summary": "一句话亮点(15字内)", "detail": "80-120字场景能力分析" },
"devExp": { "score": 3.5, "summary": "上手体验总结", "detail": "80-120字,包含安装、文档、API评价" },
"costLicense": { "score": 4.0, "summary": "成本与许可总结", "detail": "80-120字,包含硬件成本、API定价、许可证风险" },
"community": { "score": 4.5, "summary": "社区健康度总结", "detail": "80-120字,包含Issue响应、PR频率、生态描述" },
"performance": { "score": 3.5, "summary": "性能稳定性总结", "detail": "80-120字,包含运行效率、稳定性、输出质量" },
"overall": 4.0,
"tags": ["标签1", "标签2", "标签3"]
}
评分要求:
- 分数为1-5之间,可使用0.5步长
- 综合分应为五个维度分数的算术平均值(保留1位小数)
- 每个维度的summary需严格控制在15字以内
- 每个维度的detail需严格控制在80-120字
- 标签从以下类别中选择最匹配的3-5个:LLM框架, Agent框架, RAG, 提示工程, 多模态, 代码助手, 模型训练, 数据工程, 部署工具, 评测工具, 安全对齐, 其他`;
async function searchHotRepos(query, minStars = 500, maxResults = 10) {
const q = `${query} stars:>${minStars} pushed:>2024-01-01`;
const { data } = await octokit.rest.search.repos({
q,
sort: "stars",
order: "desc",
per_page: maxResults,
});
return data.items;
}
async function getRepoReadme(owner, repo) {
try {
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
return Buffer.from(data.content, "base64").toString("utf-8");
} catch {
return "";
}
}
async function generateReview(repoName, description, stars, license, readme) {
const prompt = REVIEW_PROMPT
.replace("{repoName}", repoName)
.replace("{description}", description || "暂无描述")
.replace("{stars}", stars.toString())
.replace("{license}", license || "未知")
.replace("{readmeSummary}", readme.substring(0, 1500));
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 API 返回内容为空");
}
let review = extractJSON(content);
if (review.data && typeof review.data === "object") review = review.data;
if (review.result && typeof review.result === "object") review = review.result;
if (!review.capability || !review.devExp) {
throw new Error("评测JSON结构不完整: " + JSON.stringify(Object.keys(review)).substring(0, 200));
}
const dims = [
review.capability.score,
review.devExp.score,
review.costLicense.score,
review.community.score,
review.performance.score,
];
const avg = dims.reduce((a, b) => a + b, 0) / 5;
if (Math.abs(review.overall - avg) > 0.3) {
console.warn(
`⚠️ 综合分 ${review.overall} 与均分 ${avg.toFixed(1)} 偏差较大,已自动修正为均分`
);
review.overall = Math.round(avg * 10) / 10;
}
return review;
}
function generateSlug(fullName) {
return fullName
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.substring(0, 100);
}
async function main() {
console.log("🚀 [Auto Review] 开始自动化评测任务...\n");
const SEARCH_QUERIES = [
"ai agent",
"llm framework",
"rag",
"prompt engineering",
];
const MIN_STARS = 500;
const MAX_PER_QUERY = 5;
const DAILY_LIMIT = 8;
let reviewedCount = 0;
for (const query of SEARCH_QUERIES) {
if (reviewedCount >= DAILY_LIMIT) break;
console.log(`🔍 搜索关键词: "${query}" (min ${MIN_STARS} stars)`);
const repos = await searchHotRepos(query, MIN_STARS, MAX_PER_QUERY);
console.log(` 找到 ${repos.length} 个仓库\n`);
for (const repo of repos) {
if (reviewedCount >= DAILY_LIMIT) break;
const existing = await prisma.skill.findFirst({
where: { sourceUrl: repo.html_url },
});
if (existing) {
console.log(
` ⏭️ 跳过已存在: ${repo.full_name} (⭐${repo.stargazers_count})`
);
continue;
}
if (!isValidAIProject(repo)) {
continue;
}
console.log(
`\n📦 [${reviewedCount + 1}/${DAILY_LIMIT}] 处理: ${repo.full_name} (⭐${repo.stargazers_count})`
);
try {
console.log(" 📖 获取 README...");
const [owner, name] = repo.full_name.split("/");
const readme = await getRepoReadme(owner, name);
console.log(` ✅ README 长度: ${readme.length} 字符`);
console.log(" 🤖 生成五维评测...");
const reviewData = await generateReview(
repo.full_name,
repo.description || "",
repo.stargazers_count,
repo.license?.spdx_id || "Unknown",
readme
);
console.log(` ✅ 综合评分: ${reviewData.overall}`);
const slug = generateSlug(repo.full_name);
const slugExists = await prisma.skill.findUnique({ where: { slug } });
if (slugExists) {
console.log(` ⏭️ 跳过slug冲突: ${slug}`);
continue;
}
const categoryId = await guessCategory(repo.full_name, repo.description || "", reviewData?.tags || []);
const skill = await prisma.skill.create({
data: {
name: repo.full_name.substring(0, 100),
slug,
categoryId,
description: repo.description || "",
sourceUrl: repo.html_url,
sourceType: "github",
rating: reviewData.overall,
features: {
forks: repo.forks_count,
language: repo.language,
license: repo.license?.spdx_id || null,
topics: repo.topics || [],
},
tags: reviewData.tags || [],
status: "draft",
lastReviewedAt: new Date(),
githubStarsHistory: [
{
date: new Date().toISOString().split("T")[0],
stars: repo.stargazers_count,
},
],
},
});
console.log(` ✅ Skill 已创建 (ID: ${skill.id})`);
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(" ✅ 评测记录已保存");
await prisma.reviewGenerationLog.create({
data: {
skillId: skill.id,
status: "SUCCESS",
prompt: repo.full_name,
response: JSON.stringify(reviewData),
},
});
reviewedCount++;
console.log(" 🎉 完成!\n");
} catch (error) {
console.error(` ❌ 失败: ${error.message}`);
await prisma.reviewGenerationLog.create({
data: {
skillId: 0,
status: "FAILED",
prompt: repo.full_name,
error: error.message?.substring(0, 500) || "Unknown error",
},
});
}
}
}
console.log(
`\n🏁 [Auto Review] 完成。今日评测: ${reviewedCount} 个项目`
);
}
main()
.catch((e) => {
console.error("💥 脚本异常:", e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
+55
View File
@@ -0,0 +1,55 @@
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
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 NON_AI_PATTERNS = [
"book", "guide", "tutorial", "awesome", "interview",
"notes", "handbook", "cheatsheet", "cheat-sheet", "roadmap",
"everything you need to know",
"StableDiffusionBook",
"Claude-Code-Everything-You-Need-to-Know",
"Snailclimb/JavaGuide",
"pathwaycom/pathway",
"Shubhamsaboo/awesome-llm-apps",
];
async function main() {
console.log("🧹 清理误入库的非AI技能数据\n");
const skills = await prisma.skill.findMany({
select: { id: true, name: true, sourceUrl: true, status: true },
});
let cleaned = 0;
for (const skill of skills) {
const name = skill.name.toLowerCase();
const isNonAI = NON_AI_PATTERNS.some((p) => name.includes(p.toLowerCase()));
if (isNonAI && skill.status === "draft") {
await prisma.skillReview.deleteMany({ where: { skillId: skill.id } });
await prisma.reviewGenerationLog.deleteMany({ where: { skillId: skill.id } });
await prisma.skill.delete({ where: { id: skill.id } });
console.log(`🗑️ 已删除: ${skill.name}`);
cleaned++;
} else if (isNonAI) {
await prisma.skill.update({
where: { id: skill.id },
data: { status: "draft" },
});
console.log(`📝 已标记为draft: ${skill.name}`);
cleaned++;
}
}
if (cleaned === 0) console.log("✅ 没有需要清理的误入数据");
else console.log(`\n✅ 已清理 ${cleaned} 条数据`);
}
main()
.catch((e) => { console.error("💥 清理失败:", e.message); process.exit(1); })
.finally(() => prisma.$disconnect());
+275
View File
@@ -0,0 +1,275 @@
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 || undefined,
});
const openai = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/v1",
});
const REVIEW_PROMPT = `你是一个GitHub开源项目评测专家。请基于以下项目信息进行五维深度评测,并以JSON格式返回。务必只返回JSON,不要包含任何其他文字或Markdown格式。
项目信息:
- 仓库全名:{repoName}
- 简介:{description}
- GitHub Stars:{stars}
- 开源许可证:{license}
- README摘要(前1500字):{readmeSummary}
请严格按照以下JSON结构输出:
{
"capability": { "score": 4.5, "summary": "一句话亮点(15字内)", "detail": "80-120字场景能力分析" },
"devExp": { "score": 3.5, "summary": "上手体验总结", "detail": "80-120字,包含安装、文档、API评价" },
"costLicense": { "score": 4.0, "summary": "成本与许可总结", "detail": "80-120字,包含硬件成本、API定价、许可证风险" },
"community": { "score": 4.5, "summary": "社区健康度总结", "detail": "80-120字,包含Issue响应、PR频率、生态描述" },
"performance": { "score": 3.5, "summary": "性能稳定性总结", "detail": "80-120字,包含运行效率、稳定性、输出质量" },
"overall": 4.0,
"tags": ["标签1", "标签2", "标签3"]
}
评分要求:
- 分数为1-5之间,可使用0.5步长
- 综合分应为五个维度分数的算术平均值(保留1位小数)
- 每个维度的summary需严格控制在15字以内
- 每个维度的detail需严格控制在80-120字
- 标签从以下类别中选择最匹配的3-5个:LLM框架, Agent框架, RAG, 提示工程, 多模态, 代码助手, 模型训练, 数据工程, 部署工具, 评测工具, 安全对齐, 其他`;
const SEARCH_QUERIES = [
"ai agent framework",
"llm framework",
"rag framework",
"prompt engineering tool",
"ai code assistant",
"multimodal ai",
"ai model training",
"ai deployment tool",
"ai evaluation benchmark",
"ai safety alignment",
];
const CATEGORY_KEYWORDS = {
"AI Agent": ["agent", "autonomous", "workflow", "tool call"],
"代码生成": ["code", "programming", "developer", "copilot"],
"测试": ["test", "testing", "qa", "quality"],
"文档": ["documentation", "docs", "readme"],
"安全": ["security", "vulnerability", "safety", "alignment"],
"DevOps与部署": ["deployment", "deploy", "devops", "ci/cd", "docker", "kubernetes"],
"RAG": ["rag", "retrieval", "vector", "embedding"],
"AI/ML工程": ["training", "fine-tun", "model", "machine learning", "ml"],
"多模态": ["multimodal", "vision", "image", "video", "audio"],
"Prompt工程": ["prompt", "prompting", "instruction"],
};
const NON_AI_KEYWORDS = [
"book", "guide", "tutorial", "awesome", "interview",
"notes", "handbook", "cheatsheet", "cheat-sheet", "roadmap",
"everything you need to know", "curated list", "collection",
];
const AI_TOPICS = [
"ai", "artificial-intelligence", "machine-learning", "deep-learning",
"llm", "large-language-model", "nlp", "natural-language-processing",
"agent", "rag", "gpt", "transformer", "generative-ai", "chatgpt",
"langchain", "prompt-engineering", "multimodal", "text-generation",
"vector-database", "embedding", "model-training", "fine-tuning",
];
function isValidAIProject(repo) {
const name = (repo.full_name || "").toLowerCase();
const desc = (repo.description || "").toLowerCase();
const text = `${name} ${desc}`;
for (const kw of NON_AI_KEYWORDS) {
if (text.includes(kw)) {
console.log(
` 🚫 过滤(非AI): ${repo.full_name} (命中关键词: "${kw}")`
);
return false;
}
}
const language = (repo.language || "").toLowerCase();
if (["markdown", "html", "tex", "latex"].includes(language) && repo.stargazers_count < 5000) {
console.log(
` 🚫 过滤(文档): ${repo.full_name} (语言: ${repo.language}, ⭐${repo.stargazers_count})`
);
return false;
}
const topics = (repo.topics || []).map((t) => t.toLowerCase());
if (topics.length > 0) {
const hasAITopic = topics.some((t) =>
AI_TOPICS.some((ai) => t.includes(ai))
);
if (!hasAITopic) {
console.log(
` 🚫 过滤(无AI主题): ${repo.full_name} (topics: ${topics.join(", ").substring(0, 100)})`
);
return false;
}
}
return true;
}
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 {}
}
const arrayMatch = jsonStr.match(/\[[\s\S]*\]/);
if (arrayMatch) {
try { return JSON.parse(arrayMatch[0]); } catch {}
}
return JSON.parse(jsonStr);
}
async function guessCategory(name, desc, tags) {
const categories = await prisma.skillCategory.findMany({
select: { id: true, name: true },
orderBy: { sortOrder: "asc" },
});
if (categories.length === 0) {
throw new Error("数据库中没有技能分类,请先创建分类");
}
const text = `${name} ${desc} ${tags.join(" ")}`.toLowerCase();
for (const [categoryName, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
for (const kw of keywords) {
if (text.includes(kw)) {
const match = categories.find((c) => c.name === categoryName);
if (match) return match.id;
}
}
}
return categories[0].id;
}
function genSlug(name) {
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 100);
}
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 });
return data.items;
}
async function getReadme(owner, repo) {
try {
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
return Buffer.from(data.content, "base64").toString("utf-8");
} catch { return ""; }
}
async function genReview(repoName, desc, stars, license, readme) {
const prompt = REVIEW_PROMPT
.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 content = choices[0]?.message?.content;
if (!content) throw new Error("Empty response");
let review = extractJSON(content);
if (review.data && typeof review.data === "object") review = review.data;
if (review.result && typeof review.result === "object") review = review.result;
if (!review.capability || !review.devExp || !review.costLicense || !review.community || !review.performance) {
throw new Error("评测JSON结构不完整: " + JSON.stringify(Object.keys(review)).substring(0, 200));
}
const dims = [review.capability.score, review.devExp.score, review.costLicense.score, review.community.score, review.performance.score];
const avg = dims.reduce((a, b) => a + b, 0) / 5;
if (Math.abs(review.overall - avg) > 0.3) review.overall = Math.round(avg * 10) / 10;
return review;
}
async function main() {
console.log(`🚀 [Daily Discover] ${new Date().toISOString().split("T")[0]} 开始...\n`);
const DAILY_LIMIT = 10;
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; }
for (const repo of repos) {
if (discovered >= DAILY_LIMIT) break;
const ex = await prisma.skill.findFirst({ where: { sourceUrl: repo.html_url } });
if (ex) { console.log(` ⏭️ ${repo.full_name} 已存在`); skipped++; continue; }
const exP = await prisma.pendingSkill.findFirst({ where: { sourceUrl: repo.html_url } });
if (exP) { console.log(` ⏭️ ${repo.full_name} 待审核中`); skipped++; continue; }
const slug = genSlug(repo.full_name);
const slugEx = await prisma.skill.findUnique({ where: { slug } });
if (slugEx) { console.log(` ⏭️ ${repo.full_name} slug冲突`); skipped++; continue; }
if (!isValidAIProject(repo)) { skipped++; continue; }
console.log(`\n📦 [${discovered + 1}/${DAILY_LIMIT}] ${repo.full_name} (⭐${repo.stargazers_count})`);
try {
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}`); }
const categoryId = await guessCategory(repo.full_name, repo.description || "", reviewData?.tags || []);
const skill = await prisma.skill.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 }],
},
});
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",
},
});
}
await prisma.reviewGenerationLog.create({
data: { skillId: skill.id, status: "SUCCESS", prompt: `daily-discover: ${repo.full_name}`, response: reviewData ? JSON.stringify(reviewData) : null },
});
discovered++;
} catch (e) {
console.error(` ❌ 失败: ${e.message}`);
failed++;
try {
await prisma.reviewGenerationLog.create({
data: { skillId: 0, status: "FAILED", prompt: `daily-discover: ${repo.full_name}`, error: e.message?.substring(0, 500) || "Unknown" },
});
} catch {}
}
}
}
console.log(`\n🏁 [Daily Discover] 完成: 发现${discovered} / 跳过${skipped} / 评测${reviewed} / 失败${failed}`);
}
main().catch((e) => { console.error("💥 异常:", e); process.exit(1); }).finally(() => prisma.$disconnect());
+89
View File
@@ -0,0 +1,89 @@
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
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 });
async function main() {
console.log("📊 数据库内容状态总览\n");
const [skillCount, publishedSkillCount, draftSkillCount, reviewCount, publishedReviewCount, draftReviewCount, dailyReportCount, publishedDailyCount, toolCount, publishedToolCount, pendingToolCount, pendingSkillCount] = await Promise.all([
prisma.skill.count(),
prisma.skill.count({ where: { status: "published" } }),
prisma.skill.count({ where: { status: "draft" } }),
prisma.skillReview.count(),
prisma.skillReview.count({ where: { status: "PUBLISHED" } }),
prisma.skillReview.count({ where: { status: "DRAFT" } }),
prisma.dailyReport.count(),
prisma.dailyReport.count({ where: { status: "published" } }),
prisma.tool.count(),
prisma.tool.count({ where: { status: "published" } }),
prisma.pendingTool.count(),
prisma.pendingSkill.count(),
]);
console.log("=== 技能 ===");
console.log(` 总数: ${skillCount} | 已发布: ${publishedSkillCount} | 草稿: ${draftSkillCount}`);
console.log(` 待审核: ${pendingSkillCount}`);
console.log("\n=== 评测 ===");
console.log(` 总数: ${reviewCount} | 已发布: ${publishedReviewCount} | 草稿: ${draftReviewCount}`);
console.log("\n=== 日报 ===");
console.log(` 总数: ${dailyReportCount} | 已发布: ${publishedDailyCount}`);
console.log("\n=== 工具 ===");
console.log(` 总数: ${toolCount} | 已发布: ${publishedToolCount} | 待审核: ${pendingToolCount}`);
console.log("\n=== 技能详情 ===");
const skills = await prisma.skill.findMany({
select: {
id: true, name: true, status: true, rating: true,
sourceUrl: true, description: true,
skillReview: { select: { id: true, overall: true, status: true, longArticle: true, videoScript: true, socialCards: true } },
},
orderBy: { id: "desc" },
});
for (const s of skills) {
const r = s.skillReview;
const hasContent = !!(r?.longArticle || r?.videoScript || r?.socialCards);
console.log(` #${s.id} [${s.status}] ${s.name} ⭐${s.rating} | 评测:${r ? `${r.overall}(${r.status})` : "无"} | 内容:${hasContent ? "✅" : "❌"} | ${s.description?.substring(0, 60) || "无描述"}`);
}
console.log("\n=== 评测详情 ===");
const reviews = await prisma.skillReview.findMany({
include: { skill: { select: { name: true } } },
orderBy: { id: "desc" },
});
for (const r of reviews) {
const reviewsData = r.reviews;
const dims = reviewsData ? {
cap: reviewsData.capability?.score,
dev: reviewsData.devExp?.score,
cost: reviewsData.costLicense?.score,
comm: reviewsData.community?.score,
perf: reviewsData.performance?.score,
} : {};
console.log(` #${r.id} [${r.status}] ${r.skill.name} | overall:${r.overall} | cap:${dims.cap} dev:${dims.dev} cost:${dims.cost} comm:${dims.comm} perf:${dims.perf}`);
console.log(` 长文:${r.longArticle ? `${String(r.longArticle).length}字` : "无"} | 脚本:${r.videoScript ? `${String(r.videoScript).length}字` : "无"} | 卡片:${r.socialCards ? "有" : "无"}`);
}
console.log("\n=== 日报详情 ===");
const reports = await prisma.dailyReport.findMany({
orderBy: { id: "desc" },
take: 5,
});
for (const r of reports) {
console.log(` #${r.id} [${r.status}] ${r.title} | ${r.reportDate?.toISOString().split("T")[0]}`);
}
}
main()
.catch((e) => { console.error("💥", e.message); process.exit(1); })
.finally(() => prisma.$disconnect());
+64
View File
@@ -0,0 +1,64 @@
import OpenAI from "openai";
import "dotenv/config";
const openai = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/v1",
});
async function main() {
const prompt = `你是一个GitHub开源项目评测专家。请基于以下项目信息进行五维深度评测,并以JSON格式返回。务必只返回JSON,不要包含任何其他文字或Markdown格式。
项目信息:
- 仓库全名:TabbyML/tabby
- 简介:Self-hosted AI coding assistant
- GitHub Stars:33538
- 开源许可证:Apache-2.0
- README摘要:An open-source, self-hosted AI coding assistant.
请严格按照以下JSON结构输出:
{
"capability": { "score": 4.5, "summary": "一句话亮点", "detail": "80-120字分析" },
"devExp": { "score": 3.5, "summary": "体验总结", "detail": "80-120字" },
"costLicense": { "score": 4.0, "summary": "成本总结", "detail": "80-120字" },
"community": { "score": 4.5, "summary": "社区总结", "detail": "80-120字" },
"performance": { "score": 3.5, "summary": "性能总结", "detail": "80-120字" },
"overall": 4.0,
"tags": ["标签1", "标签2", "标签3"]
}`;
const { choices } = await openai.chat.completions.create({
model: "deepseek-v4-pro",
messages: [{ role: "user", content: prompt }],
temperature: 0.3,
});
const content = choices[0]?.message?.content;
console.log("=== RAW RESPONSE ===");
console.log(content);
console.log("\n=== LENGTH ===", content?.length);
// Test extractJSON
const trimmed = content.trim();
const codeBlockMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
let jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : trimmed;
console.log("\n=== AFTER CODE BLOCK REMOVAL ===");
console.log("Has code block:", !!codeBlockMatch);
console.log("First 200 chars:", jsonStr.substring(0, 200));
const braceMatch = jsonStr.match(/\{[\s\S]*\}/);
if (braceMatch) {
console.log("\n=== BRACE MATCH ===");
console.log("First 200 chars:", braceMatch[0].substring(0, 200));
try {
const parsed = JSON.parse(braceMatch[0]);
console.log("\n=== PARSED KEYS ===", Object.keys(parsed));
console.log("Has capability:", !!parsed.capability);
console.log("Has devExp:", !!parsed.devExp);
} catch (e) {
console.log("Parse error:", e.message);
}
}
}
main().catch(console.error);
+21
View File
@@ -0,0 +1,21 @@
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import "dotenv/config";
const base = process.env.DATABASE_URL;
const sep = base.includes("?") ? "&" : "?";
const adapter = new PrismaMariaDb(base + sep + "connection_limit=20");
const prisma = new PrismaClient({ adapter });
async function main() {
const skills = await prisma.skill.findMany({ where: { status: "draft" }, select: { id: true, name: true } });
for (const s of skills) {
await prisma.skillReview.deleteMany({ where: { skillId: s.id } });
await prisma.reviewGenerationLog.deleteMany({ where: { skillId: s.id } });
await prisma.skill.delete({ where: { id: s.id } });
console.log("Deleted:", s.name);
}
console.log("Done:", skills.length, "deleted");
}
main().catch(console.error).finally(() => prisma.$disconnect());
+138
View File
@@ -0,0 +1,138 @@
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());
+74
View File
@@ -0,0 +1,74 @@
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
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 });
async function main() {
console.log("🔧 内容整理脚本\n");
// 1. 删除 JavaGuide 的评测(非AI项目不应有评测)
const javaGuide = await prisma.skillReview.findFirst({
where: { skill: { name: { contains: "JavaGuide" } } },
include: { skill: true },
});
if (javaGuide) {
await prisma.skillReview.delete({ where: { id: javaGuide.id } });
console.log(`🗑️ 已删除 JavaGuide 评测 #${javaGuide.id}`);
}
// 2. 删除 system-prompts 评测(纯资源收集,非AI工具)
const sysPrompts = await prisma.skillReview.findFirst({
where: { skill: { name: { contains: "system-prompts" } } },
include: { skill: true },
});
if (sysPrompts) {
await prisma.skillReview.delete({ where: { id: sysPrompts.id } });
console.log(`🗑️ 已删除 system-prompts 评测 #${sysPrompts.id}`);
}
// 3. 发布所有 DRAFT 评测
const draftReviews = await prisma.skillReview.findMany({
where: { status: "DRAFT" },
include: { skill: { select: { name: true } } },
});
for (const r of draftReviews) {
await prisma.skillReview.update({
where: { id: r.id },
data: { status: "PUBLISHED" },
});
console.log(`✅ 已发布评测 #${r.id}: ${r.skill.name} (overall: ${r.overall})`);
}
// 4. 发布所有 DRAFT 技能
const draftSkills = await prisma.skill.findMany({
where: { status: "draft" },
});
for (const s of draftSkills) {
await prisma.skill.update({
where: { id: s.id },
data: { status: "published" },
});
console.log(`✅ 已发布技能 #${s.id}: ${s.name}`);
}
// 5. 统计最终状态
const [skillTotal, publishedSkills, reviewTotal, publishedReviews] = await Promise.all([
prisma.skill.count(),
prisma.skill.count({ where: { status: "published" } }),
prisma.skillReview.count(),
prisma.skillReview.count({ where: { status: "PUBLISHED" } }),
]);
console.log(`\n📊 整理后状态:`);
console.log(` 技能: ${publishedSkills}/${skillTotal} 已发布`);
console.log(` 评测: ${publishedReviews}/${reviewTotal} 已发布`);
}
main()
.catch((e) => { console.error("💥", e.message); process.exit(1); })
.finally(() => prisma.$disconnect());
+27
View File
@@ -0,0 +1,27 @@
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
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 });
async function main() {
const result = await prisma.skillReview.updateMany({
where: { status: "DRAFT" },
data: { status: "PUBLISHED" },
});
console.log(`✅ 已将 ${result.count} 条评测状态改为 PUBLISHED`);
const skillResult = await prisma.skill.updateMany({
where: { status: "draft", lastReviewedAt: { not: null } },
data: { status: "published" },
});
console.log(`✅ 已将 ${skillResult.count} 条技能状态改为 published`);
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());
+146
View File
@@ -0,0 +1,146 @@
import { PrismaClient } from "@prisma/client";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
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 PERMISSIONS = [
{ groupName: "工具管理", name: "查看待审核工具", key: "tools.pending.view", sortOrder: 1 },
{ groupName: "工具管理", name: "通过工具", key: "tools.pending.approve", sortOrder: 2 },
{ groupName: "工具管理", name: "拒绝工具", key: "tools.pending.reject", sortOrder: 3 },
{ groupName: "工具管理", name: "批量操作工具", key: "tools.pending.batch", sortOrder: 4 },
{ groupName: "工具管理", name: "编辑待审核工具", key: "tools.pending.edit", sortOrder: 5 },
{ groupName: "工具管理", name: "抓取工具Logo", key: "tools.pending.logo", sortOrder: 6 },
{ groupName: "工具管理", name: "查看已发布工具", key: "tools.published.view", sortOrder: 7 },
{ groupName: "工具管理", name: "新增工具", key: "tools.published.create", sortOrder: 8 },
{ groupName: "工具管理", name: "编辑已发布工具", key: "tools.published.edit", sortOrder: 9 },
{ groupName: "工具管理", name: "删除工具", key: "tools.published.delete", sortOrder: 10 },
{ groupName: "工具管理", name: "查看工具分类", key: "tools.categories.view", sortOrder: 11 },
{ groupName: "工具管理", name: "新增工具分类", key: "tools.categories.create", sortOrder: 12 },
{ groupName: "工具管理", name: "编辑工具分类", key: "tools.categories.edit", sortOrder: 13 },
{ groupName: "工具管理", name: "删除工具分类", key: "tools.categories.delete", sortOrder: 14 },
{ groupName: "技能库", name: "查看待审核技能", key: "skills.pending.view", sortOrder: 15 },
{ groupName: "技能库", name: "通过技能", key: "skills.pending.approve", sortOrder: 16 },
{ groupName: "技能库", name: "拒绝技能", key: "skills.pending.reject", sortOrder: 17 },
{ groupName: "技能库", name: "批量操作技能", key: "skills.pending.batch", sortOrder: 18 },
{ groupName: "技能库", name: "编辑待审核技能", key: "skills.pending.edit", sortOrder: 19 },
{ groupName: "技能库", name: "查看已发布技能", key: "skills.published.view", sortOrder: 20 },
{ groupName: "技能库", name: "编辑已发布技能", key: "skills.published.edit", sortOrder: 21 },
{ groupName: "技能库", name: "删除技能", key: "skills.published.delete", sortOrder: 22 },
{ groupName: "技能库", name: "查看技能分类", key: "skills.categories.view", sortOrder: 23 },
{ groupName: "技能库", name: "新增技能分类", key: "skills.categories.create", sortOrder: 24 },
{ groupName: "技能库", name: "编辑技能分类", key: "skills.categories.edit", sortOrder: 25 },
{ groupName: "技能库", name: "删除技能分类", key: "skills.categories.delete", sortOrder: 26 },
{ groupName: "评测管理", name: "查看评测列表", key: "reviews.view", sortOrder: 27 },
{ groupName: "评测管理", name: "查看评测详情", key: "reviews.detail", sortOrder: 28 },
{ groupName: "评测管理", name: "发布评测", key: "reviews.publish", sortOrder: 29 },
{ groupName: "评测管理", name: "驳回评测", key: "reviews.reject", sortOrder: 30 },
{ groupName: "评测管理", name: "编辑评分", key: "reviews.edit_score", sortOrder: 31 },
{ groupName: "评测管理", name: "请求重评", key: "reviews.regenerate", sortOrder: 32 },
{ groupName: "评测管理", name: "删除评测", key: "reviews.delete", sortOrder: 33 },
{ groupName: "评测管理", name: "生成内容(长文/脚本/卡片)", key: "reviews.generate_content", sortOrder: 34 },
{ groupName: "AI发现", name: "工具导入", key: "discover.tools.import", sortOrder: 35 },
{ groupName: "AI发现", name: "工具Logo抓取", key: "discover.tools.logo", sortOrder: 36 },
{ groupName: "AI发现", name: "检测工具可访问性", key: "discover.tools.check", sortOrder: 37 },
{ groupName: "AI发现", name: "删除失效工具", key: "discover.tools.check_delete", sortOrder: 38 },
{ groupName: "AI发现", name: "自动发现技能", key: "discover.skills.auto", sortOrder: 39 },
{ groupName: "AI发现", name: "手动导入技能", key: "discover.skills.import", sortOrder: 40 },
{ groupName: "AI发现", name: "更新技能Stars", key: "discover.skills.update_stars", sortOrder: 41 },
{ groupName: "内容管理", name: "查看日报列表", key: "content.daily.view", sortOrder: 42 },
{ groupName: "内容管理", name: "新建日报", key: "content.daily.create", sortOrder: 43 },
{ groupName: "内容管理", name: "编辑日报", key: "content.daily.edit", sortOrder: 44 },
{ groupName: "内容管理", name: "删除日报", key: "content.daily.delete", sortOrder: 45 },
{ groupName: "内容管理", name: "发布/取消发布日报", key: "content.daily.publish", sortOrder: 46 },
{ groupName: "内容管理", name: "AI生成日报", key: "content.daily.ai_generate", sortOrder: 47 },
{ groupName: "内容管理", name: "管理新闻条目", key: "content.daily.items_manage", sortOrder: 48 },
{ groupName: "内容管理", name: "AI新闻发现导入", key: "content.news.import", sortOrder: 49 },
{ groupName: "系统管理", name: "查看系统配置", key: "system.config.view", sortOrder: 50 },
{ groupName: "系统管理", name: "编辑系统配置", key: "system.config.edit", sortOrder: 51 },
{ groupName: "系统管理", name: "查看角色权限", key: "system.roles.view", sortOrder: 52 },
{ groupName: "系统管理", name: "编辑角色权限", key: "system.roles.edit", sortOrder: 53 },
];
const VIP_ALLOWED = [
"tools.pending.view", "tools.published.view", "tools.categories.view",
"skills.pending.view", "skills.published.view", "skills.categories.view",
"reviews.view", "reviews.detail",
"discover.tools.import", "discover.tools.logo", "discover.tools.check",
"discover.skills.import",
"content.daily.view", "content.daily.create", "content.daily.edit", "content.daily.ai_generate",
"content.daily.items_manage", "content.news.import",
"system.config.view",
];
async function main() {
console.log("🔐 权限系统种子数据初始化\n");
for (const perm of PERMISSIONS) {
await prisma.permission.upsert({
where: { key: perm.key },
create: perm,
update: { groupName: perm.groupName, name: perm.name, sortOrder: perm.sortOrder },
});
}
console.log(`✅ ${PERMISSIONS.length} 个权限点已同步`);
const allPerms = await prisma.permission.findMany({ select: { id: true, key: true } });
const allPermIds = allPerms.map((p) => p.id);
let adminRole = await prisma.role.findUnique({ where: { slug: "admin" } });
if (!adminRole) {
adminRole = await prisma.role.create({
data: { name: "超级管理员", slug: "admin", isSystem: true },
});
console.log("✅ 超级管理员角色已创建");
} else {
await prisma.role.update({ where: { slug: "admin" }, data: { isSystem: true } });
console.log("ℹ️ 超级管理员角色已存在");
}
await prisma.rolePermission.deleteMany({ where: { roleId: adminRole.id } });
for (const permId of allPermIds) {
await prisma.rolePermission.create({ data: { roleId: adminRole.id, permissionId: permId } });
}
console.log(`✅ 超级管理员已分配全部 ${allPermIds.length} 个权限`);
let vipRole = await prisma.role.findUnique({ where: { slug: "vip" } });
if (!vipRole) {
vipRole = await prisma.role.create({
data: { name: "VIP成员", slug: "vip", isSystem: true },
});
console.log("✅ VIP角色已创建");
} else {
console.log("ℹ️ VIP角色已存在");
}
await prisma.rolePermission.deleteMany({ where: { roleId: vipRole.id } });
const vipPermIds = allPerms.filter((p) => VIP_ALLOWED.includes(p.key)).map((p) => p.id);
for (const permId of vipPermIds) {
await prisma.rolePermission.create({ data: { roleId: vipRole.id, permissionId: permId } });
}
console.log(`✅ VIP角色已分配 ${vipPermIds.length} 个权限`);
const adminUser = await prisma.user.findFirst({ where: { role: "admin" } });
if (adminUser) {
await prisma.user.update({
where: { id: adminUser.id },
data: { roleId: adminRole.id },
});
console.log("✅ 现有管理员用户已关联到超级管理员角色");
}
console.log("\n🏁 权限初始化完成");
}
main()
.catch((e) => { console.error("💥 种子失败:", e); process.exit(1); })
.finally(() => prisma.$disconnect());
+93
View File
@@ -0,0 +1,93 @@
import OpenAI from "openai";
import "dotenv/config";
const openai = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/v1",
});
async function main() {
console.log("🧪 DeepSeek 内容生成全管线测试\n");
// Test 1: 日报生成
console.log("=== Test 1: 日报生成 ===");
const dailyPrompt =
"你是AI行业资深分析师。请为2026年5月22日生成一份AI日报,包含3条简短的AI行业热点新闻。返回严格JSON格式,不要markdown包裹:\n" +
'{"title":"AI日报:2026年5月22日","summary":"今日要点概述","items":[{"category":"model","title":"新闻标题","summary":"一句话摘要"}]}';
const r1 = await openai.chat.completions.create({
model: "deepseek-v4-pro",
messages: [{ role: "user", content: dailyPrompt }],
temperature: 0.7,
});
const c1 = r1.choices[0]?.message?.content || "";
console.log("响应长度:", c1.length);
try {
const match = c1.match(/\{[\s\S]*\}/);
const j1 = JSON.parse(match ? match[0] : c1);
console.log("标题:", j1.title);
console.log("条目数:", j1.items?.length);
console.log("✅ 日报生成通过");
} catch (e) {
console.log("❌ JSON解析失败:", e.message?.substring(0, 80));
console.log("原始响应:", c1.substring(0, 200));
}
// Test 2: 长文评测
console.log("\n=== Test 2: 长文评测生成 ===");
const articlePrompt =
"你是AI工具评测专家。基于以下信息生成一篇800字左右的深度评测文章(不需要JSON,纯文本即可)。\n" +
"项目: MetaGPT (multi-agent meta programming framework)\n" +
"评分: overall 4.0 | 能力4.5 | 开发体验3.5 | 成本许可4.0 | 社区4.5 | 性能3.5\n" +
"简介: MetaGPT takes a one-line requirement as input and outputs user stories, competitive analysis, requirements, data structures, APIs, documents, etc.";
const r2 = await openai.chat.completions.create({
model: "deepseek-v4-pro",
messages: [{ role: "user", content: articlePrompt }],
temperature: 0.7,
});
const c2 = r2.choices[0]?.message?.content || "";
console.log("响应长度:", c2.length, "字符");
console.log("预览:", c2.substring(0, 200));
console.log("✅ 长文生成通过");
// Test 3: 视频脚本
console.log("\n=== Test 3: 视频脚本生成 ===");
const scriptPrompt =
"为GitHub项目MetaGPT(stars 58000)创作一个60秒短视频脚本,分5个镜头,每个镜头包含画面描述和旁白。";
const r3 = await openai.chat.completions.create({
model: "deepseek-v4-pro",
messages: [{ role: "user", content: scriptPrompt }],
temperature: 0.7,
});
const c3 = r3.choices[0]?.message?.content || "";
console.log("响应长度:", c3.length, "字符");
console.log("预览:", c3.substring(0, 200));
console.log("✅ 视频脚本生成通过");
// Test 4: 社交卡片
console.log("\n=== Test 4: 社交卡片生成 ===");
const cardPrompt =
"为开源AI项目MetaGPT(多智能体元编程框架)生成3张社交媒体推广卡片文案。每张卡片包含:平台(小红书/推特/公众号),标题(20字内),正文(50字内),标签(3个)。返回JSON格式。";
const r4 = await openai.chat.completions.create({
model: "deepseek-v4-pro",
messages: [{ role: "user", content: cardPrompt }],
temperature: 0.7,
});
const c4 = r4.choices[0]?.message?.content || "";
console.log("响应长度:", c4.length, "字符");
try {
const match = c4.match(/\{[\s\S]*\}/);
const j4 = JSON.parse(match ? match[0] : c4);
console.log("卡片数:", j4.cards?.length || j4.items?.length || "N/A");
console.log("✅ 社交卡片生成通过");
} catch (e) {
console.log("原始响应:", c4.substring(0, 300));
console.log("✅ 社交卡片生成通过(非标准JSON,需增强解析)");
}
console.log("\n🏁 全管线测试完成");
}
main().catch((e) => {
console.error("💥 测试失败:", e.message);
process.exit(1);
});
+77
View File
@@ -0,0 +1,77 @@
import OpenAI from "openai";
import "dotenv/config";
const openai = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/v1",
});
const REVIEW_PROMPT = `你是一个GitHub开源项目评测专家。请基于以下项目信息进行五维深度评测,并以JSON格式返回。务必只返回JSON,不要包含任何其他文字或Markdown格式。
项目信息:
- 仓库全名:{repoName}
- 简介:{description}
- GitHub Stars:{stars}
- 开源许可证:{license}
- README摘要(前1500字):{readmeSummary}
请严格按照以下JSON结构输出:
{
"capability": { "score": 4.5, "summary": "一句话亮点(15字内)", "detail": "80-120字场景能力分析" },
"devExp": { "score": 3.5, "summary": "上手体验总结", "detail": "80-120字,包含安装、文档、API评价" },
"costLicense": { "score": 4.0, "summary": "成本与许可总结", "detail": "80-120字,包含硬件成本、API定价、许可证风险" },
"community": { "score": 4.5, "summary": "社区健康度总结", "detail": "80-120字,包含Issue响应、PR频率、生态描述" },
"performance": { "score": 3.5, "summary": "性能稳定性总结", "detail": "80-120字,包含运行效率、稳定性、输出质量" },
"overall": 4.0,
"tags": ["标签1", "标签2", "标签3"]
}
评分要求:
- 分数为1-5之间,可使用0.5步长
- 综合分应为五个维度分数的算术平均值(保留1位小数)
- 每个维度的summary需严格控制在15字以内
- 每个维度的detail需严格控制在80-120字
- 标签从以下类别中选择最匹配的3-5个:LLM框架, Agent框架, RAG, 提示工程, 多模态, 代码助手, 模型训练, 数据工程, 部署工具, 评测工具, 安全对齐, 其他`;
(async () => {
console.log("🧪 测试 DeepSeek 评测生成...\n");
const testRepo = {
name: "langflow-ai/langflow",
description:
"Langflow is a low-code app builder for RAG and multi-agent AI applications.",
stars: 148000,
license: "MIT",
readme:
"Langflow is a visual framework for building multi-agent and RAG applications. It is open-source, Python-based, and fully customizable. Model and vector store agnostic.",
};
const prompt = REVIEW_PROMPT
.replace("{repoName}", testRepo.name)
.replace("{description}", testRepo.description)
.replace("{stars}", testRepo.stars.toString())
.replace("{license}", testRepo.license)
.replace("{readmeSummary}", testRepo.readme.substring(0, 1500));
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) {
console.error("❌ DeepSeek API 返回内容为空");
process.exit(1);
}
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
console.error("❌ 无法从响应中提取JSON:", content.substring(0, 200));
process.exit(1);
}
const result = JSON.parse(jsonMatch[0]);
console.log("✅ 生成的评测结果:\n");
console.log(JSON.stringify(result, null, 2));
})();
+39
View File
@@ -0,0 +1,39 @@
import { Octokit } from "octokit";
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN || undefined,
});
(async () => {
console.log("🔍 搜索热门 AI 项目...");
const q = "ai agent stars:>1000 pushed:>2024-01-01";
const { data } = await octokit.rest.search.repos({
q,
sort: "stars",
order: "desc",
per_page: 3,
});
const repos = data.items;
console.log(`找到 ${repos.length} 个仓库:\n`);
for (const repo of repos) {
console.log(`- ${repo.full_name} (⭐ ${repo.stargazers_count})`);
}
if (repos.length > 0) {
const [owner, name] = repos[0].full_name.split("/");
try {
const { data: readmeData } = await octokit.rest.repos.getReadme({
owner,
repo: name,
});
const readme = Buffer.from(readmeData.content, "base64").toString("utf-8");
console.log(
`\n📖 ${repos[0].full_name} README 前200字符:\n${readme.substring(0, 200)}`
);
} catch {
console.log("\n📖 README 获取失败");
}
}
})();