# 森林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 ./...
```

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
```

如果设置了指向隔离测试库的 `DATABASE_URL`，再运行 PostgreSQL 集成检查：

```powershell
$repoRoot = (git rev-parse --show-toplevel).Trim()
Set-Location (Join-Path $repoRoot 'backend')
go test -tags integration ./internal/models -run TestPostgresPing -v
```

不得把真实测试数据库凭据写入仓库。当前机器的逐项执行结果与产物路径见 [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 必须与任务属于同一项目。
- 文件存储路径只由文件服务构造，上传在写磁盘前校验所有权和请求体大小。
- 生产部署必须替换所有开发凭据和密钥、限制 CORS 来源、使用独立数据库与受控存储目录，并通过 HTTPS 暴露服务。
