# 前端接入说明

更新日期：2026-04-01

本文面向前端开发，说明当前 `sophnet-schedule` 在“本地托底模式 + Google 接入流程”下的接口、状态流和推荐调用顺序。

## 一、整体模式

系统现在有两种运行模式：

- `local`
  - 默认模式
  - 用户没有 Google token，或尚未完成首次同步时使用
  - 事件读写来自本地 `data/local_events.json`
  - 提醒仍通过 OpenClaw `jobs.json` 执行

- `google`
  - 用户已完成 Google 授权，且首次同步成功后启用
  - 事件读写来自 Google Calendar

前端不需要自己判断应该走哪套事件 API。

现有日历接口路径保持不变：

- `GET /events`
- `POST /events`
- `DELETE /events/{event_id}`
- `GET /events/conflicts`
- `GET /daily-plan`
- `GET /reminders`

前端只需要读取响应中的：

- `provider`
- `active_mode`
- `sync_state`

来决定页面展示文案。

## 二、认证

所有接口都需要请求头：

```http
X-API-Key: <your-api-key>
```

开发环境如果服务端 `API_KEY=dev`，仍建议前端带上 `X-API-Key: dev`。

## 三、核心状态流

### 1. 默认本地模式

初始状态：

- `active_mode = local`
- `google_state = not_connected`

这时前端可以直接：

- 创建本地事件
- 查询某天事件
- 查看冲突
- 查看日报
- 查看提醒预览

### 2. 启动 Google 接入

前端流程：

1. 调 `GET /integrations/google/status`
2. 调 `GET /integrations/google/guide`
3. 用户填写自己的 `client_id` / `client_secret`
4. 调 `POST /integrations/google/start`
5. 打开返回的 `authorize_url`
6. 用户授权完成后，Google 回调到后端
7. 前端重新查询 `GET /integrations/google/status`

授权成功但还没同步时：

- `active_mode = local`
- `google_state = connected_pending_sync`

### 3. 首次同步

前端在 `connected_pending_sync` 时调用：

- `POST /integrations/google/sync`

同步成功后：

- `active_mode = google`
- `google_state = ready`

同步失败时：

- `active_mode` 仍然保持 `local`
- `google_state = connected_pending_sync`
- 错误信息在 `last_error` 和 `last_sync_summary.last_error`

## 四、状态接口

### `GET /integrations/google/status`

用途：

- 判断当前模式
- 判断 Google 是否已接入
- 判断是否需要首次同步
- 给前端页面顶部或设置页展示连接状态

返回示例：

```json
{
  "active_mode": "local",
  "google_state": "not_connected",
  "has_token": false,
  "masked_client_id": "",
  "needs_initial_sync": false,
  "current_step": "credentials",
  "last_error": "",
  "last_sync_time": "",
  "last_sync_summary": {
    "status": "idle",
    "total": 0,
    "succeeded": 0,
    "failed": 0,
    "skipped": 0,
    "started_at": "",
    "finished_at": "",
    "last_error": ""
  }
}
```

字段说明：

- `active_mode`
  - 当前事件主来源
  - `local | google`
- `google_state`
  - Google 接入状态
  - `not_connected | authorizing | connected_pending_sync | syncing | ready | error`
- `has_token`
  - 服务端是否已经有有效 token 文件
- `needs_initial_sync`
  - 是否还需要执行首次同步
- `current_step`
  - 前端向导当前步骤
  - `credentials | authorize | sync | done`

### `GET /integrations/google/guide`

用途：

- 获取前端向导步骤文案
- 获取回调地址

返回示例：

```json
{
  "current_step": "credentials",
  "active_mode": "local",
  "google_state": "not_connected",
  "callback_url": "http://127.0.0.1:8765/integrations/google/callback",
  "required_fields": ["client_id", "client_secret"],
  "steps": [
    {
      "key": "credentials",
      "title": "填写 Google OAuth 凭据",
      "description": "用户提供自己的 Google Client ID 和 Client Secret。"
    }
  ]
}
```

## 五、授权启动与回调

### `POST /integrations/google/start`

用途：

- 启动授权会话
- 获取 Google 授权链接

请求体：

```json
{
  "client_id": "xxx.apps.googleusercontent.com",
  "client_secret": "xxx"
}
```

返回示例：

```json
{
  "ok": true,
  "authorize_url": "https://accounts.google.com/o/oauth2/v2/auth?...",
  "expires_at": "2026-04-01T08:15:00+00:00",
  "google_state": "authorizing"
}
```

前端处理建议：

- 在新窗口或弹窗中打开 `authorize_url`
- 不要在前端保存 `client_secret`
- 授权完成后关闭弹窗，再轮询或刷新状态接口

### `GET /integrations/google/callback`

用途：

- Google OAuth 回调地址
- 前端不直接调用

行为：

