233 lines
11 KiB
Markdown
233 lines
11 KiB
Markdown
# 森林AI
|
||
|
||
## 产品定位
|
||
|
||
森林AI 是一个以项目为中心、面向私有化部署的工作台 MVP,服务于个人知识工作者、团队负责人和可信的内部用户。它把项目、任务、Inbox 收集、Markdown 笔记、文件资料、AI 会话、项目内标签和轻量任务分发组织在同一工作区中。
|
||
|
||
Web 客户端桌面优先,同时保留移动浏览器可用性;登录页允许输入私有部署服务器的 IP 地址或域名。
|
||
|
||
## MVP 范围
|
||
|
||
当前版本包含:
|
||
|
||
- 项目创建、设置和聚合工作区。
|
||
- 项目内任务、标签、文件资料和计划任务元数据。
|
||
- Inbox 收集、分析建议和用户确认创建对象的流程。
|
||
- 项目、任务和笔记的关键词搜索。
|
||
- 项目内 AI 会话入口、密钥选择、限流与调用审计。
|
||
- React Web 客户端和 Tauri 桌面壳。
|
||
|
||
当前版本不包含实时即时通讯、项目级成员角色体系、匿名公开分享、自主 Agent 执行、语义或向量搜索、第三方数据源集成、原生移动 App、浏览器扩展、离线编辑或本地优先同步。
|
||
|
||
## 技术栈
|
||
|
||
- 后端:Go 1.26.1、Gin、Gorm、PostgreSQL 16。
|
||
- Web:React 18、TypeScript、Vite 8、Arco Design。
|
||
- 桌面端:Tauri 2、Rust stable,共用 Web 构建产物。
|
||
- 本地基础设施:Docker Compose。
|
||
|
||
本仓库当前验证环境使用 Node.js 24.11.0、npm 11.12.1、Rust 1.97.1;其他版本应满足各锁文件与工具链要求。
|
||
|
||
## 目录结构
|
||
|
||
```text
|
||
.
|
||
├── backend/ # Go API、数据库模型、业务逻辑和演示数据
|
||
│ ├── cmd/api/ # API 入口
|
||
│ ├── cmd/seed/ # 开发演示数据入口
|
||
│ ├── etc/ # 按模式加载的 YAML 配置
|
||
│ └── internal/ # httpx、models 与各业务包
|
||
├── apps/web_v1/ # React + TypeScript 主 Web 客户端
|
||
├── apps/desktop/ # Tauri 桌面壳
|
||
├── infra/docker-compose.yml # 本地 PostgreSQL 16
|
||
└── docs/ # 规格、计划与 MVP 验证说明
|
||
```
|
||
|
||
## 快速开始
|
||
|
||
### 1. 准备环境
|
||
|
||
安装 Go、Node.js/npm 和 Docker Desktop,并确保 Docker Desktop 已启动。桌面端构建还需要 Rust stable 与平台编译工具链。
|
||
|
||
### 2. 启动 PostgreSQL
|
||
|
||
在任意仓库子目录运行:
|
||
|
||
```powershell
|
||
$repoRoot = (git rev-parse --show-toplevel).Trim()
|
||
Set-Location $repoRoot
|
||
docker compose -f infra/docker-compose.yml up -d
|
||
```
|
||
|
||
Compose 只用于本地开发,创建 PostgreSQL 16 服务并监听 `localhost:5432`。其中的默认数据库凭据不得用于生产环境。
|
||
|
||
### 3. 初始化演示数据
|
||
|
||
```powershell
|
||
$repoRoot = (git rev-parse --show-toplevel).Trim()
|
||
Set-Location (Join-Path $repoRoot 'backend')
|
||
go run ./cmd/seed
|
||
```
|
||
|
||
默认演示账号为 `demo@senlin.ai`,默认密码为 `password123`。它们只用于本地开发;生产部署必须创建正式用户并替换所有默认凭据与密钥。
|
||
|
||
### 4. 启动 API
|
||
|
||
在新的终端中运行:
|
||
|
||
```powershell
|
||
$repoRoot = (git rev-parse --show-toplevel).Trim()
|
||
Set-Location (Join-Path $repoRoot 'backend')
|
||
go run ./cmd/api
|
||
```
|
||
|
||
开发配置默认监听 `http://localhost:9150`。可用 `GET /healthz` 检查进程健康状态;Web 登录页使用 `GET /api/v1/status` 检查服务器连接。
|
||
|
||
### 5. 启动 Web 客户端
|
||
|
||
在新的终端中运行:
|
||
|
||
```powershell
|
||
$repoRoot = (git rev-parse --show-toplevel).Trim()
|
||
Set-Location (Join-Path $repoRoot 'apps/web_v1')
|
||
npm ci
|
||
npm run dev
|
||
```
|
||
|
||
Vite 默认地址为 `http://localhost:5173`。登录页的服务器地址默认是 `http://localhost:9150`;用户可以改为私有部署服务器的 IP 地址或域名,登录及后续 API 请求会使用用户填写的地址。
|
||
|
||
## 配置
|
||
|
||
后端从 `backend/etc/agent.<mode>.yaml` 读取配置。`SENLIN_APP_MODE` 未设置时使用 `backend/etc/agent.dev.yaml`;例如设置 `$env:SENLIN_APP_MODE='prod'` 时读取 `backend/etc/agent.prod.yaml`,该生产文件需由部署方安全提供。
|
||
|
||
| 字段 | 用途 |
|
||
| --- | --- |
|
||
| `env` | 运行环境标识。 |
|
||
| `port` | API 监听端口;开发值为 `9150`。 |
|
||
| `dsn` | PostgreSQL 连接串。 |
|
||
| `storage_dir` | 服务端文件存储根目录。 |
|
||
| `max_upload_bytes` | multipart 请求体大小上限;开发值为 32 MiB。 |
|
||
| `auth_secret` | 邀请和登录会话签名密钥。 |
|
||
| `system_ai_key` | 可选的系统级 AI provider fallback key。 |
|
||
| `ai_key_encryption_secret` | 用户 AI key 的静态加密密钥。 |
|
||
| `allowed_origins` | 允许访问 API 的 Web/Tauri 来源列表。 |
|
||
|
||
不要提交生产数据库连接串、AI key、会话凭据或真实密钥。开发配置中的固定值和 Compose 数据库账号仅供本地使用,生产环境必须全部替换。修改 Web 来源或端口时,应同步更新 `allowed_origins`,否则浏览器会拦截跨域请求。
|
||
|
||
`go run ./cmd/seed` 还支持 `DATABASE_DSN` 或 `DATABASE_URL` 临时覆盖数据库地址,以及 `SENLIN_SEED_EMAIL`、`SENLIN_SEED_NAME`、`SENLIN_SEED_PASSWORD` 覆盖演示用户信息。
|
||
|
||
## API v1 概览
|
||
|
||
业务接口前缀为 `/api/v1`,JSON 字段使用 camelCase,对外 ID 使用 UUIDv7 字符串,时间使用 UTC RFC 3339。除连接状态和登录外,接口都要求有效的 `Authorization: Bearer` 会话凭据。
|
||
|
||
| 方法 | 路径 | 成功响应 |
|
||
| --- | --- | --- |
|
||
| `GET` | `/api/v1/status` | `200`,包含 `timestamp` 的对象;无需登录。 |
|
||
| `POST` | `/api/v1/auth/login` | `200`,包含签名且会过期的 `token` 字段;请求体为 `email`、`password`。 |
|
||
| `GET` | `/api/v1/projects` | `200`,项目 DTO 数组。 |
|
||
| `POST` | `/api/v1/projects` | `201`,项目 DTO。 |
|
||
| `GET` | `/api/v1/projects/:projectId` | `200`,项目 DTO。 |
|
||
| `PATCH` | `/api/v1/projects/:projectId` | `200`,更新后的项目 DTO。 |
|
||
| `GET` | `/api/v1/projects/:projectId/workspace` | `200`,聚合项目、频道、标签、Inbox、任务、AI 会话、笔记资料和计划任务的工作区 DTO。 |
|
||
| `POST` | `/api/v1/projects/:projectId/tasks` | `201`,任务 DTO。 |
|
||
| `PATCH` | `/api/v1/projects/:projectId/tasks/:taskId` | `200`,更新后的任务 DTO。 |
|
||
| `GET` | `/api/v1/projects/:projectId/tags` | `200`,该项目的标签 DTO 数组。 |
|
||
| `POST` | `/api/v1/projects/:projectId/tags` | `201`,标签 DTO。 |
|
||
| `POST` | `/api/v1/projects/:projectId/sources` | `201`,文件资料 DTO;请求为含 `file` 和可选 `title` 的 multipart 表单。 |
|
||
| `POST` | `/api/v1/projects/:projectId/cron-plans` | `201`,计划任务 DTO;只管理元数据,不执行自主 Agent。 |
|
||
| `POST` | `/api/v1/projects/:projectId/inbox` | `201`,Inbox 条目 DTO。 |
|
||
| `POST` | `/api/v1/inbox/:inboxId/analyze` | `200`,`suggestions` 草稿数组,不创建正式对象。 |
|
||
| `POST` | `/api/v1/inbox/:inboxId/confirm` | `200`,含 `createdCount`;只确认服务端已保存且由用户选中的建议。 |
|
||
| `GET` | `/api/v1/search?q=...` | `200`,`items` 数组,当前检索项目、任务和笔记。 |
|
||
| `GET` | `/api/v1/projects/:projectId/ai-sessions` | `200`,AI 会话 DTO 数组。 |
|
||
| `POST` | `/api/v1/projects/:projectId/ai-sessions` | `201`,AI 会话 DTO;请求体只接收 `title`、`context`。 |
|
||
|
||
错误统一返回 `{"error":{"code":"...","message":"..."}}`。路径中的项目、任务和 Inbox ID 必须是 UUIDv7;数据库自增 ID 不对外暴露。
|
||
|
||
## 验证
|
||
|
||
后端:
|
||
|
||
```powershell
|
||
$repoRoot = (git rev-parse --show-toplevel).Trim()
|
||
Set-Location (Join-Path $repoRoot 'backend')
|
||
go test -count=1 ./...
|
||
go vet ./...
|
||
```
|
||
|
||
Web:
|
||
|
||
```powershell
|
||
$repoRoot = (git rev-parse --show-toplevel).Trim()
|
||
Set-Location (Join-Path $repoRoot 'apps/web_v1')
|
||
node scripts/visual-check.mjs
|
||
npm run build
|
||
npm run lint
|
||
```
|
||
|
||
如果设置了指向隔离测试库的专用 `TEST_DATABASE_URL`,再运行完整 PostgreSQL 集成测试套件:
|
||
|
||
```powershell
|
||
$repoRoot = (git rev-parse --show-toplevel).Trim()
|
||
Set-Location (Join-Path $repoRoot 'backend')
|
||
$env:TEST_DATABASE_URL = '<隔离测试库连接串>'
|
||
go test -count=1 -tags=integration ./...
|
||
```
|
||
|
||
不得把真实测试数据库凭据写入仓库。带 `integration` tag 的测试在缺少 `TEST_DATABASE_URL` 时会明确失败,且不会读取运行环境的 `DATABASE_URL`。当前机器的逐项执行结果与产物路径见 [MVP 验证说明](docs/mvp-verification.md)。
|
||
|
||
## 桌面端
|
||
|
||
先完成 Web 依赖安装,再构建 Tauri 可执行文件:
|
||
|
||
```powershell
|
||
$repoRoot = (git rev-parse --show-toplevel).Trim()
|
||
Set-Location (Join-Path $repoRoot 'apps/desktop')
|
||
npm ci
|
||
npm run build
|
||
```
|
||
|
||
`npm run build` 会先执行 Web 生产构建,再运行 `tauri build --no-bundle`。Windows 产物位于 `apps/desktop/src-tauri/target/release/senlinai_workbench.exe`。
|
||
|
||
需要安装包时运行:
|
||
|
||
```powershell
|
||
$repoRoot = (git rev-parse --show-toplevel).Trim()
|
||
Set-Location (Join-Path $repoRoot 'apps/desktop')
|
||
npm run bundle
|
||
```
|
||
|
||
打包需要 Rust stable、平台编译工具链和对应安装器依赖;它与无安装包的可执行文件构建是两个独立验证项。
|
||
|
||
## 常见问题
|
||
|
||
### 数据库连接或认证失败
|
||
|
||
确认 Docker Desktop 和 Compose 服务正在运行,并用 `docker compose -f infra/docker-compose.yml ps` 查看状态。若本机已有 PostgreSQL 占用 `5432`,请停止冲突服务或同步修改 Compose 映射与 `backend/etc/agent.dev.yaml` 中的 `dsn`。演示数据和 API 必须连接到同一个数据库。
|
||
|
||
### 浏览器提示无法连接或 CORS 错误
|
||
|
||
先访问 `http://localhost:9150/api/v1/status`。如果 API 可达但浏览器仍失败,检查登录页的服务器地址、协议和端口,并把实际 Web 来源加入 `allowed_origins` 后重启 API。
|
||
|
||
### Vite 端口被占用
|
||
|
||
默认端口是 `5173`。可以停止占用进程,或让 Vite 选择可用端口;使用其他端口时必须同步配置后端 `allowed_origins`。
|
||
|
||
### 演示账号无法登录
|
||
|
||
先成功运行 `go run ./cmd/seed`,再使用 seed 命令输出所对应的开发账号。若通过环境变量覆盖过演示用户信息,应使用覆盖后的值。生产环境不得使用默认演示密码。
|
||
|
||
### AI 会话返回缺少密钥或限流错误
|
||
|
||
创建 AI 会话前会执行每用户、每动作限流并选择加密存储的用户 key 或系统 fallback key。缺少可用 key 时返回服务不可用,超过配额时返回 `429`;不要在前端或提交记录中粘贴 key。
|
||
|
||
## 安全说明
|
||
|
||
- 登录会话使用签名且会过期的 bearer token;服务端从认证中间件读取当前用户,不接受可伪造的用户 ID 字符串。
|
||
- 用户 AI key 加密存储。AI 操作先限流,再选择 provider/key,并记录 provider、key 类型、action、status 和 error;日志不得记录 key 明文。
|
||
- AI 分析和会话不会自动创建正式任务、笔记或资料。Inbox 只有在用户确认服务端保存的建议后才创建对象,并保留来源 Inbox ID。
|
||
- 项目标签只在项目内有效。任务移动、标签写入和文件上传都校验项目所有权。
|
||
- 任务分享遵循最小可见范围:被指派人只能看到任务以及被显式分享的关联对象;分享的 note/source 必须与任务属于同一项目。
|
||
- 文件存储路径只由文件服务构造,上传在写磁盘前校验所有权和请求体大小;公开 DTO 仅返回不透明存储键,不暴露物理路径。
|
||
- 生产部署必须替换所有开发凭据和密钥、限制 CORS 来源、使用独立数据库与受控存储目录,并通过 HTTPS 暴露服务。
|