diff --git a/README.md b/README.md
index 9c3fdbd..617a03d 100644
--- a/README.md
+++ b/README.md
@@ -1,135 +1,173 @@
# PR-Helper
-AI-powered PR description generator and code review tool. Self-hosted, designed for internal/local use.
+AI 驱动的 PR 描述生成器和代码审查工具。自托管设计,专为内部/本地使用。
-## Features
+## 功能特性
-- **PR Description Generation** — Select commits from an interactive Git graph, get a structured PR description via LLM
-- **AI Code Review** — Per-file analysis with severity ratings, inline suggestions on the diff view
-- **Interactive Git Graph** — D3.js visualization with click-to-select base/head refs
-- **Diff Viewer** — Split/unified view powered by diff2html with syntax highlighting
-- **Review Notes** — Add markdown notes at overall, file, or suggestion level
-- **PDF Export** — Generate printable review reports
-- **Repository Caching** — Cloned repos are cached with configurable expiry
+- **PR 描述生成** — 从交互式 Git 图形中选择提交,通过 LLM 生成结构化 PR 描述
+- **AI 代码审查** — 按文件分析,严重程度评级,在差异视图中内联显示建议
+- **交互式 Git 图形** — D3.js 可视化,点击选择 base/head 引用
+- **差异查看器** — diff2html 驱动的分割/统一视图,支持语法高亮
+- **审查笔记** — 在整体、文件或建议级别添加 Markdown 笔记
+- **PDF 导出** — 生成可打印的审查报告
+- **仓库缓存** — 克隆的仓库缓存,可配置过期时间
-## Quick Start
+## 快速开始
-### Docker (recommended)
+### Docker(推荐)
```bash
docker compose up --build
```
-Open http://localhost:8080.
+打开 http://localhost:8080。
-### Manual Build
+### 手动构建
-Requirements: Go 1.22+, CGO enabled (for SQLite), Chromium (for PDF export)
+要求:Go 1.22+,CGO 启用(用于 SQLite),Chromium(用于 PDF 导出)
```bash
-# Install dependencies
+# 安装依赖
go mod download
-# Build
+# 构建
CGO_ENABLED=1 go build -o pr-helper .
-# Run
+# 运行
./pr-helper
```
-Server listens on `:8080` by default.
+服务器默认监听 `:8080` 端口。
-## Configuration
+## 配置
-### LLM Settings (required)
+### LLM 设置(必需)
-Navigate to **Settings** (`/settings`) and configure:
+导航到 **设置** (`/settings`) 并配置:
-| Setting | Description | Default |
-|---------|-------------|---------|
-| API Endpoint | OpenAI-compatible API URL | `https://api.openai.com/v1` |
-| API Key | Your API key | (empty) |
-| Model | Model name | `gpt-4o` |
+| 设置 | 说明 | 默认值 |
+|------|------|--------|
+| API 端点 | OpenAI 兼容 API URL | `https://api.openai.com/v1` |
+| API 密钥 | 您的 API 密钥 | (空) |
+| 模型 | 模型名称 | `gpt-4o` |
-Works with any OpenAI-compatible API: OpenAI, Deepseek, Ollama, vLLM, etc.
+支持任何 OpenAI 兼容 API:OpenAI、Deepseek、Ollama、vLLM 等。
-### Review Settings
+### 审查设置
-| Setting | Description | Default |
-|---------|-------------|---------|
-| Top-N Files | Max files to analyze per review (0 = all) | `20` |
-| Concurrency | Parallel file analyses | `5` |
+| 设置 | 说明 | 默认值 |
+|------|------|--------|
+| Top-N 文件数 | 每次审查分析的最大文件数 (0 = 全部) | `20` |
+| 并发数 | 并行文件分析数 | `5` |
-### Cache Settings
+### 缓存设置
-| Setting | Description | Default |
-|---------|-------------|---------|
-| Max Age (days) | Auto-cleanup threshold | `7` |
-| Max Size (MB) | Total cache size limit | `5000` |
+| 设置 | 说明 | 默认值 |
+|------|------|--------|
+| 最大保留天数 | 自动清理阈值 | `7` |
+| 最大缓存大小 (MB) | 总缓存大小限制 | `5000` |
-### Environment Variables
+### 环境变量
-| Variable | Description | Default |
-|----------|-------------|---------|
-| `PORT` | Server port | `8080` |
-| `GIN_MODE` | Gin mode (`debug`/`release`) | `debug` |
-| `DATA_DIR` | Data directory path | `./data` |
-| `CHROME_BIN` | Chromium binary path (for PDF) | `/usr/bin/chromium-browser` |
+| 变量 | 说明 | 默认值 |
+|------|------|--------|
+| `PORT` | 服务器端口 | `8080` |
+| `GIN_MODE` | Gin 模式 (`debug`/`release`) | `debug` |
+| `DATA_DIR` | 数据目录路径 | `./data` |
+| `MYSQL_HOST` | MySQL 主机地址 | `127.0.0.1` |
+| `MYSQL_PORT` | MySQL 端口 | `3306` |
+| `MYSQL_USER` | MySQL 用户名 | `root` |
+| `MYSQL_PASSWORD` | MySQL 密码 | (空) |
+| `MYSQL_DATABASE` | MySQL 数据库名 | `pr_helper` |
-## Usage
+## 使用方法
-1. **Clone a repo** — Paste a Git URL on the homepage, optionally provide credentials for private repos
-2. **Browse the graph** — View branches, tags, and commit history in the interactive D3.js graph
-3. **Select refs** — Click nodes in the graph or use the dropdown selectors to pick base and head
-4. **Generate PR description** — Navigate to Generate, select refs, click Generate
-5. **Run code review** — Navigate to Review, configure Top-N and concurrency, click Start Review
-6. **Add notes** — Click into any suggestion or the overall section to add markdown notes
-7. **Export PDF** — Click Export PDF to download a formatted report
+1. **克隆仓库** — 在首页粘贴 Git URL,可选提供私有仓库的认证信息
+2. **浏览图形** — 在交互式 D3.js 图形中查看分支、标签和提交历史
+3. **选择引用** — 点击图形中的节点或使用下拉选择器选择 base 和 head
+4. **生成 PR 描述** — 导航到"生成",选择引用,点击"生成"
+5. **运行代码审查** — 导航到"审查",配置 Top-N 和并发数,点击"开始审查"
+6. **添加笔记** — 点击任何建议或整体部分添加 Markdown 笔记
+7. **导出 PDF** — 点击"导出 PDF"下载格式化报告
-## API Routes
+## API 路由
-### Pages
+### 页面
-| Method | Path | Description |
-|--------|------|-------------|
-| GET | `/` | Homepage — clone form and cached repos |
-| GET | `/repo/:id` | Repository — Git graph and diff viewer |
-| GET | `/repo/:id/generate` | PR description generation |
-| GET | `/repo/:id/review` | AI code review |
-| GET | `/settings` | Settings page |
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| GET | `/` | 首页 — 克隆表单和已缓存仓库 |
+| GET | `/repo/:id` | 仓库详情 — Git 图形和差异查看器 |
+| GET | `/settings` | 设置页面 |
### API
-| Method | Path | Description |
-|--------|------|-------------|
-| POST | `/api/repos` | Clone repository (SSE stream) |
-| GET | `/api/repos` | List cached repositories |
-| DELETE | `/api/repos/:id` | Delete repository cache |
-| POST | `/api/repos/:id/cleanup` | Trigger cache cleanup |
-| GET | `/api/repos/:id/graph` | Git graph data (JSON) |
-| GET | `/api/repos/:id/refs` | Branches and tags |
-| GET | `/api/repos/:id/commits` | Commit log for a ref |
-| GET | `/api/repos/:id/diff` | Diff between two refs |
-| POST | `/api/repos/:id/generate` | Generate PR description (SSE) |
-| POST | `/api/repos/:id/review` | AI code review (SSE) |
-| GET | `/api/repos/:id/review/analyses` | List past reviews |
-| GET | `/api/repos/:id/review/analyses/:aid` | Get single review |
-| POST | `/api/repos/:id/review/notes` | Save a review note |
-| GET | `/api/repos/:id/review/notes` | Get review notes |
-| POST | `/api/repos/:id/review/pdf` | Generate PDF report |
-| GET | `/api/settings` | Get settings |
-| PUT | `/api/settings` | Update settings |
+| 方法 | 路径 | 说明 |
+|------|------|------|
+| POST | `/api/repos` | 克隆仓库 (SSE 流) |
+| GET | `/api/repos` | 列出已缓存仓库 |
+| DELETE | `/api/repos/:id` | 删除仓库缓存 |
+| POST | `/api/repos/:id/cleanup` | 触发缓存清理 |
+| POST | `/api/repos/:id/pull` | 拉取更新 |
+| GET | `/api/repos/:id/graph` | Git 图形数据 (JSON) |
+| GET | `/api/repos/:id/refs` | 分支和标签 |
+| GET | `/api/repos/:id/commits` | 提交日志 |
+| GET | `/api/repos/:id/diff` | 两个引用之间的差异 |
+| POST | `/api/repos/:id/generate` | 生成 PR 描述 (SSE) |
+| POST | `/api/repos/:id/review` | AI 代码审查 (SSE) |
+| GET | `/api/repos/:id/review/analyses` | 列出历史审查 |
+| GET | `/api/repos/:id/review/analyses/:aid` | 获取单个审查 |
+| POST | `/api/repos/:id/review/notes` | 保存审查笔记 |
+| GET | `/api/repos/:id/review/notes` | 获取审查笔记 |
+| GET | `/api/settings` | 获取设置 |
+| PUT | `/api/settings` | 更新设置 |
-## Tech Stack
+## 技术栈
-- **Backend**: Go, Gin, SQLite (go-sqlite3), go-git, chromedp
-- **Frontend**: Go html/template, HTMX, D3.js, diff2html, Tailwind CSS
-- **LLM**: OpenAI-compatible API with SSE streaming
+| 层级 | 技术 |
+|------|------|
+| 后端 | Go、Gin、MySQL、go-git |
+| 前端 | Go html/template、HTMX、D3.js、diff2html、Tailwind CSS |
+| LLM | OpenAI 兼容 API (SSE 流式传输) |
+| 部署 | Docker |
-## Security
+## 项目结构
-**No authentication.** Do not expose to the public internet. Intended for internal/local use only.
+```
+PR-Helper/
+├── main.go # 应用入口
+├── config/ # 配置加载
+├── database/ # 数据库初始化和迁移
+├── handlers/ # HTTP 处理器 (页面 + JSON API + SSE)
+├── models/ # 数据模型
+├── services/ # 业务逻辑 (Git 操作、LLM 调用、缓存管理)
+├── templates/ # Go HTML 模板
+├── static/ # CSS (Tailwind)、JS、第三方库
+├── data/ # 克隆的仓库缓存
+└── docs/ # 技术文档
+```
-## License
+## 技术文档
+
+详细的技术文档位于 [docs/](docs/) 目录:
+
+| 文档 | 说明 |
+|------|------|
+| [01-architecture.md](docs/01-architecture.md) | 架构概览 — 项目结构、技术栈、数据流 |
+| [02-backend-services.md](docs/02-backend-services.md) | 后端服务层 — Git、LLM、PR 生成、代码审查 |
+| [03-frontend-interaction.md](docs/03-frontend-interaction.md) | 前端交互设计 — SSE、D3.js 图形、差异查看器 |
+| [04-database-design.md](docs/04-database-design.md) | 数据库设计 — 表结构、索引、迁移策略 |
+| [05-api-reference.md](docs/05-api-reference.md) | API 接口文档 — 完整的 RESTful API 参考 |
+| [06-sse-streaming.md](docs/06-sse-streaming.md) | SSE 流式传输 — 协议、实现、错误处理 |
+| [07-llm-integration.md](docs/07-llm-integration.md) | LLM 集成 — 配置、提示模板、流式调用 |
+| [08-deployment.md](docs/08-deployment.md) | 部署运维 — Docker、反向代理、监控 |
+| [09-development-guide.md](docs/09-development-guide.md) | 开发指南 — 环境搭建、代码规范、测试 |
+| [10-troubleshooting.md](docs/10-troubleshooting.md) | 故障排查 — 常见问题和解决方案 |
+
+## 安全
+
+**无认证机制。** 请勿暴露到公网。仅限内部/本地使用。
+
+## 许可证
MIT
diff --git a/docs/01-architecture.md b/docs/01-architecture.md
new file mode 100644
index 0000000..03a32f0
--- /dev/null
+++ b/docs/01-architecture.md
@@ -0,0 +1,163 @@
+# 架构概览
+
+## 项目简介
+
+PR-Helper 是一个自托管的 Web 服务,用于从 Git 历史自动生成 PR 描述并执行 AI 代码审查。专为内部/本地使用设计(无认证)。
+
+## 技术栈
+
+| 层级 | 技术 |
+|------|------|
+| 后端 | Go + Gin + MySQL + go-git |
+| 前端 | Go html/template + HTMX + D3.js + diff2html + Tailwind CSS |
+| LLM | OpenAI 兼容 API(SSE 流式传输) |
+| 部署 | Docker |
+
+## 目录结构
+
+```
+PR-Helper/
+├── main.go # 应用入口
+├── config/ # 配置加载
+│ └── config.go
+├── database/ # 数据库初始化和迁移
+│ └── db.go
+├── handlers/ # HTTP 处理器(页面 + JSON API + SSE)
+│ ├── auth.go # 认证处理
+│ ├── generate.go # PR 生成 SSE 端点
+│ ├── pages.go # 页面渲染
+│ ├── repos.go # 仓库管理 API
+│ ├── review.go # 代码审查 SSE 端点
+│ ├── settings.go # 设置 API
+│ └── middleware.go # 中间件
+├── models/ # 数据模型
+│ ├── repository.go # 仓库模型
+│ ├── analysis.go # 分析结果模型
+│ └── settings.go # 默认设置
+├── services/ # 业务逻辑
+│ ├── git.go # Git 操作(克隆、差异、图形数据)
+│ ├── llm.go # LLM API 调用(SSE 流式)
+│ ├── generate.go # PR 描述生成
+│ ├── review.go # AI 代码审查
+│ ├── cache.go # 仓库缓存管理
+│ └── notes.go # 审查笔记
+├── templates/ # Go HTML 模板
+│ ├── layouts/ # 布局模板
+│ ├── pages/ # 页面模板
+│ └── partials/ # 局部模板
+├── static/ # 静态资源
+│ ├── css/ # Tailwind 输出
+│ ├── js/ # 自定义 JS
+│ └── lib/ # 第三方库
+└── data/ # 数据存储
+ └── repos/ # 克隆的仓库缓存
+```
+
+## 数据流
+
+```
+浏览器 ↔ Gin 处理器 → 服务层(git/llm)→ MySQL + 文件系统(data/)
+```
+
+### 请求流程
+
+1. **浏览器** 发起 HTTP 请求(HTMX 或 fetch)
+2. **Gin 路由** 匹配处理器函数
+3. **中间件** 验证会话和用户认证
+4. **处理器** 解析请求参数
+5. **服务层** 执行业务逻辑
+6. **数据库/文件系统** 持久化数据
+7. **SSE 流** 返回实时更新(可选)
+
+## 核心模块
+
+### 配置模块 (config/)
+
+- 从 `.env` 文件加载环境变量
+- 提供默认值
+- 生成随机会话密钥(如未配置)
+
+### 数据库模块 (database/)
+
+- MySQL 连接管理
+- 自动迁移(CREATE TABLE IF NOT EXISTS)
+- 增量迁移(ALTER TABLE,忽略重复列错误)
+- 默认设置初始化
+
+### 处理器模块 (handlers/)
+
+- **页面处理器**: 渲染 HTML 模板
+- **仓库处理器**: 克隆、删除、拉取、获取图形/差异
+- **生成处理器**: PR 描述生成(SSE 流式)
+- **审查处理器**: AI 代码审查(SSE 流式)
+- **设置处理器**: 读取/更新用户设置
+
+### 服务模块 (services/)
+
+- **git.go**: 使用 go-git 库执行 Git 操作
+- **llm.go**: OpenAI 兼容 API 调用,支持流式响应
+- **generate.go**: 从提交历史和差异生成 PR 描述
+- **review.go**: Top-N 策略的 AI 代码审查
+- **cache.go**: 仓库缓存生命周期管理
+
+### 前端模块 (static/js/)
+
+- **sse.js**: SSE 客户端,支持 POST 请求
+- **graph.js**: D3.js Git 图形可视化
+- **diff-viewer.js**: diff2html 差异查看器
+- **review-inline.js**: 内联 AI 建议
+- **markdown.js**: Markdown 渲染
+- **note-editor.js**: 审查笔记编辑器
+
+## 关键设计决策
+
+### 1. SSE 流式传输
+
+选择 SSE 而非 WebSocket 的原因:
+- 单向服务器到客户端通信足够
+- HTTP 兼容性更好
+- 实现更简单
+- 自动重连
+
+### 2. Top-N 策略
+
+大型差异处理:
+- 按变更行数排序文件
+- 只分析前 N 个文件(默认 20)
+- 每个文件独立分析
+- 最后生成汇总
+
+### 3. 三级审查笔记
+
+- `overall`: 整体审查笔记
+- `file`: 文件级别笔记
+- `suggestion`: 建议级别笔记
+
+### 4. 会话认证
+
+- 基于 Cookie 的会话
+- 7 天过期
+- HttpOnly 标记
+- 随机会话密钥
+
+## 扩展性考虑
+
+### 水平扩展
+
+- 无状态设计(会话存储在 Cookie)
+- 数据库可外部化
+- 文件存储可替换为对象存储
+
+### LLM 集成
+
+- 支持任何 OpenAI 兼容 API
+- 每用户独立配置
+- 可扩展为多模型支持
+
+## 安全考虑
+
+- 仅限内部使用(无认证)
+- 不暴露到公网
+- SQL 参数化查询
+- 会话 HttpOnly
+- 凭证不明文存储在 JSON 响应中
diff --git a/docs/02-backend-services.md b/docs/02-backend-services.md
new file mode 100644
index 0000000..7a5ac07
--- /dev/null
+++ b/docs/02-backend-services.md
@@ -0,0 +1,419 @@
+# 后端服务层
+
+## 概述
+
+后端服务层是 PR-Helper 的核心业务逻辑层,位于 handlers 和数据层之间。主要职责包括 Git 操作、LLM 调用、PR 生成和代码审查。
+
+## 服务架构
+
+```
+handlers/
+ ↓ 调用
+services/
+ ├── git.go # Git 操作
+ ├── llm.go # LLM API 调用
+ ├── generate.go # PR 描述生成
+ ├── review.go # AI 代码审查
+ ├── cache.go # 缓存管理
+ └── notes.go # 审查笔记
+ ↓ 使用
+database/ + filesystem/
+```
+
+## Git 服务 (git.go)
+
+### 核心功能
+
+#### 克隆仓库
+
+```go
+func Clone(opts CloneOptions, progressFn func(event string, data interface{})) (*CloneResult, error)
+```
+
+- 支持基本认证(用户名/密码)
+- 可选深度克隆
+- 进度回调(SSE 事件)
+- 返回仓库路径、分支、标签、提交数、大小
+
+#### 获取引用
+
+```go
+func GetRefs(repo *git.Repository) ([]RefInfo, error)
+```
+
+- 返回所有分支和标签
+- 标记 HEAD 分支
+- 解析注解标签到提交
+
+#### 获取图形数据
+
+```go
+func GetGraph(repo *git.Repository, maxCommits int) (*GraphData, error)
+```
+
+- D3.js 兼容格式
+- 包含提交、引用、边
+- BFS 遍历,限制最大提交数
+
+#### 获取差异
+
+```go
+func GetDiff(repo *git.Repository, baseRef, headRef string) (string, error)
+func GetDiffFiles(repo *git.Repository, baseRef, headRef string) ([]FileDiff, error)
+```
+
+- 统一差异格式
+- 按文件分割
+- 解析变更行数
+
+#### 获取提交日志
+
+```go
+func GetCommitLog(repo *git.Repository, refName string, maxCommits int) ([]CommitInfo, error)
+```
+
+- BFS 遍历父提交
+- 限制最大提交数
+- 包含作者、时间、父提交
+
+### 数据结构
+
+```go
+type RefInfo struct {
+ Name string `json:"name"`
+ Hash string `json:"hash"`
+ IsHead bool `json:"is_head"`
+ IsTag bool `json:"is_tag"`
+}
+
+type CommitInfo struct {
+ Hash string `json:"hash"`
+ ShortHash string `json:"short_hash"`
+ Message string `json:"message"`
+ Author string `json:"author"`
+ Email string `json:"email"`
+ Timestamp string `json:"timestamp"`
+ ParentIDs []string `json:"parent_ids"`
+}
+
+type GraphData struct {
+ Commits []CommitInfo `json:"commits"`
+ Refs []RefInfo `json:"refs"`
+ Edges []Edge `json:"edges"`
+}
+
+type FileDiff struct {
+ Filename string `json:"filename"`
+ Patch string `json:"patch"`
+}
+```
+
+## LLM 服务 (llm.go)
+
+### 配置管理
+
+```go
+type LLMConfig struct {
+ Endpoint string
+ APIKey string
+ Model string
+}
+
+func GetLLMConfig(db *sql.DB, userID int64) (LLMConfig, error)
+```
+
+- 从 user_settings 表读取
+- 每用户独立配置
+- 默认模型: deepseek-v4-pro
+
+### 流式调用
+
+```go
+type StreamCallback func(event string, data interface{})
+
+func ChatStream(config LLMConfig, messages []goopenai.ChatCompletionMessage, callback StreamCallback) (string, error)
+```
+
+- 使用 go-openai 客户端
+- SSE 流式响应
+- 回调函数处理每个 chunk
+- 返回完整响应文本
+
+### JSON 提取
+
+```go
+func extractJSON(s string) string
+```
+
+- 从 Markdown 代码块提取
+- 从混合文本中提取 JSON 对象/数组
+- 处理嵌套括号
+
+## PR 生成服务 (generate.go)
+
+### 生成流程
+
+```go
+func GeneratePR(db *sql.DB, repoPath, base, head string, userID int64, callback StreamCallback) (string, error)
+```
+
+1. **读取 LLM 配置**: 从用户设置获取
+2. **打开仓库**: 使用 go-git
+3. **获取提交**: 过滤 base 和 head 之间的提交
+4. **获取差异**: 生成统一差异
+5. **构建提示**: 包含提交记录和差异
+6. **调用 LLM**: 流式生成
+7. **返回结果**: Markdown 格式的 PR 描述
+
+### 提示模板
+
+```markdown
+你是一个专业的技术文档撰写助手。根据以下 Git 变更信息,生成一份 PR 描述。
+
+## Commit 记录
+{commits}
+
+## 代码变更 (Diff)
+{diff}
+
+请直接输出 Markdown 格式的 PR 描述,包含以下部分:
+
+# 标题
+**类型**: feat|fix|refactor|docs|chore|style|test|perf
+
+## 概述
+一段话概述变更内容
+
+## 详细说明
+按模块分组的详细变更说明
+
+## 影响范围
+影响范围说明
+```
+
+### 大型差异处理
+
+- 截断到 60k 字符
+- 保留完整提交记录
+- 标记截断位置
+
+## 代码审查服务 (review.go)
+
+### 审查流程
+
+```go
+func GenerateReview(db *sql.DB, repoPath, base, head string, topN, concurrency int, userID int64, callback StreamCallback) (*ReviewResult, error)
+```
+
+1. **获取差异文件**: 按文件分割
+2. **排序**: 按变更行数降序
+3. **Top-N 过滤**: 只分析前 N 个文件
+4. **并发审查**: 使用信号量控制并发
+5. **生成汇总**: 聚合所有文件结果
+6. **返回结果**: 结构化 JSON
+
+### Top-N 策略
+
+```go
+// 按变更行数排序
+sort.Slice(files, func(i, j int) bool {
+ return countDiffLines(files[i].Patch) > countDiffLines(files[j].Patch)
+})
+
+// 应用 Top-N
+if topN > 0 && topN < len(files) {
+ files = files[:topN]
+}
+```
+
+- 默认 Top-N: 20
+- 可通过设置或请求参数调整
+- 0 表示分析所有文件
+
+### 并发控制
+
+```go
+sem := make(chan struct{}, concurrency)
+var wg sync.WaitGroup
+
+for i, file := range files {
+ wg.Add(1)
+ go func(idx int, f FileDiff) {
+ defer wg.Done()
+ sem <- struct{}{} // 获取槽位
+ defer func() { <-sem }() // 释放槽位
+ // ... 审查逻辑
+ }(i, file)
+}
+```
+
+- 默认并发数: 5
+- 信号量控制最大并发
+- 线程安全的回调函数
+
+### 单文件审查提示
+
+```markdown
+你是一个资深代码审查专家。请审查以下代码变更,给出专业的 Review 意见。
+
+## 文件: {filename}
+## 变更行数: +{additions} / -{deletions}
+
+## Diff
+{patch}
+
+请按以下 JSON 格式输出审查意见(直接输出 JSON 数组,不要包含 markdown 代码块标记):
+[
+ {
+ "severity": "critical 或 warning 或 info",
+ "description": "问题描述",
+ "suggestion": "建议的修改方案",
+ "code_example": "建议的代码(如有)"
+ }
+]
+
+严重程度说明:
+- critical: 严重问题(安全漏洞、数据丢失风险、崩溃风险)
+- warning: 建议改进(性能问题、代码规范、可维护性)
+- info: 提示信息(最佳实践、可选优化)
+
+如果代码没有问题,输出空数组 []。
+请用中文回复。
+```
+
+### 汇总生成
+
+```markdown
+以下是多个文件的代码审查结果,请给出整体评估:
+
+{reviews}
+
+请按以下 JSON 格式输出(直接输出 JSON,不要包含 markdown 代码块标记):
+{
+ "score": 7,
+ "overall": "总体评价(2-3 句话)",
+ "findings": "按严重程度排序的主要发现汇总",
+ "recommendations": "改进建议,用换行分隔多条建议"
+}
+```
+
+### 数据结构
+
+```go
+type ReviewSuggestion struct {
+ Severity string `json:"severity"`
+ Description string `json:"description"`
+ Suggestion string `json:"suggestion"`
+ CodeExample string `json:"code_example,omitempty"`
+}
+
+type FileReview struct {
+ FileName string `json:"file_name"`
+ ChangeLines int `json:"change_lines"`
+ Suggestions []ReviewSuggestion `json:"suggestions"`
+ RawReview string `json:"raw_review"`
+}
+
+type ReviewSummary struct {
+ Score int `json:"score"`
+ Overall string `json:"overall"`
+ Findings string `json:"findings"`
+ Recommendations string `json:"recommendations"`
+}
+
+type ReviewResult struct {
+ FileReviews []FileReview `json:"file_reviews"`
+ Summary ReviewSummary `json:"summary"`
+ TopN int `json:"top_n"`
+}
+```
+
+## SSE 事件格式
+
+### PR 生成事件
+
+| 事件 | 数据 | 说明 |
+|------|------|------|
+| `content` | `{content: string}` | LLM 输出的 Markdown 片段 |
+| `done` | `{content: ""}` | 生成完成 |
+
+### 代码审查事件
+
+| 事件 | 数据 | 说明 |
+|------|------|------|
+| `start` | `{total_files, reviewed_files, top_n}` | 审查开始 |
+| `file_start` | `{file, index, total}` | 开始审查文件 |
+| `content` | `{content: string}` | LLM 输出片段 |
+| `suggestion` | `{file, severity, content}` | 审查建议 |
+| `file_end` | `{file}` | 文件审查完成 |
+| `summary` | `{score, overall, findings, recommendations}` | 汇总结果 |
+| `progress` | `{step: string}` | 进度更新 |
+| `error` | `{message: string}` | 错误信息 |
+| `done` | `{content: ""}` | 审查完成 |
+
+## 缓存服务 (cache.go)
+
+### 仓库缓存
+
+- 存储路径: `data/repos/`
+- 命名规则: `{url}_{timestamp}`
+- 过期清理: 可配置天数(默认 7 大)
+- 大小限制: 可配置最大大小(默认 5GB)
+
+### 缓存生命周期
+
+1. **克隆**: 首次访问时克隆
+2. **使用**: 更新 last_used 时间
+3. **过期**: 定期清理过期仓库
+4. **删除**: 手动或自动删除
+
+## 笔记服务 (notes.go)
+
+### 笔记操作
+
+```go
+func SaveNote(db *sql.DB, analysisID int64, scope, scopeKey, content string) (*models.ReviewNote, error)
+func GetNotes(db *sql.DB, analysisID int64, scope string) ([]models.ReviewNote, error)
+```
+
+### 作用域类型
+
+- `overall`: 整体审查笔记
+- `file`: 文件级别笔记(scope_key = 文件名)
+- `suggestion`: 建议级别笔记(scope_key = 建议 ID)
+
+## 错误处理
+
+### 错误传播
+
+- 服务层返回 error
+- 处理器层转换为 HTTP 状态码
+- SSE 端点通过 error 事件发送
+
+### 常见错误
+
+- 仓库不存在
+- LLM API 密钥未配置
+- LLM 调用失败
+- 差异过大
+- 数据库操作失败
+
+## 性能优化
+
+### 并发审查
+
+- 信号量控制并发数
+- 线程安全的回调
+- 避免数据竞争
+
+### 差异截断
+
+- 单文件: 30k 字符
+- PR 生成: 60k 字符
+- 避免 token 超限
+
+### 增量渲染
+
+- 按文件分批
+- 异步处理
+- 进度反馈
diff --git a/docs/03-frontend-interaction.md b/docs/03-frontend-interaction.md
new file mode 100644
index 0000000..9e1b0e3
--- /dev/null
+++ b/docs/03-frontend-interaction.md
@@ -0,0 +1,339 @@
+# 前端交互设计
+
+## 概述
+
+PR-Helper 前端采用渐进式增强设计,结合 HTMX、D3.js、diff2html 等库,提供流畅的交互体验。
+
+## 技术栈
+
+| 技术 | 用途 |
+|------|------|
+| Go html/template | 服务端渲染 |
+| HTMX 2.x | 动态交互 |
+| D3.js 7.x | Git 图形可视化 |
+| diff2html 3.x | 差异渲染 |
+| highlight.js 11.x | 语法高亮 |
+| marked.js | Markdown 渲染 |
+| Tailwind CSS | 样式框架 |
+
+## 核心组件
+
+### 1. SSE 客户端 (sse.js)
+
+#### 设计目标
+
+- 支持 POST 请求的 SSE(EventSource 只支持 GET)
+- 流式读取响应体
+- 事件分发机制
+
+#### API 设计
+
+```javascript
+const SSE = {
+ async post(url, body, handlers = {}) {
+ // 返回 controller,支持 abort()
+ return { abort: () => controller.abort() };
+ }
+};
+
+// 使用示例
+SSE.post('/api/repos/1/review', { base: 'main', head: 'feature' }, {
+ start: (data) => console.log('开始', data),
+ content: (data) => console.log('内容', data.content),
+ suggestion: (data) => console.log('建议', data),
+ summary: (data) => console.log('汇总', data),
+ done: () => console.log('完成'),
+ error: (data) => console.error('错误', data.message)
+});
+```
+
+#### 实现细节
+
+- 使用 `fetch()` + `ReadableStream`
+- 手动解析 SSE 协议(event: / data:)
+- 支持中断请求
+- 自动处理 JSON 解析
+
+### 2. Git 图形 (graph.js)
+
+#### 功能特性
+
+- D3.js SVG 渲染
+- 多分支彩色泳道
+- 交互式 base/head 选择
+- 分支标签右对齐
+- 图例显示
+
+#### 布局算法
+
+```javascript
+assignLanes() {
+ // 1. HEAD 分支获得泳道 0
+ // 2. 其他分支按顺序分配泳道
+ // 3. 合并提交的第二父提交获得新泳道
+ // 4. 未访问的提交分配到泳道 0
+}
+```
+
+#### 交互设计
+
+- **点击**: 选择 base/head 提交
+- **悬停**: 高亮节点
+- **选择状态**:
+ - 绿色: base 提交
+ - 橙色: head 提交
+ - 蓝色: 普通提交
+
+#### 回调机制
+
+```javascript
+gitGraph.onSelectionChange = (baseHash, headHash) => {
+ // 更新 base/head 选择
+ // 触发差异加载
+};
+```
+
+### 3. 差异查看器 (diff-viewer.js)
+
+#### 功能特性
+
+- diff2html 渲染
+- 文件树侧边栏
+- 增量渲染(非阻塞)
+- 懒加载语法高亮
+- 滚动同步
+- 大型差异保护
+
+#### 增量渲染
+
+```javascript
+_renderBatch(outputFormat, startIndex) {
+ const FILES_PER_BATCH = 5;
+ // 每帧渲染 5 个文件
+ // 使用 requestAnimationFrame
+ // 避免阻塞主线程
+}
+```
+
+#### 大型差异保护
+
+```javascript
+_isTooLarge(files) {
+ const MAX_FILES = 300;
+ const MAX_LINES = 10000;
+ return files.length > MAX_FILES || (totalAdd + totalDel) > MAX_LINES;
+}
+```
+
+- 超过阈值显示警告
+- 用户确认后强制渲染
+- 避免浏览器卡顿
+
+#### 文件树
+
+- 目录折叠/展开
+- 文件类型图标
+- 变更统计(+/-)
+- 严重程度标记
+
+#### 滚动同步
+
+```javascript
+_setupScrollSpy() {
+ // IntersectionObserver 监听文件可见性
+ // 自动高亮侧边栏对应文件
+ // 点击侧边栏滚动到文件
+}
+```
+
+### 4. 内联建议 (review-inline.js)
+
+#### 功能
+
+- 在差异中嵌入 AI 建议
+- 按严重程度着色
+- 支持文件级和行级建议
+
+#### 插入机制
+
+```javascript
+insertSuggestion(filename, line, side, severity, content, suggestionId) {
+ // 1. 懒加载文件索引
+ // 2. 查找目标行
+ // 3. 创建建议卡片
+ // 4. 插入到行后
+}
+```
+
+#### 严重程度样式
+
+```javascript
+const severityStyles = {
+ critical: 'border-l-4 border-red-500 bg-red-50',
+ warning: 'border-l-4 border-yellow-500 bg-yellow-50',
+ info: 'border-l-4 border-green-500 bg-green-50',
+};
+```
+
+### 5. Markdown 渲染 (markdown.js)
+
+#### 功能
+
+- 使用 marked.js 渲染
+- 代码块语法高亮
+- 支持内联代码
+
+#### 集成
+
+```javascript
+function renderMarkdown(text) {
+ return marked.parse(text, {
+ highlight: function(code, lang) {
+ return hljs.highlightAuto(code).value;
+ }
+ });
+}
+```
+
+### 6. 笔记编辑器 (note-editor.js)
+
+#### 功能
+
+- 内联编辑审查笔记
+- 自动保存
+- 三种作用域
+
+## 页面结构
+
+### 首页 (/)
+
+- 仓库列表
+- 克隆表单
+- 快速操作
+
+### 仓库详情页 (/repo/:id)
+
+- Git 图形
+- 分支选择器
+- 差异查看器
+- PR 生成
+- AI 代码审查
+
+### 设置页 (/settings)
+
+- LLM 配置
+- 审查参数
+- 缓存设置
+
+## HTMX 集成
+
+### 动态加载
+
+```html
+
+
+
+```
+
+### 表单提交
+
+```html
+
+```
+
+### 事件触发
+
+```html
+
+```
+
+## 响应式设计
+
+### 断点
+
+- 移动端: < 640px
+- 平板: 640px - 1024px
+- 桌面: > 1024px
+
+### 适配策略
+
+- 文件树侧边栏可折叠
+- 差异查看器自适应宽度
+- 图形可横向滚动
+
+## 性能优化
+
+### 1. 增量渲染
+
+- 分批渲染文件
+- requestAnimationFrame
+- 避免长任务阻塞
+
+### 2. 懒加载
+
+- 语法高亮按需执行
+- 文件索引按需构建
+- IntersectionObserver
+
+### 3. 虚拟滚动
+
+- 大型差异只渲染可见部分
+- 减少 DOM 节点数
+
+### 4. 缓存
+
+- 静态资源缓存
+- API 响应缓存
+- 本地状态缓存
+
+## 错误处理
+
+### 网络错误
+
+- 显示错误消息
+- 提供重试按钮
+- 自动重连(SSE)
+
+### 渲染错误
+
+- 降级到纯文本
+- 显示原始数据
+- 记录错误日志
+
+## 无障碍
+
+### 键盘导航
+
+- Tab 切换焦点
+- Enter 确认选择
+- Escape 取消操作
+
+### 屏幕阅读器
+
+- ARIA 标签
+- 语义化 HTML
+- 焦点管理
+
+## 浏览器兼容性
+
+### 支持的浏览器
+
+- Chrome 90+
+- Firefox 88+
+- Safari 14+
+- Edge 90+
+
+### 依赖的现代 API
+
+- fetch
+- ReadableStream
+- IntersectionObserver
+- requestAnimationFrame
+- CSS Grid/Flexbox
diff --git a/docs/04-database-design.md b/docs/04-database-design.md
new file mode 100644
index 0000000..2e6b418
--- /dev/null
+++ b/docs/04-database-design.md
@@ -0,0 +1,394 @@
+# 数据库设计
+
+## 概述
+
+PR-Helper 使用 MySQL 作为主数据库,存储用户信息、仓库元数据、分析结果和审查笔记。
+
+## 数据库配置
+
+### 连接参数
+
+```env
+MYSQL_HOST=127.0.0.1
+MYSQL_PORT=3306
+MYSQL_USER=root
+MYSQL_PASSWORD=
+MYSQL_DATABASE=pr_helper
+```
+
+### DSN 格式
+
+```
+{user}:{password}@tcp({host}:{port})/{database}?charset=utf8mb4&parseTime=True&loc=Local
+```
+
+## 表结构
+
+### 1. settings 表
+
+全局键值存储,保存系统配置。
+
+```sql
+CREATE TABLE IF NOT EXISTS settings (
+ `key` VARCHAR(255) PRIMARY KEY,
+ value TEXT NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| key | VARCHAR(255) | 配置键名(主键) |
+| value | TEXT | 配置值 |
+
+#### 默认设置
+
+```go
+var DefaultSettings = map[string]string{
+ "llm.endpoint": "https://api.deepseek.com",
+ "llm.api_key": "",
+ "llm.model": "deepseek-v4-pro",
+ "review.top_n": "20",
+ "review.concurrency": "5",
+ "cache.max_age_days": "7",
+ "cache.max_size_mb": "5000",
+}
+```
+
+### 2. users 表
+
+用户账户信息。
+
+```sql
+CREATE TABLE IF NOT EXISTS users (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ email VARCHAR(255) NOT NULL UNIQUE,
+ password_hash TEXT NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | BIGINT | 用户 ID(主键,自增) |
+| email | VARCHAR(255) | 邮箱(唯一) |
+| password_hash | TEXT | 密码哈希 |
+| created_at | DATETIME | 创建时间 |
+| updated_at | DATETIME | 更新时间 |
+
+### 3. user_settings 表
+
+用户个人设置,覆盖全局默认值。
+
+```sql
+CREATE TABLE IF NOT EXISTS user_settings (
+ user_id BIGINT NOT NULL,
+ `key` VARCHAR(255) NOT NULL,
+ value TEXT NOT NULL,
+ PRIMARY KEY (user_id, `key`),
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| user_id | BIGINT | 用户 ID(外键) |
+| key | VARCHAR(255) | 配置键名 |
+| value | TEXT | 配置值 |
+
+#### 常用配置键
+
+| 键名 | 说明 | 默认值 |
+|------|------|--------|
+| llm.endpoint | LLM API 端点 | https://api.deepseek.com |
+| llm.api_key | LLM API 密钥 | (空) |
+| llm.model | LLM 模型名称 | deepseek-v4-pro |
+| review.top_n | 审查文件数上限 | 20 |
+| review.concurrency | 审查并发数 | 5 |
+
+### 4. repositories 表
+
+克隆的仓库元数据。
+
+```sql
+CREATE TABLE IF NOT EXISTS repositories (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ user_id BIGINT,
+ url TEXT NOT NULL,
+ local_path TEXT NOT NULL,
+ size_bytes BIGINT DEFAULT 0,
+ cloned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ last_used DATETIME DEFAULT CURRENT_TIMESTAMP,
+ auth_type VARCHAR(20) NOT NULL DEFAULT 'none',
+ credential TEXT,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | BIGINT | 仓库 ID(主键,自增) |
+| user_id | BIGINT | 所有者用户 ID(外键) |
+| url | TEXT | 仓库 URL |
+| local_path | TEXT | 本地存储路径 |
+| size_bytes | BIGINT | 仓库大小(字节) |
+| cloned_at | DATETIME | 克隆时间 |
+| last_used | DATETIME | 最后使用时间 |
+| auth_type | VARCHAR(20) | 认证类型(none/https/ssh) |
+| credential | TEXT | 认证凭证(不明文返回) |
+
+### 5. analyses 表
+
+分析结果存储(PR 描述和代码审查)。
+
+```sql
+CREATE TABLE IF NOT EXISTS analyses (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ user_id BIGINT,
+ repo_id BIGINT,
+ type VARCHAR(50) NOT NULL,
+ base_ref TEXT NOT NULL,
+ head_ref TEXT NOT NULL,
+ result LONGTEXT,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY (repo_id) REFERENCES repositories(id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | BIGINT | 分析 ID(主键,自增) |
+| user_id | BIGINT | 用户 ID(外键) |
+| repo_id | BIGINT | 仓库 ID(外键) |
+| type | VARCHAR(50) | 类型(pr_description/code_review) |
+| base_ref | TEXT | 基准分支 |
+| head_ref | TEXT | 目标分支 |
+| result | LONGTEXT | 结果 JSON |
+| created_at | DATETIME | 创建时间 |
+
+#### result 字段格式
+
+**PR 描述类型**:
+
+```json
+{
+ "content": "# 标题\n\n## 概述\n..."
+}
+```
+
+**代码审查类型**:
+
+```json
+{
+ "file_reviews": [
+ {
+ "file_name": "main.go",
+ "change_lines": 42,
+ "suggestions": [
+ {
+ "severity": "warning",
+ "description": "...",
+ "suggestion": "...",
+ "code_example": "..."
+ }
+ ],
+ "raw_review": "..."
+ }
+ ],
+ "summary": {
+ "score": 7,
+ "overall": "...",
+ "findings": "...",
+ "recommendations": "..."
+ },
+ "top_n": 20
+}
+```
+
+### 6. review_notes 表
+
+用户添加的审查笔记。
+
+```sql
+CREATE TABLE IF NOT EXISTS review_notes (
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
+ user_id BIGINT,
+ analysis_id BIGINT,
+ scope VARCHAR(50) NOT NULL,
+ scope_key TEXT NOT NULL,
+ content TEXT NOT NULL,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY (analysis_id) REFERENCES analyses(id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | BIGINT | 笔记 ID(主键,自增) |
+| user_id | BIGINT | 用户 ID(外键) |
+| analysis_id | BIGINT | 分析 ID(外键) |
+| scope | VARCHAR(50) | 作用域类型 |
+| scope_key | TEXT | 作用域键 |
+| content | TEXT | 笔记内容 |
+| created_at | DATETIME | 创建时间 |
+| updated_at | DATETIME | 更新时间 |
+
+#### scope 类型
+
+| scope | scope_key | 说明 |
+|-------|-----------|------|
+| overall | (空) | 整体审查笔记 |
+| file | 文件名 | 文件级别笔记 |
+| suggestion | 建议 ID | 建议级别笔记 |
+
+## 索引设计
+
+### 主键索引
+
+- 所有表使用 BIGINT 自增主键
+- settings 表使用 key 作为主键
+
+### 唯一索引
+
+- users.email: 唯一约束
+
+### 复合主键
+
+- user_settings: (user_id, key)
+
+### 外键索引
+
+- user_settings.user_id → users.id
+- repositories.user_id → users.id
+- analyses.user_id → users.id
+- analyses.repo_id → repositories.id
+- review_notes.user_id → users.id
+- review_notes.analysis_id → analyses.id
+
+## 迁移策略
+
+### 自动迁移
+
+```go
+func (db *DB) migrate() error {
+ stmts := []string{
+ "CREATE TABLE IF NOT EXISTS ...",
+ // ...
+ }
+ for _, s := range stmts {
+ if _, err := db.conn.Exec(s); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+```
+
+### 增量迁移
+
+```go
+alterStmts := []string{
+ "ALTER TABLE repositories ADD COLUMN auth_type VARCHAR(20) NOT NULL DEFAULT 'none'",
+ "ALTER TABLE repositories ADD COLUMN credential TEXT",
+}
+for _, s := range alterStmts {
+ db.conn.Exec(s) // 忽略错误(列已存在)
+}
+```
+
+- 使用 `IF NOT EXISTS` 避免重复创建
+- 忽略 "duplicate column" 错误实现幂等性
+
+### 默认数据初始化
+
+```go
+func (db *DB) seedDefaults() error {
+ for key, val := range models.DefaultSettings {
+ _, err := db.conn.Exec(
+ "INSERT IGNORE INTO settings (`key`, value) VALUES (?, ?)", key, val,
+ )
+ // ...
+ }
+ return nil
+}
+```
+
+- 使用 `INSERT IGNORE` 避免重复插入
+
+## 数据完整性
+
+### 外键约束
+
+- 级联删除(ON DELETE CASCADE)
+- 删除用户时自动清理相关数据
+
+### 数据验证
+
+- 应用层验证(handler 层)
+- 数据库约束(NOT NULL, UNIQUE)
+
+## 性能优化
+
+### 查询优化
+
+- 使用参数化查询避免 SQL 注入
+- 避免 SELECT *,只查询需要的字段
+- 合理使用索引
+
+### 连接管理
+
+- 使用 database/sql 连接池
+- 设置合理的连接超时
+- 及时关闭连接
+
+### 大字段处理
+
+- result 使用 LONGTEXT
+- 考虑分表或外部存储(未来优化)
+
+## 备份策略
+
+### 逻辑备份
+
+```bash
+mysqldump -u root -p pr_helper > backup.sql
+```
+
+### 恢复
+
+```bash
+mysql -u root -p pr_helper < backup.sql
+```
+
+### 定期备份
+
+- 建议每日备份
+- 保留最近 7 天备份
+- 异地备份(生产环境)
+
+## 监控
+
+### 关键指标
+
+- 连接数
+- 查询耗时
+- 慢查询
+- 表大小
+
+### 命令
+
+```sql
+-- 查看连接数
+SHOW STATUS LIKE 'Threads_connected';
+
+-- 查看慢查询
+SHOW VARIABLES LIKE 'slow_query_log';
+
+-- 查看表大小
+SELECT table_name, round(((data_length + index_length) / 1024 / 1024), 2) AS "Size (MB)"
+FROM information_schema.tables
+WHERE table_schema = 'pr_helper';
+```
diff --git a/docs/05-api-reference.md b/docs/05-api-reference.md
new file mode 100644
index 0000000..4f95d28
--- /dev/null
+++ b/docs/05-api-reference.md
@@ -0,0 +1,660 @@
+# API 接口文档
+
+## 概述
+
+PR-Helper 提供 RESTful JSON API 和 SSE 流式端点。所有 API 都需要认证(除登录/注册外)。
+
+## 认证
+
+### 会话认证
+
+- 基于 Cookie 的会话
+- Cookie 名称: `pr_session`
+- 有效期: 7 天
+- HttpOnly: true
+
+### 认证头
+
+所有受保护的 API 需要有效的会话 Cookie。
+
+## 公开端点
+
+### 登录页面
+
+```
+GET /login
+```
+
+返回登录页面 HTML。
+
+### 注册页面
+
+```
+GET /register
+```
+
+返回注册页面 HTML。
+
+### 用户登录
+
+```
+POST /api/auth/login
+Content-Type: application/json
+
+{
+ "email": "user@example.com",
+ "password": "password123"
+}
+```
+
+**响应**:
+
+```json
+{
+ "message": "登录成功"
+}
+```
+
+**错误**:
+
+```json
+{
+ "error": "邮箱或密码错误"
+}
+```
+
+### 用户注册
+
+```
+POST /api/auth/register
+Content-Type: application/json
+
+{
+ "email": "user@example.com",
+ "password": "password123"
+}
+```
+
+**响应**:
+
+```json
+{
+ "message": "注册成功"
+}
+```
+
+### 用户登出
+
+```
+POST /api/auth/logout
+```
+
+**响应**:
+
+```json
+{
+ "message": "已登出"
+}
+```
+
+## 页面端点
+
+### 首页
+
+```
+GET /
+```
+
+返回首页 HTML(仓库列表)。
+
+### 仓库详情页
+
+```
+GET /repo/:id
+```
+
+返回仓库详情页 HTML(Git 图形、差异查看器等)。
+
+### 设置页
+
+```
+GET /settings
+```
+
+返回设置页面 HTML。
+
+## 仓库管理 API
+
+### 获取仓库列表
+
+```
+GET /api/repos
+```
+
+**响应**:
+
+```json
+[
+ {
+ "id": 1,
+ "url": "https://github.com/user/repo.git",
+ "local_path": "data/repos/user_repo.git_1234567890",
+ "size_bytes": 1048576,
+ "cloned_at": "2024-01-01T00:00:00Z",
+ "last_used": "2024-01-01T12:00:00Z",
+ "auth_type": "none"
+ }
+]
+```
+
+### 克隆仓库
+
+```
+POST /api/repos
+Content-Type: application/json
+
+{
+ "url": "https://github.com/user/repo.git",
+ "auth_type": "none",
+ "credential": ""
+}
+```
+
+**认证类型**:
+
+- `none`: 无认证
+- `https`: HTTPS 基本认证(credential = "username:password")
+
+**响应 (SSE 流)**:
+
+```
+event: progress
+data: {"step":"开始克隆","current":0,"total":0}
+
+event: progress
+data: {"step":"克隆完成","current":1,"total":1}
+
+event: done
+data: {"repo_id":1,"branches":["main","dev"],"tags":["v1.0"],"commit_num":100}
+```
+
+### 删除仓库
+
+```
+DELETE /api/repos/:id
+```
+
+**响应**:
+
+```json
+{
+ "message": "仓库已删除"
+}
+```
+
+### 清理仓库缓存
+
+```
+POST /api/repos/:id/cleanup
+```
+
+删除并重新克隆仓库。
+
+**响应 (SSE 流)**:
+
+```
+event: progress
+data: {"step":"清理完成"}
+
+event: progress
+data: {"step":"开始克隆"}
+
+event: done
+data: {"repo_id":1}
+```
+
+### 拉取更新
+
+```
+POST /api/repos/:id/pull
+```
+
+**响应 (SSE 流)**:
+
+```
+event: progress
+data: {"step":"拉取完成"}
+
+event: done
+data: {"commit_num":105,"branches":["main","dev"]}
+```
+
+### 获取 Git 图形
+
+```
+GET /api/repos/:id/graph
+```
+
+**响应**:
+
+```json
+{
+ "commits": [
+ {
+ "hash": "abc123...",
+ "short_hash": "abc1234",
+ "message": "feat: add new feature",
+ "author": "John Doe",
+ "email": "john@example.com",
+ "timestamp": "2024-01-01T12:00:00Z",
+ "parent_ids": ["def456..."]
+ }
+ ],
+ "refs": [
+ {
+ "name": "main",
+ "hash": "abc123...",
+ "is_head": true,
+ "is_tag": false
+ },
+ {
+ "name": "v1.0",
+ "hash": "abc123...",
+ "is_head": false,
+ "is_tag": true
+ }
+ ],
+ "edges": [
+ {
+ "source": "abc123...",
+ "target": "def456..."
+ }
+ ]
+}
+```
+
+### 获取引用列表
+
+```
+GET /api/repos/:id/refs
+```
+
+**响应**:
+
+```json
+[
+ {
+ "name": "main",
+ "hash": "abc123...",
+ "is_head": true,
+ "is_tag": false
+ },
+ {
+ "name": "v1.0",
+ "hash": "abc123...",
+ "is_head": false,
+ "is_tag": true
+ }
+]
+```
+
+### 获取提交列表
+
+```
+GET /api/repos/:id/commits?ref=main&limit=50
+```
+
+**参数**:
+
+| 参数 | 类型 | 必需 | 说明 |
+|------|------|------|------|
+| ref | string | 否 | 分支/标签名(默认 HEAD) |
+| limit | int | 否 | 最大提交数(默认 50) |
+
+**响应**:
+
+```json
+[
+ {
+ "hash": "abc123...",
+ "short_hash": "abc1234",
+ "message": "feat: add new feature",
+ "author": "John Doe",
+ "email": "john@example.com",
+ "timestamp": "2024-01-01T12:00:00Z",
+ "parent_ids": ["def456..."]
+ }
+]
+```
+
+### 获取差异
+
+```
+GET /api/repos/:id/diff?base=main&head=feature
+```
+
+**参数**:
+
+| 参数 | 类型 | 必需 | 说明 |
+|------|------|------|------|
+| base | string | 是 | 基准分支/标签/提交 |
+| head | string | 是 | 目标分支/标签/提交 |
+| per_file | bool | 否 | 是否按文件返回 |
+
+**响应 (统一差异)**:
+
+```json
+{
+ "diff": "diff --git a/main.go b/main.go\n..."
+}
+```
+
+**响应 (按文件)**:
+
+```json
+[
+ {
+ "filename": "main.go",
+ "patch": "diff --git a/main.go b/main.go\n..."
+ }
+]
+```
+
+## PR 生成 API
+
+### 生成 PR 描述
+
+```
+POST /api/repos/:id/generate
+Content-Type: application/json
+
+{
+ "base": "main",
+ "head": "feature"
+}
+```
+
+**响应 (SSE 流)**:
+
+```
+event: content
+data: {"content":"# 标题\n\n"}
+
+event: content
+data: {"content":"**类型**: feat\n\n"}
+
+event: content
+data: {"content":"## 概述\n..."}
+
+event: done
+data: {"content":""}
+```
+
+## 代码审查 API
+
+### 执行代码审查
+
+```
+POST /api/repos/:id/review
+Content-Type: application/json
+
+{
+ "base": "main",
+ "head": "feature",
+ "top_n": 20,
+ "concurrency": 5
+}
+```
+
+**参数**:
+
+| 参数 | 类型 | 必需 | 说明 |
+|------|------|------|------|
+| base | string | 是 | 基准分支 |
+| head | string | 是 | 目标分支 |
+| top_n | int | 否 | 审查文件数上限(默认 20) |
+| concurrency | int | 否 | 并发数(默认 5) |
+
+**响应 (SSE 流)**:
+
+```
+event: start
+data: {"total_files":30,"reviewed_files":20,"top_n":20}
+
+event: file_start
+data: {"file":"main.go","index":1,"total":20}
+
+event: content
+data: {"content":"..."}
+
+event: suggestion
+data: {"file":"main.go","severity":"warning","content":"..."}
+
+event: file_end
+data: {"file":"main.go"}
+
+event: summary
+data: {"score":7,"overall":"...","findings":"...","recommendations":"..."}
+
+event: analysis_saved
+data: {"analysis_id":1}
+
+event: done
+data: {"content":""}
+```
+
+### 获取审查历史
+
+```
+GET /api/repos/:id/review/analyses
+```
+
+**响应**:
+
+```json
+[
+ {
+ "id": 1,
+ "base_ref": "main",
+ "head_ref": "feature",
+ "created_at": "2024-01-01T12:00:00Z"
+ }
+]
+```
+
+### 获取单个审查结果
+
+```
+GET /api/repos/:id/review/analyses/:aid
+```
+
+**响应**:
+
+```json
+{
+ "id": 1,
+ "base_ref": "main",
+ "head_ref": "feature",
+ "created_at": "2024-01-01T12:00:00Z",
+ "result": {
+ "file_reviews": [...],
+ "summary": {...},
+ "top_n": 20
+ }
+}
+```
+
+### 保存审查笔记
+
+```
+POST /api/repos/:id/review/notes
+Content-Type: application/json
+
+{
+ "analysis_id": 1,
+ "scope": "file",
+ "scope_key": "main.go",
+ "content": "这个文件需要重构"
+}
+```
+
+**scope 类型**:
+
+| scope | scope_key | 说明 |
+|-------|-----------|------|
+| overall | (空) | 整体笔记 |
+| file | 文件名 | 文件级笔记 |
+| suggestion | 建议 ID | 建议级笔记 |
+
+**响应**:
+
+```json
+{
+ "id": 1,
+ "analysis_id": 1,
+ "scope": "file",
+ "scope_key": "main.go",
+ "content": "这个文件需要重构",
+ "created_at": "2024-01-01T12:00:00Z",
+ "updated_at": "2024-01-01T12:00:00Z"
+}
+```
+
+### 获取审查笔记
+
+```
+GET /api/repos/:id/review/notes?analysis_id=1&scope=file
+```
+
+**参数**:
+
+| 参数 | 类型 | 必需 | 说明 |
+|------|------|------|------|
+| analysis_id | int | 是 | 分析 ID |
+| scope | string | 否 | 过滤作用域 |
+
+**响应**:
+
+```json
+[
+ {
+ "id": 1,
+ "analysis_id": 1,
+ "scope": "file",
+ "scope_key": "main.go",
+ "content": "这个文件需要重构",
+ "created_at": "2024-01-01T12:00:00Z",
+ "updated_at": "2024-01-01T12:00:00Z"
+ }
+]
+```
+
+## 设置 API
+
+### 获取设置
+
+```
+GET /api/settings
+```
+
+**响应**:
+
+```json
+{
+ "llm.endpoint": "https://api.deepseek.com",
+ "llm.api_key": "sk-...",
+ "llm.model": "deepseek-v4-pro",
+ "review.top_n": "20",
+ "review.concurrency": "5",
+ "cache.max_age_days": "7",
+ "cache.max_size_mb": "5000"
+}
+```
+
+### 更新设置
+
+```
+PUT /api/settings
+Content-Type: application/json
+
+{
+ "llm.endpoint": "https://api.openai.com",
+ "llm.api_key": "sk-...",
+ "llm.model": "gpt-4"
+}
+```
+
+**响应**:
+
+```json
+{
+ "message": "设置已更新"
+}
+```
+
+## 错误响应
+
+### 格式
+
+```json
+{
+ "error": "错误信息"
+}
+```
+
+### 常见状态码
+
+| 状态码 | 说明 |
+|--------|------|
+| 200 | 成功 |
+| 400 | 请求参数错误 |
+| 401 | 未认证 |
+| 404 | 资源不存在 |
+| 500 | 服务器内部错误 |
+
+## SSE 协议
+
+### 事件格式
+
+```
+event: {event_name}
+data: {json_data}
+
+```
+
+- 每个事件以 `event:` 开头
+- 数据以 `data:` 开头
+- 事件之间用空行分隔
+- 数据必须是有效的 JSON
+
+### 客户端实现
+
+```javascript
+SSE.post(url, body, {
+ eventName: (data) => {
+ // 处理事件
+ },
+ error: (data) => {
+ // 处理错误
+ },
+ done: () => {
+ // 流结束
+ }
+});
+```
+
+### 中断请求
+
+```javascript
+const controller = SSE.post(url, body, handlers);
+// 中断
+controller.abort();
+```
+
+## 限流
+
+当前无速率限制。建议在反向代理层实现限流。
+
+## CORS
+
+当前不支持跨域请求。仅限同源访问。
diff --git a/docs/06-sse-streaming.md b/docs/06-sse-streaming.md
new file mode 100644
index 0000000..f9a8102
--- /dev/null
+++ b/docs/06-sse-streaming.md
@@ -0,0 +1,446 @@
+# SSE 流式传输
+
+## 概述
+
+Server-Sent Events (SSE) 是 PR-Helper 的核心通信机制,用于实时推送克隆进度、PR 生成和代码审查结果。
+
+## 为什么选择 SSE
+
+### 对比 WebSocket
+
+| 特性 | SSE | WebSocket |
+|------|-----|-----------|
+| 方向 | 单向(服务器→客户端) | 双向 |
+| 协议 | HTTP | 独立协议 |
+| 实现 | 简单 | 复杂 |
+| 重连 | 自动 | 手动 |
+| 兼容性 | 好 | 需要升级 |
+
+### 适用场景
+
+- 服务器推送数据到客户端
+- 不需要客户端频繁发送数据
+- 需要 HTTP 兼容性
+- 需要自动重连
+
+## SSE 协议
+
+### 基本格式
+
+```
+event: message_type
+data: {"key": "value"}
+
+```
+
+- `event:` 事件类型(可选)
+- `data:` 事件数据(JSON 字符串)
+- 空行分隔事件
+
+### 多行数据
+
+```
+event: content
+data: {"content": "第一行\n第二行"}
+```
+
+## 后端实现
+
+### 设置响应头
+
+```go
+c.Header("Content-Type", "text/event-stream")
+c.Header("Cache-Control", "no-cache")
+c.Header("Connection", "keep-alive")
+c.Header("X-Accel-Buffering", "no")
+c.Status(http.StatusOK)
+```
+
+### 发送事件
+
+```go
+sendEvent := func(event string, data interface{}) {
+ jsonData, err := json.Marshal(data)
+ if err != nil {
+ jsonData = []byte(`{"error":"序列化事件数据失败"}`)
+ }
+ fmt.Fprintf(c.Writer, "event: %s\ndata: %s\n\n", event, jsonData)
+ flusher.Flush()
+}
+```
+
+### 获取 Flusher
+
+```go
+flusher, ok := c.Writer.(http.Flusher)
+if !ok {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "服务器不支持流式传输"})
+ return
+}
+```
+
+## 前端实现
+
+### SSE 客户端
+
+```javascript
+const SSE = {
+ async post(url, body, handlers = {}) {
+ const controller = new AbortController();
+
+ const run = async () => {
+ try {
+ const resp = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ credentials: 'same-origin',
+ body: JSON.stringify(body),
+ signal: controller.signal,
+ });
+
+ if (!resp.ok) {
+ const errText = await resp.text();
+ // 处理错误...
+ return;
+ }
+
+ const reader = resp.body.getReader();
+ const decoder = new TextDecoder();
+ let buffer = '';
+ let currentEvent = '';
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split('\n');
+ buffer = lines.pop() || '';
+
+ for (const line of lines) {
+ if (line.startsWith('event: ')) {
+ currentEvent = line.slice(7).trim();
+ } else if (line.startsWith('data: ')) {
+ const raw = line.slice(6);
+ let data;
+ try {
+ data = JSON.parse(raw);
+ } catch (_) {
+ data = raw;
+ }
+
+ if (currentEvent && handlers[currentEvent]) {
+ handlers[currentEvent](data);
+ }
+ }
+ }
+ }
+
+ if (handlers.done) handlers.done();
+ } catch (err) {
+ if (err.name === 'AbortError') return;
+ if (handlers.error) handlers.error({ message: err.message });
+ }
+ };
+
+ run();
+ return { abort: () => controller.abort() };
+ }
+};
+```
+
+### 使用示例
+
+```javascript
+SSE.post('/api/repos/1/review', {
+ base: 'main',
+ head: 'feature',
+ top_n: 20,
+ concurrency: 5
+}, {
+ // 开始事件
+ start: (data) => {
+ console.log(`开始审查 ${data.total_files} 个文件`);
+ updateProgress(0, data.reviewed_files);
+ },
+
+ // 文件开始
+ file_start: (data) => {
+ console.log(`正在审查 ${data.file} (${data.index}/${data.total})`);
+ highlightCurrentFile(data.file);
+ },
+
+ // 内容流
+ content: (data) => {
+ appendToOutput(data.content);
+ },
+
+ // 审查建议
+ suggestion: (data) => {
+ showSuggestion(data.file, data.severity, data.content);
+ },
+
+ // 文件结束
+ file_end: (data) => {
+ markFileComplete(data.file);
+ },
+
+ // 汇总
+ summary: (data) => {
+ showSummary(data.score, data.overall, data.findings, data.recommendations);
+ },
+
+ // 分析保存
+ analysis_saved: (data) => {
+ console.log(`分析已保存,ID: ${data.analysis_id}`);
+ },
+
+ // 进度
+ progress: (data) => {
+ if (data.step === 'generating_summary') {
+ showSpinner('生成汇总中...');
+ }
+ },
+
+ // 完成
+ done: () => {
+ console.log('审查完成');
+ hideSpinner();
+ },
+
+ // 错误
+ error: (data) => {
+ console.error('错误:', data.message);
+ showError(data.message);
+ }
+});
+```
+
+## 事件类型
+
+### PR 生成事件
+
+| 事件 | 数据 | 说明 |
+|------|------|------|
+| `content` | `{content: string}` | Markdown 片段 |
+| `done` | `{content: ""}` | 完成 |
+
+### 代码审查事件
+
+| 事件 | 数据 | 说明 |
+|------|------|------|
+| `start` | `{total_files, reviewed_files, top_n}` | 开始 |
+| `file_start` | `{file, index, total}` | 文件开始 |
+| `content` | `{content: string}` | LLM 输出 |
+| `suggestion` | `{file, severity, content}` | 建议 |
+| `file_end` | `{file}` | 文件结束 |
+| `summary` | `{score, overall, findings, recommendations}` | 汇总 |
+| `progress` | `{step: string}` | 进度 |
+| `error` | `{message: string}` | 错误 |
+| `analysis_saved` | `{analysis_id}` | 保存完成 |
+| `done` | `{content: ""}` | 完成 |
+
+### 克隆事件
+
+| 事件 | 数据 | 说明 |
+|------|------|------|
+| `progress` | `{step, current, total}` | 进度 |
+| `error` | `{message: string}` | 错误 |
+| `done` | `{repo_id, branches, tags, commit_num}` | 完成 |
+
+## 错误处理
+
+### 后端错误
+
+```go
+if err != nil {
+ sendEvent("error", map[string]interface{}{
+ "message": err.Error(),
+ })
+ return
+}
+```
+
+### 前端错误
+
+```javascript
+error: (data) => {
+ // 显示错误消息
+ showErrorToast(data.message);
+
+ // 重置 UI
+ resetProgressBar();
+ hideSpinner();
+}
+```
+
+### 网络错误
+
+```javascript
+catch (err) {
+ if (err.name === 'AbortError') {
+ // 用户中断,忽略
+ return;
+ }
+ if (handlers.error) {
+ handlers.error({ message: err.message });
+ }
+}
+```
+
+## 中断请求
+
+### 前端中断
+
+```javascript
+const controller = SSE.post(url, body, handlers);
+
+// 用户点击取消按钮
+cancelButton.onclick = () => {
+ controller.abort();
+};
+```
+
+### 后端处理
+
+- 客户端断开连接时,Gin 会检测到
+- 服务层应检查 context 取消
+- 长时间运行的操作应支持取消
+
+## 性能优化
+
+### 缓冲控制
+
+```
+X-Accel-Buffering: no
+```
+
+- 禁用 Nginx 缓冲
+- 确保事件立即发送
+
+### 批量发送
+
+- 避免频繁发送小事件
+- 合并相关数据
+
+### 压缩
+
+- SSE 不支持 gzip 压缩
+- 数据量大时考虑压缩正文
+
+## 并发安全
+
+### 线程安全回调
+
+```go
+safeCallback := callback
+if callback != nil {
+ safeCallback = func(event string, data interface{}) {
+ mu.Lock()
+ defer mu.Unlock()
+ callback(event, data)
+ }
+}
+```
+
+- 使用互斥锁保护回调
+- 避免并发写入
+
+### Goroutine 管理
+
+```go
+var wg sync.WaitGroup
+
+for i, file := range files {
+ wg.Add(1)
+ go func(idx int, f FileDiff) {
+ defer wg.Done()
+ // ...
+ }(i, file)
+}
+
+wg.Wait()
+```
+
+- 等待所有 goroutine 完成
+- 避免资源泄漏
+
+## 测试
+
+### 手动测试
+
+```bash
+curl -N -X POST http://localhost:8080/api/repos/1/review \
+ -H "Content-Type: application/json" \
+ -d '{"base":"main","head":"feature"}'
+```
+
+### 自动化测试
+
+```go
+func TestSSEStream(t *testing.T) {
+ // 创建测试服务器
+ // 发送请求
+ // 读取事件流
+ // 验证事件序列
+}
+```
+
+## 监控
+
+### 关键指标
+
+- 连接数
+- 事件发送速率
+- 错误率
+- 响应时间
+
+### 日志
+
+```go
+log.Printf("SSE connected: %s", c.ClientIP())
+log.Printf("SSE event: %s", event)
+log.Printf("SSE disconnected: %s", c.ClientIP())
+```
+
+## 安全考虑
+
+### 认证
+
+- 所有 SSE 端点需要认证
+- 使用会话 Cookie
+
+### 速率限制
+
+- 限制并发 SSE 连接数
+- 限制事件发送频率
+
+### 数据验证
+
+- 验证输入参数
+- 防止注入攻击
+
+## 故障排查
+
+### 常见问题
+
+1. **事件不发送**
+ - 检查 Flusher 是否可用
+ - 确认响应头设置正确
+ - 检查 Nginx 缓冲配置
+
+2. **连接断开**
+ - 检查超时设置
+ - 确认网络稳定
+ - 查看错误日志
+
+3. **数据乱码**
+ - 确认 JSON 序列化正确
+ - 检查字符编码
+ - 验证事件格式
+
+### 调试工具
+
+- 浏览器开发者工具 Network 面板
+- curl 命令行测试
+- Wireshark 抓包
diff --git a/docs/07-llm-integration.md b/docs/07-llm-integration.md
new file mode 100644
index 0000000..d4535a0
--- /dev/null
+++ b/docs/07-llm-integration.md
@@ -0,0 +1,481 @@
+# LLM 集成
+
+## 概述
+
+PR-Helper 集成了 OpenAI 兼容的 LLM API,用于自动生成 PR 描述和执行 AI 代码审查。支持流式响应,提供实时反馈。
+
+## 架构
+
+```
+前端 → SSE 客户端 → Handler → LLM 服务 → OpenAI API
+ ↑ ↓
+ └──────── 流式响应 ←────────────┘
+```
+
+## 配置
+
+### 全局默认配置
+
+```go
+var DefaultSettings = map[string]string{
+ "llm.endpoint": "https://api.deepseek.com",
+ "llm.api_key": "",
+ "llm.model": "deepseek-v4-pro",
+}
+```
+
+### 用户配置
+
+每个用户可以独立配置 LLM 参数,存储在 `user_settings` 表中。
+
+| 键名 | 说明 | 示例 |
+|------|------|------|
+| llm.endpoint | API 端点 | https://api.deepseek.com |
+| llm.api_key | API 密钥 | sk-... |
+| llm.model | 模型名称 | deepseek-v4-pro |
+
+### 支持的 LLM 提供商
+
+| 提供商 | 端点 | 模型 |
+|--------|------|------|
+| DeepSeek | https://api.deepseek.com | deepseek-v4-pro |
+| OpenAI | https://api.openai.com | gpt-4, gpt-3.5-turbo |
+| Azure OpenAI | 自定义 | 自定义 |
+| 本地部署 | http://localhost:8000 | 自定义 |
+
+## LLM 服务 (services/llm.go)
+
+### 配置读取
+
+```go
+func GetLLMConfig(db *sql.DB, userID int64) (LLMConfig, error) {
+ config := LLMConfig{}
+
+ rows, err := db.Query("SELECT `key`, value FROM user_settings WHERE user_id = ? AND `key` IN ('llm.endpoint', 'llm.api_key', 'llm.model')", userID)
+ // ...
+
+ if config.APIKey == "" {
+ return config, fmt.Errorf("LLM API key not configured — please set it in the settings page")
+ }
+
+ if config.Model == "" {
+ config.Model = "deepseek-v4-pro"
+ }
+
+ return config, nil
+}
+```
+
+### 流式调用
+
+```go
+func ChatStream(config LLMConfig, messages []goopenai.ChatCompletionMessage, callback StreamCallback) (string, error) {
+ clientConfig := goopenai.DefaultConfig(config.APIKey)
+ if config.Endpoint != "" {
+ clientConfig.BaseURL = config.Endpoint
+ }
+ client := goopenai.NewClientWithConfig(clientConfig)
+
+ ctx := context.Background()
+ stream, err := client.CreateChatCompletionStream(ctx, goopenai.ChatCompletionRequest{
+ Model: config.Model,
+ Messages: messages,
+ Stream: true,
+ })
+ // ...
+
+ var fullResponse strings.Builder
+ for {
+ response, err := stream.Recv()
+ if err == io.EOF {
+ break
+ }
+ // ...
+ if len(response.Choices) > 0 {
+ content := response.Choices[0].Delta.Content
+ if content != "" {
+ fullResponse.WriteString(content)
+ if callback != nil {
+ callback("content", map[string]interface{}{"content": content})
+ }
+ }
+ }
+ }
+
+ return fullResponse.String(), nil
+}
+```
+
+### JSON 提取
+
+```go
+func extractJSON(s string) string {
+ // 尝试从 Markdown 代码块提取
+ if idx := strings.Index(s, "```json"); idx >= 0 {
+ start := idx + 7
+ if end := strings.Index(s[start:], "```"); end >= 0 {
+ return strings.TrimSpace(s[start : start+end])
+ }
+ }
+
+ // 查找第一个 { 或 [
+ startObj := strings.Index(s, "{")
+ startArr := strings.Index(s, "[")
+
+ // 找到匹配的闭合括号
+ // ...
+
+ return s[start:]
+}
+```
+
+## PR 描述生成
+
+### 提示模板
+
+```markdown
+你是一个专业的技术文档撰写助手。根据以下 Git 变更信息,生成一份 PR 描述。
+
+## Commit 记录
+- abc1234 feat: add new feature
+- def5678 fix: bug fix
+
+## 代码变更 (Diff)
+diff --git a/main.go b/main.go
+...
+
+请直接输出 Markdown 格式的 PR 描述,包含以下部分:
+
+# 标题
+**类型**: feat|fix|refactor|docs|chore|style|test|perf
+
+## 概述
+一段话概述变更内容
+
+## 详细说明
+按模块分组的详细变更说明
+
+## 影响范围
+影响范围说明
+
+格式要求:
+- 引用文件名、函数名、变量名等代码标识时,必须用反引号包裹
+- 直接写出实际的代码名称,不要用任何占位符替代
+- 不要输出 JSON,直接输出 Markdown
+```
+
+### 生成流程
+
+1. **读取 LLM 配置**: 从用户设置获取
+2. **打开仓库**: 使用 go-git
+3. **获取提交**: 过滤 base 和 head 之间的提交
+4. **获取差异**: 生成统一差异
+5. **构建提示**: 包含提交记录和差异
+6. **调用 LLM**: 流式生成
+7. **返回结果**: Markdown 格式的 PR 描述
+
+### 差异截断
+
+```go
+// 截断 diff 如果太大(约 60k 字符以保持在 token 限制内)
+if len(diff) > 60000 {
+ diff = diff[:60000] + "\n\n... [diff truncated due to size]"
+}
+```
+
+## AI 代码审查
+
+### 单文件审查提示
+
+```markdown
+你是一个资深代码审查专家。请审查以下代码变更,给出专业的 Review 意见。
+
+## 文件: main.go
+## 变更行数: +42 / -10
+
+## Diff
+diff --git a/main.go b/main.go
+...
+
+请按以下 JSON 格式输出审查意见(直接输出 JSON 数组,不要包含 markdown 代码块标记):
+[
+ {
+ "severity": "critical 或 warning 或 info",
+ "description": "问题描述",
+ "suggestion": "建议的修改方案",
+ "code_example": "建议的代码(如有)"
+ }
+]
+
+严重程度说明:
+- critical: 严重问题(安全漏洞、数据丢失风险、崩溃风险)
+- warning: 建议改进(性能问题、代码规范、可维护性)
+- info: 提示信息(最佳实践、可选优化)
+
+如果代码没有问题,输出空数组 []。
+请用中文回复。
+```
+
+### 汇总生成提示
+
+```markdown
+以下是多个文件的代码审查结果,请给出整体评估:
+
+### main.go (42 行变更)
+[warning] 变量未使用
+[info] 可以使用更简洁的写法
+
+### utils.go (10 行变更)
+没有发现问题
+
+请按以下 JSON 格式输出(直接输出 JSON,不要包含 markdown 代码块标记):
+{
+ "score": 7,
+ "overall": "总体评价(2-3 句话)",
+ "findings": "按严重程度排序的主要发现汇总",
+ "recommendations": "改进建议,用换行分隔多条建议"
+}
+注意:所有字段必须是字符串类型,不要使用数组。请用中文回复。
+```
+
+### Top-N 策略
+
+```go
+// 按变更行数排序
+sort.Slice(files, func(i, j int) bool {
+ return countDiffLines(files[i].Patch) > countDiffLines(files[j].Patch)
+})
+
+// 应用 Top-N
+if topN > 0 && topN < len(files) {
+ files = files[:topN]
+}
+```
+
+- 默认 Top-N: 20
+- 可通过设置或请求参数调整
+- 0 表示分析所有文件
+
+### 并发审查
+
+```go
+sem := make(chan struct{}, concurrency)
+var wg sync.WaitGroup
+
+for i, file := range files {
+ wg.Add(1)
+ go func(idx int, f FileDiff) {
+ defer wg.Done()
+ sem <- struct{}{} // 获取槽位
+ defer func() { <-sem }() // 释放槽位
+ // ... 审查逻辑
+ }(i, file)
+}
+```
+
+- 默认并发数: 5
+- 信号量控制最大并发
+- 线程安全的回调函数
+
+## 流式响应处理
+
+### 后端回调
+
+```go
+type StreamCallback func(event string, data interface{})
+
+// 使用回调发送事件
+callback("content", map[string]interface{}{"content": content})
+callback("suggestion", map[string]interface{}{
+ "file": filename,
+ "severity": severity,
+ "content": content,
+})
+```
+
+### 前端处理
+
+```javascript
+SSE.post(url, body, {
+ content: (data) => {
+ // 追加 Markdown 内容
+ appendToOutput(data.content);
+ },
+ suggestion: (data) => {
+ // 显示审查建议
+ showSuggestion(data.file, data.severity, data.content);
+ },
+ summary: (data) => {
+ // 显示汇总
+ showSummary(data);
+ }
+});
+```
+
+## 错误处理
+
+### API 密钥未配置
+
+```go
+if config.APIKey == "" {
+ return config, fmt.Errorf("LLM API key not configured — please set it in the settings page")
+}
+```
+
+### API 调用失败
+
+```go
+stream, err := client.CreateChatCompletionStream(ctx, request)
+if err != nil {
+ return "", fmt.Errorf("create stream: %w", err)
+}
+```
+
+### 流式读取错误
+
+```go
+response, err := stream.Recv()
+if err != nil {
+ return fullResponse.String(), fmt.Errorf("stream recv: %w", err)
+}
+```
+
+### JSON 解析失败
+
+```go
+jsonStr := extractJSON(fullResponse)
+var suggestions []ReviewSuggestion
+if err := json.Unmarshal([]byte(jsonStr), &suggestions); err != nil {
+ // 尝试单个对象
+ var single ReviewSuggestion
+ if err2 := json.Unmarshal([]byte(jsonStr), &single); err2 == nil {
+ suggestions = []ReviewSuggestion{single}
+ } else {
+ // 使用原始响应作为描述
+ suggestions = []ReviewSuggestion{{
+ Severity: "info",
+ Description: fullResponse,
+ }}
+ }
+}
+```
+
+## Token 管理
+
+### 估算
+
+- 1 个中文字符 ≈ 2 tokens
+- 1 个英文单词 ≈ 1 token
+- 代码行 ≈ 2-3 tokens
+
+### 限制
+
+- 输入: 通常 4k-128k tokens
+- 输出: 通常 4k-8k tokens
+
+### 优化
+
+- 截断大型差异
+- 只分析 Top-N 文件
+- 压缩提示文本
+
+## 模型选择建议
+
+### DeepSeek
+
+- 优势: 中文支持好,代码理解强
+- 适用: 中文项目,代码审查
+- 模型: deepseek-v4-pro
+
+### OpenAI GPT-4
+
+- 优势: 综合能力强
+- 适用: 复杂项目,多语言
+- 模型: gpt-4, gpt-4-turbo
+
+### 本地部署
+
+- 优势: 数据隐私,无 API 费用
+- 适用: 敏感代码,离线环境
+- 模型: CodeLlama, Qwen
+
+## 成本优化
+
+### 减少 Token 使用
+
+- 截断大型差异
+- 只分析关键文件
+- 使用更小的模型
+
+### 缓存
+
+- 缓存相同输入的输出
+- 避免重复分析
+
+### 批量处理
+
+- 合并多个小请求
+- 减少 API 调用次数
+
+## 监控
+
+### 关键指标
+
+- API 调用次数
+- Token 使用量
+- 响应时间
+- 错误率
+
+### 日志
+
+```go
+log.Printf("LLM call: model=%s, tokens=%d, duration=%v", model, tokens, duration)
+```
+
+## 安全
+
+### API 密钥
+
+- 不要在代码中硬编码
+- 使用环境变量或配置文件
+- 定期轮换密钥
+
+### 数据隐私
+
+- 敏感代码考虑本地部署
+- 不要发送不必要的上下文
+- 遵守数据合规要求
+
+## 故障排查
+
+### 常见问题
+
+1. **API 密钥错误**
+ - 检查密钥是否正确
+ - 确认密钥是否有效
+
+2. **连接超时**
+ - 检查网络连接
+ - 确认端点 URL 正确
+
+3. **Token 超限**
+ - 减少输入大小
+ - 使用更大的上下文窗口
+
+4. **JSON 解析失败**
+ - 检查提示格式
+ - 添加更明确的格式要求
+
+### 调试命令
+
+```bash
+# 测试 API 连接
+curl https://api.deepseek.com/v1/models \
+ -H "Authorization: Bearer sk-..."
+
+# 测试流式响应
+curl -N https://api.deepseek.com/v1/chat/completions \
+ -H "Authorization: Bearer sk-..." \
+ -H "Content-Type: application/json" \
+ -d '{"model":"deepseek-v4-pro","messages":[{"role":"user","content":"Hello"}],"stream":true}'
+```
diff --git a/docs/08-deployment.md b/docs/08-deployment.md
new file mode 100644
index 0000000..35a442f
--- /dev/null
+++ b/docs/08-deployment.md
@@ -0,0 +1,548 @@
+# 部署运维
+
+## 概述
+
+PR-Helper 支持多种部署方式,包括直接运行、Docker 容器化和反向代理配置。
+
+## 环境要求
+
+### 系统要求
+
+| 资源 | 最低要求 | 推荐 |
+|------|----------|------|
+| CPU | 1 核 | 2 核 |
+| 内存 | 512MB | 2GB |
+| 磁盘 | 1GB | 10GB+ |
+| OS | Linux/macOS/Windows | Linux |
+
+### 软件依赖
+
+| 软件 | 版本 |
+|------|------|
+| Go | 1.21+ |
+| MySQL | 5.7+ / 8.0+ |
+| Git | 2.0+ |
+
+## 快速开始
+
+### 1. 克隆项目
+
+```bash
+git clone https://github.com/your-org/pr-helper.git
+cd pr-helper
+```
+
+### 2. 配置环境
+
+```bash
+cp .env.example .env
+# 编辑 .env 文件,设置 MySQL 连接信息
+```
+
+### 3. 构建
+
+```bash
+go build -o pr-helper .
+```
+
+### 4. 运行
+
+```bash
+./pr-helper
+```
+
+服务器将在 `http://localhost:8080` 启动。
+
+## 环境变量
+
+### 完整配置
+
+```env
+# 服务器
+PORT=8080 # 监听端口
+GIN_MODE=release # Gin 模式 (debug/release)
+SESSION_SECRET= # 会话密钥(留空自动生成)
+
+# 数据目录
+DATA_DIR=data # 数据存储目录
+
+# MySQL
+MYSQL_HOST=127.0.0.1 # 数据库主机
+MYSQL_PORT=3306 # 数据库端口
+MYSQL_USER=root # 数据库用户
+MYSQL_PASSWORD= # 数据库密码
+MYSQL_DATABASE=pr_helper # 数据库名称
+```
+
+### 配置说明
+
+| 变量 | 必需 | 默认值 | 说明 |
+|------|------|--------|------|
+| PORT | 否 | 8080 | HTTP 监听端口 |
+| GIN_MODE | 否 | debug | Gin 运行模式 |
+| SESSION_SECRET | 否 | (随机) | 会话加密密钥 |
+| DATA_DIR | 否 | data | 数据存储路径 |
+| MYSQL_HOST | 否 | 127.0.0.1 | MySQL 主机地址 |
+| MYSQL_PORT | 否 | 3306 | MySQL 端口 |
+| MYSQL_USER | 否 | root | MySQL 用户名 |
+| MYSQL_PASSWORD | 否 | (空) | MySQL 密码 |
+| MYSQL_DATABASE | 否 | pr_helper | MySQL 数据库名 |
+
+## Docker 部署
+
+### 使用 Docker Compose
+
+```yaml
+# docker-compose.yml
+version: '3.8'
+
+services:
+ app:
+ build: .
+ ports:
+ - "8080:8080"
+ environment:
+ - MYSQL_HOST=db
+ - MYSQL_PORT=3306
+ - MYSQL_USER=root
+ - MYSQL_PASSWORD=pr_helper_pass
+ - MYSQL_DATABASE=pr_helper
+ - GIN_MODE=release
+ volumes:
+ - app-data:/app/data
+ depends_on:
+ db:
+ condition: service_healthy
+
+ db:
+ image: mysql:8.0
+ environment:
+ - MYSQL_ROOT_PASSWORD=pr_helper_pass
+ - MYSQL_DATABASE=pr_helper
+ volumes:
+ - mysql-data:/var/lib/mysql
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+volumes:
+ app-data:
+ mysql-data:
+```
+
+### 构建镜像
+
+```bash
+docker compose build
+```
+
+### 启动服务
+
+```bash
+docker compose up -d
+```
+
+### 查看日志
+
+```bash
+docker compose logs -f app
+```
+
+### 停止服务
+
+```bash
+docker compose down
+```
+
+## Dockerfile
+
+```dockerfile
+# 构建阶段
+FROM golang:1.21-alpine AS builder
+WORKDIR /app
+COPY go.mod go.sum ./
+RUN go mod download
+COPY . .
+RUN CGO_ENABLED=0 go build -o pr-helper .
+
+# 运行阶段
+FROM alpine:3.18
+RUN apk --no-cache add ca-certificates git
+WORKDIR /app
+COPY --from=builder /app/pr-helper .
+COPY --from=builder /app/templates ./templates
+COPY --from=builder /app/static ./static
+EXPOSE 8080
+CMD ["./pr-helper"]
+```
+
+## 反向代理
+
+### Nginx 配置
+
+```nginx
+server {
+ listen 80;
+ server_name pr-helper.example.com;
+
+ # SSE 支持
+ proxy_buffering off;
+ proxy_cache off;
+
+ location / {
+ proxy_pass http://127.0.0.1:8080;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+
+ # WebSocket 支持(如果需要)
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+
+ # SSE 超时
+ proxy_read_timeout 300s;
+ proxy_send_timeout 300s;
+ }
+}
+```
+
+### Caddy 配置
+
+```
+pr-helper.example.com {
+ reverse_proxy localhost:8080
+}
+```
+
+## SSL/TLS
+
+### Let's Encrypt
+
+```bash
+# 安装 certbot
+sudo apt install certbot python3-certbot-nginx
+
+# 获取证书
+sudo certbot --nginx -d pr-helper.example.com
+
+# 自动续期
+sudo certbot renew --dry-run
+```
+
+### 手动配置
+
+```nginx
+server {
+ listen 443 ssl;
+ server_name pr-helper.example.com;
+
+ ssl_certificate /etc/ssl/certs/pr-helper.crt;
+ ssl_certificate_key /etc/ssl/private/pr-helper.key;
+
+ # ... 其他配置
+}
+```
+
+## 系统服务
+
+### systemd 服务
+
+```ini
+# /etc/systemd/system/pr-helper.service
+[Unit]
+Description=PR-Helper Service
+After=network.target mysql.service
+
+[Service]
+Type=simple
+User=pr-helper
+Group=pr-helper
+WorkingDirectory=/opt/pr-helper
+ExecStart=/opt/pr-helper/pr-helper
+Restart=always
+RestartSec=5
+Environment=GIN_MODE=release
+EnvironmentFile=/opt/pr-helper/.env
+
+[Install]
+WantedBy=multi-user.target
+```
+
+### 启用服务
+
+```bash
+sudo systemctl daemon-reload
+sudo systemctl enable pr-helper
+sudo systemctl start pr-helper
+sudo systemctl status pr-helper
+```
+
+### 查看日志
+
+```bash
+sudo journalctl -u pr-helper -f
+```
+
+## 数据备份
+
+### 数据库备份
+
+```bash
+# 备份
+mysqldump -u root -p pr_helper > backup_$(date +%Y%m%d).sql
+
+# 恢复
+mysql -u root -p pr_helper < backup_20240101.sql
+```
+
+### 文件备份
+
+```bash
+# 备份数据目录
+tar -czf data_backup_$(date +%Y%m%d).tar.gz data/
+
+# 恢复
+tar -xzf data_backup_20240101.tar.gz
+```
+
+### 自动备份脚本
+
+```bash
+#!/bin/bash
+# backup.sh
+
+BACKUP_DIR="/backup/pr-helper"
+DATE=$(date +%Y%m%d)
+
+# 创建备份目录
+mkdir -p $BACKUP_DIR
+
+# 备份数据库
+mysqldump -u root -p pr_helper > $BACKUP_DIR/db_$DATE.sql
+
+# 备份数据目录
+tar -czf $BACKUP_DIR/data_$DATE.tar.gz data/
+
+# 删除 7 天前的备份
+find $BACKUP_DIR -type f -mtime +7 -delete
+```
+
+### 定时任务
+
+```bash
+# 每天凌晨 2 点备份
+0 2 * * * /opt/pr-helper/backup.sh
+```
+
+## 监控
+
+### 健康检查
+
+```bash
+curl http://localhost:8080/health
+```
+
+### 关键指标
+
+- HTTP 请求速率
+- 响应时间
+- 错误率
+- 内存使用
+- CPU 使用
+- 磁盘使用
+
+### Prometheus 集成
+
+```yaml
+# prometheus.yml
+scrape_configs:
+ - job_name: 'pr-helper'
+ static_configs:
+ - targets: ['localhost:8080']
+```
+
+### Grafana 仪表板
+
+- HTTP 请求速率
+- 响应时间分布
+- 错误率趋势
+- 系统资源使用
+
+## 日志管理
+
+### 日志级别
+
+- debug: 调试信息
+- info: 一般信息
+- warn: 警告
+- error: 错误
+
+### 日志格式
+
+```
+2024/01/01 12:00:00 PR-Helper starting on :8080
+2024/01/01 12:00:01 SSE connected: 192.168.1.100
+2024/01/01 12:00:02 LLM call: model=deepseek-v4-pro, tokens=1500
+```
+
+### 日志轮转
+
+```bash
+# /etc/logrotate.d/pr-helper
+/opt/pr-helper/logs/*.log {
+ daily
+ missingok
+ rotate 7
+ compress
+ delaycompress
+ notifempty
+ create 0644 pr-helper pr-helper
+}
+```
+
+## 性能优化
+
+### 数据库优化
+
+```sql
+-- 添加索引
+CREATE INDEX idx_analyses_repo_id ON analyses(repo_id);
+CREATE INDEX idx_analyses_user_id ON analyses(user_id);
+CREATE INDEX idx_review_notes_analysis_id ON review_notes(analysis_id);
+
+-- 优化查询
+ANALYZE TABLE analyses;
+ANALYZE TABLE repositories;
+```
+
+### 应用优化
+
+- 启用 Gzip 压缩
+- 使用 CDN 加速静态资源
+- 配置合理的连接池大小
+- 启用 HTTP/2
+
+### 系统优化
+
+```bash
+# 增加文件描述符限制
+echo "* soft nofile 65536" >> /etc/security/limits.conf
+echo "* hard nofile 65536" >> /etc/security/limits.conf
+
+# 优化内核参数
+echo "net.core.somaxconn = 65535" >> /etc/sysctl.conf
+sysctl -p
+```
+
+## 安全加固
+
+### 防火墙
+
+```bash
+# 只允许必要端口
+sudo ufw allow 22/tcp
+sudo ufw allow 80/tcp
+sudo ufw allow 443/tcp
+sudo ufw enable
+```
+
+### SELinux
+
+```bash
+# 允许网络连接
+setsebool -P httpd_can_network_connect 1
+```
+
+### 安全头
+
+```nginx
+add_header X-Frame-Options "SAMEORIGIN";
+add_header X-Content-Type-Options "nosniff";
+add_header X-XSS-Protection "1; mode=block";
+add_header Referrer-Policy "strict-origin-when-origin";
+```
+
+## 升级
+
+### 停止服务
+
+```bash
+sudo systemctl stop pr-helper
+```
+
+### 备份
+
+```bash
+./backup.sh
+```
+
+### 更新代码
+
+```bash
+git pull origin main
+go build -o pr-helper .
+```
+
+### 数据库迁移
+
+```bash
+# 自动迁移会在启动时执行
+```
+
+### 启动服务
+
+```bash
+sudo systemctl start pr-helper
+```
+
+### 验证
+
+```bash
+curl http://localhost:8080/
+```
+
+## 故障排查
+
+### 常见问题
+
+1. **数据库连接失败**
+ - 检查 MySQL 服务状态
+ - 验证连接参数
+ - 检查防火墙规则
+
+2. **端口被占用**
+ ```bash
+ lsof -i :8080
+ kill -9
+ ```
+
+3. **权限问题**
+ ```bash
+ chown -R pr-helper:pr-helper /opt/pr-helper
+ chmod 755 /opt/pr-helper/pr-helper
+ ```
+
+4. **内存不足**
+ - 增加系统内存
+ - 调整 MySQL 缓冲区大小
+ - 限制并发数
+
+### 调试模式
+
+```bash
+GIN_MODE=debug ./pr-helper
+```
+
+### 查看日志
+
+```bash
+# systemd 日志
+sudo journalctl -u pr-helper -f
+
+# 应用日志
+tail -f /var/log/pr-helper/app.log
+```
diff --git a/docs/09-development-guide.md b/docs/09-development-guide.md
new file mode 100644
index 0000000..799c6c9
--- /dev/null
+++ b/docs/09-development-guide.md
@@ -0,0 +1,711 @@
+# 开发指南
+
+## 概述
+
+本文档为 PR-Helper 开发者提供开发环境搭建、代码规范和贡献指南。
+
+## 开发环境
+
+### 系统要求
+
+| 工具 | 版本 | 说明 |
+|------|------|------|
+| Go | 1.21+ | 后端语言 |
+| MySQL | 5.7+ / 8.0+ | 数据库 |
+| Git | 2.0+ | 版本控制 |
+| Node.js | 18+ (可选) | 前端工具链 |
+
+### IDE 推荐
+
+- **GoLand**: JetBrains Go IDE
+- **VS Code**: + Go 扩展
+- **Vim/Neovim**: + vim-go
+
+### 环境配置
+
+```bash
+# 安装 Go
+wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
+sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz
+export PATH=$PATH:/usr/local/go/bin
+
+# 安装 MySQL
+sudo apt install mysql-server
+sudo mysql_secure_installation
+
+# 安装 Git
+sudo apt install git
+```
+
+## 项目搭建
+
+### 克隆项目
+
+```bash
+git clone https://github.com/your-org/pr-helper.git
+cd pr-helper
+```
+
+### 安装依赖
+
+```bash
+go mod download
+```
+
+### 配置环境
+
+```bash
+cp .env.example .env
+# 编辑 .env 文件
+```
+
+### 创建数据库
+
+```sql
+CREATE DATABASE pr_helper CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+```
+
+### 运行项目
+
+```bash
+go run .
+```
+
+## 项目结构
+
+```
+pr-helper/
+├── main.go # 应用入口
+├── go.mod # Go 模块定义
+├── go.sum # 依赖校验
+├── .env.example # 环境变量示例
+├── Dockerfile # Docker 构建
+├── docker-compose.yml # Docker Compose
+├── config/ # 配置加载
+│ └── config.go
+├── database/ # 数据库操作
+│ └── db.go
+├── handlers/ # HTTP 处理器
+│ ├── auth.go
+│ ├── generate.go
+│ ├── pages.go
+│ ├── repos.go
+│ ├── review.go
+│ ├── settings.go
+│ └── middleware.go
+├── models/ # 数据模型
+│ ├── repository.go
+│ ├── analysis.go
+│ ├── settings.go
+│ └── user.go
+├── services/ # 业务逻辑
+│ ├── git.go
+│ ├── llm.go
+│ ├── generate.go
+│ ├── review.go
+│ ├── cache.go
+│ └── notes.go
+├── templates/ # HTML 模板
+│ ├── layouts/
+│ ├── pages/
+│ └── partials/
+├── static/ # 静态资源
+│ ├── css/
+│ ├── js/
+│ └── lib/
+└── docs/ # 文档
+```
+
+## 代码规范
+
+### Go 代码规范
+
+#### 命名规范
+
+```go
+// 包名: 小写单词
+package services
+
+// 结构体: 大驼峰
+type ReviewResult struct {
+ FileReviews []FileReview
+ Summary ReviewSummary
+}
+
+// 函数: 大驼峰(导出)/ 小驼峰(未导出)
+func GenerateReview(...) (*ReviewResult, error) {
+ // ...
+}
+
+func countDiffLines(patch string) int {
+ // ...
+}
+
+// 常量: 大驼峰或全大写
+const MaxDiffSize = 60000
+const MAX_FILES = 300
+
+// 变量: 小驼峰
+var defaultModel = "deepseek-v4-pro"
+```
+
+#### 注释规范
+
+```go
+// GenerateReview performs AI code review on diff files with Top-N strategy.
+// It streams events (file_start, suggestion, file_end, summary, done) via callback
+// and returns the complete ReviewResult for persistence.
+func GenerateReview(db *sql.DB, repoPath, base, head string, topN, concurrency int, userID int64, callback StreamCallback) (*ReviewResult, error) {
+ // ...
+}
+```
+
+#### 错误处理
+
+```go
+// 显式错误检查
+result, err := doSomething()
+if err != nil {
+ return fmt.Errorf("do something: %w", err)
+}
+
+// 忽略不需要的错误
+db.conn.Exec(s) // 忽略错误(列已存在)
+```
+
+#### 表驱动测试
+
+```go
+func TestCountDiffLines(t *testing.T) {
+ tests := []struct {
+ name string
+ patch string
+ expected int
+ }{
+ {"empty", "", 0},
+ {"single add", "+line", 1},
+ {"single del", "-line", 1},
+ {"mixed", "+line1\n-line2", 2},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := countDiffLines(tt.patch)
+ if result != tt.expected {
+ t.Errorf("expected %d, got %d", tt.expected, result)
+ }
+ })
+ }
+}
+```
+
+### JavaScript 代码规范
+
+#### 命名规范
+
+```javascript
+// 常量: 全大写
+const MAX_FILES = 300;
+const BRANCH_COLORS = ['#3b82f6', '#f97316', ...];
+
+// 变量/函数: 小驼峰
+let selectedBase = null;
+function countDiffLines(patch) { ... }
+
+// 类/对象: 大驼峰
+const DiffViewer = { ... };
+const GitGraph = { ... };
+
+// 私有方法: 下划线前缀
+_ensureFileIndexed(filename) { ... }
+```
+
+#### 注释规范
+
+```javascript
+/**
+ * POST to a streaming endpoint and handle SSE events.
+ * @param {string} url - The endpoint URL
+ * @param {object} body - JSON request body
+ * @param {object} handlers - Map of event name → callback(data)
+ * @returns {object} controller with abort() method
+ */
+async post(url, body, handlers = {}) {
+ // ...
+}
+```
+
+### HTML 模板规范
+
+```html
+
+
+...
+
+
+
+
+
标题
+
+
+
+
+```
+
+## 添加新功能
+
+### 1. 添加新 API 端点
+
+#### 定义路由 (main.go)
+
+```go
+r.POST("/api/repos/:id/new-feature", authMw, handler.NewFeature)
+```
+
+#### 实现处理器 (handlers/new_feature.go)
+
+```go
+type NewFeatureHandler struct {
+ db *sql.DB
+}
+
+func NewNewFeatureHandler(db *sql.DB) *NewFeatureHandler {
+ return &NewFeatureHandler{db: db}
+}
+
+func (h *NewFeatureHandler) NewFeature(c *gin.Context) {
+ user := GetCurrentUser(c)
+ if user == nil {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
+ return
+ }
+
+ // 解析请求
+ var req struct {
+ Param1 string `json:"param1" binding:"required"`
+ Param2 int `json:"param2"`
+ }
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ // 调用服务层
+ result, err := services.DoSomething(h.db, req.Param1, req.Param2)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+
+ // 返回响应
+ c.JSON(http.StatusOK, result)
+}
+```
+
+#### 实现服务层 (services/new_feature.go)
+
+```go
+func DoSomething(db *sql.DB, param1 string, param2 int) (*Result, error) {
+ // 业务逻辑
+ // ...
+ return result, nil
+}
+```
+
+### 2. 添加 SSE 流式端点
+
+#### 处理器
+
+```go
+func (h *Handler) StreamEndpoint(c *gin.Context) {
+ // 设置 SSE 头
+ c.Header("Content-Type", "text/event-stream")
+ c.Header("Cache-Control", "no-cache")
+ c.Header("Connection", "keep-alive")
+ c.Header("X-Accel-Buffering", "no")
+ c.Status(http.StatusOK)
+
+ flusher, ok := c.Writer.(http.Flusher)
+ if !ok {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "不支持流式传输"})
+ return
+ }
+
+ sendEvent := func(event string, data interface{}) {
+ jsonData, _ := json.Marshal(data)
+ fmt.Fprintf(c.Writer, "event: %s\ndata: %s\n\n", event, jsonData)
+ flusher.Flush()
+ }
+
+ // 调用服务层
+ err := services.DoStream(h.db, sendEvent)
+ if err != nil {
+ sendEvent("error", map[string]interface{}{"message": err.Error()})
+ }
+}
+```
+
+#### 前端调用
+
+```javascript
+SSE.post('/api/repos/1/stream', {}, {
+ progress: (data) => updateProgress(data),
+ done: () => hideSpinner(),
+ error: (data) => showError(data.message)
+});
+```
+
+### 3. 添加新数据模型
+
+#### 定义模型 (models/new_model.go)
+
+```go
+type NewModel struct {
+ ID int64 `json:"id"`
+ Name string `json:"name"`
+ CreatedAt time.Time `json:"created_at"`
+}
+```
+
+#### 数据库迁移 (database/db.go)
+
+```go
+// 在 migrate() 函数中添加
+"CREATE TABLE IF NOT EXISTS new_models (" +
+ "id BIGINT AUTO_INCREMENT PRIMARY KEY," +
+ "name VARCHAR(255) NOT NULL," +
+ "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
+") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
+```
+
+### 4. 添加新前端组件
+
+#### 创建 JS 文件 (static/js/new_component.js)
+
+```javascript
+const NewComponent = {
+ init(containerId) {
+ this.container = document.getElementById(containerId);
+ if (!this.container) {
+ console.error('Container not found:', containerId);
+ return;
+ }
+ this.loadData();
+ },
+
+ async loadData() {
+ try {
+ const resp = await fetch('/api/data');
+ const data = await resp.json();
+ this.render(data);
+ } catch (err) {
+ this.container.innerHTML = `加载失败
`;
+ }
+ },
+
+ render(data) {
+ this.container.innerHTML = `
+
+ ${data.map(item => `
${item.name}
`).join('')}
+
+ `;
+ }
+};
+
+window.NewComponent = NewComponent;
+```
+
+#### 在模板中使用
+
+```html
+
+
+
+```
+
+## 测试
+
+### 单元测试
+
+```go
+// services/git_test.go
+package services
+
+import (
+ "testing"
+)
+
+func TestCountDiffLines(t *testing.T) {
+ tests := []struct {
+ name string
+ patch string
+ expected int
+ }{
+ {"empty", "", 0},
+ {"single add", "+line", 1},
+ {"single del", "-line", 1},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := countDiffLines(tt.patch)
+ if result != tt.expected {
+ t.Errorf("expected %d, got %d", tt.expected, result)
+ }
+ })
+ }
+}
+```
+
+### 集成测试
+
+```go
+// handlers/repos_test.go
+package handlers
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+)
+
+func TestListRepos(t *testing.T) {
+ gin.SetMode(gin.TestMode)
+ r := gin.New()
+ r.GET("/api/repos", handler.ListRepos)
+
+ req, _ := http.NewRequest("GET", "/api/repos", nil)
+ w := httptest.NewRecorder()
+ r.ServeHTTP(w, req)
+
+ if w.Code != http.StatusOK {
+ t.Errorf("expected status 200, got %d", w.Code)
+ }
+}
+```
+
+### 运行测试
+
+```bash
+# 运行所有测试
+go test ./...
+
+# 运行特定包的测试
+go test ./services/...
+
+# 运行特定测试
+go test -run TestCountDiffLines ./services/
+
+# 生成覆盖率报告
+go test -cover ./...
+```
+
+## 调试
+
+### Go 调试
+
+```bash
+# 使用 delve
+dlv debug .
+
+# 设置断点
+(dlv) break main.main
+(dlv) continue
+(dlv) print cfg
+```
+
+### 日志调试
+
+```go
+import "log"
+
+log.Printf("DEBUG: variable = %v", variable)
+```
+
+### 浏览器调试
+
+- 打开开发者工具 (F12)
+- Network 面板查看请求
+- Console 面板查看日志
+- Elements 面板查看 DOM
+
+## Git 工作流
+
+### 分支策略
+
+```
+main ← 生产分支
+├── develop ← 开发分支
+│ ├── feature/xxx ← 功能分支
+│ └── fix/xxx ← 修复分支
+└── release/x.x.x ← 发布分支
+```
+
+### 提交规范
+
+```
+():
+
+
+
+