diff --git a/design/detail.html b/design/detail.html deleted file mode 100644 index d238591..0000000 --- a/design/detail.html +++ /dev/null @@ -1,434 +0,0 @@ - - - - -Agent Park - Project Details - - - - - - - -
- Discover the future of AI Agents arrow_forward -
- -
-
- -arrow_back - BACK TO PROJECTS - -
-
-
-
- -

- AutoGPT X -

-
-
-calendar_today -Added Oct 24, 2023 -
- -
-category -Autonomous Agent -
- -
-code -Open Source -
-
-
-Python -LLM -Automation -GPT-4 -
-
-AutoGPT X Interface Abstract Visualization -
v2.0.1 Stable
-
-
-
-

- AutoGPT X is an experimental open-source application showcasing the capabilities of the GPT-4 language model. This program, driven by GPT-4, chains together LLM "thoughts", to autonomously achieve whatever goal you set. -

-

Overview

-

- Unlike typical AI chatbots that require constant prompting, AutoGPT X takes a single goal and breaks it down into sub-tasks. It utilizes the internet for searching and gathering information, manages long-term and short-term memory, and can execute code or scripts to accomplish complex workflows. -

-

- As one of the first examples of GPT-4 running fully autonomously, AutoGPT X pushes the boundaries of what is possible with AI agents. It serves as a framework for building sophisticated AI assistants capable of performing real-world tasks with minimal human intervention. -

-

Key Features

-
    -
  • Internet Access: Searches and gathers information autonomously.
  • -
  • Long-Term Memory: Retains context over extended sessions.
  • -
  • GPT-4 Powered: Leverages the reasoning capabilities of state-of-the-art LLMs.
  • -
  • File Management: Can read and write files to your local system.
  • -
  • Extensible: Plugin system allows for custom integrations.
  • -
-

Getting Started

-

- To install AutoGPT X, you will need Python 3.10 or later. Clone the repository and install the dependencies: -

-
git clone https://github.com/agentpark/autogpt-x.git
-cd autogpt-x
-pip install -r requirements.txt
-python main.py
-
- "The goal of AutoGPT X is to make GPT-4 fully autonomous." -
-

Use Cases

-

- Developers and enthusiasts are using AutoGPT X for a variety of tasks, including: -

-
    -
  • Automated market research and report generation.
  • -
  • Code debugging and autonomous software development.
  • -
  • Personal assistant tasks like booking flights or ordering food.
  • -
  • Content creation and social media management.
  • -
-
-
-
- - -
-
-DID THIS AGENT FIX THIS? - -
-
-
- -
-
-
-

Related Projects

- - View All arrow_forward - -
-
-
-
-

BabyAGI

-psychology -
-

- Task-driven autonomous agent that uses OpenAI and Pinecone APIs to create, prioritize, and execute tasks. -

-
-Python -Task -
-
- - View Details arrow_forward - -
-
-
-
-

LangChain

-link -
-

- Building applications with LLMs through composability. The standard framework for chaining LLM capabilities. -

-
-Framework -Python -
-
- - View Details arrow_forward - -
-
-
-
-

GodMode

-bolt -
-

- A dedicated web platform to access the powers of AutoGPT and BabyAGI directly from your browser. -

-
-Web -UI -
-
- - View Details arrow_forward - -
-
-
-
-
- - - - \ No newline at end of file diff --git a/design/homepage.html b/design/homepage.html deleted file mode 100644 index ebaa0e0..0000000 --- a/design/homepage.html +++ /dev/null @@ -1,346 +0,0 @@ - - - - -Agent Park Homepage - - - - - - - -
-DISCOVER THE FUTURE OF AI AGENTS -arrow_forward -
-
-
-
-
-smart_toy -Agent Park -
- - -
- -
-
-
-
-
- - -
-

- AI PROJECT NAVIGATOR -

-

- Discover and explore high-quality AI projects from across the web. Curated for developers and enthusiasts. -

-
-
-
-
-search -
- - -
-
-
-
-

BROWSE BY TAGS

- -
-
-
-

FEATURED PROJECTS

- -
-
-
-
-

n8n

-auto_awesome -
-

- Open-source workflow automation tool. Connect various services and APIs through a visual interface to build complex automations without code. -

-
-
-Automation -Workflow -
- - VIEW DETAILS - arrow_forward - -
-
-
-
-

Stable Diffusion

-image -
-

- Open-source image generation model. Supports local deployment and custom training, enabling powerful creative workflows. -

-
-
-Image Gen -Open Source -
- - VIEW DETAILS - arrow_forward - -
-
-
-
-

Midjourney

-palette -
-

- Powerful AI image generation tool. Create exquisite images through text descriptions with industry-leading artistic quality. -

-
-
-Image Gen -Art -
- - VIEW DETAILS - arrow_forward - -
-
-
-
-

ChatGPT

-chat -
-

- Large language model developed by OpenAI. Supports conversation, writing, coding, and a wide variety of tasks. -

-
-
-Chat AI -Coding Assistant -
- - VIEW DETAILS - arrow_forward - -
-
-
-
-

Claude

-psychology -
-

- AI assistant developed by Anthropic. Excels at analysis, writing, and coding tasks with a focus on safety and reliability. -

-
-
-Chat AI -Analysis -
- - VIEW DETAILS - arrow_forward - -
-
-
-
-add -
-

Submit Your Project

-

- Have an AI tool worth sharing? Add it to Agent Park. -

- - SUBMIT NOW - -
-
- -
-
-
-
-

STAY UPDATED

-

- Get the latest AI tools and trends delivered straight to your inbox. No spam, just intelligence. -

-
- -
- - -
- -
-
-
-
-
-
-
-rocket_launch -
-
-
-
-
-
- - - \ No newline at end of file diff --git a/design/projectlist.html b/design/projectlist.html deleted file mode 100644 index d8b122a..0000000 --- a/design/projectlist.html +++ /dev/null @@ -1,344 +0,0 @@ - - - - -Agent Park - AI Project Navigator - - - - - - - -
- DISCOVER THE FUTURE OF AI AGENTS arrow_forward -
- -
- - -
-

- PROJECT
NAVIGATOR -

-

- Discover and explore high-quality AI projects from across the web. Curated for developers and enthusiasts. -

-
-
-
-
-search - -
-
- -expand_more -
- -
-
-

Browse by Tags

-
- - - - - - -
-
-
-
-

Featured Projects

- - View All arrow_forward - -
-
-
-
-
-
-TOP PICK -auto_awesome -
-

AutoGPT X

-

- An experimental open-source application showcasing the capabilities of the GPT-4 language model. This program, driven by GPT-4, chains together LLM "thoughts", to autonomously achieve whatever goal you set. -

-
-Automation -Agent -
-
- - View Details arrow_forward - -
-
-
-psychology -
-
-
-
-
-hub -
-

n8n

-

- Open-source workflow automation tool. Connect various services and APIs through a visual interface to build complex automations without code. -

-
-
-
-Automation -Workflow -
-
- - View Details arrow_forward - - -
- - - -42.5k -
-
-
-
-
-
-
-image -
-

Stable Diffusion

-

- Open-source image generation model. Supports local deployment and custom training, enabling powerful creative workflows. -

-
-
-
-Image Gen -Open Source -
- - View Details arrow_forward - -
-
-
-
-
-palette -
-

Midjourney

-

- Powerful AI image generation tool. Create exquisite images through text descriptions with industry-leading artistic quality. -

-
-
-
-Image Gen -Art -
- - View Details arrow_forward - -
-
-
-
-
-chat -
-

ChatGPT

-

- Large language model developed by OpenAI. Supports conversation, writing, coding, and a wide variety of tasks. -

-
-
-
-Chat AI -Assistant -
- - View Details arrow_forward - -
-
-
-
-add -
-

Submit Your Project

-

- Have an AI tool worth sharing? Add it to Agent Park. -

- -
-
-
-
- - - - - -
-
-
-
-
-

STAY UPDATED

-

- Get the latest AI tools and trends delivered straight to your inbox. No spam, just intelligence. -

