chore: 初始化 Agent Park v2 项目
- 更新项目章程,从模板更新为完整版本,包含 TypeScript 严格模式、组件优先架构和 TDD 原则 - 添加项目配置文件(.claude/settings.json、.gitignore、CLAUDE.md) - 添加完整的 specs 目录,包含需求、契约和文档 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"env": {
|
||||
"HTTP_PROXY": "http://proxy3.bj.petrochina:8080",
|
||||
"HTTPS_PROXY": "http://proxy3.bj.petrochina:8080"
|
||||
}
|
||||
}
|
||||
+79
@@ -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
|
||||
+185
-35
@@ -1,50 +1,200 @@
|
||||
# [PROJECT_NAME] 项目章程
|
||||
<!-- 示例: Spec 章程, TaskFlow 章程等 -->
|
||||
<!--
|
||||
================================================================================
|
||||
同步影响报告 | Constitution Update
|
||||
================================================================================
|
||||
|
||||
版本变更: 未定义 → 1.0.0 (初始版本)
|
||||
|
||||
新增原则:
|
||||
- I. TypeScript 严格模式与类型安全
|
||||
- II. 组件优先架构
|
||||
- III. 测试驱动开发(不可协商)
|
||||
- IV. 性能优先
|
||||
- V. 用户体验一致性
|
||||
- VI. 代码质量与可维护性
|
||||
|
||||
新增章节:
|
||||
- 技术标准(技术栈要求、框架版本)
|
||||
- 开发工作流程(代码审查、质量门禁)
|
||||
|
||||
模板状态:
|
||||
✅ plan-template.md - 已验证兼容
|
||||
✅ spec-template.md - 已验证兼容
|
||||
✅ tasks-template.md - 已验证兼容
|
||||
✅ speckit.plan.md - 已更新章程检查引用
|
||||
✅ speckit.specify.md - 已验证兼容
|
||||
|
||||
后续 TODO: 无
|
||||
|
||||
================================================================================
|
||||
-->
|
||||
|
||||
# Agent Park v2 项目章程
|
||||
|
||||
## 核心原则
|
||||
|
||||
### [PRINCIPLE_1_NAME]
|
||||
<!-- 示例: I. 库优先 -->
|
||||
[PRINCIPLE_1_DESCRIPTION]
|
||||
<!-- 示例: 每个功能都从独立的库开始; 库必须是自包含的、可独立测试的、有文档的; 需要明确的目的 - 不允许仅用于组织的库 -->
|
||||
### I. TypeScript 严格模式与类型安全
|
||||
|
||||
### [PRINCIPLE_2_NAME]
|
||||
<!-- 示例: II. CLI 接口 -->
|
||||
[PRINCIPLE_2_DESCRIPTION]
|
||||
<!-- 示例: 每个库都通过 CLI 暴露功能; 文本输入/输出协议: stdin/args → stdout, 错误 → stderr; 支持 JSON + 人类可读格式 -->
|
||||
TypeScript 严格模式是强制要求。所有代码必须:
|
||||
|
||||
### [PRINCIPLE_3_NAME]
|
||||
<!-- 示例: III. 测试优先(不可协商) -->
|
||||
[PRINCIPLE_3_DESCRIPTION]
|
||||
<!-- 示例: TDD 强制要求: 编写测试 → 用户批准 → 测试失败 → 然后实现; 严格执行红-绿-重构循环 -->
|
||||
- 在 tsconfig.json 中启用 `strict: true` 和所有严格选项
|
||||
- 所有函数必须显式声明参数和返回值类型(禁止隐式 any)
|
||||
- 禁止使用 `@ts-ignore` 和 `@ts-nocheck`(除非用于临时迁移, 需要工单跟踪)
|
||||
- 外部数据必须经过运行时验证(如使用 Zod、Yup 等架构验证库)
|
||||
- 所有 API 响应必须定义明确的类型接口
|
||||
|
||||
### [PRINCIPLE_4_NAME]
|
||||
<!-- 示例: IV. 集成测试 -->
|
||||
[PRINCIPLE_4_DESCRIPTION]
|
||||
<!-- 示例: 需要集成测试的重点领域: 新库契约测试、契约变更、服务间通信、共享模式 -->
|
||||
**理由**: TypeScript 严格模式在编译时捕获潜在错误, 减少运行时问题, 提高代码可维护性和开发体验。类型安全使重构更安全, 代码更自文档化。
|
||||
|
||||
### [PRINCIPLE_5_NAME]
|
||||
<!-- 示例: V. 可观测性, VI. 版本控制和破坏性变更, VII. 简单性 -->
|
||||
[PRINCIPLE_5_DESCRIPTION]
|
||||
<!-- 示例: 文本 I/O 确保可调试性; 需要结构化日志; 或者: MAJOR.MINOR.BUILD 格式; 或者: 从简单开始, YAGNI 原则 -->
|
||||
### 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]
|
||||
<!-- 示例: 所有 PR/审查必须验证合规性; 复杂性必须得到证明; 使用 [GUIDANCE_FILE] 进行运行时开发指导 -->
|
||||
本章程优先于所有其他实践和规范。
|
||||
|
||||
**版本**: [CONSTITUTION_VERSION] | **批准日期**: [RATIFICATION_DATE] | **最后修正**: [LAST_AMENDED_DATE]
|
||||
<!-- 示例: 版本: 2.1.1 | 批准日期: 2025-06-13 | 最后修正: 2025-07-16 -->
|
||||
### 修正流程
|
||||
|
||||
1. 提出修正: 创建议题描述需要修改的原则及原因
|
||||
2. 团队审查: 讨论影响、权衡和替代方案
|
||||
3. 批准: 需要多数团队核心成员同意
|
||||
4. 文档化: 更新章程版本号和修正日期
|
||||
5. 迁移计划: 为破坏性变更提供迁移指南
|
||||
|
||||
### 合规审查
|
||||
|
||||
- 每个 PR 必须符合章程原则
|
||||
- 每次功能规划必须检查章程兼容性
|
||||
- 每季度审查章程并更新(如需要)
|
||||
- 违反章程需要明确理由和记录(在复杂度跟踪中)
|
||||
|
||||
### 版本控制
|
||||
|
||||
- **MAJOR**: 向后不兼容的原则删除或重新定义
|
||||
- **MINOR**: 新原则添加或实质性扩展
|
||||
- **PATCH**: 澄清、措辞、拼写错误修复
|
||||
|
||||
**版本**: 1.0.0 | **批准日期**: 2025-12-25 | **最后修正**: 2025-12-25
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
1\.开发过程中如果遇到api不确定的情况 要用context7 mcp
|
||||
2.如果需要查看页面效果或者修复BUG 要用chrome dev mcp
|
||||
3\.如果要查看数据库要使用dbhub mcp
|
||||
4\.如果要确认shadcn ui组件 要用 shadcn mcp
|
||||
|
||||
@@ -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` 之前更新规范
|
||||
- 所有检查项目均已通过验证,规范质量符合要求
|
||||
- 规范已准备好进入下一阶段(规划或澄清)
|
||||
@@ -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}}"
|
||||
@@ -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<typeof ExternalLinkSchema>
|
||||
export type Tag = z.infer<typeof TagSchema>
|
||||
export type ProjectInput = z.infer<typeof ProjectInputSchema>
|
||||
export type WebhookPayload = z.infer<typeof WebhookPayloadSchema>
|
||||
export type ProjectQuery = z.infer<typeof ProjectQuerySchema>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据迁移策略
|
||||
|
||||
### 初始化迁移
|
||||
|
||||
```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 领域快速变化
|
||||
- **可扩展性**: 清晰的实体关系,便于后续功能扩展
|
||||
- **性能优先**: 合理的索引设计,优化查询性能
|
||||
@@ -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/` 自动管理。
|
||||
|
||||
## 复杂度跟踪
|
||||
|
||||
*仅在章程检查有必须证明的违规时填写*
|
||||
|
||||
无违规项。项目设计完全符合章程要求。
|
||||
|
||||
| 违规 | 为什么需要 | 拒绝更简单替代方案的原因 |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| - | - | - |
|
||||
@@ -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) - 技术选型研究
|
||||
@@ -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<typeof ProjectInputSchema>
|
||||
export type WebhookPayload = z.infer<typeof WebhookPayloadSchema>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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 支持中英双语
|
||||
|
||||
这些技术栈完全符合项目章程要求,为后续开发奠定了坚实基础。
|
||||
@@ -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导入)
|
||||
- 多语言支持(专注于中文用户体验)
|
||||
- 实时通知或更新提醒功能
|
||||
- 付费内容或高级会员功能
|
||||
@@ -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 个任务
|
||||
Reference in New Issue
Block a user