Merge branch 'main' of https://github.com/Mzaxd/agent_park
This commit is contained in:
@@ -0,0 +1,669 @@
|
||||
# API 接口文档
|
||||
|
||||
> AI 项目导航站对外提供的 REST API 接口文档
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 概述](#1-概述)
|
||||
- [2. 认证方式](#2-认证方式)
|
||||
- [3. 接口列表](#3-接口列表)
|
||||
- [3.1 创建/更新项目 (Webhook)](#31-创建更新项目-webhook)
|
||||
- [3.2 获取项目详情](#32-获取项目详情)
|
||||
- [3.3 删除项目](#33-删除项目)
|
||||
- [4. 数据模型](#4-数据模型)
|
||||
- [5. 错误码](#5-错误码)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 Base URL
|
||||
|
||||
```
|
||||
生产环境: https://your-domain.com
|
||||
开发环境: http://localhost:3000
|
||||
```
|
||||
|
||||
### 1.2 响应格式
|
||||
|
||||
所有接口返回 JSON 格式数据:
|
||||
|
||||
```typescript
|
||||
// 成功响应
|
||||
{
|
||||
"success": true,
|
||||
"data": { ... },
|
||||
"message": "操作成功"
|
||||
}
|
||||
|
||||
// 错误响应
|
||||
{
|
||||
"success": false,
|
||||
"error": "错误类型",
|
||||
"details": ["详细错误信息1", "详细错误信息2"]
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 通用请求头
|
||||
|
||||
```
|
||||
Content-Type: application/json
|
||||
x-api-key: your-api-key-here # 需要认证的接口
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 认证方式
|
||||
|
||||
### API Key 认证
|
||||
|
||||
所有 API 接口均使用 API Key 进行认证。API Key 通过请求头 `x-api-key` 传递。
|
||||
|
||||
```bash
|
||||
# 设置环境变量
|
||||
WEBHOOK_API_KEY=your-secret-api-key
|
||||
|
||||
# 请求示例
|
||||
curl -X POST https://your-domain.com/api/webhook/projects \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-api-key: your-secret-api-key" \
|
||||
-d '...'
|
||||
```
|
||||
|
||||
**注意事项**:
|
||||
- API Key 需要在服务端环境变量中配置 `WEBHOOK_API_KEY`
|
||||
- 请妥善保管 API Key,不要在客户端代码中暴露
|
||||
- 建议定期轮换 API Key
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口列表
|
||||
|
||||
### 3.1 创建/更新项目 (Webhook)
|
||||
|
||||
批量创建或更新项目数据。支持多级去重策略自动识别已存在的项目。
|
||||
|
||||
#### 3.1.1 接口信息
|
||||
|
||||
```
|
||||
POST /api/webhook/projects
|
||||
```
|
||||
|
||||
#### 3.1.2 请求参数
|
||||
|
||||
**Headers**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| x-api-key | string | 是 | API 密钥 |
|
||||
| Content-Type | string | 是 | 必须为 `application/json` |
|
||||
|
||||
**Body**:
|
||||
|
||||
```typescript
|
||||
{
|
||||
apiKey: string; // API 密钥(与 header x-api-key 二选一)
|
||||
projects: ProjectInput[]; // 项目数组(1-100个)
|
||||
}
|
||||
```
|
||||
|
||||
**ProjectInput 类型**:
|
||||
|
||||
```typescript
|
||||
{
|
||||
// 基础信息(必填)
|
||||
name: string; // 中文名称
|
||||
nameEn?: string; // 英文名称(可选)
|
||||
description: string; // 中文描述
|
||||
descriptionEn?: string; // 英文描述(可选)
|
||||
|
||||
// 内容(可选)
|
||||
content?: string; // 中文内容(Markdown 格式)
|
||||
contentEn?: string; // 英文内容(Markdown 格式)
|
||||
|
||||
// 状态(可选)
|
||||
status?: "ACTIVE" | "ARCHIVED"; // 默认: "ACTIVE"
|
||||
source?: string; // 数据来源标识
|
||||
|
||||
// 关联(必填)
|
||||
tags: Array<{ // 标签数组(1-10个)
|
||||
name: string; // 标签名
|
||||
nameEn?: string; // 英文标签名(可选)
|
||||
}>;
|
||||
links: Array<{ // 外部链接数组(1-10个)
|
||||
type: "WEBSITE" | "GITHUB" | "HUGGINGFACE" | "PAPER";
|
||||
url: string; // 链接 URL
|
||||
title?: string; // 链接标题(可选)
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.3 多级去重策略
|
||||
|
||||
Webhook 会按以下优先级识别已存在的项目:
|
||||
|
||||
1. **GitHub URL 精确匹配**(最准确)
|
||||
2. **Website URL 精确匹配**
|
||||
3. **slug 匹配**(兜底)
|
||||
|
||||
如果找到已存在的项目,将执行更新操作:
|
||||
- 更新所有项目字段
|
||||
- 替换所有标签(删除旧的,创建新的)
|
||||
- 替换所有链接(删除旧的,创建新的)
|
||||
|
||||
#### 3.1.4 请求示例
|
||||
|
||||
```bash
|
||||
curl -X POST https://your-domain.com/api/webhook/projects \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-api-key: your-api-key" \
|
||||
-d '{
|
||||
"apiKey": "your-api-key",
|
||||
"projects": [
|
||||
{
|
||||
"name": "LangChain",
|
||||
"nameEn": "LangChain",
|
||||
"description": "开发由语言模型驱动的应用程序框架",
|
||||
"descriptionEn": "Developing applications powered by language models",
|
||||
"content": "# LangChain\n\nLangChain 是一个...",
|
||||
"contentEn": "# LangChain\n\nLangChain is a...",
|
||||
"status": "ACTIVE",
|
||||
"source": "GITHUB",
|
||||
"tags": [
|
||||
{ "name": "LLM", "nameEn": "Large Language Model" },
|
||||
{ "name": "Python", "nameEn": "Python" },
|
||||
{ "name": "开发框架", "nameEn": "Development Framework" }
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"type": "GITHUB",
|
||||
"url": "https://github.com/langchain-ai/langchain",
|
||||
"title": "GitHub 仓库"
|
||||
},
|
||||
{
|
||||
"type": "WEBSITE",
|
||||
"url": "https://langchain.com",
|
||||
"title": "官方网站"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
#### 3.1.5 响应示例
|
||||
|
||||
**成功响应** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"processed": 1,
|
||||
"created": 0,
|
||||
"updated": 1,
|
||||
"failed": 0,
|
||||
"errors": []
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应** (400 Bad Request):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Validation error",
|
||||
"details": [
|
||||
"tags: Field must contain at least 1 element",
|
||||
"links: Field must contain at most 10 elements"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**认证失败** (401 Unauthorized):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Unauthorized",
|
||||
"details": ["Invalid or missing API Key"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 获取项目详情
|
||||
|
||||
根据项目的 slug 获取项目详细信息。
|
||||
|
||||
#### 3.2.1 接口信息
|
||||
|
||||
```
|
||||
GET /api/projects/:slug
|
||||
```
|
||||
|
||||
#### 3.2.2 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| slug | string | 是 | 项目的唯一标识符 |
|
||||
|
||||
#### 3.2.3 请求示例
|
||||
|
||||
```bash
|
||||
curl -X GET https://your-domain.com/api/projects/langchain \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
#### 3.2.4 响应示例
|
||||
|
||||
**成功响应** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "clx1234567890",
|
||||
"name": "LangChain",
|
||||
"nameEn": "LangChain",
|
||||
"slug": "langchain",
|
||||
"description": "开发由语言模型驱动的应用程序框架",
|
||||
"descriptionEn": "Developing applications powered by language models",
|
||||
"content": "# LangChain\n\nLangChain 是一个...",
|
||||
"contentEn": "# LangChain\n\nLangChain is a...",
|
||||
"status": "ACTIVE",
|
||||
"source": "GITHUB",
|
||||
"createdAt": "2024-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2024-01-15T00:00:00.000Z",
|
||||
"links": [
|
||||
{
|
||||
"id": "link123",
|
||||
"type": "GITHUB",
|
||||
"url": "https://github.com/langchain-ai/langchain",
|
||||
"title": "GitHub 仓库"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
"id": "tag123",
|
||||
"name": "LLM",
|
||||
"nameEn": "Large Language Model",
|
||||
"slug": "large-language-model"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**项目不存在** (404 Not Found):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Not Found",
|
||||
"details": ["Project with slug \"nonexistent\" not found"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 删除项目
|
||||
|
||||
根据项目的 slug 删除项目及其所有关联数据。
|
||||
|
||||
#### 3.3.1 接口信息
|
||||
|
||||
```
|
||||
DELETE /api/projects/:slug
|
||||
```
|
||||
|
||||
#### 3.3.2 路径参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| slug | string | 是 | 项目的唯一标识符 |
|
||||
|
||||
#### 3.3.3 请求头
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| x-api-key | string | 是 | API 密钥 |
|
||||
| Content-Type | string | 是 | 必须为 `application/json` |
|
||||
|
||||
#### 3.3.4 级联删除说明
|
||||
|
||||
由于数据库配置了 `onDelete: Cascade`,删除项目时会自动删除:
|
||||
|
||||
- ✅ 该项目的所有外部链接(`ExternalLink`)
|
||||
- ✅ 该项目的所有标签关联(`ProjectTag`)
|
||||
- ❌ Tag 本身不会被删除(只删除项目与标签的关联关系)
|
||||
|
||||
#### 3.3.5 请求示例
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://your-domain.com/api/projects/langchain \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-api-key: your-api-key"
|
||||
```
|
||||
|
||||
#### 3.3.6 响应示例
|
||||
|
||||
**成功响应** (200 OK):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Project deleted successfully",
|
||||
"data": {
|
||||
"project": {
|
||||
"id": "clx1234567890",
|
||||
"name": "LangChain",
|
||||
"nameEn": "LangChain",
|
||||
"slug": "langchain"
|
||||
},
|
||||
"deleted": {
|
||||
"linksCount": 2,
|
||||
"tagsCount": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**项目不存在** (404 Not Found):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Not Found",
|
||||
"details": ["Project with slug \"nonexistent\" not found"]
|
||||
}
|
||||
```
|
||||
|
||||
**认证失败** (401 Unauthorized):
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Unauthorized",
|
||||
"details": ["Invalid or missing API Key"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据模型
|
||||
|
||||
### 4.1 Project 状态枚举
|
||||
|
||||
```typescript
|
||||
enum ProjectStatus {
|
||||
ACTIVE = "ACTIVE", // 活跃项目
|
||||
ARCHIVED = "ARCHIVED" // 已归档项目
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 链接类型枚举
|
||||
|
||||
```typescript
|
||||
enum LinkType {
|
||||
WEBSITE = "WEBSITE", // 官方网站
|
||||
GITHUB = "GITHUB", // GitHub 仓库
|
||||
HUGGINGFACE = "HUGGINGFACE", // Hugging Face 模型/数据集
|
||||
PAPER = "PAPER" // 论文链接
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 完整项目模型
|
||||
|
||||
```typescript
|
||||
interface Project {
|
||||
id: string; // 项目唯一 ID(cuid 格式)
|
||||
name: string; // 中文名称
|
||||
nameEn: string | null; // 英文名称
|
||||
slug: string; // URL 友好标识符(唯一)
|
||||
description: string; // 中文描述
|
||||
descriptionEn: string | null;// 英文描述
|
||||
content: string | null; // 中文内容(Markdown)
|
||||
contentEn: string | null; // 英文内容(Markdown)
|
||||
status: ProjectStatus; // 项目状态
|
||||
source: string | null; // 数据来源
|
||||
createdAt: Date; // 创建时间
|
||||
updatedAt: Date; // 更新时间
|
||||
|
||||
// 关联数据
|
||||
tags: Tag[]; // 标签数组
|
||||
links: ExternalLink[]; // 外部链接数组
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 Tag 模型
|
||||
|
||||
```typescript
|
||||
interface Tag {
|
||||
id: string; // 标签唯一 ID
|
||||
name: string; // 中文名称(唯一)
|
||||
nameEn: string | null; // 英文名称
|
||||
slug: string; // URL 友好标识符(唯一)
|
||||
createdAt: Date; // 创建时间
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 ExternalLink 模型
|
||||
|
||||
```typescript
|
||||
interface ExternalLink {
|
||||
id: string; // 链接唯一 ID
|
||||
type: LinkType; // 链接类型
|
||||
url: string; // 链接 URL
|
||||
title: string | null; // 链接标题
|
||||
projectId: string; // 所属项目 ID
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误码
|
||||
|
||||
### 5.1 HTTP 状态码
|
||||
|
||||
| 状态码 | 说明 | 示例场景 |
|
||||
|--------|------|----------|
|
||||
| 200 OK | 请求成功 | 成功获取/创建/更新/删除数据 |
|
||||
| 400 Bad Request | 请求参数错误 | 必填字段缺失、字段格式错误 |
|
||||
| 401 Unauthorized | 认证失败 | API Key 无效或缺失 |
|
||||
| 404 Not Found | 资源不存在 | 请求的项目 slug 不存在 |
|
||||
| 500 Internal Server Error | 服务器内部错误 | 数据库连接失败、程序异常 |
|
||||
|
||||
### 5.2 业务错误类型
|
||||
|
||||
| 错误类型 | 说明 | 处理建议 |
|
||||
|----------|------|----------|
|
||||
| Validation error | 数据验证失败 | 检查请求体字段是否符合要求 |
|
||||
| Unauthorized | API Key 无效 | 检查 API Key 是否正确 |
|
||||
| Not Found | 资源不存在 | 确认 slug 是否正确 |
|
||||
| Internal server error | 服务器错误 | 联系技术支持或稍后重试 |
|
||||
|
||||
### 5.3 验证规则
|
||||
|
||||
#### 项目数据验证
|
||||
|
||||
```typescript
|
||||
// 必填字段
|
||||
- name: 非空字符串,长度 1-200
|
||||
- description: 非空字符串,长度 1-5000
|
||||
- tags: 数组,长度 1-10,每个 tag.name 非空
|
||||
- links: 数组,长度 1-10,每个 link.url 和 link.type 非空
|
||||
|
||||
// 可选字段
|
||||
- nameEn: 字符串,长度 1-200
|
||||
- descriptionEn: 字符串,长度 1-5000
|
||||
- content/contentEn: 文本类型,支持 Markdown
|
||||
- status: 枚举值 "ACTIVE" 或 "ARCHIVED",默认 "ACTIVE"
|
||||
- source: 字符串,标识数据来源
|
||||
|
||||
// URL 格式
|
||||
- links[*].url: 必须是有效的 HTTP/HTTPS URL
|
||||
- links[*].type: 必须是 LinkType 枚举值之一
|
||||
```
|
||||
|
||||
#### Webhook 批量操作限制
|
||||
|
||||
```typescript
|
||||
// 批量操作
|
||||
- projects: 数组,长度 1-100
|
||||
- apiKey: 必须与环境变量 WEBHOOK_API_KEY 匹配
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 使用示例
|
||||
|
||||
### 6.1 完整的工作流示例
|
||||
|
||||
```javascript
|
||||
// 1. 创建/更新项目
|
||||
const createResponse = await fetch('https://your-domain.com/api/webhook/projects', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': 'your-api-key'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
apiKey: 'your-api-key',
|
||||
projects: [{
|
||||
name: 'My AI Project',
|
||||
nameEn: 'My AI Project',
|
||||
description: '一个创新的 AI 项目',
|
||||
descriptionEn: 'An innovative AI project',
|
||||
status: 'ACTIVE',
|
||||
source: 'MANUAL',
|
||||
tags: [
|
||||
{ name: 'AI', nameEn: 'Artificial Intelligence' },
|
||||
{ name: '机器学习', nameEn: 'Machine Learning' }
|
||||
],
|
||||
links: [
|
||||
{ type: 'GITHUB', url: 'https://github.com/user/project', title: 'GitHub' },
|
||||
{ type: 'WEBSITE', url: 'https://project.com', title: 'Website' }
|
||||
]
|
||||
}]
|
||||
})
|
||||
});
|
||||
|
||||
const createResult = await createResponse.json();
|
||||
console.log('创建结果:', createResult);
|
||||
// { success: true, processed: 1, created: 1, updated: 0, failed: 0, errors: [] }
|
||||
|
||||
// 2. 获取项目详情
|
||||
const slug = 'my-ai-project'; // 根据 nameEn 自动生成
|
||||
const getResponse = await fetch(`https://your-domain.com/api/projects/${slug}`);
|
||||
const getResult = await getResponse.json();
|
||||
console.log('项目详情:', getResult.data);
|
||||
|
||||
// 3. 删除项目
|
||||
const deleteResponse = await fetch(`https://your-domain.com/api/projects/${slug}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': 'your-api-key'
|
||||
}
|
||||
});
|
||||
const deleteResult = await deleteResponse.json();
|
||||
console.log('删除结果:', deleteResult);
|
||||
```
|
||||
|
||||
### 6.2 批量创建项目示例
|
||||
|
||||
```javascript
|
||||
const projects = [
|
||||
{
|
||||
name: '项目 A',
|
||||
nameEn: 'Project A',
|
||||
description: '项目 A 的描述',
|
||||
descriptionEn: 'Description of Project A',
|
||||
tags: [{ name: '分类1' }],
|
||||
links: [{ type: 'GITHUB', url: 'https://github.com/user/a' }]
|
||||
},
|
||||
{
|
||||
name: '项目 B',
|
||||
nameEn: 'Project B',
|
||||
description: '项目 B 的描述',
|
||||
descriptionEn: 'Description of Project B',
|
||||
tags: [{ name: '分类2' }],
|
||||
links: [{ type: 'GITHUB', url: 'https://github.com/user/b' }]
|
||||
}
|
||||
];
|
||||
|
||||
const response = await fetch('https://your-domain.com/api/webhook/projects', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': 'your-api-key'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
apiKey: 'your-api-key',
|
||||
projects: projects
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
console.log('批量创建结果:', result);
|
||||
// { success: true, processed: 2, created: 2, updated: 0, failed: 0, errors: [] }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 注意事项
|
||||
|
||||
### 7.1 Slug 生成规则
|
||||
|
||||
项目 slug 根据以下规则自动生成:
|
||||
|
||||
1. 优先使用 `nameEn`(英文)
|
||||
2. 如果 `nameEn` 不存在,使用 `name`(中文)
|
||||
3. 转换为小写
|
||||
4. 空格替换为连字符 `-`
|
||||
5. 移除特殊字符
|
||||
|
||||
示例:
|
||||
- `nameEn: "LangChain"` → `slug: "langchain"`
|
||||
- `name: "大语言模型"` → `slug: "大语言模型"` (会进行拼音转换)
|
||||
|
||||
### 7.2 标签去重
|
||||
|
||||
- 标签的 `name` 字段在数据库中是唯一的
|
||||
- 如果创建已存在的标签,会自动复用现有标签
|
||||
- 标签的 `slug` 也是唯一的,会根据 `nameEn` 或 `name` 自动生成
|
||||
|
||||
### 7.3 链接去重
|
||||
|
||||
- 同一个项目不能有重复的 URL
|
||||
- 通过 `projectId` + `url` 的组合保证唯一性
|
||||
|
||||
### 7.4 更新策略
|
||||
|
||||
- Webhook 使用 **替换策略** 更新标签和链接
|
||||
- 不是增量更新,而是完全替换
|
||||
- 更新时会删除旧的标签/链接关联,创建新的
|
||||
|
||||
---
|
||||
|
||||
## 8. 附录
|
||||
|
||||
### 8.1 环境变量配置
|
||||
|
||||
```bash
|
||||
# .env
|
||||
WEBHOOK_API_KEY=your-secret-api-key-here
|
||||
DATABASE_URL=postgresql://user:password@host:5432/dbname?sslmode=require
|
||||
```
|
||||
|
||||
### 8.2 相关文档
|
||||
|
||||
- [数据库 Schema](../prisma/schema.prisma)
|
||||
- [数据验证规则](../src/lib/validations.ts)
|
||||
- [数据新增流程设计](./data-ingestion-flow.md)
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0.0
|
||||
**最后更新**: 2024-01-11
|
||||
**维护者**: AI 项目导航站团队
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,511 @@
|
||||
# GitHub 项目分析 Agent (增强版 - 支持图片)
|
||||
|
||||
## 任务目标
|
||||
|
||||
分析单个 GitHub 项目并生成符合 webhook API 规范的 JSON 数据,用于项目入库。目标是生成包含丰富图文内容的高质量项目介绍页面。
|
||||
|
||||
## 输入数据
|
||||
|
||||
- **GitHub 项目 URL**: {{ $json.githubUrl }}
|
||||
|
||||
## 可用工具
|
||||
|
||||
- **MCP Client (web_reader)**: 访问 GitHub 仓库页面及相关链接
|
||||
|
||||
---
|
||||
|
||||
## 执行步骤
|
||||
|
||||
### Step 1: 使用 MCP 获取项目基础信息
|
||||
|
||||
调用 MCP 工具访问 GitHub 仓库,提取:
|
||||
- 仓库名称和描述
|
||||
- README.md 完整内容(**保留 Markdown 格式和图片链接**)
|
||||
- GitHub Topics
|
||||
- 主要编程语言
|
||||
- Stars、Forks 数量
|
||||
- 最新更新时间
|
||||
- 许可证类型
|
||||
- 主页 URL
|
||||
- Releases 信息(如果有的话)
|
||||
|
||||
**关键操作:提取图片列表**
|
||||
- 从 README.md 中提取所有图片链接(`` 或 `<img src="...">`)
|
||||
- 记录图片类型:架构图、截图、流程图、logo、GIF 动图等
|
||||
- 对于相对路径图片,转换为 GitHub 绝对 URL:
|
||||
```
|
||||
https://raw.githubusercontent.com/[owner]/[repo]/[branch]/[path]
|
||||
```
|
||||
|
||||
### Step 2: 深度理解项目价值和内容
|
||||
|
||||
从 README 内容中提炼(尽量保留图片):
|
||||
|
||||
**项目用途**:
|
||||
- 这个项目解决什么核心问题?
|
||||
- 主要功能是什么?
|
||||
- 有什么独特价值?
|
||||
- 与同类项目相比的优势?
|
||||
|
||||
**适用场景**:
|
||||
- 谁会使用这个项目?
|
||||
- 典型使用场景是什么?
|
||||
- 属于哪个应用领域?
|
||||
|
||||
**技术特点**:
|
||||
- 使用了什么技术栈?
|
||||
- 有什么技术亮点或创新点?
|
||||
- 架构设计特点是什么?
|
||||
|
||||
**如何使用**:
|
||||
- 安装步骤
|
||||
- 配置说明
|
||||
- 快速开始指南
|
||||
- 常见操作
|
||||
|
||||
### Step 3: 生成中英双语内容
|
||||
|
||||
#### 3.1 name / nameEn
|
||||
|
||||
- 通常使用英文名称
|
||||
- 如果有中文品牌名,使用原名
|
||||
|
||||
#### 3.2 description / descriptionEn ⚠️ 重要
|
||||
|
||||
简短的一句话总结,10-500 字符
|
||||
|
||||
**中文格式**:
|
||||
```
|
||||
[项目名] 是一个[用途定位]的[类型],通过[核心特点]实现[价值主张]
|
||||
```
|
||||
|
||||
示例:
|
||||
```
|
||||
AutoGen 是一个由微软开发的多智能体应用框架,通过分层设计和可扩展架构,简化了构建能够自主运行或与人类协作的多智能体工作流程的开发流程
|
||||
```
|
||||
|
||||
**英文格式**:
|
||||
```
|
||||
[Project] is a [type] for [purpose], featuring [key characteristics]
|
||||
```
|
||||
|
||||
#### 3.3 content / contentEn ⚠️ 关键 - 严格结构 + 图片支持
|
||||
|
||||
完整的 Markdown 文档,最多 10000 字符。**必须严格按结构组织,并在适当位置嵌入图片**:
|
||||
|
||||
```markdown
|
||||
# 项目用途
|
||||
|
||||
[2-3句话详细描述项目的核心功能和解决的问题,说明项目的核心价值主张]
|
||||
|
||||
[如果有项目 logo 或主展示图,在此插入]
|
||||

|
||||
|
||||
# 适用场景
|
||||
|
||||
[列出3-5个典型使用场景]
|
||||
- **[场景1名称]**:[具体说明,包含适用对象和具体用途]
|
||||
- **[场景2名称]**:[具体说明,包含适用对象和具体用途]
|
||||
- **[场景3名称]**:[具体说明,包含适用对象和具体用途]
|
||||
|
||||
[如果有场景示意图,在此插入]
|
||||

|
||||
|
||||
# 核心功能
|
||||
|
||||
[列出项目的主要功能特性,4-8项]
|
||||
- **[功能1名称]**:[一句话说明这个功能的作用和价值]
|
||||
- **[功能2名称]**:[一句话说明这个功能的作用和价值]
|
||||
- **[功能3名称]**:[一句话说明这个功能的作用和价值]
|
||||
- **[功能4名称]**:[一句话说明这个功能的作用和价值]
|
||||
|
||||
[如果有功能截图,在此插入]
|
||||

|
||||
|
||||
# 技术架构
|
||||
|
||||
[详细说明技术架构和亮点,3-6点]
|
||||
- **[技术1]**:[基于什么技术/框架,有什么特点]
|
||||
- **[技术2]**:[支持什么具体特性,带来什么好处]
|
||||
- **[技术3]**:[采用什么架构模式,解决什么问题]
|
||||
|
||||
[**架构图优先在此位置插入**]
|
||||

|
||||
|
||||
> 📐 **架构说明**:[对架构图的补充说明,描述主要组件、数据流向、技术栈等]
|
||||
|
||||
# 如何使用
|
||||
|
||||
[详细的安装和配置步骤,6-10个步骤]
|
||||
- **[步骤1标题]**:[具体操作,如安装命令、下载链接等]
|
||||
```bash
|
||||
[命令示例]
|
||||
```
|
||||
- **[步骤2标题]**:[配置说明,如环境变量、配置文件等]
|
||||
- **[步骤3标题]**:[创建或初始化项目]
|
||||
- **[步骤4标题]**:[核心功能使用方法]
|
||||
- **[步骤5标题]**:[常见操作说明]
|
||||
- **[步骤6标题]**:[高级功能或最佳实践]
|
||||
|
||||
[**如果有安装演示截图,在对应步骤后插入**]
|
||||

|
||||
|
||||
# 快速示例
|
||||
|
||||
[提供完整的、可运行的代码示例,10-20行代码]
|
||||
```python/[javascript]
|
||||
[从 README 中提取的实际代码示例,不要捏造]
|
||||
```
|
||||
|
||||
[对代码示例的说明]
|
||||
|
||||
[**如果有代码运行结果截图,在此插入**]
|
||||

|
||||
|
||||
# 实际效果展示
|
||||
|
||||
[如果有 GIF 动图或截图展示实际使用效果,在此集中展示]
|
||||
|
||||
[**GIF 动图展示核心流程**]
|
||||

|
||||
|
||||
[**界面截图展示**]
|
||||

|
||||

|
||||
|
||||
> 💡 **效果说明**:[对截图/动图展示的功能进行说明]
|
||||
|
||||
# 定价/成本
|
||||
|
||||
[说明项目的经济成本,明确透明]
|
||||
- 开源免费:[如果完全免费,明确说明"完全免费和开源"]
|
||||
- API 成本:[如果需要调用付费 API,说明相关成本]
|
||||
- 企业版/付费版:[如果有商业版本,说明定价方案]
|
||||
- 自部署成本:[如果需要自己部署,说明资源需求]
|
||||
|
||||
# 常见问题
|
||||
|
||||
[3-5个 FAQ]
|
||||
- Q: [问题1]?
|
||||
A: [详细回答,2-3句话]
|
||||
- Q: [问题2]?
|
||||
A: [详细回答,2-3句话]
|
||||
- Q: [问题3]?
|
||||
A: [详细回答,2-3句话]
|
||||
```
|
||||
|
||||
**英文版本保持相同结构**:
|
||||
|
||||
```markdown
|
||||
# Overview
|
||||
|
||||
[2-3 sentences describing core functionality and value proposition]
|
||||
|
||||

|
||||
|
||||
# Use Cases
|
||||
|
||||
- **[Case 1]**: [Specific explanation]
|
||||
- **[Case 2]**: [Specific explanation]
|
||||
|
||||

|
||||
|
||||
# Key Features
|
||||
|
||||
- **[Feature 1]**: [One-sentence explanation]
|
||||
- **[Feature 2]**: [One-sentence explanation]
|
||||
|
||||

|
||||
|
||||
# Technical Architecture
|
||||
|
||||
- **[Tech 1]**: [Details]
|
||||
- **[Tech 2]**: [Details]
|
||||
|
||||

|
||||
|
||||
> 📐 **Architecture Notes**: [Additional explanation of the architecture]
|
||||
|
||||
# How to Use
|
||||
|
||||
- **[Step 1]**: [Specific actions]
|
||||
```bash
|
||||
[Commands]
|
||||
```
|
||||
- **[Step 2]**: [Configuration]
|
||||
|
||||

|
||||
|
||||
# Quick Example
|
||||
|
||||
```python/[javascript]
|
||||
[Code example from README]
|
||||
```
|
||||
|
||||

|
||||
|
||||
# Live Demo
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
> 💡 **Demo Notes**: [Explanation of what's shown]
|
||||
|
||||
# Pricing/Cost
|
||||
|
||||
- Open Source: [Free or cost details]
|
||||
- API Costs: [If applicable]
|
||||
|
||||
# FAQ
|
||||
|
||||
- Q: [Question 1]?
|
||||
A: [Detailed answer]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 4: 图片处理和质量控制
|
||||
|
||||
#### 4.1 图片 URL 规范化
|
||||
|
||||
**规则**:
|
||||
1. 对于相对路径图片(如 `docs/architecture.png`),转换为:
|
||||
```
|
||||
https://raw.githubusercontent.com/[owner]/[repo]/[default-branch]/docs/architecture.png
|
||||
```
|
||||
|
||||
2. 对于已使用 `https://github.com/.../raw/...` 的链接,保持不变
|
||||
|
||||
3. 对于外部图片(如 imgur、cloudinary 等),保持原链接
|
||||
|
||||
4. **特殊处理**:
|
||||
- 如果是 GitHub Issues/Comments 中的图片,通常在 `https://user-images.githubusercontent.com/`
|
||||
- 如果是 docs 网站链接(如 `https://project.dev/images/...`),保持原链接
|
||||
|
||||
#### 4.2 图片选择优先级
|
||||
|
||||
**必须包含的图片类型**(按优先级):
|
||||
1. **系统架构图**:展示技术栈、组件关系
|
||||
2. **功能演示 GIF**:展示核心工作流程
|
||||
3. **界面截图**:展示 UI/UX
|
||||
4. **安装/配置截图**:帮助用户快速上手
|
||||
5. **数据流程图**:展示数据流向
|
||||
6. **部署架构图**:展示部署方案
|
||||
|
||||
**选择性包含**:
|
||||
- Logo(可在顶部添加一次)
|
||||
- 团队照片(非必需)
|
||||
- 会议照片(非必需)
|
||||
|
||||
**限制条件**:
|
||||
- 最多包含 **15 张图片**(避免内容过于冗长)
|
||||
- 优先选择高质量、信息量大的图片
|
||||
- 如果图片过大(>2MB),建议使用缩略图或描述替代
|
||||
|
||||
#### 4.3 图片描述规范
|
||||
|
||||
每个图片后应添加简短说明:
|
||||
|
||||
```markdown
|
||||

|
||||
|
||||
> 📐 **架构说明**:本项目采用微服务架构,包含 API Gateway、服务注册中心、3个核心微服务,使用 Redis 作为缓存,MySQL 作为持久化存储。
|
||||
```
|
||||
|
||||
特殊说明标签:
|
||||
- `📐 架构说明` - 架构图
|
||||
- `💡 效果说明` - 功能演示
|
||||
- `⚙️ 配置说明` - 配置截图
|
||||
- `🎯 使用说明` - 操作演示
|
||||
|
||||
---
|
||||
|
||||
### Step 5: 提取标签 (6-20个)
|
||||
|
||||
优先级顺序:
|
||||
1. **核心技术**:LLM、Multi-Agent、Computer Vision、RAG
|
||||
2. **编程语言**:Python、TypeScript、Rust
|
||||
3. **框架/库**:React、PyTorch、LangChain
|
||||
4. **应用领域**:NLP、Chatbot、Automation、DevOps
|
||||
5. **公司/组织**:Microsoft、OpenAI、Meta
|
||||
|
||||
---
|
||||
|
||||
### Step 6: 构造链接数组 ⚠️ 严格枚举值
|
||||
|
||||
**link.type 必须严格使用以下 4 种枚举值之一(全大写)**:
|
||||
|
||||
| type 值 | 适用场景 | 示例 |
|
||||
|---------|----------|------|
|
||||
| `GITHUB` | GitHub 仓库地址 | `https://github.com/xxx/xxx` |
|
||||
| `WEBSITE` | 官方文档/官网/博客/PyPI/npm | `https://example.com/docs` |
|
||||
| `HUGGINGFACE` | Hugging Face 模型页 | `https://huggingface.co/xxx` |
|
||||
| `PAPER` | 论文/Arxiv 链接 | `https://arxiv.org/abs/xxx` |
|
||||
|
||||
---
|
||||
|
||||
### Step 7: 计算质量评分
|
||||
|
||||
```javascript
|
||||
score = 0
|
||||
if (description.length >= 20 && description.length <= 200) score += 10
|
||||
if (content.length >= 1000) score += 15
|
||||
if (content.includes("# 如何使用") || content.includes("# How to Use")) score += 15
|
||||
if (content.includes("```")) score += 10
|
||||
if (content.includes("![") && content.match(/!\[.*\]\(.*\)/g).length >= 3) score += 15 // 🆕 包含3+图片
|
||||
if (content.includes("# 技术架构") || content.includes("# Technical Architecture")) score += 10 // 🆕 有架构说明
|
||||
if (stars >= 1000) score += 20
|
||||
else if (stars >= 100) score += 10
|
||||
if (最近30天有更新) score += 20
|
||||
else if (最近180天有更新) score += 10
|
||||
if (有文档链接) score += 10
|
||||
if (forks >= 10) score += 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔴 关键输出要求(必须严格遵守)
|
||||
|
||||
**⚠️ 直接返回纯 JSON 对象,严禁使用以下格式:**
|
||||
|
||||
- ❌ **不要使用代码块标记**:禁止使用 ` ```json ` 或 ` ``` ` 包裹输出
|
||||
- ❌ **不要添加额外包装层**:禁止添加 `"output"`、`"data"` 等外层字段
|
||||
- ❌ **不要添加注释或解释**:禁止在 JSON 外添加任何文字说明
|
||||
|
||||
**✅ 正确的输出格式示例:**
|
||||
```json
|
||||
{"success": true, "project": {...}, "qualityScore": 95, "qualityPassed": true, "metadata": {...}}
|
||||
```
|
||||
|
||||
**❌ 错误的输出格式示例:**
|
||||
```
|
||||
```json
|
||||
{
|
||||
"output": {
|
||||
"success": true,
|
||||
"project": {...}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
**检查方法**:
|
||||
- 输出必须以 `{` 开头,以 `}` 结尾
|
||||
- 第一层必须直接包含 `success`、`project`、`qualityScore` 等字段
|
||||
- 不包含任何 Markdown 代码块标记
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 输出格式要求
|
||||
|
||||
**直接返回纯 JSON,不要使用代码块标记**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"project": {
|
||||
"name": "项目名称",
|
||||
"nameEn": "Project Name",
|
||||
"description": "一句话描述,10-500字符",
|
||||
"descriptionEn": "One sentence description, 10-500 chars",
|
||||
"content": "# 项目用途\n\n完整Markdown内容,**包含图片链接**、架构图、使用示例等...",
|
||||
"contentEn": "# Overview\n\nFull Markdown content with **image links**, architecture diagrams, usage examples...",
|
||||
"status": "ACTIVE",
|
||||
"source": "N8N_WORKFLOW",
|
||||
"tags": [
|
||||
{ "name": "核心技术", "nameEn": "Core Tech" }
|
||||
],
|
||||
"links": [
|
||||
{ "type": "GITHUB", "url": "...", "title": "GitHub 仓库" }
|
||||
]
|
||||
},
|
||||
"qualityScore": 95,
|
||||
"qualityPassed": true,
|
||||
"metadata": {
|
||||
"stars": 数量,
|
||||
"forks": 数量,
|
||||
"language": "主要语言",
|
||||
"lastUpdate": "YYYY-MM-DD",
|
||||
"analyzedAt": "ISO 8601格式",
|
||||
"imageCount": 8 // 🆕 提取的图片数量
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 质量检查清单(生成前自查)
|
||||
|
||||
### 基础要求
|
||||
- ✅ description 长度在 10-500 字符之间
|
||||
- ✅ content 包含完整的 8 个部分(项目用途、适用场景、核心功能、技术架构、如何使用、快速示例、实际效果展示、定价/成本、常见问题)
|
||||
- ✅ content 包含至少一个代码示例(从 README 提取)
|
||||
- ✅ content 包含至少 3 张图片(架构图、功能截图、演示 GIF 等)
|
||||
- ✅ 图片 URL 已转换为可直接访问的绝对路径
|
||||
- ✅ 每张图片后有简短说明(使用 > 引用格式)
|
||||
- ✅ tags 数量在 6-20 个之间
|
||||
- ✅ links 包含至少 GITHUB 类型链接
|
||||
- ✅ 所有枚举值使用全大写(ACTIVE、GITHUB、WEBSITE 等)
|
||||
- ✅ 中英文内容结构一致
|
||||
- ✅ 没有使用 ```json 代码块包裹输出
|
||||
|
||||
### 图片质量检查
|
||||
- ✅ 架构图包含说明文字,解释主要组件和关系
|
||||
- ✅ 代码示例后有运行结果截图(如果有)
|
||||
- ✅ "如何使用"部分的关键步骤有截图辅助说明
|
||||
- ✅ 所有图片链接可直接访问(非相对路径)
|
||||
- ✅ 图片数量控制在 15 张以内,选择最具代表性的
|
||||
|
||||
---
|
||||
|
||||
## 🎯 最佳实践示例
|
||||
|
||||
### 好的架构图插入示例:
|
||||
|
||||
```markdown
|
||||
# 技术架构
|
||||
|
||||
LangChain.js 基于 TypeScript 重新实现,采用模块化设计:
|
||||
|
||||
- **TypeScript + ESM**: 原生支持类型推断和 tree-shaking
|
||||
- **模块化架构**: 核心 @langchain/core 与集成包分离,减小包体积
|
||||
- **Web-first**: 专为浏览器和 Edge Runtime 优化
|
||||
|
||||

|
||||
|
||||
> 📐 **架构说明**:左侧为 LangChain Core 核心模块(包含 Chains、Prompts、Models 等基础抽象),右侧为集成包(支持 OpenAI、Anthropic、向量数据库等)。底层统一使用 @langchain/core 的标准接口,上层应用可灵活组合不同集成。
|
||||
```
|
||||
|
||||
### 好的功能展示示例:
|
||||
|
||||
```markdown
|
||||
# 实际效果展示
|
||||
|
||||
通过对话式接口创建 Multi-Agent 系统:
|
||||
|
||||

|
||||
|
||||
> 💡 **效果说明**:用户输入"创建一个多智能体系统用于代码审查",Agent 会自动:
|
||||
> 1. 创建 Assistant Agent(负责代码分析)
|
||||
> 2. 创建 User Proxy Agent(负责执行代码)
|
||||
> 3. 配置两人之间的对话模式
|
||||
> 4. 自动生成初始提示词
|
||||
|
||||
以下是一个真实的对话示例:
|
||||
|
||||

|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 最终检查清单(输出前必须确认)
|
||||
|
||||
在返回结果前,请确认:
|
||||
- [ ] 输出以 `{` 开头,以 `}` 结尾
|
||||
- [ ] 没有任何 Markdown 代码块标记(```json 或 ```)
|
||||
- [ ] 第一层直接包含 `success` 字段(没有 `output` 包装)
|
||||
- [ ] 没有在 JSON 外添加任何文字说明
|
||||
- [ ] 图片是项目介绍的重要组成部分,已妥善处理
|
||||
|
||||
**🔴 最后提醒:直接输出纯 JSON 对象,不要用代码块包裹,不要添加包装层!**
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.6",
|
||||
"eslint-config-next": "15.1.11",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"postcss": "^8",
|
||||
"prettier": "^3.4.2",
|
||||
|
||||
Generated
+9
-9
@@ -97,8 +97,8 @@ importers:
|
||||
specifier: ^9
|
||||
version: 9.39.2(jiti@1.21.7)
|
||||
eslint-config-next:
|
||||
specifier: 15.1.6
|
||||
version: 15.1.6(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
specifier: 15.1.11
|
||||
version: 15.1.11(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
eslint-config-prettier:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.2(eslint@9.39.2(jiti@1.21.7))
|
||||
@@ -591,8 +591,8 @@ packages:
|
||||
'@next/env@15.1.11':
|
||||
resolution: {integrity: sha512-yp++FVldfLglEG5LoS2rXhGypPyoSOyY0kxZQJ2vnlYJeP8o318t5DrDu5Tqzr03qAhDWllAID/kOCsXNLcwKw==}
|
||||
|
||||
'@next/eslint-plugin-next@15.1.6':
|
||||
resolution: {integrity: sha512-+slMxhTgILUntZDGNgsKEYHUvpn72WP1YTlkmEhS51vnVd7S9jEEy0n9YAMcI21vUG4akTw9voWH02lrClt/yw==}
|
||||
'@next/eslint-plugin-next@15.1.11':
|
||||
resolution: {integrity: sha512-jpAu+46v5FF/TO8YUdOBHn/Wr4SCiU4IgjQ45S9Nn3vR4nZVS2SR+m9lpxcCv/xqMUoYuYFQZUP0H/ptw0W6+w==}
|
||||
|
||||
'@next/swc-darwin-arm64@15.1.9':
|
||||
resolution: {integrity: sha512-sQF6MfW4nk0PwMYYq8xNgqyxZJGIJV16QqNDgaZ5ze9YoVzm4/YNx17X0exZudayjL9PF0/5RGffDtzXapch0Q==}
|
||||
@@ -2086,8 +2086,8 @@ packages:
|
||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
eslint-config-next@15.1.6:
|
||||
resolution: {integrity: sha512-Wd1uy6y7nBbXUSg9QAuQ+xYEKli5CgUhLjz1QHW11jLDis5vK5XB3PemL6jEmy7HrdhaRFDz+GTZ/3FoH+EUjg==}
|
||||
eslint-config-next@15.1.11:
|
||||
resolution: {integrity: sha512-RK5q3f8CKMTwNXULOqd2TAsz+7kA5+5fy5YK7T6SeczLFOuOUcuJOGlYUbyoeU6+UKQrpFsYgCz71hI1F9q5Cg==}
|
||||
peerDependencies:
|
||||
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
|
||||
typescript: '>=3.3.1'
|
||||
@@ -4200,7 +4200,7 @@ snapshots:
|
||||
|
||||
'@next/env@15.1.11': {}
|
||||
|
||||
'@next/eslint-plugin-next@15.1.6':
|
||||
'@next/eslint-plugin-next@15.1.11':
|
||||
dependencies:
|
||||
fast-glob: 3.3.1
|
||||
|
||||
@@ -5665,9 +5665,9 @@ snapshots:
|
||||
|
||||
escape-string-regexp@5.0.0: {}
|
||||
|
||||
eslint-config-next@15.1.6(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3):
|
||||
eslint-config-next@15.1.11(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 15.1.6
|
||||
'@next/eslint-plugin-next': 15.1.11
|
||||
'@rushstack/eslint-patch': 1.15.0
|
||||
'@typescript-eslint/eslint-plugin': 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.50.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
/**
|
||||
* DELETE /api/projects/[slug]
|
||||
*
|
||||
* 根据项目的 slug 删除项目及其所有关联数据
|
||||
*
|
||||
* 由于数据库 schema 配置了 onDelete: Cascade,
|
||||
* 删除项目时会自动删除:
|
||||
* - 该项目的所有外部链接(ExternalLink)
|
||||
* - 该项目的所有标签关联(ProjectTag)
|
||||
*
|
||||
* 注意:Tag 本身不会被删除,只会删除项目与标签的关联关系
|
||||
*/
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
try {
|
||||
const { slug } = await params
|
||||
|
||||
// Verify API Key
|
||||
const apiKey = request.headers.get('x-api-key') || process.env.WEBHOOK_API_KEY
|
||||
const validApiKey = process.env.WEBHOOK_API_KEY
|
||||
|
||||
if (apiKey !== validApiKey) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Unauthorized',
|
||||
details: ['Invalid or missing API Key'],
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
// Check if project exists
|
||||
const existingProject = await prisma.project.findUnique({
|
||||
where: { slug },
|
||||
include: {
|
||||
links: true,
|
||||
tags: {
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (!existingProject) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Not Found',
|
||||
details: [`Project with slug "${slug}" not found`],
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Delete project (cascade delete will handle links and project_tags)
|
||||
await prisma.project.delete({
|
||||
where: { slug },
|
||||
})
|
||||
|
||||
console.warn(
|
||||
`[API] Deleted project "${existingProject.name}" (slug: ${slug}, id: ${existingProject.id})`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Project deleted successfully',
|
||||
data: {
|
||||
project: {
|
||||
id: existingProject.id,
|
||||
name: existingProject.name,
|
||||
nameEn: existingProject.nameEn,
|
||||
slug: existingProject.slug,
|
||||
},
|
||||
deleted: {
|
||||
linksCount: existingProject.links.length,
|
||||
tagsCount: existingProject.tags.length,
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[API] Error deleting project:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
details: [error instanceof Error ? error.message : 'Unknown error'],
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/projects/[slug]
|
||||
*
|
||||
* 根据项目的 slug 获取项目详情
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
try {
|
||||
const { slug } = await params
|
||||
|
||||
const project = await prisma.project.findUnique({
|
||||
where: { slug },
|
||||
include: {
|
||||
links: true,
|
||||
tags: {
|
||||
include: {
|
||||
tag: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if (!project) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Not Found',
|
||||
details: [`Project with slug "${slug}" not found`],
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Transform response to match frontend structure
|
||||
const transformedProject = {
|
||||
...project,
|
||||
tags: project.tags.map((pt) => ({
|
||||
id: pt.tag.id,
|
||||
name: pt.tag.name,
|
||||
nameEn: pt.tag.nameEn,
|
||||
slug: pt.tag.slug,
|
||||
})),
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: transformedProject,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[API] Error fetching project:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
details: [error instanceof Error ? error.message : 'Unknown error'],
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -213,15 +213,19 @@ const components: Components = {
|
||||
</a>
|
||||
),
|
||||
|
||||
// Images
|
||||
// Images - wrapped in container for size control
|
||||
img: ({ src, alt, ...props }) => (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="rounded-lg border border-gray-300 dark:border-gray-600 my-4 max-w-full h-auto"
|
||||
loading="lazy"
|
||||
{...props}
|
||||
/>
|
||||
<div className="my-4 flex justify-center">
|
||||
<div className="max-w-md w-full">
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="rounded-lg border border-gray-300 dark:border-gray-600 w-full h-auto object-contain"
|
||||
loading="lazy"
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
|
||||
// Horizontal rule
|
||||
|
||||
@@ -48,13 +48,6 @@ function formatDate(date: Date | string, locale: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if URL is an image
|
||||
function isImageUrl(url: string): boolean {
|
||||
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp']
|
||||
const lowerUrl = url.toLowerCase()
|
||||
return imageExtensions.some(ext => lowerUrl.includes(ext))
|
||||
}
|
||||
|
||||
export async function ProjectDetail({ project, locale }: ProjectDetailProps) {
|
||||
const t = await getTranslations('project')
|
||||
|
||||
@@ -114,26 +107,6 @@ export async function ProjectDetail({ project, locale }: ProjectDetailProps) {
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Featured Image/Video Placeholder - only show if source exists */}
|
||||
{project.source && (
|
||||
<div className="relative w-full border-2 border-black dark:border-gray-600 bg-gray-100 dark:bg-gray-800 mb-10 overflow-hidden shadow-brutal dark:shadow-brutal-dark">
|
||||
{isImageUrl(project.source) ? (
|
||||
<img
|
||||
src={project.source}
|
||||
alt={`${displayName} demo`}
|
||||
className="w-full h-auto"
|
||||
/>
|
||||
) : (
|
||||
<iframe
|
||||
src={project.source}
|
||||
className="w-full aspect-video"
|
||||
title={`${displayName} demo`}
|
||||
allowFullScreen
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* Article Content */}
|
||||
@@ -145,21 +118,6 @@ export async function ProjectDetail({ project, locale }: ProjectDetailProps) {
|
||||
|
||||
{/* Full content with Markdown rendering */}
|
||||
{displayContent && <MarkdownContent content={displayContent} />}
|
||||
|
||||
{/* Installation section if GitHub link exists */}
|
||||
{project.links.some((l) => l.type === 'GITHUB') && (
|
||||
<>
|
||||
<h3>{t('gettingStarted')}</h3>
|
||||
<p>{t('installInstructions')}</p>
|
||||
<pre className="bg-gray-100 dark:bg-gray-800 border border-black dark:border-gray-600 p-4 font-mono text-sm overflow-x-auto">
|
||||
<code>{`git clone ${
|
||||
project.links.find((l) => l.type === 'GITHUB')?.url || 'https://github.com/example/project'
|
||||
}
|
||||
cd ${project.slug}
|
||||
npm install`}</code>
|
||||
</pre>
|
||||
</>
|
||||
)}
|
||||
</article>
|
||||
|
||||
{/* Share and Feedback Section - Temporarily disabled for debugging */}
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
"buildCommand": "pnpm prisma generate && pnpm build",
|
||||
"installCommand": "pnpm install",
|
||||
"framework": "nextjs",
|
||||
"regions": ["sin1"]
|
||||
"regions": ["hkg1"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user