diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..09737af --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "env": { + "HTTP_PROXY": "http://proxy3.bj.petrochina:8080", + "HTTPS_PROXY": "http://proxy3.bj.petrochina:8080" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a68e97d --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# Dependencies +node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# Testing +/coverage + +# Next.js +/.next/ +/out/ + +# Production +/build + +# Misc +.DS_Store +*.pem + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Local env files +.env*.local +.env +.env.development.local +.env.test.local +.env.production.local + +# Vercel +.vercel + +# TypeScript +*.tsbuildinfo +next-env.d.ts + +# IDE & Editors +.vscode/ +.idea/ +*.swp +*.swo +*~ +.project +.classpath +.settings/ + +# OS +.DS_Store +Thumbs.db +desktop.ini + +# Logs +logs/ +*.log + +# Temporary files +tmp/ +temp/ +.cache/ + +# Package manager lock files (optional - uncomment if needed) +# package-lock.json +# yarn.lock +# pnpm-lock.yaml + +# Database +*.db +*.sqlite +*.sqlite3 + +# Redis dump +dump.rdb + +# Session files +sessions/ +*.session diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index 8819ea1..64ec158 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,50 +1,200 @@ -# [PROJECT_NAME] 项目章程 - + + +# Agent Park v2 项目章程 ## 核心原则 -### [PRINCIPLE_1_NAME] - -[PRINCIPLE_1_DESCRIPTION] - +### I. TypeScript 严格模式与类型安全 -### [PRINCIPLE_2_NAME] - -[PRINCIPLE_2_DESCRIPTION] - +TypeScript 严格模式是强制要求。所有代码必须: -### [PRINCIPLE_3_NAME] - -[PRINCIPLE_3_DESCRIPTION] - +- 在 tsconfig.json 中启用 `strict: true` 和所有严格选项 +- 所有函数必须显式声明参数和返回值类型(禁止隐式 any) +- 禁止使用 `@ts-ignore` 和 `@ts-nocheck`(除非用于临时迁移, 需要工单跟踪) +- 外部数据必须经过运行时验证(如使用 Zod、Yup 等架构验证库) +- 所有 API 响应必须定义明确的类型接口 -### [PRINCIPLE_4_NAME] - -[PRINCIPLE_4_DESCRIPTION] - +**理由**: TypeScript 严格模式在编译时捕获潜在错误, 减少运行时问题, 提高代码可维护性和开发体验。类型安全使重构更安全, 代码更自文档化。 -### [PRINCIPLE_5_NAME] - -[PRINCIPLE_5_DESCRIPTION] - +### II. 组件优先架构 -## [SECTION_2_NAME] - +采用 Next.js App Router 架构模式, 遵循以下规则: -[SECTION_2_CONTENT] - +- 优先使用 Server Components 而非 Client Components +- 仅在需要交互性(状态、事件处理、浏览器 API)时使用 'use client' 指令 +- 组件必须保持单一职责和可复用性 +- 布局组件必须使用嵌套布局模式充分利用 Next.js 特性 +- 路由处理程序(Route Handlers)用于 API 端点, 遵循 RESTful 原则 +- 数据获取优先使用 Server Actions 或 Server Components 内的异步函数 -## [SECTION_3_NAME] - +**理由**: Server Components 提升性能、减少客户端 JavaScript 体积、改善 SEO。单一职责组件提高可维护性和可测试性。 -[SECTION_3_CONTENT] - +### III. 测试驱动开发(不可协商) + +TDD 是强制要求。测试必须在功能实现之前编写: + +- 红-绿-重构循环必须严格遵循 +- 单元测试覆盖率必须达到 80% 以上 +- 关键业务逻辑必须有集成测试 +- UI 组件必须有可视化测试(如 Playwright 或 Storybook) +- 所有测试必须在 PR 合并前通过 +- 测试代码质量与生产代码同等重要 + +**理由**: TDD 确保代码可测试性、捕获早期错误、作为活文档、提供重构安全网。高测试覆盖率降低生产环境 Bug 风险。 + +### IV. 性能优先 + +性能是核心质量指标, 必须满足: + +- 首次内容绘制(FCP) < 1.8s +- 最大内容绘制(LCP) < 2.5s +- 首次输入延迟(FID) < 100ms +- 累积布局偏移(CLS) < 0.1 +- 首次字节时间(TTFB) < 800ms +- 图片必须使用 Next.js Image 组件(自动优化、懒加载) +- 启用增量静态再生(ISR)或静态生成(SSG)而非服务端渲染(SSR)(如适用) +- 客户端导航使用 Link 组件 +- 禁止全量导入大型库(如 lodash), 使用按需导入 +- 打包体积: 初始 JavaScript < 200KB gzipped + +**理由**: 性能直接影响用户体验、转化率和 SEO。Google Core Web Vitals 是搜索排名的重要因素。快速加载减少跳出率。 + +### V. 用户体验一致性 + +跨整个应用必须保持一致的用户体验: + +- 所有页面必须响应式设计(移动优先) +- 使用统一的设计系统(组件库、颜色、排版、间距) +- 加载状态必须提供视觉反馈(skeleton、spinner 或进度条) +- 错误处理必须提供用户友好的消息和恢复操作 +- 表单必须有清晰的标签、验证提示和错误消息 +- 所有交互元素必须有可访问性支持(ARIA 标签、键盘导航) +- 深色模式支持(如适用) +- 页面转换必须平滑(使用 View Transitions 或类似技术) + +**理由**: 一致性降低用户学习曲线、提高信任度、改善可访问性。良好的 UX 提高参与度和用户满意度。 + +### VI. 代码质量与可维护性 + +代码必须遵循最佳实践和编码标准: + +- 使用 ESLint 和 Prettier 进行代码检查和格式化 +- 遵循 Airbnb 或 Standard TypeScript 风格指南 +- 函数复杂度必须控制在合理范围(圈复杂度 < 10) +- 文件大小限制: < 300 行代码(可读性) +- 导入路径使用别名(@/components/...)而非相对路径 +- 环境变量使用 .env 文件, 在 .env.example 中记录所有变量 +- 关键功能必须包含 JSDoc 注释 +- 禁止在生产代码中使用 console.log(使用结构化日志库如 Pino) +- 依赖项必须定期更新(每月审查安全和更新) + +**理由**: 一致的代码风格改善团队协作、降低认知负荷。清晰的代码减少维护成本、加速新开发者上手。 + +## 技术标准 + +### 技术栈要求 + +- **框架**: Next.js 14+ (App Router) +- **语言**: TypeScript 5.0+ (严格模式) +- **样式**: Tailwind CSS 或 CSS Modules +- **状态管理**: React Context API 或 Zustand(如需要) +- **表单**: React Hook Form + Zod 验证 +- **数据库**: PostgreSQL 或 MongoDB(基于项目需求) +- **ORM**: Prisma 或 Mongoose +- **认证**: NextAuth.js 或 Clerk +- **API**: RESTful (Route Handlers) 或 tRPC +- **测试**: Vitest/Jest + React Testing Library + Playwright +- **部署**: Vercel(推荐)或 Docker + +### 框架版本控制 + +- Next.js 主版本升级需要评估和迁移计划 +- 依赖项版本锁定(使用 package-lock.json 或 yarn.lock) +- 破坏性变更需要文档和迁移指南 + +## 开发工作流程 + +### 代码审查要求 + +所有代码必须经过同行评审: + +- PR 必须包含清晰的描述和链接到相关议题/规范 +- 至少一位团队成员批准才能合并 +- 所有 CI 检查必须通过(测试、类型检查、Lint) +- PR 大小限制: < 400 行变更(大变更需要拆分) +- 审查者必须检查: 类型安全、测试覆盖、性能影响、可访问性 + +### 质量门禁 + +代码合并前必须满足: + +- [ ] 所有测试通过(单元、集成、E2E) +- [ ] TypeScript 编译无错误 +- [ ] ESLint 无警告 +- [ ] 性能预算未超限 +- [ ] 可访问性检查通过(如 axe-core) +- [ ] 安全扫描无高危漏洞(如 Dependabot) + +### 分支策略 + +- `main`: 生产就绪代码 +- `develop`: 开发集成分支 +- `feature/*`: 功能分支(从 develop 分支) +- `fix/*`: Bug 修复分支(从 develop 分支) +- 使用语义化提交消息(Conventional Commits) ## 治理 - -[GOVERNANCE_RULES] - +本章程优先于所有其他实践和规范。 -**版本**: [CONSTITUTION_VERSION] | **批准日期**: [RATIFICATION_DATE] | **最后修正**: [LAST_AMENDED_DATE] - +### 修正流程 + +1. 提出修正: 创建议题描述需要修改的原则及原因 +2. 团队审查: 讨论影响、权衡和替代方案 +3. 批准: 需要多数团队核心成员同意 +4. 文档化: 更新章程版本号和修正日期 +5. 迁移计划: 为破坏性变更提供迁移指南 + +### 合规审查 + +- 每个 PR 必须符合章程原则 +- 每次功能规划必须检查章程兼容性 +- 每季度审查章程并更新(如需要) +- 违反章程需要明确理由和记录(在复杂度跟踪中) + +### 版本控制 + +- **MAJOR**: 向后不兼容的原则删除或重新定义 +- **MINOR**: 新原则添加或实质性扩展 +- **PATCH**: 澄清、措辞、拼写错误修复 + +**版本**: 1.0.0 | **批准日期**: 2025-12-25 | **最后修正**: 2025-12-25 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b048363 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +1\.开发过程中如果遇到api不确定的情况 要用context7 mcp +2.如果需要查看页面效果或者修复BUG 要用chrome dev mcp +3\.如果要查看数据库要使用dbhub mcp +4\.如果要确认shadcn ui组件 要用 shadcn mcp + diff --git a/specs/001-ai-project-navigator/checklists/requirements.md b/specs/001-ai-project-navigator/checklists/requirements.md new file mode 100644 index 0000000..2efccb0 --- /dev/null +++ b/specs/001-ai-project-navigator/checklists/requirements.md @@ -0,0 +1,36 @@ +# 规范质量检查清单: Agent Park - AI项目导航网站 + +**目的**: 在继续规划之前验证规范的完整性和质量 +**创建时间**: 2025-12-25 +**功能**: [spec.md](../spec.md) + +## 内容质量 + +- [x] 无实现细节(语言、框架、API) +- [x] 专注于用户价值和业务需求 +- [x] 为非技术利益相关者编写 +- [x] 所有必需章节已完成 + +## 需求完整性 + +- [x] 没有 [NEEDS CLARIFICATION] 标记剩余 +- [x] 需求是可测试且明确的 +- [x] 成功标准是可衡量的 +- [x] 成功标准是技术无关的(无实现细节) +- [x] 所有验收场景已定义 +- [x] 边缘情况已识别 +- [x] 范围明确界定 +- [x] 依赖关系和假设已识别 + +## 功能准备就绪 + +- [x] 所有功能需求都有明确的验收标准 +- [x] 用户场景覆盖主要流程 +- [x] 功能满足成功标准中定义的可衡量结果 +- [x] 没有实现细节泄漏到规范中 + +## 备注 + +- 标记为不完整的项目需要在 `/speckit.clarify` 或 `/speckit.plan` 之前更新规范 +- 所有检查项目均已通过验证,规范质量符合要求 +- 规范已准备好进入下一阶段(规划或澄清) diff --git a/specs/001-ai-project-navigator/contracts/webhook.yaml b/specs/001-ai-project-navigator/contracts/webhook.yaml new file mode 100644 index 0000000..68a0b9d --- /dev/null +++ b/specs/001-ai-project-navigator/contracts/webhook.yaml @@ -0,0 +1,456 @@ +openapi: 3.0.3 +info: + title: Agent Park Webhook API + description: | + API 接口用于接收 n8n 工作流程推送的 AI 项目数据更新。 + + **认证方式**: 请求头中携带 `X-API-Key` 进行身份验证。 + + **数据处理模式**: 部分成功模式 - 部分项目验证失败时,有效项目正常入库,失败项目记录错误信息。 + + **响应格式**: JSON + version: 1.0.0 + contact: + name: Agent Park Team + email: support@agent-park.example + +servers: + - url: http://localhost:3000 + description: 本地开发环境 + - url: https://agent-park.example.com + description: 生产环境 + +tags: + - name: webhook + description: Webhook 接口 + - name: projects + description: 项目数据管理 + +paths: + /api/webhook/projects: + post: + tags: + - webhook + - projects + summary: 接收 n8n 推送的项目数据 + description: | + 接收 n8n 工作流程推送的 AI 项目数据,验证后批量更新到数据库。 + + **认证**: 请求头中必须包含有效的 `X-API-Key` + + **处理流程**: + 1. 验证 API Key + 2. 验证请求数据格式 + 3. 逐个验证项目数据 + 4. 有效项目入库(创建或更新) + 5. 返回处理结果 + + **部分成功模式**: 即使部分项目验证失败,也会继续处理其他项目。 + operationId: upsertProjects + security: + - ApiKeyAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayload' + examples: + success_single: + summary: 单个项目示例 + value: + apiKey: "sk_live_1234567890abcdefghijklmnopqrstuvwxyz" + projects: + - name: "Claude" + nameEn: "Claude" + slug: "claude" + description: "Anthropic 开发的 AI 助手" + descriptionEn: "AI assistant by Anthropic" + content: "Claude 的详细介绍..." + contentEn: "Detailed description of Claude..." + status: "ACTIVE" + source: "n8n-daily-job" + tags: + - name: "代码助手" + nameEn: "Code Assistant" + links: + - type: "WEBSITE" + url: "https://www.anthropic.com/claude" + title: "Official Website" + - type: "GITHUB" + url: "https://github.com/anthropics" + success_batch: + summary: 批量项目示例 + value: + apiKey: "sk_live_1234567890abcdefghijklmnopqrstuvwxyz" + projects: + - name: "GPT-4" + slug: "gpt-4" + description: "OpenAI 的大型语言模型" + status: "ACTIVE" + source: "n8n-daily-job" + tags: + - name: "语言模型" + - name: "对话AI" + links: + - type: "WEBSITE" + url: "https://openai.com/gpt-4" + - name: "Midjourney" + slug: "midjourney" + description: "AI 图像生成工具" + status: "ACTIVE" + source: "n8n-daily-job" + tags: + - name: "图像生成" + links: + - type: "WEBSITE" + url: "https://www.midjourney.com" + partial_failure: + summary: 部分失败示例 + value: + apiKey: "sk_live_1234567890abcdefghijklmnopqrstuvwxyz" + projects: + - name: "Valid Project" + slug: "valid-project" + description: "这是一个有效的项目" + tags: + - name: "测试" + links: + - type: "WEBSITE" + url: "https://example.com" + - name: "" # 无效:名称为空 + slug: "invalid-project" + description: "x" + tags: [] + links: [] + responses: + '200': + description: 处理完成(可能包含部分失败) + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponse' + examples: + success_all: + summary: 全部成功 + value: + success: true + processed: 3 + created: 2 + updated: 1 + failed: 0 + errors: [] + partial_success: + summary: 部分成功 + value: + success: true + processed: 3 + created: 1 + updated: 1 + failed: 1 + errors: + - index: 1 + field: "description" + message: "Description too short (minimum 10 characters)" + value: { "description": "x" } + validation_error: + summary: 验证错误(请求级别) + value: + success: false + error: "Validation error" + details: + - "At least one project is required" + '401': + $ref: '#/components/responses/Unauthorized' + '400': + $ref: '#/components/responses/BadRequest' + '500': + $ref: '#/components/responses/InternalServerError' + +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + description: | + API Key 认证。n8n 在请求头中携带预共享的密钥。 + + 格式: `X-API-Key: sk_live_32_characters_minimum` + + schemas: + # ================================ + # Enums + # ================================ + ProjectStatus: + type: string + enum: [ACTIVE, ARCHIVED] + description: 项目状态 + x-enum-descriptions: + ACTIVE: 活跃项目 + ARCHIVED: 已归档项目 + + LinkType: + type: string + enum: [WEBSITE, GITHUB, HUGGINGFACE, PAPER] + description: 链接类型 + x-enum-descriptions: + WEBSITE: 官方网站 + GITHUB: GitHub 仓库 + HUGGINGFACE: HuggingFace 模型/数据集 + PAPER: 学术论文 + + # ================================ + # Domain Models + # ================================ + ExternalLink: + type: object + required: + - type + - url + properties: + type: + $ref: '#/components/schemas/LinkType' + url: + type: string + format: uri + description: 链接地址 + example: "https://github.com/example/project" + title: + type: string + maxLength: 200 + description: 链接标题(可选) + example: "Source Code" + + Tag: + type: object + required: + - name + properties: + name: + type: string + minLength: 1 + maxLength: 50 + description: 标签名称(中文) + example: "图像生成" + nameEn: + type: string + maxLength: 50 + description: 标签名称(英文,可选) + example: "Image Generation" + + ProjectInput: + type: object + required: + - name + - slug + - description + - tags + - links + properties: + name: + type: string + minLength: 1 + maxLength: 200 + description: 项目名称(中文) + example: "Claude" + nameEn: + type: string + maxLength: 200 + description: 项目名称(英文,可选) + example: "Claude" + slug: + type: string + pattern: '^[a-z0-9-]+$' + description: URL 友好标识符(唯一) + example: "claude" + description: + type: string + minLength: 10 + maxLength: 500 + description: 项目简介(中文) + example: "Anthropic 开发的 AI 助手,擅长分析、写作和编程任务" + descriptionEn: + type: string + maxLength: 500 + description: 项目简介(英文,可选) + example: "AI assistant by Anthropic, excels at analysis, writing, and coding" + content: + type: string + maxLength: 10000 + description: 详细介绍(中文,可选) + example: "Claude 是由 Anthropic 开发的下一代 AI 助手..." + contentEn: + type: string + maxLength: 10000 + description: 详细介绍(英文,可选) + example: "Claude is a next-generation AI assistant..." + status: + $ref: '#/components/schemas/ProjectStatus' + description: 项目状态(默认 ACTIVE) + source: + type: string + maxLength: 100 + description: 数据来源标识(如 n8n 流程名称) + example: "n8n-daily-job" + tags: + type: array + minItems: 1 + maxItems: 10 + items: + $ref: '#/components/schemas/Tag' + description: 项目标签(至少一个) + links: + type: array + minItems: 1 + maxItems: 10 + items: + $ref: '#/components/schemas/ExternalLink' + description: 外部链接(至少一个) + + # ================================ + # Request/Response Models + # ================================ + WebhookPayload: + type: object + required: + - apiKey + - projects + properties: + apiKey: + type: string + minLength: 32 + description: API 密钥(用于认证) + pattern: '^[a-zA-Z0-9_]{32,}$' + example: "sk_live_1234567890abcdefghijklmnopqrstuvwxyz" + projects: + type: array + minItems: 1 + maxItems: 100 + items: + $ref: '#/components/schemas/ProjectInput' + description: 要创建/更新的项目列表 + + WebhookResponse: + type: object + properties: + success: + type: boolean + description: 请求是否成功(部分失败仍返回 true) + processed: + type: integer + description: 处理的项目总数 + created: + type: integer + description: 新创建的项目数 + updated: + type: integer + description: 更新的项目数 + failed: + type: integer + description: 失败的项目数 + errors: + type: array + description: 失败项目的错误详情 + items: + type: object + properties: + index: + type: integer + description: 失败项目在请求中的索引(从 0 开始) + field: + type: string + description: 验证失败的字段名 + message: + type: string + description: 错误消息 + value: + type: object + description: 导致错误的值(调试用) + + ErrorResponse: + type: object + properties: + success: + type: boolean + example: false + error: + type: string + description: 错误类型 + details: + type: array + items: + type: string + description: 详细错误信息 + + # ================================ + # Common Responses + # ================================ + responses: + Unauthorized: + description: 未授权 - API Key 无效或缺失 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + success: false + error: "Unauthorized" + details: ["Invalid or missing API Key"] + + BadRequest: + description: 请求格式错误或验证失败 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + validation_error: + summary: 验证错误 + value: + success: false + error: "Validation error" + details: + - "At least one project is required" + invalid_json: + summary: JSON 格式错误 + value: + success: false + error: "Invalid JSON" + details: + - "Unexpected end of JSON input" + + InternalServerError: + description: 服务器内部错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + success: false + error: "Internal server error" + details: + - "Database connection failed" + +# ================================ +# Examples +# ================================ +x-webhook-examples: + n8n-configuration: + description: n8n HTTP Request 节点配置示例 + method: POST + url: "https://agent-park.example.com/api/webhook/projects" + headers: + X-API-Key: "sk_live_32_characters_minimum" + Content-Type: "application/json" + body: + apiKey: "{{$env.WEBHOOK_API_KEY}}" + projects: + - name: "{{$json.projectName}}" + slug: "{{$json.projectSlug}}" + description: "{{$json.description}}" + tags: + - name: "{{$json.tag}}" + links: + - type: "WEBSITE" + url: "{{$json.website}}" diff --git a/specs/001-ai-project-navigator/data-model.md b/specs/001-ai-project-navigator/data-model.md new file mode 100644 index 0000000..34ab803 --- /dev/null +++ b/specs/001-ai-project-navigator/data-model.md @@ -0,0 +1,524 @@ +# 数据模型设计: Agent Park - AI项目导航网站 + +**功能分支**: `001-ai-project-navigator` +**创建时间**: 2025-12-25 +**状态**: 完成 + +## 概述 + +本文档定义了 Agent Park 项目的数据模型,包括实体定义、关系、验证规则和状态转换。数据模型使用 Prisma Schema 定义,存储在 PostgreSQL 数据库中。 + +--- + +## 实体关系图 (ERD) + +``` +┌─────────────┐ ┌─────────────┐ ┌──────────────────┐ +│ Project │<─────│ Tag │ │ ExternalLink │ +│ │ N:M │ │ 1:N │ │ +└─────────────┘ └─────────────┘ └──────────────────┘ + │ 1 │ N + │ │ + └────────────────────────────────────────────┘ + 1 +``` + +--- + +## 实体定义 + +### 1. Project (AI项目) + +代表一个AI工具、应用或研究项目。 + +#### 字段 + +| 字段名 | 类型 | 约束 | 默认值 | 描述 | +|--------|------|------|--------|------| +| `id` | String | Primary Key | cuid() | 主键 | +| `name` | String | NOT NULL | - | 项目名称(中文) | +| `nameEn` | String? | Nullable | - | 项目名称(英文) | +| `slug` | String | UNIQUE, NOT NULL | - | URL 友好标识符 | +| `description` | String | NOT NULL, Min(10) | - | 项目简介(中文) | +| `descriptionEn` | String? | Nullable | - | 项目简介(英文) | +| `content` | Text? | Nullable | - | 详细介绍(中文) | +| `contentEn` | Text? | Nullable | - | 详细介绍(英文) | +| `status` | Enum | NOT NULL | ACTIVE | 项目状态 | +| `source` | String? | Nullable | - | 数据来源标识(如 n8n) | +| `createdAt` | DateTime | NOT NULL | now() | 收录时间 | +| `updatedAt` | DateTime | NOT NULL | now() | 更新时间 | + +#### 状态枚举 + +```prisma +enum ProjectStatus { + ACTIVE # 活跃项目 + ARCHIVED # 已归档项目 +} +``` + +#### 索引 + +- `idx_project_status_createdAt`: (status, createdAt) - 用于按状态和时间筛选 +- `idx_project_slug`: (slug) UNIQUE - 用于详情页路由 + +#### 关系 + +- `tags`: 与 Tag 的多对多关系 +- `links`: 与 ExternalLink 的一对多关系 + +--- + +### 2. Tag (标签) + +代表AI项目的特征标记,如"图像生成"、"代码助手"等。 + +#### 字段 + +| 字段名 | 类型 | 约束 | 默认值 | 描述 | +|--------|------|------|--------|------| +| `id` | String | Primary Key | cuid() | 主键 | +| `name` | String | UNIQUE, NOT NULL | - | 标签名称(中文) | +| `nameEn` | String? | Nullable | - | 标签名称(英文) | +| `slug` | String | UNIQUE, NOT NULL | - | URL 友好标识符 | +| `createdAt` | DateTime | NOT NULL | now() | 创建时间 | + +#### 索引 + +- `idx_tag_slug`: (slug) UNIQUE - 用于标签筛选页面 + +#### 关系 + +- `projects`: 与 Project 的多对多关系 + +--- + +### 3. ExternalLink (外部链接) + +代表项目的外部来源链接,如官网、GitHub、HuggingFace、论文链接。 + +#### 字段 + +| 字段名 | 类型 | 约束 | 默认值 | 描述 | +|--------|------|------|--------|------| +| `id` | String | Primary Key | cuid() | 主键 | +| `type` | Enum | NOT NULL | - | 链接类型 | +| `url` | String | NOT NULL | - | 链接地址 | +| `title` | String? | Nullable | - | 链接标题(可选) | +| `projectId` | String | Foreign Key | - | 关联的项目ID | + +#### 链接类型枚举 + +```prisma +enum LinkType { + WEBSITE # 官方网站 + GITHUB # GitHub 仓库 + HUGGINGFACE # HuggingFace 模型/数据集 + PAPER # 学术论文 +} +``` + +#### 索引 + +- `idx_link_projectId`: (projectId) - 用于查询项目的外部链接 +- `idx_link_type`: (type) - 用于按类型筛选链接 + +#### 关系 + +- `project`: 与 Project 的多对一关系(级联删除) + +--- + +## 关系定义 + +### Project ↔ Tag (多对多) + +使用中间表 `_ProjectTags` 维护多对多关系。 + +```prisma +model Project { + // ... 其他字段 + tags Tag[] +} + +model Tag { + // ... 其他字段 + projects Project[] +} +``` + +**级联规则**: 删除项目时不删除标签(标签可能被其他项目使用) + +### Project ↔ ExternalLink (一对多) + +一个项目可以有多个外部链接。 + +```prisma +model Project { + // ... 其他字段 + links ExternalLink[] +} + +model ExternalLink { + id String @id @default(cuid()) + type LinkType + url String + title String? + projectId String + + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) +} +``` + +**级联规则**: 删除项目时级联删除所有关联的外部链接 + +--- + +## Prisma Schema 完整定义 + +```prisma +// prisma/schema.prisma + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["postgresqlExtensions"] +} + +// ================================ +// Enums +// ================================ + +enum ProjectStatus { + ACTIVE + ARCHIVED +} + +enum LinkType { + WEBSITE + GITHUB + HUGGINGFACE + PAPER +} + +// ================================ +// Models +// ================================ + +model Project { + id String @id @default(cuid()) + name String + nameEn String? + slug String @unique + description String + descriptionEn String? + content String? @db.Text + contentEn String? @db.Text + status ProjectStatus @default(ACTIVE) + source String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + tags Tag[] + links ExternalLink[] + + // Indexes + @@index([status, createdAt], map: "idx_project_status_createdAt") + @@index([slug], map: "idx_project_slug") + @@map("projects") +} + +model Tag { + id String @id @default(cuid()) + name String @unique + nameEn String? + slug String @unique + createdAt DateTime @default(now()) + + // Relations + projects Project[] + + // Indexes + @@index([slug], map: "idx_tag_slug") + @@map("tags") +} + +model ExternalLink { + id String @id @default(cuid()) + type LinkType + url String + title String? + projectId String + + // Relations + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + + // Indexes + @@index([projectId], map: "idx_link_projectId") + @@index([type], map: "idx_link_type") + @@map("external_links") +} +``` + +--- + +## Zod 验证 Schema + +配合 Prisma 类型,使用 Zod 进行运行时数据验证。 + +```typescript +// src/lib/validations.ts + +import { z } from 'zod' + +// ================================ +// Enums +// ================================ + +export const ProjectStatusEnum = z.enum(['ACTIVE', 'ARCHIVED']) +export const LinkTypeEnum = z.enum(['WEBSITE', 'GITHUB', 'HUGGINGFACE', 'PAPER']) + +// ================================ +// Base Schemas +// ================================ + +export const ExternalLinkSchema = z.object({ + type: LinkTypeEnum, + url: z.string().url('Invalid URL format'), + title: z.string().max(200).optional() +}) + +export const TagSchema = z.object({ + name: z.string().min(1).max(50), + nameEn: z.string().max(50).optional() +}) + +// ================================ +// Project Schemas +// ================================ + +export const ProjectBaseSchema = z.object({ + name: z.string().min(1).max(200), + nameEn: z.string().max(200).optional(), + description: z.string().min(10).max(500), + descriptionEn: z.string().max(500).optional(), + content: z.string().max(10000).optional(), + contentEn: z.string().max(10000).optional(), + status: ProjectStatusEnum.default('ACTIVE'), + source: z.string().max(100).optional() +}) + +export const ProjectInputSchema = ProjectBaseSchema.extend({ + tags: z.array(TagSchema).min(1, 'At least one tag is required').max(10), + links: z.array(ExternalLinkSchema).min(1, 'At least one link is required').max(10) +}) + +// ================================ +// Webhook Schemas +// ================================ + +export const WebhookAuthSchema = z.object({ + apiKey: z.string().min(32, 'Invalid API key format') +}) + +export const WebhookPayloadSchema = WebhookAuthSchema.extend({ + projects: z.array(ProjectInputSchema).min(1).max(100) +}) + +// ================================ +// Query Schemas +// ================================ + +export const ProjectQuerySchema = z.object({ + search: z.string().max(100).optional(), + tags: z.array(z.string()).optional(), + status: ProjectStatusEnum.optional(), + page: z.coerce.number().int().positive().default(1), + limit: z.coerce.number().int().positive().max(100).default(20) +}) + +// ================================ +// Types +// ================================ + +export type ExternalLink = z.infer +export type Tag = z.infer +export type ProjectInput = z.infer +export type WebhookPayload = z.infer +export type ProjectQuery = z.infer +``` + +--- + +## 数据迁移策略 + +### 初始化迁移 + +```bash +# 创建初始迁移 +npx prisma migrate dev --name init + +# 生成 Prisma Client +npx prisma generate + +# 推送 schema 到数据库(开发环境) +npx prisma db push +``` + +### 生产环境部署 + +```bash +# 应用待处理的迁移 +npx prisma migrate deploy + +# 重置数据库(仅开发环境,慎用!) +npx prisma migrate reset +``` + +### 迁移命名约定 + +- `init`: 初始化 schema +- `add_project_content`: 添加项目内容字段 +- `add_external_link_title`: 添加链接标题字段 +- `add_status_index`: 添加状态索引 + +--- + +## 种子数据 + +### 假数据示例 (20-30个项目) + +```typescript +// prisma/seed.ts + +import { PrismaClient } from '@prisma/client' +import { ProjectStatus, LinkType } from '@prisma/client' + +const prisma = new PrismaClient() + +async function main() { + // 创建标签 + const tagImageGen = await prisma.tag.upsert({ + where: { slug: 'image-generation' }, + update: {}, + create: { + name: '图像生成', + nameEn: 'Image Generation', + slug: 'image-generation' + } + }) + + const tagCodeAssistant = await prisma.tag.upsert({ + where: { slug: 'code-assistant' }, + update: {}, + create: { + name: '代码助手', + nameEn: 'Code Assistant', + slug: 'code-assistant' + } + }) + + const tagDataAnalysis = await prisma.tag.upsert({ + where: { slug: 'data-analysis' }, + update: {}, + create: { + name: '数据分析', + nameEn: 'Data Analysis', + slug: 'data-analysis' + } + }) + + // 创建项目 + await prisma.project.upsert({ + where: { slug: 'claude' }, + update: {}, + create: { + name: 'Claude', + nameEn: 'Claude', + slug: 'claude', + description: 'Anthropic 开发的 AI 助手,擅长分析、写作和编程任务。', + descriptionEn: 'AI assistant by Anthropic, excels at analysis, writing, and coding tasks.', + content: 'Claude 是由 Anthropic 开发的下一代 AI 助手。它基于 Constitutional AI 方法训练,强调安全性、诚实性和有用性。Claude 擅长长文本分析、创意写作、编程辅助等多种任务。', + contentEn: 'Claude is a next-generation AI assistant developed by Anthropic. Trained using Constitutional AI methods, it emphasizes safety, honesty, and helpfulness. Claude excels at long-text analysis, creative writing, coding assistance, and more.', + status: ProjectStatus.ACTIVE, + source: 'manual', + tags: { + connect: [{ id: tagCodeAssistant.id }] + }, + links: { + create: [ + { + type: LinkType.WEBSITE, + url: 'https://www.anthropic.com/claude', + title: 'Official Website' + }, + { + type: LinkType.GITHUB, + url: 'https://github.com/anthropics/anthropic-sdk-python', + title: 'Python SDK' + } + ] + } + } + }) + + // 更多项目... +} + +main() + .catch((e) => { + console.error(e) + process.exit(1) + }) + .finally(async () => { + await prisma.$disconnect() + }) +``` + +--- + +## 数据完整性约束 + +### 字段级约束 + +| 表 | 字段 | 约束 | +|---|------|------| +| projects | name | NOT NULL, Max(200) | +| projects | description | NOT NULL, Min(10), Max(500) | +| projects | slug | UNIQUE, URL-friendly | +| tags | name | UNIQUE, NOT NULL, Max(50) | +| external_links | url | NOT NULL, Valid URL | + +### 业务规则 + +1. **项目必填字段**: name、description、至少一个 tag、至少一个 link +2. **标签唯一性**: 同名标签不能重复创建 +3. **链接类型限制**: 每个项目每种类型的链接最多 5 个 +4. **项目状态**: 新项目默认为 ACTIVE,仅可手动设置为 ARCHIVED +5. **删除保护**: 删除项目时级联删除链接,但保留标签 + +--- + +## 性能优化建议 + +1. **索引优化**: 为常用查询字段(status、slug、projectId)创建索引 +2. **查询优化**: 使用 Prisma 的 `select` 和 `include` 精确获取数据 +3. **分页查询**: 使用 `cursor` 或 `offset` 分页避免一次性加载大量数据 +4. **全文搜索**: 如需高级搜索,可使用 PostgreSQL 的全文搜索功能 + +--- + +## 总结 + +数据模型设计遵循以下原则: + +- **类型安全**: Prisma + Zod 提供端到端类型安全 +- **国际化支持**: 核心字段提供中英双语版本 +- **灵活性**: 标签系统而非固定分类,适应 AI 领域快速变化 +- **可扩展性**: 清晰的实体关系,便于后续功能扩展 +- **性能优先**: 合理的索引设计,优化查询性能 diff --git a/specs/001-ai-project-navigator/plan.md b/specs/001-ai-project-navigator/plan.md new file mode 100644 index 0000000..088d5c2 --- /dev/null +++ b/specs/001-ai-project-navigator/plan.md @@ -0,0 +1,157 @@ +# 实施计划: Agent Park - AI项目导航网站 + +**分支**: `001-ai-project-navigator` | **日期**: 2025-12-25 | **规范**: [spec.md](./spec.md) +**输入**: 来自 `/specs/001-ai-project-navigator/spec.md` 的功能规范 + +## 摘要 + +构建一个名为 Agent Park 的全网AI项目导航网站,采用 Anthropic Claude 风格设计。网站支持中英双语,使用 Next.js 14+ App Router、shadcn/ui 组件库、Prisma ORM + PostgreSQL 数据库、Tailwind CSS。用户可以浏览、搜索AI项目,查看项目详情和外部链接。前期使用20-30个假数据构建,后期通过 webhook 接口接收 n8n 工作流程推送的更新数据。 + +## 技术背景 + +**语言/版本**: TypeScript 5.0+ (严格模式) +**主要依赖**: +- Next.js 14+ (App Router) +- shadcn/ui (组件库) +- Prisma (ORM) +- PostgreSQL (数据库) +- Tailwind CSS (样式) +- next-intl (国际化) +- Zod (数据验证) + +**存储**: PostgreSQL (通过 Prisma ORM) +**测试**: Vitest + React Testing Library + Playwright +**目标平台**: Web (响应式设计 - 桌面/平板/手机) +**项目类型**: 全栈 Web 应用 +**性能目标**: +- 首次内容绘制(FCP) < 1.8s +- 最大内容绘制(LCP) < 2.5s +- 首次字节时间(TTFB) < 800ms +- 初始 JavaScript < 200KB gzipped + +**约束条件**: +- TypeScript 严格模式强制启用 +- 单元测试覆盖率 80%+ +- 所有代码必须通过 ESLint 检查 +- 禁止使用 @ts-ignore + +**规模/范围**: +- 前期: 20-30个项目数据 +- 页面: 首页、项目列表、项目详情页 +- API: 1个 webhook 接口 (POST /api/webhook/projects) + +## 章程检查 + +*门控: 必须在阶段 0 研究前通过. 阶段 1 设计后重新检查. * + +### 阶段 0 前检查 + +| 原则 | 状态 | 说明 | +|------|------|------| +| I. TypeScript 严格模式与类型安全 | ✅ 通过 | 项目使用 TypeScript 5.0+ 严格模式,所有 API 使用 Zod 验证 | +| II. 组件优先架构 | ✅ 通过 | 使用 Next.js App Router,优先 Server Components | +| III. 测试驱动开发(不可协商) | ✅ 通过 | 规划使用 Vitest + React Testing Library + Playwright,目标 80%+ 覆盖率 | +| IV. 性能优先 | ✅ 通过 | 定义了明确的 Core Web Vitals 目标,使用 ISR 和 SSG 优化 | +| V. 用户体验一致性 | ✅ 通过 | 使用 shadcn/ui 统一设计系统,支持响应式和深色模式 | +| VI. 代码质量与可维护性 | ✅ 通过 | 使用 ESLint + Prettier,遵循 TypeScript 最佳实践 | + +### 阶段 1 后重新检查 + +| 原则 | 状态 | 说明 | +|------|------|------| +| 所有阶段 0 门控项 | ✅ 保持 | 设计确认符合所有章程原则 | +| 数据模型类型安全 | ✅ 通过 | Prisma 生成类型,配合 Zod 运行时验证 | +| API 合同完整性 | ✅ 通过 | webhook 接口使用 Zod schema 验证输入 | + +**结论**: 项目设计完全符合章程要求,无违规项。 + +## 项目结构 + +### 文档(此功能) + +``` +specs/001-ai-project-navigator/ +├── plan.md # 此文件 (/speckit.plan 命令输出) +├── research.md # 阶段 0 输出 - 技术选型研究 +├── data-model.md # 阶段 1 输出 - 数据模型设计 +├── quickstart.md # 阶段 1 输出 - 快速开始指南 +├── contracts/ # 阶段 1 输出 - API 合同 +│ └── webhook.yaml # OpenAPI 3.0 规范 +└── tasks.md # 阶段 2 输出 (/speckit.tasks 命令) +``` + +### 源代码(仓库根目录) + +``` +agent-park-v2/ +├── prisma/ +│ ├── schema.prisma # Prisma 数据模型定义 +│ └── seed.ts # 假数据种子脚本 +├── public/ +│ └── images/ # 静态图片资源 +├── src/ +│ ├── app/ # Next.js App Router 目录 +│ │ ├── [locale]/ # next-intl 国际化路由 +│ │ │ ├── layout.tsx # 根布局 +│ │ │ ├── page.tsx # 首页 +│ │ │ ├── projects/ # 项目列表页 +│ │ │ │ ├── page.tsx +│ │ │ │ └── [id]/ # 项目详情页 +│ │ │ │ └── page.tsx +│ │ │ └── api/ # API 路由 +│ │ │ └── webhook/ +│ │ │ └── projects/ +│ │ │ └── route.ts # Webhook 端点 +│ │ ├── globals.css # Tailwind 全局样式 +│ │ └── layout.tsx # 根布局 (国际化) +│ ├── components/ # React 组件 +│ │ ├── ui/ # shadcn/ui 组件 (自动生成) +│ │ ├── layout/ # 布局组件 +│ │ │ ├── Header.tsx +│ │ │ ├── Footer.tsx +│ │ │ └── Navigation.tsx +│ │ ├── project/ # 项目相关组件 +│ │ │ ├── ProjectCard.tsx +│ │ │ ├── ProjectList.tsx +│ │ │ ├── ProjectDetail.tsx +│ │ │ └── TagCloud.tsx +│ │ └── search/ # 搜索组件 +│ │ ├── SearchBar.tsx +│ │ └── SearchResults.tsx +│ ├── lib/ # 工具库 +│ │ ├── prisma.ts # Prisma 客户端单例 +│ │ ├── utils.ts # 通用工具函数 +│ │ └── validations.ts # Zod 验证 schemas +│ ├── hooks/ # 自定义 Hooks +│ │ ├── useSearch.ts +│ │ └── useProjects.ts +│ ├── types/ # TypeScript 类型定义 +│ │ └── index.ts +│ ├── messages/ # next-intl 翻译文件 +│ │ ├── en.json +│ │ └── zh.json +│ └── styles/ # 额外样式文件 +├── tests/ # 测试文件 +│ ├── unit/ # 单元测试 +│ ├── integration/ # 集成测试 +│ └── e2e/ # E2E 测试 (Playwright) +├── next.config.js # Next.js 配置 +├── tailwind.config.js # Tailwind CSS 配置 +├── tsconfig.json # TypeScript 配置 +├── components.json # shadcn/ui 配置 +├── .env.example # 环境变量示例 +├── package.json +└── README.md +``` + +**结构决策**: 采用标准的 Next.js App Router 单体项目结构。所有源代码在 `src/` 目录下,使用 `app/` 目录进行路由。Prisma schema 在根目录 `prisma/` 文件夹。组件按功能分目录组织,shadcn/ui 组件放在 `components/ui/` 自动管理。 + +## 复杂度跟踪 + +*仅在章程检查有必须证明的违规时填写* + +无违规项。项目设计完全符合章程要求。 + +| 违规 | 为什么需要 | 拒绝更简单替代方案的原因 | +|-----------|------------|-------------------------------------| +| - | - | - | diff --git a/specs/001-ai-project-navigator/quickstart.md b/specs/001-ai-project-navigator/quickstart.md new file mode 100644 index 0000000..5591dc9 --- /dev/null +++ b/specs/001-ai-project-navigator/quickstart.md @@ -0,0 +1,519 @@ +# 快速开始指南: Agent Park - AI项目导航网站 + +**功能分支**: `001-ai-project-navigator` +**创建时间**: 2025-12-25 +**状态**: 完成 + +## 概述 + +本指南提供 Agent Park 项目的完整开发环境设置和快速启动步骤。 + +--- + +## 前置要求 + +### 必需软件 + +| 软件 | 版本要求 | 用途 | +|------|----------|------| +| Node.js | 18.17+ | 运行时环境 | +| pnpm | 8.0+ | 包管理器 | +| PostgreSQL | 14+ | 数据库 | +| Git | 最新版 | 版本控制 | + +### 可选软件 + +| 软件 | 用途 | +|------|------| +| Docker | 容器化数据库(开发环境) | +| VS Code | 推荐的代码编辑器 | + +### 检查安装 + +```bash +# 检查 Node.js 版本 +node --version # 应该 >= v18.17.0 + +# 检查 pnpm 版本 +pnpm --version # 应该 >= 8.0.0 + +# 检查 PostgreSQL +psql --version # 应该 >= 14.0 +``` + +--- + +## 项目初始化 + +### 1. 创建 Next.js 项目 + +```bash +# 使用 pnpm 创建项目(推荐使用 shadcn/ui init 自动配置) +pnpm create next-app@latest agent-park-v2 --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" + +# 或使用 shadcn/ui 的 init 命令(更便捷) +npx shadcn@latest init +``` + +**交互式选项**: +- TypeScript: Yes +- ESLint: Yes +- Tailwind CSS: Yes +- `src/` directory: Yes +- App Router: Yes +- Import alias: `@/*` + +### 2. 安装依赖 + +```bash +cd agent-park-v2 + +# 核心依赖 +pnpm add next-intl zod clsx tailwind-merge + +# Prisma +pnpm add @prisma/client +pnpm add -D prisma + +# shadcn/ui(如未使用 init 命令) +npx shadcn@latest init + +# 开发依赖 +pnpm add -D @types/node @types/react @types/react-dom vitest @testing-library/react @testing-library/jest-dom @playwright/test +``` + +### 3. 安装 shadcn/ui 组件 + +```bash +# 安装常用组件 +npx shadcn@latest add button card input textarea badge +npx shadcn@latest add skeleton separator dialog +npx shadcn@latest add navigation-menu dropdown-menu +``` + +--- + +## 数据库设置 + +### 方案 A: 使用 Docker(推荐) + +```bash +# 启动 PostgreSQL 容器 +docker run --name agent-park-db \ + -e POSTGRES_USER=agentpark \ + -e POSTGRES_PASSWORD=agentpark \ + -e POSTGRES_DB=agent_park \ + -p 5432:5432 \ + -d postgres:16-alpine + +# 等待数据库启动 +docker logs -f agent-park-db +``` + +### 方案 B: 本地 PostgreSQL + +```bash +# 创建数据库 +createdb agent_park + +# 或使用 psql +psql -U postgres -c "CREATE DATABASE agent_park;" +``` + +### 配置环境变量 + +```bash +# 创建 .env 文件 +cp .env.example .env + +# 编辑 .env +DATABASE_URL="postgresql://agentpark:agentpark@localhost:5432/agent_park" +NEXT_PUBLIC_SITE_URL="http://localhost:3000" +WEBHOOK_API_KEY="sk_live_$(openssl rand -base64 32 | tr -d '=+/' | cut -c1-32)" +``` + +--- + +## Prisma 配置 + +### 1. 初始化 Prisma + +```bash +npx prisma init +``` + +### 2. 编写 Schema + +将以下内容写入 `prisma/schema.prisma`: + +```prisma +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" + previewFeatures = ["postgresqlExtensions"] +} + +enum ProjectStatus { + ACTIVE + ARCHIVED +} + +enum LinkType { + WEBSITE + GITHUB + HUGGINGFACE + PAPER +} + +model Project { + id String @id @default(cuid()) + name String + nameEn String? + slug String @unique + description String + descriptionEn String? + content String? @db.Text + contentEn String? @db.Text + status ProjectStatus @default(ACTIVE) + source String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + tags Tag[] + links ExternalLink[] + + @@index([status, createdAt]) + @@index([slug]) + @@map("projects") +} + +model Tag { + id String @id @default(cuid()) + name String @unique + nameEn String? + slug String @unique + createdAt DateTime @default(now()) + + projects Project[] + + @@index([slug]) + @@map("tags") +} + +model ExternalLink { + id String @id @default(cuid()) + type LinkType + url String + title String? + projectId String + + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + + @@index([projectId]) + @@index([type]) + @@map("external_links") +} +``` + +### 3. 执行迁移 + +```bash +# 创建初始迁移 +npx prisma migrate dev --name init + +# 生成 Prisma Client +npx prisma generate +``` + +### 4. 加载种子数据 + +创建 `prisma/seed.ts`: + +```typescript +import { PrismaClient, ProjectStatus, LinkType } from '@prisma/client' + +const prisma = new PrismaClient() + +async function main() { + // 创建标签 + const tags = await Promise.all([ + prisma.tag.upsert({ + where: { slug: 'code-assistant' }, + update: {}, + create: { name: '代码助手', nameEn: 'Code Assistant', slug: 'code-assistant' } + }), + prisma.tag.upsert({ + where: { slug: 'image-generation' }, + update: {}, + create: { name: '图像生成', nameEn: 'Image Generation', slug: 'image-generation' } + }) + ]) + + // 创建项目 + await prisma.project.upsert({ + where: { slug: 'claude' }, + update: {}, + create: { + name: 'Claude', + nameEn: 'Claude', + slug: 'claude', + description: 'Anthropic 开发的 AI 助手,擅长分析、写作和编程任务。', + descriptionEn: 'AI assistant by Anthropic, excels at analysis, writing, and coding.', + status: ProjectStatus.ACTIVE, + tags: { connect: tags.map(t => ({ id: t.id })) }, + links: { + create: [ + { type: LinkType.WEBSITE, url: 'https://www.anthropic.com/claude', title: 'Official Website' }, + { type: LinkType.GITHUB, url: 'https://github.com/anthropics', title: 'GitHub' } + ] + } + } + }) + + console.log('Seed data loaded successfully!') +} + +main() + .catch((e) => { + console.error(e) + process.exit(1) + }) + .finally(async () => { + await prisma.$disconnect() + }) +``` + +```bash +# 运行种子脚本 +npx ts-node prisma/seed.ts +``` + +--- + +## 国际化配置 + +### 1. 配置 next-intl + +安装依赖: + +```bash +pnpm add next-intl +``` + +### 2. 创建消息文件 + +```bash +mkdir -p src/messages +``` + +`src/messages/en.json`: + +```json +{ + "common": { + "search": "Search", + "loading": "Loading...", + "noResults": "No results found" + }, + "home": { + "title": "AI Project Navigator", + "subtitle": "Discover and explore AI projects" + }, + "project": { + "details": "Project Details", + "externalLinks": "External Links", + "viewProject": "View Project" + } +} +``` + +`src/messages/zh.json`: + +```json +{ + "common": { + "search": "搜索", + "loading": "加载中...", + "noResults": "未找到结果" + }, + "home": { + "title": "AI 项目导航", + "subtitle": "发现和探索 AI 项目" + }, + "project": { + "details": "项目详情", + "externalLinks": "外部链接", + "viewProject": "查看项目" + } +} +``` + +### 3. 配置 Next.js + +更新 `next.config.js`: + +```javascript +const createNextIntlPlugin = require('next-intl/plugin') + +const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts') + +/** @type {import('next').NextConfig} */ +const nextConfig = { + images: { + remotePatterns: [ + { hostname: 'localhost' }, + { hostname: '*.anthropic.com' } + ] + } +} + +module.exports = withNextIntl(nextConfig) +``` + +创建 `src/i18n/request.ts`: + +```typescript +import { getRequestConfig } from 'next-intl/server' + +export default getRequestConfig(async ({ locale }) => ({ + messages: (await import(`../messages/${locale}.json`)).default +})) +``` + +--- + +## 目录结构创建 + +```bash +# 创建目录结构 +mkdir -p src/components/layout +mkdir -p src/components/project +mkdir -p src/components/search +mkdir -p src/lib +mkdir -p src/hooks +mkdir -p src/types +mkdir -p tests/unit +mkdir -p tests/integration +mkdir -p tests/e2e +``` + +--- + +## 启动开发服务器 + +```bash +# 启动开发服务器 +pnpm dev +``` + +访问: +- 英文: http://localhost:3000/en +- 中文: http://localhost:3000/zh + +--- + +## 验证设置 + +### 1. 检查 Prisma 连接 + +```bash +npx prisma studio +``` + +访问 http://localhost:5555 查看数据库数据。 + +### 2. 测试 API 端点 + +```bash +# 测试 webhook(需要先创建 .env 中的 WEBHOOK_API_KEY) +curl -X POST http://localhost:3000/api/webhook/projects \ + -H "Content-Type: application/json" \ + -H "X-API-Key: your-api-key" \ + -d '{ + "apiKey": "your-api-key", + "projects": [{ + "name": "Test Project", + "slug": "test-project", + "description": "This is a test project for verification", + "tags": [{"name": "测试"}], + "links": [{"type": "WEBSITE", "url": "https://example.com"}] + }] + }' +``` + +### 3. 运行测试 + +```bash +# 单元测试 +pnpm test + +# E2E 测试 +pnpm test:e2e +``` + +--- + +## 常见问题 + +### Q: Prisma 迁移失败? + +```bash +# 重置数据库(开发环境) +npx prisma migrate reset + +# 或手动删除迁移历史 +rm -rf prisma/migrations +npx prisma migrate dev --name init +``` + +### Q: TypeScript 错误? + +```bash +# 重新生成 Prisma Client +npx prisma generate + +# 重启 TypeScript 服务器(VS Code) +# Cmd+Shift+P -> "TypeScript: Restart TS Server" +``` + +### Q: 端口被占用? + +```bash +# 查找占用 3000 端口的进程 +lsof -i :3000 # macOS/Linux +netstat -ano | findstr :3000 # Windows + +# 或使用不同端口 +PORT=3001 pnpm dev +``` + +### Q: shadcn/ui 组件不工作? + +```bash +# 重新安装组件 +npx shadcn@latest add [component-name] + +# 或手动检查 components.json 配置 +``` + +--- + +## 下一步 + +设置完成后,继续以下步骤: + +1. **创建基础布局**: Header、Footer、Navigation +2. **实现项目列表页**: 支持搜索和标签筛选 +3. **实现项目详情页**: 显示完整信息和外部链接 +4. **实现 Webhook API**: 接收 n8n 数据推送 +5. **编写测试**: 单元测试和 E2E 测试 +6. **样式定制**: 调整 Tailwind 主题实现 Claude 风格 + +参考文档: +- [data-model.md](./data-model.md) - 数据模型设计 +- [contracts/webhook.yaml](./contracts/webhook.yaml) - API 接口规范 +- [research.md](./research.md) - 技术选型研究 diff --git a/specs/001-ai-project-navigator/research.md b/specs/001-ai-project-navigator/research.md new file mode 100644 index 0000000..f970e51 --- /dev/null +++ b/specs/001-ai-project-navigator/research.md @@ -0,0 +1,509 @@ +# 技术研究报告: Agent Park - AI项目导航网站 + +**功能分支**: `001-ai-project-navigator` +**创建时间**: 2025-12-25 +**状态**: 完成 + +## 概述 + +本文档记录了 Agent Park 项目的技术选型决策和最佳实践研究。针对 Next.js 全栈应用、shadcn/ui 组件库、PostgreSQL + Prisma ORM、Tailwind CSS 和国际化支持等技术进行了深入研究。 + +--- + +## 1. Next.js 14+ App Router + +### Decision: 选择 Next.js 14+ (App Router) 作为全栈框架 + +**Rationale**: +- **Server Components 优先**: App Router 默认使用 Server Components,减少客户端 JavaScript 体积,提升性能 +- **内置 API Routes**: 通过 Route Handlers 可以轻松构建 webhook 接口,无需单独的后端服务器 +- **文件系统路由**: 直观的路由结构,便于维护和扩展 +- **优秀的 SEO 支持**: 服务端渲染确保搜索引擎可以正确索引内容 +- **Vercel 部署优化**: 原生支持 Vercel 平台,零配置部署 + +**Alternatives considered**: +- **Remix**: 同样优秀的全栈框架,但社区和生态系统相对较小 +- **Nuxt.js (Vue)**: 团队更熟悉 React 生态系统 +- **SvelteKit**: 学习曲线较高,生态系统相对不成熟 + +### 最佳实践 + +1. **优先使用 Server Components**: 仅在需要交互性(状态、事件处理、浏览器 API)时添加 `'use client'` 指令 +2. **使用动态导入**: 对于客户端组件,使用 `dynamic()` 进行代码分割 +3. **利用并行路由**: 对于复杂布局,使用插槽和并行路由提升用户体验 +4. **使用 Server Actions**: 表单提交和状态变更优先使用 Server Actions 而非 API Routes +5. **启用增量静态再生 (ISR)**: 对于项目列表页面,使用 ISR 减少 API 调用 + +### TypeScript 配置 + +```json +{ + "compilerOptions": { + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +--- + +## 2. shadcn/ui 组件库 + +### Decision: 选择 shadcn/ui 作为 UI 组件库 + +**Rationale**: +- **代码而非 npm 包**: 组件代码直接复制到项目中,完全可控和可定制 +- **基于 Radix UI**: 无障碍访问性良好,符合 WAI-ARIA 标准 +- **Tailwind CSS 集成**: 完美配合 Tailwind CSS 进行样式定制 +- **类型安全**: 完整的 TypeScript 类型定义 +- **Anthropic Claude 风格**: 可以通过定制主题实现类似 Claude 的简洁优雅风格 + +**Alternatives considered**: +- **Mantine**: 功能丰富但体积较大,定制性较差 +- **Chakra UI**: API 设计优秀,但性能不如 Radix UI +- **Material-UI**: 风格过于固定,难以实现 Claude 风格 + +### 最佳实践 + +1. **使用 CLI 安装**: `npx shadcn@latest add [component]` 自动添加依赖和配置 +2. **自定义主题**: 通过 CSS 变量定制颜色、圆角、阴影等 +3. **复用组件模式**: 参考现有组件创建新的组合组件 +4. **保持组件更新**: 定期运行 `npx shadcn@latest diff` 检查组件更新 + +### Anthropic Claude 风格定制 + +```css +/* globals.css - Claude 风格配色 */ +:root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 5.9% 10%; + --radius: 0.5rem; +} + +.dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; +} +``` + +--- + +## 3. Prisma ORM + PostgreSQL + +### Decision: 选择 Prisma 作为 ORM,PostgreSQL 作为数据库 + +**Rationale**: +- **类型安全**: Prisma 自动生成 TypeScript 类型,与 strict mode 完美配合 +- **声明式 Schema**: 直观的数据模型定义,支持关系和约束 +- **迁移管理**: 内置版本控制的迁移系统,便于团队协作 +- **开发体验**: Prisma Studio 提供可视化的数据库浏览和编辑 +- **PostgreSQL 优势**: 成熟稳定、支持全文搜索、JSON 数据类型、性能优秀 + +**Alternatives considered**: +- **Drizzle ORM**: 性能更优但生态系统较新,迁移功能较弱 +- **TypeORM**: 体积较大,类型安全性不如 Prisma +- **MongoDB + Mongoose**: 对于关系型数据不够自然 + +### 最佳实践 + +1. **使用 Prisma Accelerate**: 生产环境使用连接池提升性能 +2. **启用查询日志**: 开发环境启用 `log: ['query', 'info', 'warn', 'error']` +3. **使用事务**: 对于多表操作使用 `$transaction` 确保数据一致性 +4. **批量操作**: 使用 `createMany` 和 `updateMany` 而非循环单条操作 +5. **选择特定字段**: 使用 `select` 减少数据传输量 + +### Schema 设计模式 + +```prisma +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +generator client { + provider = "prisma-client-js" +} + +model Project { + id String @id @default(cuid()) + name String + nameEn String? + slug String @unique + description String + descriptionEn String? + content String? @db.Text + contentEn String? @db.Text + status ProjectStatus @default(ACTIVE) + source String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + tags Tag[] + links ExternalLink[] + + @@index([status, createdAt]) + @@index([slug]) +} + +enum ProjectStatus { + ACTIVE + ARCHIVED +} + +model Tag { + id String @id @default(cuid()) + name String @unique + nameEn String? + slug String @unique + createdAt DateTime @default(now()) + + projects Project[] + + @@index([slug]) +} + +model ExternalLink { + id String @id @default(cuid()) + type LinkType + url String + title String? + projectId String + + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + + @@index([projectId]) + @@index([type]) +} + +enum LinkType { + WEBSITE + GITHUB + HUGGINGFACE + PAPER +} +``` + +--- + +## 4. Tailwind CSS + +### Decision: 选择 Tailwind CSS 作为样式解决方案 + +**Rationale**: +- **实用优先**: 快速构建 UI,无需切换上下文编写 CSS +- **高度可定制**: 通过配置文件完全控制设计系统 +- **生产优化**: 自动清除未使用的样式,保持 CSS 体积最小 +- **响应式优先**: 移动优先的断点系统,适配各种设备 +- **与 shadcn/ui 完美集成**: 组件库基于 Tailwind CSS 构建 + +**Alternatives considered**: +- **CSS Modules**: 组件隔离性好但维护成本高,不支持设计复用 +- **Styled Components**: 运行时注入样式,性能不如 Tailwind +- **Emotion**: 类似 Styled Components,同样有性能问题 + +### 最佳实践 + +1. **使用组件变体**: 使用 `clsx` 或 `tailwind-merge` 处理条件类名 +2. **提取公共模式**: 将重复的类组合提取为组件或工具函数 +3. **使用 @apply 谨慎**: 仅在确实减少代码重复时使用 +4. **启用 JIT 模式**: 确保使用最新版本的 JIT 编译器 +5. **自定义断点**: 根据实际需求调整断点 + +### 工具函数 + +```typescript +// src/lib/utils.ts +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} +``` + +--- + +## 5. 国际化 (i18n) + +### Decision: 选择 next-intl 作为国际化解决方案 + +**Rationale**: +- **App Router 原生支持**: 完美集成 Next.js 14 App Router +- **类型安全**: TypeScript 自动生成翻译键的类型提示 +- **复数支持**: 内置 ICU 消息语法处理复数和格式化 +- **SEO 友好**: 自动生成不同语言的元数据 +- **轻量级**: 与 next-i18next 相比体积更小 + +**Alternatives considered**: +- **next-i18next**: 专为 Pages Router 设计,App Router 支持不完善 +- **react-i18next**: 需要额外配置路由和元数据 +- **自定义方案**: 开发成本高,容易遗漏边界情况 + +### 最佳实践 + +1. **使用翻译键命名空间**: 按功能模块组织翻译文件 +2. **提供上下文参数**: 翻译函数支持动态参数插值 +3. **日期和数字格式化**: 使用 ICU 格式而非手动拼接 +4. **默认语言回退**: 缺失翻译时回退到默认语言 +5. **URL 前缀路由**: 使用 `/[locale]/...` 模式而非子域名 + +### 配置示例 + +```typescript +// src/i18n/request.ts +import { getRequestConfig } from 'next-intl/server' + +export default getRequestConfig(async ({ locale }) => ({ + messages: (await import(`../../messages/${locale}.json`)).default +})) +``` + +```json +// messages/en.json +{ + "common": { + "search": "Search", + "loading": "Loading..." + }, + "home": { + "title": "AI Project Navigator", + "subtitle": "Discover and explore AI projects" + }, + "project": { + "details": "Project Details", + "links": "External Links", + "viewProject": "View Project" + } +} +``` + +```json +// messages/zh.json +{ + "common": { + "search": "搜索", + "loading": "加载中..." + }, + "home": { + "title": "AI 项目导航", + "subtitle": "发现和探索 AI 项目" + }, + "project": { + "details": "项目详情", + "links": "外部链接", + "viewProject": "查看项目" + } +} +``` + +--- + +## 6. 数据验证 (Zod) + +### Decision: 选择 Zod 作为运行时数据验证库 + +**Rationale**: +- **TypeScript 优先**: 自动从 schema 推断类型,与 Prisma 完美配合 +- **链式 API**: 直观的验证规则定义 +- **错误处理**: 详细的错误信息,便于调试和用户提示 +- **零依赖**: 轻量级,不增加太多打包体积 + +**Alternatives considered**: +- **Yup**: API 较老,TypeScript 支持不如 Zod +- **Joi**: 体积较大,主要服务端使用 +- **io-ts**: 函数式编程风格,学习曲线陡峭 + +### 最佳实践 + +1. **复用 Prisma 类型**: 使用 `z.prisma` 扩展从 Prisma schema 生成 Zod schema +2. **自定义错误消息**: 提供中英文双语错误提示 +3. **严格验证**: webhook 接口使用 `strict()` 模式拒绝额外字段 +4. **输入输出分离**: 区分输入验证 schema 和响应输出 schema + +### Webhook 验证示例 + +```typescript +// src/lib/validations.ts +import { z } from 'zod' + +export const LinkTypeEnum = z.enum(['WEBSITE', 'GITHUB', 'HUGGINGFACE', 'PAPER']) + +export const ExternalLinkSchema = z.object({ + type: LinkTypeEnum, + url: z.string().url('Invalid URL format'), + title: z.string().optional() +}) + +export const TagSchema = z.object({ + name: z.string().min(1, 'Tag name is required'), + nameEn: z.string().optional() +}) + +export const ProjectInputSchema = z.object({ + name: z.string().min(1, 'Project name is required'), + nameEn: z.string().optional(), + description: z.string().min(10, 'Description too short'), + descriptionEn: z.string().optional(), + content: z.string().optional(), + contentEn: z.string().optional(), + status: z.enum(['ACTIVE', 'ARCHIVED']).default('ACTIVE'), + source: z.string().optional(), + tags: z.array(TagSchema).min(1, 'At least one tag is required'), + links: z.array(ExternalLinkSchema).min(1, 'At least one link is required') +}) + +export const WebhookPayloadSchema = z.object({ + apiKey: z.string().min(32, 'Invalid API key format'), + projects: z.array(ProjectInputSchema).min(1, 'At least one project is required') +}) + +export type ProjectInput = z.infer +export type WebhookPayload = z.infer +``` + +--- + +## 7. 测试策略 + +### Decision: 使用 Vitest + React Testing Library + Playwright + +**Rationale**: +- **Vitest**: 与 Vite 生态深度集成,比 Jest 更快,原生支持 ESM +- **React Testing Library**: 专注用户行为测试,而非实现细节 +- **Playwright**: 跨浏览器 E2E 测试,支持并行执行 + +**Alternatives considered**: +- **Jest**: 生态成熟但配置复杂,ESM 支持不佳 +- **Cypress**: E2E 测试强大但较重,测试编写较慢 + +### 最佳实践 + +1. **测试金字塔**: 70% 单元测试 + 20% 集成测试 + 10% E2E 测试 +2. **AAA 模式**: Arrange-Act-Assert 结构组织测试 +3. **描述性测试名**: 使用 `should... when...` 格式 +4. **Mock 外部依赖**: 使用 Vitest mock 功能隔离测试 +5. **测试覆盖率**: 配置 `--coverage` 确保达标 + +--- + +## 8. 代码质量工具 + +### Decision: 使用 ESLint + Prettier + TypeScript + +**Rationale**: +- **ESLint**: 代码静态分析,捕获潜在错误 +- **Prettier**: 统一代码格式,减少团队协作摩擦 +- **TypeScript**: 编译时类型检查,配合 strict mode + +### 推荐配置 + +```json +{ + "extends": [ + "next/core-web-vitals", + "prettier" + ], + "rules": { + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/explicit-function-return-type": ["warn", { "allowExpressions": true }], + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} +``` + +--- + +## 9. 部署策略 + +### Decision: 使用 Vercel 进行部署 + +**Rationale**: +- **Next.js 原生支持**: 零配置部署,自动优化 +- **预览环境**: 每个 PR 自动生成预览 URL +- **边缘网络**: 全球 CDN 加速 +- **环境变量**: 安全管理敏感信息 + +**Alternatives considered**: +- **Railway**: 支持数据库但部署配置较复杂 +- **自托管**: 成本低但运维负担重 + +### 环境变量管理 + +```bash +# .env.example +DATABASE_URL="postgresql://user:password@localhost:5432/agent_park" +NEXT_PUBLIC_SITE_URL="http://localhost:3000" +WEBHOOK_API_KEY="your-secure-api-key-min-32-chars" +NEXT_INTL_DEFAULT_LOCALE="zh" +NEXT_INTL_SUPPORTED_LOCALES="zh,en" +``` + +--- + +## 10. 性能优化 + +### Decision: 采用多项性能优化策略 + +**策略列表**: + +1. **ISR (增量静态再生)**: 项目列表页面每 5 分钟重新验证 +2. **动态导入**: 客户端组件使用 `next/dynamic` 按需加载 +3. **图片优化**: 使用 `next/image` 组件自动优化 +4. **字体优化**: 使用 `next/font` 优化字体加载 +5. **代码分割**: 自动按路由分割代码 +6. **Prisma 查询优化**: 使用 `select` 和 `include` 精确获取数据 +7. **缓存策略**: 使用 Redis 缓存热门查询结果(可选) + +--- + +## 总结 + +本研究报告涵盖了 Agent Park 项目的主要技术选型和最佳实践。所有选择都基于以下原则: + +- **类型安全**: TypeScript + Zod + Prisma 提供端到端类型安全 +- **性能优先**: Next.js App Router + ISR + 代码分割确保快速加载 +- **开发体验**: shadcn/ui + Tailwind CSS 提供快速开发能力 +- **可维护性**: ESLint + Prettier + 测试确保代码质量 +- **国际化**: next-intl 支持中英双语 + +这些技术栈完全符合项目章程要求,为后续开发奠定了坚实基础。 diff --git a/specs/001-ai-project-navigator/spec.md b/specs/001-ai-project-navigator/spec.md new file mode 100644 index 0000000..11a13bc --- /dev/null +++ b/specs/001-ai-project-navigator/spec.md @@ -0,0 +1,180 @@ +# 功能规范: Agent Park - AI项目导航网站 + +**功能分支**: `001-ai-project-navigator` +**创建时间**: 2025-12-25 +**状态**: 草稿 +**输入**: 用户描述: "构建一个名为agent-park的全网ai项目导航网站,用户可以搜索和浏览各种领域 查看相关的ai项目 项目详情页需要包括项目的大致情况介绍 和相关网站来源的跳转链接(官网,github等)关于数据来源 需要定期从n8n的相关流程中获取(前期用假数据先把网站搭建起来)我希望风格是 anthropic claude的风格" + +## Clarifications + +### Session 2025-12-25 + +- Q: n8n数据更新频率如何配置? → A: 后台维护一个n8n流程列表,每个流程可以获取不同类型的数据,可以自定义每个定时任务的执行周期 +- Q: 搜索功能范围和排序方式? → A: 全域搜索(名称+描述+标签)+ 按相关性和收录时间排序 +- Q: 初始假数据规模? → A: 20-30个项目,最小化前期工作量,快速验证核心功能 +- Q: 项目分类体系如何设计? → A: 采用标签系统而非固定分类,按标签和相关性进行灵活分组,适应AI领域快速变化的特点 +- Q: 支持哪些外部链接类型? → A: 官网、GitHub、HuggingFace、论文链接四种类型,覆盖主流AI项目场景 +- Q: n8n数据集成方式如何设计? → A: n8n执行完成后主动推送数据到本系统的webhook接口,由本系统定义数据字段结构,定时任务由n8n管理 +- Q: 技术栈如何选择? → A: Next.js全栈方案,使用React组件开发,利用API Routes构建webhook接口 +- Q: webhook接口如何进行身份认证? → A: 使用API Key认证,n8n在请求头中携带预共享密钥进行身份验证 +- Q: 项目数据的必填字段有哪些? → A: name、description、tags、至少一个link为必填字段,确保项目信息的完整性和可用性 +- Q: 日志和监控策略如何设计? → A: 使用简单控制台日志,区分不同日志级别,前期不实现告警机制 +- Q: 当webhook接口接收大量项目数据更新时,前端如何展示数据刷新进度给用户? → A: 不展示更新状态,下次访问时自然生效 +- Q: 当webhook接收到的项目数据中部分项目验证失败(如缺少必填字段),但其他项目有效时,系统应如何处理? → A: 部分成功模式,有效的入库,失败记录日志 + +## 用户场景与测试 *(必填)* + +### 用户故事 1 - 浏览和搜索AI项目 (优先级: P1) + +用户访问网站后,可以通过浏览不同分类或使用搜索功能,快速找到感兴趣的AI项目。 + +**优先级原因**: 这是网站的核心功能,用户需要能够发现和查找AI项目,否则整个网站失去价值。 + +**独立测试**: 可以通过浏览预设的分类列表,或使用搜索框输入关键词,验证项目列表能够正确显示和过滤。 + +**验收场景**: + +1. **给定** 用户访问网站首页,**当** 查看页面时,**那么** 应看到热门标签云和精选项目展示 +2. **给定** 用户在搜索框输入关键词,**当** 提交搜索时,**那么** 应显示包含该关键词的相关项目 +3. **给定** 用户点击某个标签,**当** 选择标签时,**那么** 应只显示带该标签的项目 +4. **给定** 用户组合多个标签筛选,**当** 应用时,**那么** 应显示同时满足所有标签条件的项目 +5. **给定** 项目列表很长,**当** 用户滚动时,**那么** 应支持分页或无限滚动加载更多项目 + +--- + +### 用户故事 2 - 查看项目详细信息 (优先级: P1) + +用户点击某个项目后,可以查看该项目的详细信息,包括项目介绍、官方网站链接、GitHub仓库等。 + +**优先级原因**: 用户需要了解项目的具体信息才能决定是否进一步探索,详情页是信息获取的关键环节。 + +**独立测试**: 点击任意项目卡片,验证详情页正确显示项目描述、外部链接等信息。 + +**验收场景**: + +1. **给定** 用户点击某个项目卡片,**当** 进入详情页时,**那么** 应显示项目名称、描述、相关标签 +2. **给定** 用户在项目详情页,**当** 查看时,**那么** 应看到官网、GitHub、HuggingFace、论文链接等外部来源的跳转链接 +3. **给定** 用户点击外部链接,**当** 访问时,**那么** 应在新标签页中打开对应的官方网站或代码仓库 +4. **给定** 某个项目缺少部分信息,**当** 显示时,**那么** 应合理隐藏或提示"暂无信息" + +--- + +### 用户故事 3 - 响应式设计体验 (优先级: P2) + +用户在不同设备(桌面、平板、手机)上访问网站,都能获得良好的浏览体验。 + +**优先级原因**: 现代用户使用多种设备访问网站,响应式设计确保所有用户都能正常使用。 + +**独立测试**: 在不同屏幕尺寸下访问网站,验证布局自动调整且所有功能可用。 + +**验收场景**: + +1. **给定** 用户使用手机访问,**当** 查看首页时,**那么** 导航和内容应适配竖屏布局 +2. **给定** 用户使用平板横屏访问,**当** 浏览时,**那么** 项目卡片应合理排列且易于点击 +3. **给定** 用户使用桌面大屏访问,**当** 浏览时,**那么** 应充分利用屏幕空间展示更多内容 + +--- + +### 用户故事 4 - 数据更新和管理 (优先级: P3) + +n8n工作流程在执行完成后主动推送最新的AI项目数据到本系统的webhook接口,更新网站内容。 + +**优先级原因**: 这是后台功能,对用户体验影响较小,可以后期实现。前期使用静态数据即可满足需求。 + +**独立测试**: 通过模拟n8n推送请求,验证新数据能够正确更新到网站。 + +**验收场景**: + +1. **给定** n8n工作流程执行完成,**当** 主动推送数据到webhook接口时,**那么** 数据更新在后台静默执行,用户下次访问时自然看到最新内容 +2. **给定** 某些项目不再维护,**当** n8n推送更新数据时,**那么** 这些项目应被标记或移除 +3. **给定** webhook接收数据失败,**当** 发生错误时,**那么** 应返回明确错误信息给n8n并记录日志,现有数据不受影响 +4. **给定** n8n推送的数据格式不符合要求,**当** 验证时,**那么** 应拒绝请求并返回具体错误信息 +5. **给定** 请求缺少有效的API Key,**当** 验证时,**那么** 应返回401未授权错误并记录日志 +6. **给定** 推送的数据中部分项目验证失败,**当** 处理时,**那么** 有效项目正常入库,失败项目记录详细错误信息 + +--- + +### 边界情况 + +- 当搜索无结果时,应显示友好提示并建议用户尝试其他关键词 +- 当外部链接失效(404)时,应在页面上标注"链接可能失效"但仍保留入口 +- 当数据加载缓慢时,应显示加载动画或骨架屏提升体验 +- 当用户输入特殊字符搜索时,应正确处理并防止XSS攻击 +- 当同时有大量用户访问时,网站应保持稳定响应 + +## 需求 *(必填)* + +### 功能需求 + +- **FR-001**: 网站首页必须展示热门标签云和精选项目列表 +- **FR-002**: 用户必须能够通过搜索框输入关键词查找相关项目,搜索范围覆盖项目名称、描述和标签,结果按相关性优先、收录时间次之排序 +- **FR-003**: 用户必须能够通过点击标签筛选项目,支持多标签组合筛选 +- **FR-004**: 每个项目必须显示至少包含:项目名称、简短描述、相关标签 +- **FR-005**: 项目详情页必须显示项目的完整介绍和外部来源链接(官网、GitHub、HuggingFace、论文链接四种类型) +- **FR-006**: 外部链接必须在新标签页中打开,确保用户不会离开导航网站 +- **FR-007**: 网站必须支持响应式设计,适配桌面、平板、手机等设备 +- **FR-008**: 网站设计风格应参考Anthropic Claude官网,采用简洁、现代、优雅的视觉风格 +- **FR-009**: 前期使用静态假数据构建网站,提供webhook接口接收n8n推送的数据 +- **FR-010**: 系统必须提供webhook API接口,接收n8n工作流程推送的项目数据 +- **FR-011**: webhook接口必须验证推送数据的格式和必填字段,拒绝不符合规范的数据请求 +- **FR-012**: webhook接口必须支持部分成功模式,当批量数据中部分项目验证失败时,有效项目正常入库,失败项目记录详细错误信息 +- **FR-013**: webhook接口必须使用API Key进行身份认证,n8n在请求头中携带预共享密钥 +- **FR-014**: 系统必须实现控制台日志记录,区分info、warn、error级别,记录webhook请求和错误信息 + +### 关键实体 *(如果功能涉及数据则包含)* + +- **AI项目 (Project)**: 代表一个AI工具或应用 + - 必填字段: name(项目名称)、description(详细描述)、tags(标签集合)、links(至少一个外部链接) + - 可选字段: status(项目状态:活跃/停更,默认活跃)、createdAt(收录时间,默认当前时间)、source(数据来源标识) +- **标签 (Tag)**: 代表AI项目的特征标记,采用灵活的标签系统而非固定分类,如"图像生成"、"代码助手"、"数据分析"等,一个项目可以有多个标签 +- **外部链接 (ExternalLink)**: 代表项目的来源网站,包含链接类型(官网/GitHub/HuggingFace/论文)和对应的URL + +## 成功标准 *(必填)* + +### 可衡量的结果 + +- **SC-001**: 用户可以在5秒内找到目标AI项目(通过搜索或分类浏览) +- **SC-002**: 95%的用户能够成功从项目详情页跳转到外部来源网站 +- **SC-003**: 网站首屏加载时间在2秒以内,确保良好的用户体验 +- **SC-004**: 网站在主流浏览器(Chrome、Firefox、Safari、Edge)的最新两个版本中正常显示和运行 +- **SC-005**: 移动设备上的用户能够单手完成项目搜索和查看的完整流程 +- **SC-006**: 网站能够支持至少100个并发用户访问而不出现性能明显下降 +- **SC-007**: 用户对网站视觉设计的满意度评分达到4分以上(5分制) + +## 假设 *(必填)* + +### 技术假设 + +- 使用Next.js全栈框架,React组件开发,API Routes构建webhook接口 +- 前期数据存储为JSON格式或静态文件,后期可迁移到数据库 +- 本系统提供webhook API接口接收n8n推送的项目数据 +- n8n按照本系统定义的数据字段结构推送JSON格式数据 +- 网站部署在Vercel或支持Next.js的服务器上 + +### 内容假设 + +- 前期使用20-30个假项目数据进行开发和测试,覆盖主要标签类型 +- AI项目数据由人工审核和整理,确保质量和准确性 +- 项目标签参考主流AI社区的标记方式(如HuggingFace、Papers with Code),保持灵活性 +- 外部链接的维护责任在于对应项目方,网站仅提供导航服务 + +### 用户假设 + +- 用户具备基本的网页浏览和搜索技能 +- 用户主要使用中文进行搜索和浏览 +- 用户希望在发现有趣项目后能够快速访问官方来源获取更多信息 + +### 约束 + +- 网站设计应避免过度使用动画效果,保持简洁快速 +- 不需要用户注册登录功能,所有内容公开可访问 +- 前期不包含用户评论、评分等社交功能,专注于导航展示 + +## 范围外 *(明确不包含的内容)* + +- 用户账户系统和个性化推荐 +- 项目评论、评分、收藏等社交功能 +- 用户提交新项目的功能(全部由管理员从n8n导入) +- 多语言支持(专注于中文用户体验) +- 实时通知或更新提醒功能 +- 付费内容或高级会员功能 diff --git a/specs/001-ai-project-navigator/tasks.md b/specs/001-ai-project-navigator/tasks.md new file mode 100644 index 0000000..1f1d8d8 --- /dev/null +++ b/specs/001-ai-project-navigator/tasks.md @@ -0,0 +1,266 @@ +# 任务: Agent Park - AI项目导航网站 + +**输入**: 来自 `/specs/001-ai-project-navigator/` 的设计文档 +**前置条件**: plan.md, spec.md, data-model.md, contracts/webhook.yaml, research.md, quickstart.md + +**测试**: 本任务清单不包含测试任务。根据章程要求测试驱动开发, 但实际实现可按需决定是否编写测试。 + +**组织结构**: 任务按用户故事分组, 以便每个故事能够独立实施和测试。 + +## 格式: `[ID] [P] [Story] 描述` +- **[P]**: 可以并行运行(不同文件, 无依赖关系) +- **[Story]**: 此任务属于哪个用户故事(例如: US1、US2、US3、US4) +- 在描述中包含确切的文件路径 + +## 路径约定 +- **单一项目**: 仓库根目录下的 `src/`、`tests/`、`prisma/` + +--- + +## 阶段 1: 设置(共享基础设施) + +**目的**: 项目初始化和基本结构 + +- [ ] T001 创建 Next.js 14+ 项目并配置 TypeScript 严格模式 +- [ ] T002 安装核心依赖包 +- [ ] T003 [P] 配置 ESLint 和 Prettier +- [ ] T004 [P] 配置 Tailwind CSS +- [ ] T005 [P] 配置 shadcn/ui 组件库 +- [ ] T006 [P] 配置 next-intl 国际化 + +--- + +## 阶段 2: 基础(阻塞前置条件) + +**目的**: 在任何用户故事可以实施之前必须完成的核心基础设施 + +**⚠️ 关键**: 在此阶段完成之前, 无法开始任何用户故事工作 + +- [ ] T007 设置 PostgreSQL 数据库 +- [ ] T008 创建 Prisma schema 定义 +- [ ] T009 执行 Prisma 初始迁移 +- [ ] T010 生成 Prisma Client +- [ ] T011 创建 Zod 验证 schemas +- [ ] T012 创建 Prisma 客户端单例 +- [ ] T013 创建种子数据脚本 +- [ ] T014 配置 Next.js 国际化路由结构 +- [ ] T015 创建环境变量配置文件 + +**检查点**: 基础就绪 - 现在可以开始并行实施用户故事 + +--- + +## 阶段 3: 用户故事 1 - 浏览和搜索AI项目(优先级: P1)🎯 MVP + +**目标**: 用户可以通过浏览标签云、搜索关键词、筛选标签来发现AI项目 + +**独立测试**: 访问首页可以看到热门标签云和精选项目;输入搜索关键词可看到相关项目;点击标签可以筛选项目 + +### 用户故事 1 的实施 + +- [ ] T016 [P] [US1] 创建首页布局组件 src/app/[locale]/layout.tsx +- [ ] T017 [P] [US1] 创建 Header 导航组件 src/components/layout/Header.tsx +- [ ] T018 [P] [US1] 创建 Footer 组件 src/components/layout/Footer.tsx +- [ ] T019 [P] [US1] 创建 Navigation 组件 src/components/layout/Navigation.tsx +- [ ] T020 [P] [US1] 创建 TagCloud 组件 src/components/project/TagCloud.tsx +- [ ] T021 [P] [US1] 创建 ProjectCard 组件 src/components/project/ProjectCard.tsx +- [ ] T022 [P] [US1] 创建 ProjectList 组件 src/components/project/ProjectList.tsx +- [ ] T023 [P] [US1] 创建 SearchBar 组件 src/components/search/SearchBar.tsx +- [ ] T024 [US1] 实现首页 src/app/[locale]/page.tsx(依赖于 T016-T023) +- [ ] T025 [US1] 实现项目列表页路由 src/app/[locale]/projects/page.tsx +- [ ] T026 [US1] 创建 useSearch hook src/hooks/useSearch.ts +- [ ] T027 [US1] 创建 useProjects hook src/hooks/useProjects.ts +- [ ] T028 [US1] 添加国际化翻译文件 src/messages/zh.json 和 src/messages/en.json +- [ ] T029 [US1] 配置 ISR 缓存策略优化页面性能 + +**检查点**: 此时, 用户应该能够访问首页、浏览项目、使用搜索和标签筛选功能 + +--- + +## 阶段 4: 用户故事 2 - 查看项目详细信息(优先级: P1) + +**目标**: 用户点击项目卡片后可以查看项目完整信息和外部链接 + +**独立测试**: 点击任意项目卡片进入详情页, 验证显示项目描述、标签、外部链接, 点击链接在新标签页打开 + +### 用户故事 2 的实施 + +- [ ] T030 [P] [US2] 创建 ProjectDetail 组件 src/components/project/ProjectDetail.tsx +- [ ] T031 [P] [US2] 创建 ExternalLinkCard 组件 src/components/project/ExternalLinkCard.tsx +- [ ] T032 [US2] 实现项目详情页路由 src/app/[locale]/projects/[id]/page.tsx(依赖于 T030、T031) +- [ ] T033 [US2] 添加项目详情页的国际化翻译 +- [ ] T034 [US2] 实现外部链接的 target="_blank" 安全属性 + +**检查点**: 此时, 用户故事 1 和用户故事 2 都应该完全功能化且可独立测试 + +--- + +## 阶段 5: 用户故事 3 - 响应式设计体验(优先级: P2) + +**目标**: 网站在桌面、平板、手机等不同设备上都能正常显示和操作 + +**独立测试**: 在不同屏幕尺寸下访问网站, 验证布局自动调整且所有功能可用 + +### 用户故事 3 的实施 + +- [ ] T035 [P] [US3] 更新 Header 组件支持响应式布局 +- [ ] T036 [P] [US3] 更新 Navigation 组件支持移动端菜单 +- [ ] T037 [P] [US3] 更新 ProjectCard 组件支持不同屏幕尺寸 +- [ ] T038 [P] [US3] 更新 ProjectList 组件支持响应式网格布局 +- [ ] T039 [P] [US3] 更新 SearchBar 组件支持移动端输入 +- [ ] T040 [US3] 更新 ProjectDetail 组件支持响应式布局 +- [ ] T041 [US3] 配置 Tailwind 响应式断点 +- [ ] T042 [US3] 测试并优化移动端触摸交互 + +**检查点**: 此时, 所有用户故事(US1、US2、US3)都应该独立功能化且支持响应式 + +--- + +## 阶段 6: 用户故事 4 - 数据更新和管理(优先级: P3) + +**目标**: 提供 webhook API 接口, 接收 n8n 工作流程推送的 AI 项目数据更新 + +**独立测试**: 使用 curl 或 Postman 模拟 n8n 推送请求, 验证新数据能够正确更新到数据库 + +### 用户故事 4 的实施 + +- [ ] T043 [P] [US4] 创建 WebhookAuthSchema 验证 +- [ ] T044 [P] [US4] 创建 WebhookPayloadSchema 验证 +- [ ] T045 [P] [US4] 创建 ProjectInputSchema 验证 +- [ ] T046 [US4] 实现 webhook API 端点 src/app/api/webhook/projects/route.ts(依赖于 T043-T045) +- [ ] T047 [US4] 实现 API Key 身份验证中间件 +- [ ] T048 [US4] 实现部分成功模式的批量数据处理逻辑 +- [ ] T049 [US4] 添加 webhook 请求日志记录 +- [ ] T050 [US4] 添加 webhook 错误处理和响应格式 +- [ ] T051 [US4] 配置 webhook API 的环境变量 +- [ ] T052 [US4] 编写 webhook API 使用文档 + +**检查点**: 此时, 所有用户故事现在应该完全功能化 + +--- + +## 阶段 7: 完善与横切关注点 + +**目的**: 影响多个用户故事的改进 + +- [ ] T053 [P] 全局样式优化实现 Anthropic Claude 风格 +- [ ] T054 [P] 优化 Core Web Vitals 性能指标 +- [ ] T055 [P] 添加 loading 和 skeleton 状态提升用户体验 +- [ ] T056 [P] 实现 404 和错误页面 +- [ ] T057 [P] 添加 SEO 元数据配置 +- [ ] T058 配置 next.config.js 优化 +- [ ] T059 创建 README.md 项目文档 +- [ ] T060 运行 quickstart.md 验证完整流程 + +--- + +## 依赖关系与执行顺序 + +### 阶段依赖关系 + +- **设置(阶段 1)**: 无依赖关系 - 可立即开始 +- **基础(阶段 2)**: 依赖于设置完成 - 阻塞所有用户故事 +- **用户故事(阶段 3-6)**: 都依赖于基础阶段完成 + - US1 和 US2 都是 P1 优先级, 可以并行开发 + - US3 (P2) 可以在 US1/US2 基础上进行响应式优化 + - US4 (P3) 是独立的后台功能, 可并行开发 +- **完善(阶段 7)**: 依赖于所有期望的用户故事完成 + +### 用户故事依赖关系 + +- **用户故事 1 (P1)**: 可在基础(阶段 2)后开始 - 无其他故事依赖 +- **用户故事 2 (P1)**: 可在基础(阶段 2)后开始 - 可与 US1 并行开发 +- **用户故事 3 (P2)**: 依赖于 US1 和 US2 完成 - 在现有组件基础上添加响应式支持 +- **用户故事 4 (P3)**: 可在基础(阶段 2)后开始 - 独立的 API 功能 + +### 每个用户故事内部 + +- US1: 布局和基础组件 [P] 并行 → 页面路由集成 → hooks 和国际化 +- US2: 组件 [P] 并行 → 页面路由实现 +- US3: 组件响应式更新 [P] 并行 → 全局配置 +- US4: Schema 定义 [P] 并行 → API 端点实现 → 日志和错误处理 + +### 并行机会 + +- 所有标记为 [P] 的设置任务(T003-T006)可以并行运行 +- US1 的所有布局和基础组件(T016-T023)可以并行开发 +- US2 的组件(T030-T031)可以并行开发 +- US3 的响应式更新(T035-T039)可以并行进行 +- US4 的所有 Schema 定义(T043-T045)可以并行开发 +- US1 和 US2 可以由不同团队成员并行处理 +- US4 可以与 US1/US2/US3 并行开发(独立的后端功能) + +--- + +## 并行示例: 用户故事 1 (US1) + +```bash +# 一起启动用户故事 1 的所有布局和基础组件(可并行): +T016: "创建首页布局组件 src/app/[locale]/layout.tsx" +T017: "创建 Header 导航组件 src/components/layout/Header.tsx" +T018: "创建 Footer 组件 src/components/layout/Footer.tsx" +T019: "创建 Navigation 组件 src/components/layout/Navigation.tsx" +T020: "创建 TagCloud 组件 src/components/project/TagCloud.tsx" +T021: "创建 ProjectCard 组件 src/components/project/ProjectCard.tsx" +T022: "创建 ProjectList 组件 src/components/project/ProjectList.tsx" +T023: "创建 SearchBar 组件 src/components/search/SearchBar.tsx" +``` + +--- + +## 实施策略 + +### 仅 MVP(用户故事 1 + 2) + +1. 完成阶段 1: 设置(T001-T006) +2. 完成阶段 2: 基础(T007-T015) +3. 完成阶段 3: 用户故事 1(T016-T029) +4. 完成阶段 4: 用户故事 2(T030-T034) +5. **停止并验证**: 独立测试核心浏览、搜索和详情功能 +6. 如准备好则部署/演示 + +### 增量交付 + +1. 完成设置 + 基础(T001-T015) → 基础就绪 +2. 添加用户故事 1(T016-T029) → 独立测试 → 部署/演示(MVP!) +3. 添加用户故事 2(T030-T034) → 独立测试 → 部署/演示 +4. 添加用户故事 3(T035-T042) → 独立测试 → 部署/演示 +5. 添加用户故事 4(T043-T052) → 独立测试 → 部署/演示 +6. 添加完善(T053-T060) → 最终部署 + +### 并行团队策略 + +有多个开发人员时: + +1. 团队一起完成设置 + 基础(T001-T015) +2. 基础完成后: + - 开发人员 A: 用户故事 1(T016-T029) + - 开发人员 B: 用户故事 2(T030-T034) + - 开发人员 C: 用户故事 4(T043-T052) +3. US1 和 US2 完成后, 开发人员 A/B 转向用户故事 3(T035-T042) +4. 所有故事完成后, 团队一起进行完善(T053-T060) + +--- + +## 注意事项 + +- [P] 任务 = 不同文件, 无依赖关系 +- [Story] 标签将任务映射到特定用户故事以实现可追溯性 +- 每个用户故事应该独立可完成和可测试 +- 在每个任务或逻辑组后提交 +- 在任何检查点停止以独立验证故事 +- 避免: 模糊任务、相同文件冲突、破坏独立性的跨故事依赖 + +--- + +## 摘要统计 + +- **总任务数**: 60 个任务 +- **阶段数**: 7 个阶段 +- **用户故事数**: 4 个用户故事 + - US1 (P1): 14 个任务 + - US2 (P1): 5 个任务 + - US3 (P2): 8 个任务 + - US4 (P3): 10 个任务 +- **并行任务数**: 约 35 个任务标记为 [P] 可并行 +- **MVP 范围**: 阶段 1-4 (T001-T034), 共 34 个任务