-
- -
- - -
- -
-
-
-
-
-rocket_launch -
-
-
-
-
- - - \ No newline at end of file diff --git a/docs/api-reference.md b/docs/api-reference.md deleted file mode 100644 index 88aeb1a..0000000 --- a/docs/api-reference.md +++ /dev/null @@ -1,1049 +0,0 @@ -# API 接口文档 - -> AI 项目导航站对外提供的 REST API 接口文档 - -## 目录 - -- [1. 概述](#1-概述) -- [2. 认证方式](#2-认证方式) -- [3. 接口列表](#3-接口列表) - - [3.1 创建/更新项目 (Webhook)](#31-创建更新项目-webhook) - - [3.2 获取项目详情](#32-获取项目详情) - - [3.3 删除项目](#33-删除项目) - - [3.4 项目发现系统 API](#34-项目发现系统-api) - - [3.5 去重检查 API](#35-去重检查-api) -- [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; // 中文名称 (1-200 字符) - nameEn?: string; // 英文名称(可选,1-200 字符) - description: string; // 中文描述 (10-500 字符) - descriptionEn?: string; // 英文描述(可选,10-500 字符) - - // 内容(可选) - content?: string; // 中文内容(Markdown 格式,最大 10000 字符) - contentEn?: string; // 英文内容(Markdown 格式,最大 10000 字符) - - // 状态(可选) - 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"] -} -``` - ---- - -### 3.4 项目发现系统 API - -项目发现系统用于自动化探索和收录 AI 项目,支持任务创建、状态追踪和项目提交。 - -#### 3.4.1 创建探索任务 - -批量创建新的项目探索任务。 - -``` -POST /api/discovery/tasks -``` - -**请求体**: - -```typescript -{ - apiKey: string; // API 密钥 - tasks: Array<{ - sourceUrl: string; // 探索目标 URL (GitHub 仓库链接等) - sourceType?: string; // 来源类型,默认 "manual" - }>; // 1-50 个任务 -} -``` - -**响应示例** (200 OK): - -```json -{ - "success": true, - "created": 5, - "skipped": 2, - "total": 7 -} -``` - -**去重逻辑**: -- 如果 `sourceUrl` 已存在任务,自动跳过并计入 `skipped` - ---- - -#### 3.4.2 获取任务列表 - -获取待处理或指定状态的探索任务列表。 - -``` -GET /api/discovery/tasks?status=PENDING&limit=10&offset=0 -``` - -**查询参数**: - -| 参数 | 类型 | 必填 | 说明 | -|------|------|------|------| -| status | string | 否 | 筛选状态: PENDING/IN_PROGRESS/COMPLETED/FAILED | -| limit | number | 否 | 每页数量,默认 10,最大 100 | -| offset | number | 否 | 偏移量,默认 0 | - -**请求头**: - -``` -x-api-key: your-api-key -``` - -**响应示例** (200 OK): - -```json -{ - "success": true, - "tasks": [ - { - "id": "clx1234567890", - "sourceUrl": "https://github.com/user/repo", - "sourceType": "manual", - "status": "PENDING", - "createdAt": "2024-01-15T00:00:00.000Z", - "startedAt": null, - "completedAt": null, - "projectId": null, - "explorationData": null, - "explorationSummary": null, - "errorMessage": null, - "retryCount": 0 - } - ], - "total": 25, - "hasMore": true -} -``` - ---- - -#### 3.4.3 获取任务详情 - -获取单个探索任务的详细信息。 - -``` -GET /api/discovery/tasks/:id -``` - -**无需认证** (只读端点) - -**响应示例** (200 OK): - -```json -{ - "success": true, - "task": { - "id": "clx1234567890", - "sourceUrl": "https://github.com/user/repo", - "sourceType": "manual", - "status": "COMPLETED", - "createdAt": "2024-01-15T00:00:00.000Z", - "startedAt": "2024-01-15T00:01:00.000Z", - "completedAt": "2024-01-15T00:05:00.000Z", - "projectId": "clx0987654321", - "explorationData": { ... }, - "explorationSummary": "LangChain 是一个 LLM 应用开发框架...", - "errorMessage": null, - "retryCount": 0 - } -} -``` - ---- - -#### 3.4.4 更新任务状态 - -更新探索任务的状态和相关信息。 - -``` -PATCH /api/discovery/tasks/:id -``` - -**请求体**: - -```typescript -{ - apiKey: string; - status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETED' | 'FAILED'; - explorationData?: Record; // 探索结果数据 (JSON) - explorationSummary?: string; // 探索摘要,最大 1000 字符 - errorMessage?: string; // 错误信息,最大 2000 字符 -} -``` - -**状态转换规则**: - -| 当前状态 | 允许转换到 | -|----------|------------| -| PENDING | IN_PROGRESS | -| IN_PROGRESS | COMPLETED, FAILED | -| COMPLETED | (终态,不可转换) | -| FAILED | PENDING (允许重试) | - -**响应示例** (200 OK): - -```json -{ - "success": true, - "task": { - "id": "clx1234567890", - "status": "IN_PROGRESS", - "startedAt": "2024-01-15T00:01:00.000Z", - ... - } -} -``` - ---- - -#### 3.4.5 完成任务并提交项目 - -完成探索并提交项目数据(自动创建或更新项目)。 - -``` -POST /api/discovery/tasks/:id/complete -``` - -**请求体**: - -```typescript -{ - apiKey: string; - explorationData: ProjectInput; // 符合 ProjectInputSchema 的项目数据 -} -``` - -**功能说明**: -- 验证 `explorationData` 格式 -- 多级去重策略识别已存在项目(GitHub URL → Website URL → slug) -- 使用事务确保任务状态更新和项目创建/更新的原子性 -- 成功时任务状态更新为 COMPLETED,关联 projectId -- 失败时任务状态更新为 FAILED,记录错误信息 - -**响应示例** (200 OK): - -```json -{ - "success": true, - "taskId": "clx1234567890", - "projectId": "clx0987654321", - "action": "created", - "duration": 1234 -} -``` - -**错误响应** (400 Bad Request): - -```json -{ - "success": false, - "error": "Invalid exploration data format", - "details": [ - "tags: Field must contain at least 1 element", - "links: Field must contain at least 1 element" - ] -} -``` - ---- - -#### 3.4.6 检查任务去重 - -在创建任务前检查 URL 是否应该创建新任务。 - -``` -POST /api/discovery/check-duplicates -``` - -**请求体**: - -```typescript -{ - apiKey: string; - urls: string[]; // 1-100 个 URL - sourceType?: string; // 可选来源标识 -} -``` - -**去重优先级**: -1. PENDING/IN_PROGRESS 任务 → 不创建(任务处理中) -2. COMPLETED/FAILED 任务 → 不创建(已探索过) -3. 已存在的项目(通过 ExternalLink)→ 不创建(已收录) -4. 无任何记录 → 允许创建 - -**响应示例** (200 OK): - -```json -{ - "success": true, - "results": [ - { - "url": "https://github.com/langchain-ai/langchain", - "shouldCreate": false, - "reason": "Task already completed", - "existingTask": { - "id": "clx123", - "status": "COMPLETED", - "sourceUrl": "https://github.com/langchain-ai/langchain", - "createdAt": "2024-01-15T00:00:00.000Z", - "projectId": "clx456" - }, - "existingProject": { - "id": "clx456", - "name": "LangChain", - "slug": "langchain" - } - } - ], - "stats": { - "total": 10, - "shouldCreate": 5, - "duplicate": 5 - } -} -``` - ---- - -### 3.5 去重检查 API - -检查项目是否已存在(用于提交前的预检查)。 - -#### 3.5.1 检查项目去重 - -根据 URL 或 slug 检查项目是否已存在。 - -``` -POST /api/webhook/check-duplicates -``` - -**请求体**: - -```typescript -{ - apiKey: string; - projects: Array<{ - githubUrl?: string; - huggingfaceUrl?: string; - websiteUrl?: string; - slug?: string; - }>; -} -``` - -**匹配优先级**: -1. GitHub URL 精确匹配 -2. Hugging Face URL 精确匹配 -3. Website URL 精确匹配 -4. slug 匹配(兜底) - -**响应示例** (200 OK): - -```json -{ - "success": true, - "results": [ - { - "githubUrl": "https://github.com/langchain-ai/langchain", - "exists": true, - "matchType": "GITHUB_URL", - "projectId": "clx456", - "projectName": "LangChain" - }, - { - "websiteUrl": "https://newproject.com", - "exists": false, - "matchType": "NONE" - } - ], - "stats": { - "total": 2, - "exists": 1, - "new": 1, - "breakdown": { - "githubUrl": 1, - "huggingfaceUrl": 0, - "websiteUrl": 0, - "slug": 0 - } - } -} -``` - ---- - -## 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; // 中文名称 (1-200 字符) - nameEn: string | null; // 英文名称 - slug: string; // URL 友好标识符(唯一) - description: string; // 中文描述 (10-500 字符) - descriptionEn: string | null;// 英文描述 - content: string | null; // 中文内容(Markdown,最大 10000 字符) - contentEn: string | null; // 英文内容(Markdown,最大 10000 字符) - 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 -} -``` - -### 4.6 ProjectDiscoveryTask 模型 - -```typescript -interface ProjectDiscoveryTask { - id: string; // 任务唯一 ID (cuid 格式) - sourceUrl: string; // 探索目标 URL - sourceType: string; // 来源类型 (如 "manual", "github-trending") - status: TaskStatus; // 任务状态 - createdAt: Date; // 创建时间 - startedAt: Date | null; // 开始处理时间 - completedAt: Date | null; // 完成时间 - projectId: string | null; // 关联的项目 ID (完成后) - explorationData: JsonValue | null; // 探索结果数据 (JSON) - explorationSummary: string | null; // 探索摘要 - errorMessage: string | null; // 错误信息 - retryCount: number; // 重试次数 - lastRetryAt: Date | null; // 最后重试时间 -} -``` - -### 4.7 TaskStatus 状态枚举 - -```typescript -enum TaskStatus { - PENDING = 'PENDING', // 待处理 - IN_PROGRESS = 'IN_PROGRESS', // 处理中 - COMPLETED = 'COMPLETED', // 已完成 - FAILED = 'FAILED' // 失败 -} -``` - ---- - -## 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: 非空字符串,长度 10-500 -- tags: 数组,长度 1-10,每个 tag.name 非空 -- links: 数组,长度 1-10,每个 link.url 和 link.type 非空 - -// 可选字段 -- nameEn: 字符串,长度 1-200 -- descriptionEn: 字符串,长度 10-500 -- content/contentEn: 文本类型,支持 Markdown,最大 10000 字符 -- 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) -- [项目发现系统](../.claude/commands/discover-projects.md) -- [Discovery Service](../src/app/api/discovery/lib/discovery-service.ts) - ---- - -**文档版本**: v2.0.0 -**最后更新**: 2025-01-20 -**维护者**: AI 项目导航站团队 diff --git a/docs/api-testing-guide.md b/docs/api-testing-guide.md deleted file mode 100644 index 9752191..0000000 --- a/docs/api-testing-guide.md +++ /dev/null @@ -1,161 +0,0 @@ -# API 测试指南 - -## GET /api/events - -获取所有事件: - -```bash -curl http://localhost:3000/api/events -``` - -筛选特定年份: - -```bash -curl "http://localhost:3000/api/events?year=2024" -``` - -限制返回数量: - -```bash -curl "http://localhost:3000/api/events?limit=10" -``` - -分页: - -```bash -curl "http://localhost:3000/api/events?offset=10&limit=10" -``` - -## POST /api/events - -创建单个事件: - -```bash -curl -X POST http://localhost:3000/api/events \ - -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ - -d '[ - { - "title": "事件标题", - "eventDate": "2023-03-14T00:00:00Z", - "description": "事件描述(10-500字)", - "imageUrl": "https://example.com/image.jpg" - } - ]' -``` - -批量创建事件: - -```bash -curl -X POST http://localhost:3000/api/events \ - -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ - -d '[ - { "title": "事件1", "eventDate": "2023-01-01T00:00:00Z", "description": "描述1", "imageUrl": "https://example.com/1.jpg" }, - { "title": "事件2", "eventDate": "2023-02-01T00:00:00Z", "description": "描述2", "imageUrl": "https://example.com/2.jpg" } - ]' -``` - -包含可选字段: - -```bash -curl -X POST http://localhost:3000/api/events \ - -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ - -d '[ - { - "title": "GPT-4 Release", - "titleEn": "GPT-4 发布", - "eventDate": "2023-03-14T00:00:00Z", - "description": "OpenAI launches multimodal LLM", - "descriptionEn": "OpenAI 发布多模态大语言模型", - "imageUrl": "https://example.com/gpt4.jpg", - "sourceUrl": "https://openai.com/blog/gpt-4" - } - ]' -``` - -## 验证规则 - -### 输入验证 (AIEventInputSchema) - -- `title`: 1-200 字符(必填) -- `titleEn`: 最多 200 字符(可选) -- `eventDate`: ISO 8601 datetime 格式(必填) -- `description`: 10-500 字符(必填) -- `descriptionEn`: 最多 500 字符(可选) -- `imageUrl`: 有效 URL(必填) -- `sourceUrl`: 有效 URL(可选) - -### 查询参数验证 (AIEventQuerySchema) - -- `year`: 4位数字年份(可选) -- `limit`: 正整数(可选,默认 100) -- `offset`: 非负整数(可选,默认 0) - -## 认证 - -所有 POST 请求必须在请求头中包含 API Key: - -``` -X-API-Key: YOUR_API_KEY -``` - -## 响应示例 - -### 成功响应 (POST) - -```json -{ - "created": 2, - "total": 2 -} -``` - -### 成功响应 (GET) - -```json -{ - "events": [ - { - "id": "cmkwn6q0300004jjz4lobtkpf", - "title": "Transformer论文", - "titleEn": null, - "eventDate": "2017-06-12T00:00:00.000Z", - "description": "Google团队发表Transformer架构", - "descriptionEn": null, - "imageUrl": "https://images.unsplash.com/photo-1677442136019-21780ecad995?w=800", - "sourceUrl": null, - "createdAt": "2026-01-27T13:38:39.268Z", - "updatedAt": "2026-01-27T13:38:39.268Z" - } - ] -} -``` - -### 错误响应 - -```json -{ - "error": "Validation failed", - "details": [ - { - "code": "too_small", - "path": ["0", "description"], - "message": "String must contain at least 10 character(s)" - } - ] -} -``` - -## 测试记录 - -### 2025-01-27 测试结果 - -- ✅ GET /api/events - 返回空列表 -- ✅ POST /api/events - 单个事件创建成功 -- ✅ GET /api/events - 验证事件已创建(1个事件) -- ✅ POST /api/events - 批量创建成功(2个事件) -- ✅ GET /api/events?year=2018 - 年份筛选成功(返回2个事件) - -所有基础功能测试通过! diff --git a/docs/discovery-workflow.md b/docs/discovery-workflow.md deleted file mode 100644 index 1e1a423..0000000 --- a/docs/discovery-workflow.md +++ /dev/null @@ -1,548 +0,0 @@ -# 项目发现工作流 (Project Discovery Workflow) - -本文档详细说明了 AI 项目自动发现和收录的完整工作流程。 - -## 📋 工作流概览 - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ 项目发现完整流程 │ -└─────────────────────────────────────────────────────────────────┘ - -1. n8n 自动化平台 - │ - ├─ 定期抓取 GitHub/Twitter/HackerNews 等平台 - ├─ 筛选符合条件的项目链接 - │ - ▼ -2. 调用去重检查 API (推荐) - POST /api/discovery/check-duplicates - │ - ├─ 检查 URL 是否已存在任务 - ├─ 检查 URL 对应项目是否已收录 - ├─ 过滤出需要创建的 URL - │ - ▼ -3. 调用创建任务 API - POST /api/discovery/tasks - │ - ├─ 存入 ProjectDiscoveryTask 表 - ├─ 状态: PENDING - │ - ▼ -4. 手动触发本地命令 (定期执行) - /discover-projects - │ - ├─ 通过 curl 获取待处理任务 - ├─ 调用 Content Explorer Agent - │ ├─ 使用 agent-browser 探索项目 - │ ├─ 提取项目信息 (README, 代码结构等) - │ ├─ 生成结构化 JSON 数据 - │ └─ 应用内容质量标准 - │ - ├─ 调用 API Submitter Agent - │ ├─ 批量标记任务为 IN_PROGRESS - │ ├─ 提交探索数据到完成 API - │ └─ 自动重试失败的提交 - │ - ▼ -5. 完成任务并入库 - POST /api/discovery/tasks/{id}/complete - │ - ├─ 验证数据格式 (Zod Schema) - ├─ 多级去重检测 (GitHub/Website URL) - ├─ 创建或更新 Project - ├─ 创建 Tag 和关联关系 - ├─ 创建 ExternalLink - │ - ├─ 更新任务状态: COMPLETED/FAILED - │ - ▼ -6. 数据已入库,可在前台展示 -``` - ---- - -## 🔄 详细步骤说明 - -### 步骤 1: n8n 自动收集项目链接 - -**平台**: n8n 自动化平台 (独立部署) - -**工作内容**: -- 定期抓取 GitHub Trending、Twitter、HackerNews 等平台 -- 根据关键词筛选 AI 相关项目 -- 提取项目的基本信息 (名称、链接、简介等) - -**输出数据格式**: -```json -{ - "sourceUrl": "https://github.com/langchain-ai/langchain", - "sourceType": "github_trending" // 或 "twitter", "hackernews" 等 -} -``` - ---- - -### 步骤 2: 调用创建任务 API - -**API 端点**: `POST /api/discovery/tasks` - -**调用示例**: -```bash -curl -X POST https://your-domain.com/api/discovery/tasks \ - -H "Content-Type: application/json" \ - -H "x-api-key: YOUR_API_KEY" \ - -d '{ - "apiKey": "YOUR_API_KEY", - "tasks": [ - { - "sourceUrl": "https://github.com/langchain-ai/langchain", - "sourceType": "github_trending" - }, - { - "sourceUrl": "https://github.com/openai/openai-quickstart-python", - "sourceType": "github_trending" - } - ] - }' -``` - -**数据库变更**: -- 在 `ProjectDiscoveryTask` 表中插入新记录 -- `status` = `PENDING` -- `sourceUrl` 和 `sourceType` 来自 n8n -- `createdAt` = 当前时间 - ---- - -### 步骤 2.5: 调用去重检查 API(推荐) - -**API 端点**: `POST /api/discovery/check-duplicates` - -**调用示例**: -```bash -curl -X POST https://your-domain.com/api/discovery/check-duplicates \ - -H "Content-Type: application/json" \ - -d '{ - "apiKey": "YOUR_API_KEY", - "urls": [ - "https://github.com/langchain-ai/langchain", - "https://github.com/openai/openai-quickstart-python" - ] - }' -``` - -**返回结果**: -```json -{ - "success": true, - "results": [ - { - "url": "https://github.com/langchain-ai/langchain", - "shouldCreate": false, - "reason": "Task already exists with status PENDING", - "existingTask": { - "id": "cmxxxxx", - "status": "PENDING", - "sourceUrl": "https://github.com/langchain-ai/langchain", - "createdAt": "2025-01-18T10:00:00Z", - "projectId": null - } - }, - { - "url": "https://github.com/openai/openai-quickstart-python", - "shouldCreate": true, - "reason": "No existing task or project found" - } - ], - "stats": { - "total": 2, - "shouldCreate": 1, - "duplicate": 1 - } -} -``` - -**去重逻辑**(按优先级): -1. **优先级 1**: 检查是否有 PENDING/IN_PROGRESS 的相同 URL 任务 - - 如果存在 → `shouldCreate: false` - - 原因:任务已在处理中,避免重复探索 - -2. **优先级 2**: 检查是否有 COMPLETED/FAILED 的相同 URL 任务 - - 如果存在 → `shouldCreate: false` - - 原因:任务已探索过,无需重复 - -3. **优先级 3**: 检查 URL 对应的项目是否已存在(通过 ExternalLink) - - 如果存在 → `shouldCreate: false` - - 原因:项目已通过其他来源收录 - -4. **默认**: 允许创建新任务 - - `shouldCreate: true` - -**n8n 集成建议**: -```javascript -// n8n Workflow 示例 -const checkResponse = await fetch('https://your-domain.com/api/discovery/check-duplicates', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - apiKey: 'YOUR_API_KEY', - urls: collectedUrls // 从上一步收集的 URL 列表 - }) -}) - -const { results, stats } = await checkResponse.json() - -// 过滤出应该创建任务的 URL -const urlsToCreate = results - .filter(r => r.shouldCreate) - .map(r => r.url) - -// 只为不重复的 URL 创建任务 -if (urlsToCreate.length > 0) { - await fetch('https://your-domain.com/api/discovery/tasks', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - apiKey: 'YOUR_API_KEY', - tasks: urlsToCreate.map(url => ({ - sourceUrl: url, - sourceType: 'github_trending' - })) - }) - }) -} - -console.log(`创建 ${urlsToCreate.length} 个新任务,跳过 ${stats.duplicate} 个重复任务`) -``` - ---- - -### 步骤 3: 手动触发本地命令 - -**执行环境**: 本地开发环境 (Local Development) - -**执行命令**: -```bash -# 处理默认 10 个任务 (每批 3 个) -/discover-projects - -# 处理指定数量的任务 -/discover-projects 20 - -# 自定义批次大小 -/discover-projects 9 --batch=2 - -# 处理所有待处理任务 -/discover-projects all --batch=5 -``` - -**命令执行流程**: -1. 通过 curl 调用 `GET /api/discovery/tasks?status=PENDING&limit=N` -2. 获取待处理的任务列表 -3. 分批处理 (默认每批 3 个任务) -4. 为每个任务启动 **Content Explorer Agent** - ---- - -### 步骤 3.1: Content Explorer Agent (项目探索) - -**Agent 定义**: `.claude/agents/content-explorer-agent.md` - -**核心能力**: -- 使用 `agent-browser` 子任务并行探索 GitHub 项目 -- 访问项目主页、README、代码结构 -- 提取项目元数据 (名称、描述、标签、链接等) -- 生成符合 `ProjectInputSchema` 的 JSON 数据 - -**内容质量标准** (参考: `.claude/schemas/project-content-template.md`): -- ✅ **描述**: 客观说明功能、突出价值、避免营销术语、10-500字 -- ✅ **内容**: 从 README 提取并重新组织、符合中文表达习惯 -- ✅ **链接**: 必须包含 GITHUB 链接、所有链接可访问 -- ✅ **标签**: 1-10 个标签、按技术/应用/状态分类 -- ✅ **动态数据**: Star/Fork 等动态数据不写入内容,使用 GitHub Badge - -**输出数据格式**: -```json -{ - "name": "LangChain", - "nameEn": "LangChain", - "description": "开发由 LLM 驱动的应用程序的框架,提供文档加载、文本分割、向量存储等核心组件", - "descriptionEn": "Framework for developing applications powered by language models", - "content": "## 核心功能\n\n- 文档加载: 支持 PDF、TXT、网页等多种格式\n- 文本分割: 智能分割长文本\n...", - "tags": [ - { "name": "LLM", "nameEn": "Large Language Model" }, - { "name": "框架", "nameEn": "Framework" } - ], - "links": [ - { "type": "GITHUB", "url": "https://github.com/langchain-ai/langchain", "title": "GitHub 仓库" }, - { "type": "WEBSITE", "url": "https://python.langchain.com", "title": "官方文档" } - ], - "status": "ACTIVE" -} -``` - ---- - -### 步骤 3.2: API Submitter Agent (数据提交) - -**Agent 定义**: `.claude/agents/api-submitter-agent.md` - -**核心能力**: -- 批量标记任务为 `IN_PROGRESS` -- 调用 `POST /api/discovery/tasks/{id}/complete` 提交数据 -- 自动重试失败的提交 (指数退避,最多 3 次) -- 处理部分成功/失败情况 - -**提交流程**: -``` -1. PATCH /api/discovery/tasks/{id} → status=IN_PROGRESS - ↓ -2. POST /api/discovery/tasks/{id}/complete → 提交探索数据 - ↓ -3. 检查响应 - ├─ 成功 → 标记任务完成 - ├─ 失败 → 重试 (最多 3 次) - └─ 最终失败 → 记录错误信息 -``` - ---- - -### 步骤 4: 完成任务并入库 - -**API 端点**: `POST /api/discovery/tasks/{id}/complete` - -**处理逻辑** (参考: `src/app/api/discovery/tasks/[id]/complete/route.ts`): - -1. **验证数据格式**: 使用 `ProjectInputSchema` 验证 -2. **多级去重检测**: - - 优先级 1: GitHub URL 精确匹配 - - 优先级 2: Website URL 精确匹配 - - 优先级 3: slug 匹配 -3. **创建或更新项目**: - - 如果存在重复项目 → 更新所有字段、标签、链接 - - 如果不存在 → 创建新项目 -4. **更新任务状态**: - - 成功 → `COMPLETED` - - 失败 → `FAILED` (记录错误信息) - -**数据库变更**: -- `Project` 表: 创建或更新记录 -- `Tag` 表: Upsert 标签 -- `ProjectTag` 表: 创建关联关系 -- `ExternalLink` 表: 创建或更新链接 -- `ProjectDiscoveryTask` 表: 更新 `status`、`projectId`、`completedAt` - ---- - -## 🗂️ 数据模型关系 - -``` -ProjectDiscoveryTask (任务表) - ├─ id: String (主键) - ├─ status: TaskStatus (PENDING/IN_PROGRESS/COMPLETED/FAILED) - ├─ sourceUrl: String (n8n 提供的原始 URL) - ├─ sourceType: String (github_trending/twitter/hackernews) - ├─ explorationData: Json (Agent 探索结果) - ├─ explorationSummary: String (探索摘要) - ├─ projectId: String (关联到 Project.id) - ├─ errorMessage: String (失败原因) - └─ createdAt/startedAt/completedAt: DateTime - -Project (项目表) ← 通过 projectId 关联 - ├─ id, name, nameEn, slug - ├─ description, descriptionEn - ├─ content, contentEn - ├─ status (ACTIVE/ARCHIVED) - └─ 关联: tags, links, discoveryTasks - -Tag (标签表) - └─ 通过 ProjectTag 多对多关联 - -ExternalLink (外部链接表) - └─ 通过 projectId 一对多关联 -``` - ---- - -## 🔧 环境配置 - -### 环境变量 - -```env -# .env.local (本地开发) -WEBHOOK_API_KEY=sk_live_your_secure_api_key_min_32_chars # 用于调用完成 API - -# 生产环境 (Vercel Dashboard 配置) -DATABASE_URL=postgres://... -WEBHOOK_API_KEY=sk_live_your_secure_api_key_min_32_chars -``` - -### 依赖服务 - -1. **n8n 平台**: - - 独立部署 (自托管或云服务) - - 配置定时工作流 (Workflow) - - 存储 `WEBHOOK_API_KEY` 用于 API 调用 - -2. **本地开发环境**: - - Node.js 18+ - - pnpm 包管理器 - - Claude Code CLI (支持斜杠命令) - -3. **生产环境**: - - Vercel (Next.js 部署) - - Neon PostgreSQL (数据库) - ---- - -## 📊 执行监控 - -### 查看待处理任务 - -```bash -# 查询待处理任务数量 -curl https://your-domain.com/api/discovery/tasks?status=PENDING&limit=100 - -# 查询进行中的任务 -curl https://your-domain.com/api/discovery/tasks?status=IN_PROGRESS - -# 查询失败的任务 (需要重试) -curl https://your-domain.com/api/discovery/tasks?status=FAILED -``` - -### 重试失败任务 - -```bash -# 手动重试失败的任务 -/discover-projects 10 --status=FAILED -``` - ---- - -## 🎯 质量保障 - -### 内容质量标准 (详细参考: `.claude/schemas/project-content-template.md`) - -1. **描述要求**: - - 清晰说明项目的核心功能 - - 突出项目的独特价值 - - 避免使用营销术语 ("最好"、"第一"、"革命性" 等) - - 字数控制在 10-500 字 - -2. **内容要求**: - - 从项目 README 提取并重新组织 - - 避免机械翻译,符合中文表达习惯 - - 支持 Markdown 格式 - - 动态数据 (Star/Fork) 不写入内容 - -3. **链接要求**: - - 必须包含 GITHUB 链接 - - 所有链接必须可访问 - - 链接类型必须正确 (WEBSITE/GITHUB/HUGGINGFACE/PAPER) - -4. **标签要求**: - - 1-10 个标签 - - 按技术栈/应用领域/开发状态分类 - - 中英文对应 - -### 数据验证 - -所有提交的数据必须通过 `ProjectInputSchema` 验证 (详见 `src/lib/validations.ts`): - -```typescript -ProjectInputSchema { - name: string (1-200字符, 必填) - description: string (10-500字符, 必填) - tags: Tag[] (1-10个, 必填) - links: ExternalLink[] (1-10个, 必填) - nameEn?: string (1-200字符) - descriptionEn?: string - content?: string (Markdown, 最多10000字符) - contentEn?: string - status?: "ACTIVE" | "ARCHIVED" - source?: string -} -``` - ---- - -## 🚀 快速开始 - -### 第一次使用 - -1. **配置 n8n 工作流**: - - 创建新的 n8n Workflow - - 配置定时触发器 (如每天凌晨 2 点) - - 添加 HTTP Request 节点调用 `POST /api/discovery/tasks` - -2. **本地执行命令**: - ```bash - # 启动开发服务器 (如果未运行) - pnpm dev - - # 执行项目发现命令 - /discover-projects - ``` - -3. **查看结果**: - - 访问 https://your-domain.com 查看新收录的项目 - - 检查数据库确认数据正确性 - -### 日常维护 - -1. **定期执行命令** (建议每天 1-2 次): - ```bash - /discover-projects 20 - ``` - -2. **监控失败任务**: - - 检查 `status=FAILED` 的任务 - - 分析错误原因 (网络问题、数据格式等) - - 必要时手动重试 - -3. **优化内容质量**: - - 随机抽查已收录项目的描述和内容 - - 调整 Agent 的提示词以提升质量 - ---- - -## 🔍 故障排查 - -### 常见问题 - -1. **任务长时间处于 IN_PROGRESS 状态**: - - 可能原因: Agent 探索超时、网络问题 - - 解决方案: 手动更新任务状态为 PENDING 后重试 - -2. **大量任务失败**: - - 检查 `errorMessage` 字段 - - 常见原因: 数据格式不正确、URL 无法访问、API Key 错误 - -3. **重复项目被创建**: - - 检查去重逻辑是否正常工作 - - 确认 `ExternalLink` 表的索引 `idx_link_type_url` 存在 - -4. **n8n 无法调用 API**: - - 检查 `WEBHOOK_API_KEY` 是否正确配置 - - 确认 API 端点可访问 - - 查看 n8n 执行日志 - ---- - -## 📚 相关文档 - -- **API 参考**: `docs/api-reference.md` -- **数据模型**: `prisma/schema.prisma` -- **验证规则**: `src/lib/validations.ts` -- **Agent 定义**: `.claude/agents/content-explorer-agent.md` -- **Agent 定义**: `.claude/agents/api-submitter-agent.md` -- **内容质量标准**: `.claude/schemas/project-content-template.md` -- **项目主文档**: `CLAUDE.md` - ---- - -## 📝 更新日志 - -- **2025-01-18**: 创建文档,记录完整的项目发现工作流 diff --git a/docs/e2e-testing-guide.md b/docs/e2e-testing-guide.md deleted file mode 100644 index d36d5c4..0000000 --- a/docs/e2e-testing-guide.md +++ /dev/null @@ -1,106 +0,0 @@ -# Timeline E2E 测试指南 - -## 使用 chrome-devtools-mcp 测试 - -### 1. 启动测试环境 - -```bash -# 确保开发服务器运行 -pnpm dev -``` - -### 2. 页面加载测试 - -使用 `new_page` 或 `navigate_page`: -``` -URL: http://localhost:3000/timeline -预期: 页面成功加载,中文路径重定向到 /zh/timeline -``` - -使用 `take_snapshot`: -- 验证页面结构正确,包含 header 和 timeline sections -- 验证导航菜单包含"AI 时间轴"链接 - -### 3. 数据验证 - -使用 `evaluate_script`: -```javascript -() => { - const yearSections = document.querySelectorAll('section'); - const eventCards = document.querySelectorAll('.stack-card'); - - return { - yearCount: yearSections.length, - eventCount: eventCards.length, - hasHeader: document.querySelector('h1') !== null - }; -} -``` - -预期结果: -```json -{ - "yearCount": 2, - "eventCount": 3, - "hasHeader": true -} -``` - -### 4. 无控制台错误 - -使用 `list_console_messages` with `types: ["error", "warn"]`: -- Expected: 空数组或仅资源预加载警告(可忽略) - -### 5. 响应式测试 - -使用 `resize_page`: -- 桌面: 1920x1080 -- 移动: 375x667 (iPhone SE) - -验证: 布局在不同尺寸下正常显示,移动端显示汉堡菜单 - -### 6. 截图对比 - -使用 `take_screenshot`: -- 保存路径: `tests/screenshots/timeline-page.png` -- 手动对比与设计原型 -- 验证视觉风格符合 Neo-brutalism 设计 - -## 测试记录 - -### 2025-01-27 测试结果 - -- ✅ 页面加载成功(自动重定向到 /zh/timeline) -- ✅ 数据渲染正确(2年3事件:2018年2个,2017年1个) -- ✅ 年份降序排列(2018 → 2017) -- ✅ 导航菜单"AI 时间轴"链接正常 -- ✅ 无控制台错误(仅1个资源预加载警告) -- ✅ 移动端响应式布局正常 -- ✅ 桌面端截图已保存 - -### 测试数据 - -**2018年事件(2个):** -1. BERT发布 - Google发布BERT预训练模型 (2018/10/11) -2. GPT-1发布 - OpenAI发布第一代GPT模型 (2018/6/11) - -**2017年事件(1个):** -1. Transformer论文 - Google团队发表Transformer架构 (2017/6/12) - -### 测试环境 - -- Node.js: v22 -- Next.js: 15.1.11 -- 浏览器: Chrome (chrome-devtools-mcp) -- 测试时间: 2025-01-27 21:40 - -### 已知问题 - -无 - -### 后续优化 - -- 添加更多历史事件数据 -- 实现搜索和筛选功能 -- 添加事件详情页面 -- 优化移动端卡片间距 diff --git a/docs/n8n-tag-janitor.md b/docs/n8n-tag-janitor.md deleted file mode 100644 index da4d1d8..0000000 --- a/docs/n8n-tag-janitor.md +++ /dev/null @@ -1,145 +0,0 @@ -# n8n Tag Janitor Workflow - -## Overview - -Daily automated workflow to clean up duplicate/similar tags using AI semantic analysis. - -## Workflow Structure - -[Cron] → [HTTP GET /api/tags] → [Code: Preprocess] → [AI: Generate Plan] → [Code: Validate JSON] → [HTTP POST /api/tags/maintenance] → [Notification] - -## Node Configuration - -### 1. Schedule Trigger (Cron) - -- Trigger: Daily at 03:00 UTC -- Timezone: UTC - -### 2. HTTP Request - Fetch Tags - -- Method: GET -- URL: `{{$env.SITE_BASE_URL}}/api/tags` -- Response: JSON with `.tags` array - -### 3. Code Node - Preprocess - -Purpose: Format tags for LLM input, handle chunking if >200 tags - -```javascript -const tags = $input.first().json.tags; -const formatted = tags.map((t) => ({ - id: t.id, - name: t.name, - nameEn: t.nameEn || "", - projectCount: t._count.projects, -})); -// Sort by projectCount desc for prioritization -formatted.sort((a, b) => b.projectCount - a.projectCount); -return [{ json: { tags: formatted, total: formatted.length } }]; -``` - -### 4. AI Node - Generate Merge Plan - -Model: GPT-4o / Claude 3.5 Sonnet -Temperature: 0.1 (deterministic) - -Prompt Template: - -``` -You are a tag management expert. Analyze these tags and identify: -1. Semantic duplicates that should be merged (e.g., "机器学习" and "ML" → keep "机器学习" with nameEn "Machine Learning") -2. Tags missing English names that need nameEn补全 - -Tags (JSON): -{{$json.tags}} - -Output STRICT JSON (no markdown, no explanation): -{ - "merges": [ - { - "target": { "name": "保留的标签名", "nameEn": "Canonical English Name" }, - "sourceTagIds": ["id1", "id2"] - } - ], - "updates": [ - { "tagId": "id", "nameEn": "English Name" } - ] -} - -Rules: -- Keep the tag with higher projectCount as target -- For merges, target can be { "id": "existing_id" } if keeping existing tag, or { "name": "...", "nameEn": "..." } to create new -- Only include tags that NEED action (empty arrays if nothing to do) -- nameEn should be proper English, not pinyin -- Common tech terms: 机器学习=Machine Learning, 深度学习=Deep Learning, 自然语言处理=NLP -``` - -### 5. Code Node - Validate & Parse - -```javascript -const response = $input.first().json; -let plan; -try { - plan = typeof response === "string" ? JSON.parse(response) : response; -} catch (e) { - throw new Error("Invalid JSON from AI: " + e.message); -} - -// Validate structure -if (!Array.isArray(plan.merges)) plan.merges = []; -if (!Array.isArray(plan.updates)) plan.updates = []; - -// Self-merge check: target.id cannot be in sourceTagIds -for (const merge of plan.merges) { - if (merge.target.id && merge.sourceTagIds.includes(merge.target.id)) { - throw new Error("Self-merge detected: " + merge.target.id); - } -} - -return [{ json: plan }]; -``` - -### 6. HTTP Request - Execute Maintenance - -- Method: POST -- URL: `{{$env.SITE_BASE_URL}}/api/tags/maintenance` -- Body: - -```json -{ - "apiKey": "{{$env.WEBHOOK_API_KEY}}", - "updates": {{$json.updates}}, - "merges": {{$json.merges}} -} -``` - -### 7. Notification (Slack/Email/Webhook) - -Send summary: - -- Tags merged: X -- Tags deleted: Y -- Names updated: Z -- Errors: [list] - -## Environment Variables Required - -- `SITE_BASE_URL`: https://your-site.com -- `WEBHOOK_API_KEY`: API key for authentication - -## Chunking Strategy (for >200 tags) - -1. Split tags into batches of 100 -2. Process each batch sequentially -3. Aggregate results before final notification - -## Error Handling - -- Retry HTTP requests 3x with exponential backoff -- On AI parse failure: skip and alert -- On maintenance failure: log error, continue with notification - -## Testing - -1. Dry run: Comment out HTTP POST node, check AI output only -2. Real run: Enable all nodes, monitor /api/tags count before/after diff --git a/docs/n8n/frontier-signals-workflow.md b/docs/n8n/frontier-signals-workflow.md deleted file mode 100644 index 906dfc5..0000000 --- a/docs/n8n/frontier-signals-workflow.md +++ /dev/null @@ -1,121 +0,0 @@ -# Frontier Signals Workflow - -## Goal - -Aggregate frontier discussions from multiple platforms, keep only **AI Agent-related** signals via AI filtering, and ingest into `POST /api/webhook/signals`. - -## Workflow - -- Workflow name: `前沿信号聚合(多源+AI Agent过滤)` -- Workflow ID: `bAxNZKGq2ApUUiw9` -- Status: `active` -- Trigger: every 4 hours (`Schedule Trigger`) -- Activated at: `2026-02-23` - -## Source Research (Endpoints + Extracted Elements) - -| Source | Endpoint | Node Type | Extracted Elements | -| --- | --- | --- | --- | -| Hacker News | `https://hacker-news.firebaseio.com/v0/topstories.json` + `.../item/{id}.json` | HTTP Request | `title`, `url`, `text`, `score`, `descendants`, `time` | -| GitHub | `https://api.github.com/search/issues` | HTTP Request | `title`, `html_url`, `body`, `comments`, `reactions`, `labels`, `updated_at` | -| arXiv | `https://export.arxiv.org/api/query?...` | RSS Read | `title`, `link`, `content/contentSnippet`, `pubDate/isoDate`, `categories` | -| Reddit | `https://www.reddit.com/r/LocalLLaMA/new.json?limit=40` | HTTP Request | `title`, `permalink`, `selftext/url`, `ups`, `num_comments`, `created_utc` | -| Product Hunt | `https://www.producthunt.com/feed` | RSS Read | `title`, `link`, `content/contentSnippet`, `published/updated` | -| Hugging Face | `https://huggingface.co/blog/feed.xml` | RSS Read | `title`, `link`, `content/contentSnippet`, `published/updated` | - -## Why These Nodes - -- `Schedule Trigger`: periodic ingestion -- `HTTP Request` / `RSS Read`: source fetching with stable machine-readable endpoints -- `Code`: per-source normalization and schema-safe cleanup -- `Merge (append)`: multi-source union -- `Remove Duplicates`: `source + sourceUrl` dedupe before/after AI -- `Limit`: cap candidate volume before AI -- `LLM Chain + Structured Output Parser`: relevance filtering + structured sections -- `If`: keep only `shouldKeep=true` -- `HTTP Request` (POST): write to `/api/webhook/signals` - -## Target Contract Mapping - -The workflow emits payload compatible with `SignalWebhookPayloadSchema`: - -- `apiKey`: manually configured in node `构建 Webhook Payload` -- `signals[]`: - - `source` -> one of: - - `hacker_news` - - `github` - - `arxiv` - - `hugging_face` - - `reddit` - - `product_hunt` - - `sourceUrl`, `title`, `summary`, `topic`, `tags`, `sections`, `engagement`, `hotScore`, `isHot`, `publishedAt`, `isActive` - -Sections are constrained to: - -- style: `focus | debate | evidence | action | risk` -- max 6 sections, max 6 items per section - -## AI Filtering Policy - -The AI node does **not** score ideas by novelty/reliability/feasibility. -It only decides whether a signal is about AI Agent topics and then structures content for reading efficiency. - -- Keep (`shouldKeep=true`) if discussion is materially agent-related -- Drop (`shouldKeep=false`) if clearly unrelated to AI Agent -- Drop low-discussion or low-value question items (`shouldKeep=false`) -- If information is insufficient, default to drop - -## High-Heat Gate(AI 前硬过滤) - -`筛掉占位候选` 节点会在 AI 前做硬性筛选,减少低价值输入: - -- source-level minimum engagement: - - `hacker_news >= 35` - - `github >= 8` - - `reddit >= 25` -- source-level freshness window: - - `hacker_news <= 72h` - - `github <= 168h` - - `reddit/arxiv/hugging_face/product_hunt <= 120h` -- low-value question filtering: - - 求助型单问题 + 短摘要 + 低互动,直接丢弃 -- per-source candidate cap(AI 前限流): - - `hacker_news 6`, `github 6`, `reddit 6`, `arxiv 4`, `hugging_face 4`, `product_hunt 4` -- strict keyword gate for low-discussion sources: - - `arxiv/hugging_face/product_hunt` 必须命中 agent 相关关键词(如 `agent`, `agentic`, `multi-agent`, `tool calling`, `mcp`, `智能体`)才进入 AI - -## HOT 判定(流程内) - -`清洗并映射入库字段` Code 节点会基于**互动量 + 时间新鲜度**计算: - -- `hotScore`:0-100 -- `isHot`:布尔值,用于页面 HOT 标签与排序优先 - -该判定只衡量讨论热度,不评价观点对错或质量。 - -## DB 升级(新增 HOT 字段) - -由于当前仓库默认忽略 `prisma/migrations/*_*` 目录,建议在数据库手动执行一次: - -```sql -ALTER TABLE "signals" -ADD COLUMN IF NOT EXISTS "hotScore" INTEGER NOT NULL DEFAULT 0, -ADD COLUMN IF NOT EXISTS "isHot" BOOLEAN NOT NULL DEFAULT false; - -CREATE INDEX IF NOT EXISTS "idx_signal_hot_score" ON "signals"("hotScore"); -CREATE INDEX IF NOT EXISTS "idx_signal_active_hot_sort" ON "signals"("isActive", "isHot", "hotScore", "publishedAt"); -``` - -## Manual Configuration (No Env) - -This workflow is intentionally configured without `$env` usage. - -- Node `构建 Webhook Payload`: - - set `apiKey` to your real webhook key (replace placeholder string) -- Node `发送到 Signals Webhook`: - - set the target URL to your actual API base (current default is `https://agentpark.fun/api/webhook/signals`) - -## Notes - -- Reddit source uses JSON API with explicit `User-Agent` headers to reduce 403 blocking risk. -- `n8n_test_workflow` cannot trigger schedule-only workflows via API; runtime verification should be done by waiting for scheduled execution or manual run in n8n UI. diff --git a/docs/n8n/historical-workflow-design.md b/docs/n8n/historical-workflow-design.md deleted file mode 100644 index 0080338..0000000 --- a/docs/n8n/historical-workflow-design.md +++ /dev/null @@ -1,177 +0,0 @@ -# n8n 历史数据初始化 Workflow - -## 概述 - -此 workflow 用于一次性收集和初始化 2017-2025 年的 AI 重大事件数据。 - -## Workflow 结构 - -### Node 1: Cron 触发器(手动触发) - -- 节点类型: `Manual Trigger` -- 用途: 开发测试时手动运行 - -### Node 2: 设置年份列表 - -- 节点类型: `Code` -- 用途: 定义要处理的年份列表 - -```javascript -// 返回年份数组 -return [ - { year: 2017 }, - { year: 2018 }, - { year: 2019 }, - { year: 2020 }, - { year: 2021 }, - { year: 2022 }, - { year: 2023 }, - { year: 2024 }, - { year: 2025 }, -]; -``` - -### Node 3: 搜索 Agent(循环每年) - -- 节点类型: `Loop Over Items` -- 用途: 遍历每个年份 - -### Node 4: Web Search - Agent 1 - -- 节点类型: `HTTP Request` -- 方法: POST -- URL: `http://localhost:3000/api/web-search` (或 MCP 端点) -- Headers: - ```json - { - "Content-Type": "application/json" - } - ``` -- Body: - ```json - { - "search_query": "AI breakthrough {{ $json.year }} LLM release transformer model", - "search_recency_filter": "noLimit", - "content_size": "high" - } - ``` - -### Node 5: 筛选 Agent - Agent 2 - -- 节点类型: `Code` -- 用途: 根据权威来源筛选 - -```javascript -const trustedDomains = [ - 'arxiv.org', - 'openai.com', - 'anthropic.com', - 'google.ai', - 'meta.ai', - 'deepmind.com', - 'research.google', -]; - -const items = $input.all(); - -const filtered = items.filter(item => { - const url = item.json.url || ''; - return trustedDomains.some(domain => url.includes(domain)); -}); - -return filtered; -``` - -### Node 6: 格式化 Agent - Agent 3 - -- 节点类型: `Code` -- 用途: 转换为 API 格式 - -```javascript -const items = $input.all(); - -const formatted = items.map(item => { - const publishedDate = item.json.published_date || new Date().toISOString(); - - return { - json: { - title: item.json.title || 'Untitled', - eventDate: new Date(publishedDate).toISOString(), - description: (item.json.description || item.json.snippet || '').substring(0, 500), - imageUrl: item.json.image_url || 'https://images.unsplash.com/photo-1677442136019-21780ecad995?w=800', - sourceUrl: item.json.url, - }, - }; -}); - -return formatted; -``` - -### Node 7: 提交到 API - -- 节点类型: `HTTP Request` -- 方法: POST -- URL: `http://localhost:3000/api/events` -- Headers: - ```json - { - "Content-Type": "application/json", - "X-API-Key": "={{ $env.WEBHOOK_API_KEY }}" - } - ``` -- Body: `={{ $json }}` (发送整个数组) - -### Node 8: 错误处理 - -- 节点类型: `IF` -- 条件: 检查上一个节点的 status code -- On True: 记录成功 -- On False: 发送错误邮件 - -## 环境变量 - -在 n8n 中设置: -- `WEBHOOK_API_KEY`: 你的 API 密钥(从 .env.local 获取) -- `API_ENDPOINT`: `http://localhost:3000/api/events` (开发) 或生产 URL - -## 测试步骤 - -1. 在 n8n UI 中创建此 workflow -2. 手动触发运行 -3. 检查数据库: `pnpm prisma studio` -4. 验证事件已正确创建 - -## 数据质量标准 - -### 标题要求 -- 清晰描述事件 -- 1-200 字符 -- 避免营销术语 - -### 描述要求 -- 客观描述功能和价值 -- 10-500 字符 -- 突出技术亮点 - -### 日期要求 -- ISO 8601 格式 -- 准确的发布日期 - -### 链接要求 -- 必须包含 sourceUrl(权威来源) -- 链接可访问 -- 优先 arxiv.org、openai.com 等 - -## 权威来源列表 - -- 学术论文: arxiv.org -- 官方博客: openai.com, anthropic.com, google.ai, meta.ai -- 研究机构: deepmind.com, research.google -- 新闻媒体: techcrunch.com, theverge.com (需人工审核) - -## 注意事项 - -1. **去重**: workflow 会自动跳过重复的事件(基于 sourceUrl) -2. **图片**: 如果没有图片,使用默认占位图 -3. **错误处理**: 失败的事件会被记录,不会中断整个流程 -4. **数据验证**: API 会验证所有字段,不符合标准的数据会被拒绝 diff --git a/docs/n8n/incremental-workflow-design.md b/docs/n8n/incremental-workflow-design.md deleted file mode 100644 index 55d581b..0000000 --- a/docs/n8n/incremental-workflow-design.md +++ /dev/null @@ -1,135 +0,0 @@ -# n8n 增量更新 Workflow - -## 概述 - -此 workflow 每周一自动运行,收集最近 7 天的新 AI 事件。 - -## Workflow 结构 - -### Node 1: Cron 触发器 - -- 节点类型: `Cron` -- 表达式: `0 9 * * 1` (每周一早上 9:00) -- 时区: Asia/Shanghai - -### Node 2: Web Search - Agent 1 - -- 节点类型: `HTTP Request` -- URL: `http://localhost:3000/api/web-search` (或 MCP 端点) -- Body: - ```json - { - "search_query": "AI news LLM release model launch this week", - "search_recency_filter": "oneWeek" - } - ``` - -### Node 3: 筛选 Agent - Agent 2 - -- 节点类型: `Code` -- 用途: 筛选 + 去重(查询数据库避免重复) - -```javascript -const trustedDomains = [ - 'arxiv.org', - 'openai.com', - 'anthropic.com', - 'google.ai', - 'meta.ai', - 'deepmind.com', -]; - -// 过滤权威来源 -const items = $input.all(); -const filtered = items.filter(item => { - const url = item.json.url || ''; - return trustedDomains.some(domain => url.includes(domain)); -}); - -// TODO: 添加数据库查询去重 -// 这里可以调用 GET /api/events 检查 sourceUrl 是否已存在 - -return filtered; -``` - -### Node 4: 格式化 Agent - Agent 3 - -- 节点类型: `Code` -- 代码: 同历史 workflow - -### Node 5: 提交到 API - -- 节点类型: `HTTP Request` -- 配置: 同历史 workflow - -### Node 6: 发送通知邮件 - -- 节点类型: `Send Email` -- 条件: 仅在创建新事件时发送 -- 内容: - ``` - 主题: AI Timeline - 新事件已添加 - - 本次更新添加了 {{ $json.created }} 个新事件。 - - 查看: https://your-domain.com/timeline - ``` - -### Node 7: 错误处理 - -- 节点类型: `Error Trigger` -- 动作: 发送错误邮件到管理员 - -## 测试 - -1. 修改 Cron 为手动触发进行测试 -2. 验证只有新事件被添加 -3. 检查邮件通知是否正常发送 -4. 确认错误处理工作正常 - -## 数据质量保证 - -### 自动筛选规则 - -1. **来源可信**: 仅来自权威域名 -2. **时效性**: 仅最近 7 天的内容 -3. **去重**: 基于 sourceUrl 自动去重 - -### 人工审核流程 - -建议在自动导入后进行人工审核: -1. 检查标题是否准确 -2. 验证描述是否客观 -3. 确认图片是否合适 -4. 测试链接是否可访问 - -## 邮件通知配置 - -### 成功通知 - -当有新事件添加时发送: -- 收件人: 内容团队 -- 主题: "AI Timeline - {{ count }} 个新事件已添加" -- 内容: 包含事件列表和链接 - -### 错误通知 - -当 workflow 失败时发送: -- 收件人: 技术团队 -- 主题: "⚠️ AI Timeline Workflow 失败" -- 内容: 错误详情和日志 - -## 监控指标 - -建议监控以下指标: -- 每周添加的事件数量 -- workflow 执行时间 -- 失败率和错误类型 -- 去重率 - -## 优化建议 - -1. **AI 辅助筛选**: 使用 AI 模型评估新闻相关性 -2. **多源聚合**: 整合多个搜索 API -3. **智能去重**: 基于标题相似度去重 -4. **自动翻译**: 自动生成英文翻译(titleEn, descriptionEn) diff --git a/docs/n8n/project-tag-reset-workflow.json b/docs/n8n/project-tag-reset-workflow.json deleted file mode 100644 index 07f8e12..0000000 --- a/docs/n8n/project-tag-reset-workflow.json +++ /dev/null @@ -1,435 +0,0 @@ -{ - "name": "Project Tag Reset - Multi AI Classifier", - "nodes": [ - { - "parameters": {}, - "id": "manual-trigger", - "name": "手动触发", - "type": "n8n-nodes-base.manualTrigger", - "typeVersion": 1, - "position": [ - 0, - 0 - ] - }, - { - "parameters": { - "url": "https://www.agentpark.fun/api/tags", - "options": {} - }, - "id": "fetch-tags", - "name": "获取标签池", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.4, - "position": [ - 220, - 0 - ] - }, - { - "parameters": { - "url": "https://www.agentpark.fun/api/projects", - "sendQuery": true, - "specifyQuery": "keypair", - "queryParameters": { - "parameters": [ - { - "name": "limit", - "value": "100" - }, - { - "name": "page", - "value": "1" - }, - { - "name": "sort", - "value": "latest" - } - ] - }, - "options": { - "pagination": { - "pagination": { - "paginationMode": "updateAParameterInEachRequest", - "parameters": { - "parameters": [ - { - "type": "qs", - "name": "page", - "value": "={{($response.body.pagination.page ?? $response.body.pagination.currentPage ?? 1) + 1}}" - } - ] - }, - "paginationCompleteWhen": "other", - "completeExpression": "={{!$response.body.pagination || (($response.body.pagination.page ?? $response.body.pagination.currentPage ?? 1) >= ($response.body.pagination.totalPages ?? 1))}}", - "limitPagesFetched": true, - "maxRequests": 20, - "requestInterval": 0 - } - } - } - }, - "id": "fetch-projects", - "name": "获取项目列表", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.4, - "position": [ - 440, - 0 - ] - }, - { - "parameters": { - "jsCode": "const tagsResponse = $('获取标签池').first().json;\nconst project = $input.first().json.projects;\n\nif (!tagsResponse?.success || !Array.isArray(tagsResponse?.tags)) {\n throw new Error('Failed to fetch tags from /api/tags');\n}\n\nif (!project || typeof project !== 'object' || !project.slug) {\n throw new Error('Invalid project item after split');\n}\n\nconst allowedCategories = [\n 'FIXED_PROJECT_TYPE',\n 'TECH_STACK',\n 'AI_PARADIGM',\n 'PRODUCT_FORM',\n 'DOMAIN_SCENARIO',\n];\n\nconst tagPools = Object.fromEntries(\n allowedCategories.map((category) => [\n category,\n tagsResponse.tags\n .filter((tag) => tag.category === category)\n .map((tag) => ({\n id: tag.id,\n slug: tag.slug,\n name: tag.name,\n nameEn: tag.nameEn || '',\n })),\n ])\n);\n\nreturn [\n {\n json: {\n projectSlug: project.slug,\n projectName: project.name,\n projectNameEn: project.nameEn || '',\n projectDescription: project.description,\n projectDescriptionEn: project.descriptionEn || '',\n currentTags: Array.isArray(project.tags)\n ? project.tags.map((tag) => ({\n slug: tag.slug,\n name: tag.name,\n nameEn: tag.nameEn || '',\n }))\n : [],\n tagPools,\n },\n },\n];" - }, - "id": "prepare-classification-items", - "name": "准备分类输入", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 660, - 0 - ] - }, - { - "parameters": { - "model": "gpt-4o-mini", - "messages": { - "values": [ - { - "role": "system", - "content": "You classify projects into FIXED_PROJECT_TYPE. Return STRICT JSON only." - }, - { - "role": "user", - "content": "=Project:\\n- slug: {{$json.projectSlug}}\\n- name: {{$json.projectName}}\\n- nameEn: {{$json.projectNameEn}}\\n- description: {{$json.projectDescription}}\\n- descriptionEn: {{$json.projectDescriptionEn}}\\n- currentTags: {{JSON.stringify($json.currentTags)}}\\n\\nCandidate pool (FIXED_PROJECT_TYPE):\\n{{JSON.stringify($json.tagPools.FIXED_PROJECT_TYPE)}}\\n\\nTask:\\n- Choose exactly ONE best slug from candidate pool.\\n\\nOutput strict JSON only:\\n{\\n \"selected\": [\"one-slug\"]\\n}" - } - ] - }, - "options": { - "temperature": 0.1 - } - }, - "id": "ai-fixed-project-type", - "name": "AI 固定项目分类", - "type": "@n8n/n8n-nodes-langchain.openAi", - "typeVersion": 1.8, - "position": [ - 880, - 0 - ] - }, - { - "parameters": { - "model": "gpt-4o-mini", - "messages": { - "values": [ - { - "role": "system", - "content": "You classify DOMAIN_SCENARIO tags. Return STRICT JSON only." - }, - { - "role": "user", - "content": "=Project:\\n- slug: {{$json.projectSlug}}\\n- name: {{$json.projectName}}\\n- description: {{$json.projectDescription}}\\n- currentTags: {{JSON.stringify($json.currentTags)}}\\n\\nCandidate pool (DOMAIN_SCENARIO):\\n{{JSON.stringify($json.tagPools.DOMAIN_SCENARIO)}}\\n\\nTask:\\n- Choose 1 to 3 slugs from candidate pool.\\n- Keep only the most representative domains for this project.\\n\\nOutput strict JSON only:\\n{\\n \"selected\": [\"slug1\", \"slug2\"]\\n}" - } - ] - }, - "options": { - "temperature": 0.1 - } - }, - "id": "ai-domain-scenario", - "name": "AI 领域场景分类", - "type": "@n8n/n8n-nodes-langchain.openAi", - "typeVersion": 1.8, - "position": [ - 1100, - 0 - ] - }, - { - "parameters": { - "model": "gpt-4o-mini", - "messages": { - "values": [ - { - "role": "system", - "content": "You classify PRODUCT_FORM tags. Return STRICT JSON only." - }, - { - "role": "user", - "content": "=Project:\\n- slug: {{$json.projectSlug}}\\n- name: {{$json.projectName}}\\n- description: {{$json.projectDescription}}\\n- currentTags: {{JSON.stringify($json.currentTags)}}\\n\\nCandidate pool (PRODUCT_FORM):\\n{{JSON.stringify($json.tagPools.PRODUCT_FORM)}}\\n\\nTask:\\n- Choose 1 to 3 slugs from candidate pool that best represent product form.\\n\\nOutput strict JSON only:\\n{\\n \"selected\": [\"slug1\", \"slug2\"]\\n}" - } - ] - }, - "options": { - "temperature": 0.1 - } - }, - "id": "ai-product-form", - "name": "AI 产品形态分类", - "type": "@n8n/n8n-nodes-langchain.openAi", - "typeVersion": 1.8, - "position": [ - 1320, - 0 - ] - }, - { - "parameters": { - "model": "gpt-4o-mini", - "messages": { - "values": [ - { - "role": "system", - "content": "You classify TECH_STACK tags. Return STRICT JSON only." - }, - { - "role": "user", - "content": "=Project:\\n- slug: {{$json.projectSlug}}\\n- name: {{$json.projectName}}\\n- description: {{$json.projectDescription}}\\n- currentTags: {{JSON.stringify($json.currentTags)}}\\n\\nCandidate pool (TECH_STACK):\\n{{JSON.stringify($json.tagPools.TECH_STACK)}}\\n\\nTask:\\n- Choose 1 to 8 slugs from candidate pool for the main technologies used by this project.\\n\\nOutput strict JSON only:\\n{\\n \"selected\": [\"slug1\", \"slug2\"]\\n}" - } - ] - }, - "options": { - "temperature": 0.1 - } - }, - "id": "ai-tech-stack", - "name": "AI 技术栈分类", - "type": "@n8n/n8n-nodes-langchain.openAi", - "typeVersion": 1.8, - "position": [ - 1540, - 0 - ] - }, - { - "parameters": { - "model": "gpt-4o-mini", - "messages": { - "values": [ - { - "role": "system", - "content": "You classify AI_PARADIGM tags. Return STRICT JSON only." - }, - { - "role": "user", - "content": "=Project:\\n- slug: {{$json.projectSlug}}\\n- name: {{$json.projectName}}\\n- description: {{$json.projectDescription}}\\n- currentTags: {{JSON.stringify($json.currentTags)}}\\n\\nCandidate pool (AI_PARADIGM):\\n{{JSON.stringify($json.tagPools.AI_PARADIGM)}}\\n\\nTask:\\n- Choose 0 to 5 slugs from candidate pool for AI paradigm.\\n\\nOutput strict JSON only:\\n{\\n \"selected\": [\"slug1\", \"slug2\"]\\n}" - } - ] - }, - "options": { - "temperature": 0.1 - } - }, - "id": "ai-ai-paradigm", - "name": "AI 技术范式分类", - "type": "@n8n/n8n-nodes-langchain.openAi", - "typeVersion": 1.8, - "position": [ - 1760, - 0 - ] - }, - { - "parameters": { - "jsCode": "function parseNodeJson(nodeJson) {\n const content = nodeJson?.message?.content || nodeJson?.text || nodeJson?.response || nodeJson;\n\n if (typeof content === 'string') {\n const trimmed = content.trim();\n try {\n return JSON.parse(trimmed);\n } catch {\n const jsonMatch = trimmed.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n return JSON.parse(jsonMatch[0]);\n }\n return { selected: [] };\n }\n }\n\n if (typeof content === 'object' && content !== null) {\n return content;\n }\n\n return { selected: [] };\n}\n\nfunction normalizeSelected(parsed, pool, minCount, maxCount) {\n const poolSlugs = pool.map((tag) => String(tag.slug).toLowerCase());\n const allowed = new Set(poolSlugs);\n const selectedRaw = Array.isArray(parsed?.selected) ? parsed.selected : [];\n const selected = [];\n\n for (const slug of selectedRaw) {\n const normalizedSlug = String(slug || '').trim().toLowerCase();\n if (!normalizedSlug || !allowed.has(normalizedSlug) || selected.includes(normalizedSlug)) {\n continue;\n }\n selected.push(normalizedSlug);\n if (selected.length >= maxCount) {\n break;\n }\n }\n\n if (selected.length < minCount) {\n return poolSlugs.slice(0, minCount);\n }\n return selected;\n}\n\nconst preparedItems = $items('准备分类输入', 0);\nconst fixedItems = $items('AI 固定项目分类', 0);\nconst domainItems = $items('AI 领域场景分类', 0);\nconst productItems = $items('AI 产品形态分类', 0);\nconst techItems = $items('AI 技术栈分类', 0);\nconst paradigmItems = $items('AI 技术范式分类', 0);\n\nif (\n preparedItems.length !== fixedItems.length ||\n preparedItems.length !== domainItems.length ||\n preparedItems.length !== productItems.length ||\n preparedItems.length !== techItems.length ||\n preparedItems.length !== paradigmItems.length\n) {\n throw new Error('AI node output item count mismatch');\n}\n\nconst output = [];\nfor (let i = 0; i < preparedItems.length; i++) {\n const base = preparedItems[i].json;\n const pools = base.tagPools;\n\n const fixed = normalizeSelected(\n parseNodeJson(fixedItems[i].json),\n pools.FIXED_PROJECT_TYPE,\n 1,\n 1\n );\n const domains = normalizeSelected(\n parseNodeJson(domainItems[i].json),\n pools.DOMAIN_SCENARIO,\n 1,\n 3\n );\n const productForms = normalizeSelected(\n parseNodeJson(productItems[i].json),\n pools.PRODUCT_FORM,\n 1,\n 3\n );\n const techStack = normalizeSelected(\n parseNodeJson(techItems[i].json),\n pools.TECH_STACK,\n 1,\n 8\n );\n const paradigms = normalizeSelected(\n parseNodeJson(paradigmItems[i].json),\n pools.AI_PARADIGM,\n 0,\n 5\n );\n\n output.push({\n json: {\n projectSlug: base.projectSlug,\n selectedTagSlugsByCategory: {\n FIXED_PROJECT_TYPE: fixed,\n TECH_STACK: techStack,\n AI_PARADIGM: paradigms,\n PRODUCT_FORM: productForms,\n DOMAIN_SCENARIO: domains,\n },\n },\n });\n}\n\nreturn output;" - }, - "id": "build-reset-payload", - "name": "组装重置载荷", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 1980, - 0 - ] - }, - { - "parameters": { - "method": "POST", - "url": "https://www.agentpark.fun/api/tags/reset-projects", - "sendBody": true, - "specifyBody": "json", - "jsonBody": "={\n \"apiKey\": \"{{$env.WEBHOOK_API_KEY}}\",\n \"dryRun\": false,\n \"replaceAllCategories\": true,\n \"projects\": [\n {\n \"projectSlug\": \"{{$json.projectSlug}}\",\n \"selectedTagSlugsByCategory\": {{JSON.stringify($json.selectedTagSlugsByCategory)}}\n }\n ]\n}", - "options": {} - }, - "id": "reset-project-tags", - "name": "调用标签重置接口", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.4, - "position": [ - 2200, - 0 - ] - }, - { - "parameters": { - "jsCode": "const results = $input.all().map((item) => item.json);\nconst summary = {\n totalRequests: results.length,\n successRequests: results.filter((r) => r.success === true).length,\n failedRequests: results.filter((r) => r.success !== true).length,\n timestamp: new Date().toISOString(),\n};\n\nreturn [{ json: { summary, results } }];" - }, - "id": "summarize-results", - "name": "汇总执行结果", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [ - 2420, - 0 - ] - }, - { - "parameters": { - "operation": "splitOutItems", - "fieldToSplitOut": "projects", - "include": "noOtherFields", - "options": {} - }, - "id": "split-projects", - "name": "拆分项目列表", - "type": "n8n-nodes-base.itemLists", - "typeVersion": 3.1, - "position": [ - 560, - 0 - ] - } - ], - "connections": { - "手动触发": { - "main": [ - [ - { - "node": "获取标签池", - "type": "main", - "index": 0 - } - ] - ] - }, - "获取标签池": { - "main": [ - [ - { - "node": "获取项目列表", - "type": "main", - "index": 0 - } - ] - ] - }, - "获取项目列表": { - "main": [ - [ - { - "node": "拆分项目列表", - "type": "main", - "index": 0 - } - ] - ] - }, - "准备分类输入": { - "main": [ - [ - { - "node": "AI 固定项目分类", - "type": "main", - "index": 0 - } - ] - ] - }, - "AI 固定项目分类": { - "main": [ - [ - { - "node": "AI 领域场景分类", - "type": "main", - "index": 0 - } - ] - ] - }, - "AI 领域场景分类": { - "main": [ - [ - { - "node": "AI 产品形态分类", - "type": "main", - "index": 0 - } - ] - ] - }, - "AI 产品形态分类": { - "main": [ - [ - { - "node": "AI 技术栈分类", - "type": "main", - "index": 0 - } - ] - ] - }, - "AI 技术栈分类": { - "main": [ - [ - { - "node": "AI 技术范式分类", - "type": "main", - "index": 0 - } - ] - ] - }, - "AI 技术范式分类": { - "main": [ - [ - { - "node": "组装重置载荷", - "type": "main", - "index": 0 - } - ] - ] - }, - "组装重置载荷": { - "main": [ - [ - { - "node": "调用标签重置接口", - "type": "main", - "index": 0 - } - ] - ] - }, - "调用标签重置接口": { - "main": [ - [ - { - "node": "汇总执行结果", - "type": "main", - "index": 0 - } - ] - ] - }, - "拆分项目列表": { - "main": [ - [ - { - "node": "准备分类输入", - "type": "main", - "index": 0 - } - ] - ] - } - }, - "settings": { - "executionOrder": "v1" - }, - "meta": { - "templateCredsSetupCompleted": true - } -} diff --git a/docs/n8n/project-tag-reset-workflow.md b/docs/n8n/project-tag-reset-workflow.md deleted file mode 100644 index 0b53a1e..0000000 --- a/docs/n8n/project-tag-reset-workflow.md +++ /dev/null @@ -1,52 +0,0 @@ -# Project Tag Reset Workflow - -## Goal - -Reset every project's tags according to the new taxonomy, using **one dedicated AI node per tag category**: - -- `FIXED_PROJECT_TYPE` -- `DOMAIN_SCENARIO` -- `PRODUCT_FORM` -- `TECH_STACK` -- `AI_PARADIGM` - -Each AI node must select only from its own category pool. - -## Files - -- Workflow JSON: `docs/n8n/project-tag-reset-workflow.json` -- API endpoint used by workflow: `POST /api/tags/reset-projects` - -## Required Environment Variables in n8n - -- `WEBHOOK_API_KEY`: same key configured on Next.js server - -## Fixed Base URL - -- All workflow API URLs are hardcoded to `https://www.agentpark.fun` - -## Execution Flow - -1. `手动触发` -2. `获取标签池` (`HTTP GET /api/tags`) 读取标签池 -3. `获取项目列表` (`HTTP GET /api/projects`) 使用 Query 参数(`limit=100,page=1,sort=latest`)+ HTTP 节点内置分页拉取全部项目 -4. `拆分项目列表` 将每页 `projects[]` 拆分为“单项目一条记录” -5. `准备分类输入` 仅做轻量字段整理(不再负责分页/HTTP 请求) -6. 五个独立 AI 节点分别分类: - - `AI 固定项目分类` - - `AI 领域场景分类` - - `AI 产品形态分类` - - `AI 技术栈分类` - - `AI 技术范式分类` -7. `组装重置载荷` 归一化 AI 输出并生成 `selectedTagSlugsByCategory` -8. `调用标签重置接口` (`HTTP POST /api/tags/reset-projects`) 回写标签 -9. `汇总执行结果` 输出成功/失败统计 - -## Notes - -- Workflow uses HTTP node built-in pagination for `/api/projects` (`limit=100`) to process full project volume. -- `page` 不再写死在 URL 上,避免出现重复请求同一页导致的 identical response 停止问题。 -- Pagination and project fan-out no longer rely on complex Code-node network logic. -- Workflow no longer depends on `SITE_BASE_URL`; base URL is fixed to `https://www.agentpark.fun`. -- Endpoint supports `dryRun`. You can set `"dryRun": true` first in the request node for safe validation. -- Endpoint performs category validation and rejects cross-category slug usage. diff --git a/docs/n8n/tag-janitor-workflow.json b/docs/n8n/tag-janitor-workflow.json deleted file mode 100644 index b58e088..0000000 --- a/docs/n8n/tag-janitor-workflow.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "name": "Tag Janitor - Daily Cleanup", - "nodes": [ - { - "parameters": { - "rule": { - "interval": [ - { - "triggerAtHour": 3 - } - ] - } - }, - "id": "schedule", - "name": "Daily 3AM UTC", - "type": "n8n-nodes-base.scheduleTrigger", - "typeVersion": 1.2, - "position": [0, 0] - }, - { - "parameters": { - "url": "={{$env.SITE_BASE_URL}}/api/tags", - "options": {} - }, - "id": "fetch-tags", - "name": "Fetch Tags", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.2, - "position": [220, 0] - }, - { - "parameters": { - "jsCode": "const response = $input.first().json;\nif (!response.success) {\n throw new Error('Failed to fetch tags: ' + JSON.stringify(response));\n}\n\nconst tags = response.tags;\nconst formatted = tags.map(t => ({\n id: t.id,\n name: t.name,\n nameEn: t.nameEn || '',\n projectCount: t._count?.projects || 0\n}));\n\n// Sort by project count descending\nformatted.sort((a, b) => b.projectCount - a.projectCount);\n\nreturn [{ json: { tags: formatted, total: formatted.length } }];" - }, - "id": "preprocess", - "name": "Preprocess Tags", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [440, 0] - }, - { - "parameters": { - "model": "gpt-4o", - "messages": { - "values": [ - { - "role": "system", - "content": "You are a tag management expert. Analyze tags and identify semantic duplicates for merging and missing English names for completion. Output STRICT JSON only." - }, - { - "role": "user", - "content": "Analyze these tags and identify:\n1. Semantic duplicates to merge (e.g., \"机器学习\" and \"ML\" → keep higher projectCount)\n2. Tags missing nameEn that need English names\n\nTags ({{$json.total}} total):\n{{JSON.stringify($json.tags)}}\n\nOutput STRICT JSON (no markdown):\n{\n \"merges\": [\n {\n \"target\": { \"name\": \"保留的标签\", \"nameEn\": \"Canonical Name\" },\n \"sourceTagIds\": [\"id1\", \"id2\"]\n }\n ],\n \"updates\": [\n { \"tagId\": \"id\", \"nameEn\": \"English Name\" }\n ]\n}\n\nRules:\n- Keep tag with higher projectCount as merge target\n- target can use { \"id\": \"existing\" } to keep existing tag\n- Only include tags needing action (empty arrays if none)\n- nameEn must be proper English, not pinyin" - } - ] - }, - "options": { - "temperature": 0.1 - } - }, - "id": "ai-analyze", - "name": "AI Analyze Tags", - "type": "@n8n/n8n-nodes-langchain.openAi", - "typeVersion": 1.8, - "position": [660, 0] - }, - { - "parameters": { - "jsCode": "const response = $input.first().json;\nlet plan;\n\ntry {\n const content = response.message?.content || response.text || response;\n plan = typeof content === 'string' ? JSON.parse(content) : content;\n} catch (e) {\n throw new Error('Failed to parse AI response: ' + e.message);\n}\n\nif (!Array.isArray(plan.merges)) plan.merges = [];\nif (!Array.isArray(plan.updates)) plan.updates = [];\n\nfor (const merge of plan.merges) {\n if (merge.target.id && merge.sourceTagIds.includes(merge.target.id)) {\n throw new Error('Self-merge detected: ' + merge.target.id);\n }\n}\n\nif (plan.merges.length === 0 && plan.updates.length === 0) {\n return [{ json: { skipped: true, reason: 'No changes needed' } }];\n}\n\nreturn [{ json: plan }];" - }, - "id": "validate-json", - "name": "Validate JSON", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [880, 0] - }, - { - "parameters": { - "conditions": { - "boolean": [ - { - "value1": "={{$json.skipped}}", - "value2": true - } - ] - } - }, - "id": "check-skip", - "name": "Check Skip", - "type": "n8n-nodes-base.if", - "typeVersion": 2, - "position": [1100, 0] - }, - { - "parameters": { - "method": "POST", - "url": "={{$env.SITE_BASE_URL}}/api/tags/maintenance", - "sendBody": true, - "specifyBody": "json", - "jsonBody": "={\n \"apiKey\": \"{{$env.WEBHOOK_API_KEY}}\",\n \"updates\": {{JSON.stringify($json.updates)}},\n \"merges\": {{JSON.stringify($json.merges)}}\n}", - "options": {} - }, - "id": "execute-maintenance", - "name": "Execute Maintenance", - "type": "n8n-nodes-base.httpRequest", - "typeVersion": 4.2, - "position": [1320, -100] - }, - { - "parameters": { - "jsCode": "const maintenanceResult = $('Execute Maintenance').first()?.json || {};\nconst skipped = $('Check Skip').first()?.json?.skipped;\n\nif (skipped) {\n return [{ json: {\n status: 'skipped',\n message: 'No changes needed',\n timestamp: new Date().toISOString()\n }}];\n}\n\nconst result = maintenanceResult.result || {};\n\nreturn [{ json: {\n status: maintenanceResult.success ? 'success' : 'failed',\n updatedCount: result.updatedCount || 0,\n mergedCount: result.mergedCount || 0,\n deletedTagCount: result.deletedTagCount || 0,\n timestamp: new Date().toISOString(),\n error: maintenanceResult.error || null\n}}];" - }, - "id": "summarize", - "name": "Summarize Results", - "type": "n8n-nodes-base.code", - "typeVersion": 2, - "position": [1540, 0] - } - ], - "connections": { - "Daily 3AM UTC": { - "main": [[{ "node": "Fetch Tags", "type": "main", "index": 0 }]] - }, - "Fetch Tags": { - "main": [[{ "node": "Preprocess Tags", "type": "main", "index": 0 }]] - }, - "Preprocess Tags": { - "main": [[{ "node": "AI Analyze Tags", "type": "main", "index": 0 }]] - }, - "AI Analyze Tags": { - "main": [[{ "node": "Validate JSON", "type": "main", "index": 0 }]] - }, - "Validate JSON": { - "main": [[{ "node": "Check Skip", "type": "main", "index": 0 }]] - }, - "Check Skip": { - "main": [ - [{ "node": "Execute Maintenance", "type": "main", "index": 0 }], - [{ "node": "Summarize Results", "type": "main", "index": 0 }] - ] - }, - "Execute Maintenance": { - "main": [[{ "node": "Summarize Results", "type": "main", "index": 0 }]] - } - }, - "settings": { - "executionOrder": "v1" - }, - "meta": { - "templateCredsSetupCompleted": true - } -}