- 成功时返回简单 HTML 成功页
- 失败时返回简单 HTML 失败页

前端只需在授权完成后重新请求：

- `GET /integrations/google/status`

## 六、同步接口

### `POST /integrations/google/sync`

用途：

- 将本地事件补推到 Google
- 成功后把系统切换到 `google` 模式

返回示例：

```json
{
  "ok": true,
  "active_mode": "google",
  "google_state": "ready",
  "summary": {
    "status": "finished",
    "total": 3,
    "succeeded": 3,
    "failed": 0,
    "skipped": 0,
    "started_at": "2026-04-01T08:00:00+00:00",
    "finished_at": "2026-04-01T08:00:02+00:00",
    "last_error": ""
  }
}
```

前端处理建议：

- 成功后立刻刷新 `GET /integrations/google/status`
- 如果 `summary.failed > 0`，保持在设置页提示用户重试

### `GET /integrations/google/sync-status`

用途：

- 查看最近一次同步结果

适合：

- 设置页
- 授权完成后的结果展示

## 七、事件接口

### `GET /events`

支持两种查询方式：

- `GET /events?date=YYYY-MM-DD`
- `GET /events?start=<ISO>&end=<ISO>`

返回新增字段：

- `provider`
- `active_mode`
- 每条事件上的 `sync_state`

返回示例：

```json
{
  "date": "2026-04-01",
  "provider": "local",
  "active_mode": "local",
  "events": [
    {
      "id": "local_abc123",
      "summary": "本地会议",
      "start": "2026-04-01T09:00:00+08:00",
      "end": "2026-04-01T10:00:00+08:00",
      "status": "confirmed",
      "duration_minutes": 60,
      "provider": "local",
      "sync_state": "local_only"
    }
  ],
  "conflicts": []
}
```

前端展示建议：

- `provider=local` 时可显示“本地模式”
- `sync_state=local_only` 时可显示“未同步到 Google”
- `sync_state=failed` 时可显示“同步失败”

### `POST /events`

请求体保持原样：

```json
{
  "summary": "产品评审",
  "date": "2026-04-01",
  "start": "10:00",
  "end": "11:30",
  "location": "线上",
  "description": "Q2 规划",
  "remind": 15
}
```

返回新增字段：

- `provider`
- `sync_state`

说明：

- `local` 模式下，事件写入本地 JSON
- `google` 模式下，事件写入 Google Calendar
- `remind > 0` 时，仍会注册 OpenClaw 一次性提醒任务

### `DELETE /events/{event_id}`

说明：

- 本地模式下，本地事件会被标记取消
- Google 模式下，会删除 Google 事件

### `GET /events/conflicts`

说明：

- 继续复用 `/events` 的冲突检测结果
- 本地模式和 Google 模式都可用

## 八、日报与提醒

### `GET /daily-plan`

作用：

- 获取某一天的事件、冲突和已生成 Markdown

新增字段：

- `provider`

说明：

- Markdown 仍然只是人类可读日报，不是事件数据库
- 前端如要做日历视图，应该使用 `/events`，不要解析 Markdown

### `GET /reminders?window=30`

作用：

- 预览未来 N 分钟内将触发的提醒

新增字段：

- `provider`
- `active_mode`

说明：

- 本地模式下从本地事件计算
- Google 模式下从 Google 日历计算

## 九、前端推荐接入顺序

### 场景 A：用户完全没有 Google

1. 页面启动先调 `GET /integrations/google/status`
2. 发现 `active_mode=local`
3. 日历页直接用 `/events`
4. 创建事件直接用 `POST /events`
5. 日报页用 `/daily-plan`
6. 提醒预览用 `/reminders`

### 场景 B：用户要接入 Google

1. 调 `GET /integrations/google/guide`
2. 展示凭据输入表单
3. 调 `POST /integrations/google/start`
4. 打开 `authorize_url`
5. 授权完成后刷新 `GET /integrations/google/status`
6. 若 `google_state=connected_pending_sync`，显示“开始同步”按钮
7. 用户点击后调 `POST /integrations/google/sync`
8. 成功后重新拉 `GET /integrations/google/status`
9. 当 `active_mode=google` 后，前端其余日历调用不需要换接口

## 十、错误处理建议

- 401
  - `X-API-Key` 缺失或错误
- 400
  - 日期、时间或请求参数格式不合法
- 502
  - Google 授权或 Google API 上游失败
- 500
  - 服务端内部错误

前端建议：

- 对 `/integrations/google/*` 的错误单独显示，不要影响本地模式日程页面
- 即使 Google 接入失败，只要 `active_mode=local`，日历功能仍应保持可用

## 十一、已知边界

- 当前是单用户模型，不支持多账号切换
- 首次同步只做“本地 -> Google”单向补推
- 不做双向合并，也不处理 Google 现有事件去重
- Markdown 报表不是结构化托底源，只做展示用途
