# 接口文档

## 概述

前后端通信接口契约。WebSocket 承载实时对话，REST API 支撑基础运维。

**设计原则**：
- WebSocket 为主：所有对话数据走 WebSocket
- REST 为辅：仅用于健康检查、认证、对话管理等低频操作
- 接口先行：先定义契约，再填充实现——前后端可并行开发

## 接口全景

```
浏览器                    Go Gateway :8080
  WebSocket Client  <-->  /ws?token=<jwt>             （实时对话，需 JWT 认证）
  HTTP Client       -->   GET /api/health              （健康检查）
  HTTP Client       <-->  POST /api/auth/*             （注册/登录/刷新/登出）
  HTTP Client       <-->  GET/POST/PATCH/DELETE        （对话 CRUD）
                           /api/conversations/*
  HTTP Client       <-->  GET /api/conversations/:id   （历史消息）
                           /messages
```

---

## 一、WebSocket 协议

连接地址：`ws://localhost:8080/ws?token=<access_token>&conversation_id=<uuid>`

| 参数 | 必填 | 说明 |
|------|------|------|
| `token` | 是 | JWT access_token，缺失或无效时返回 401 |
| `conversation_id` | 否 | 恢复已有对话；省略则创建新对话 |

### 消息格式约定

所有 WebSocket 消息均为 JSON 文本帧，统一结构：

```typescript
interface WsMessage {
  type: string;        // 消息类型，必填
  request_id?: string; // 可选，用于请求-响应关联
  timestamp?: number;  // 可选，毫秒时间戳
  [key: string]: any;  // 类型特定字段
}
```

### 客户端 → 服务端消息

#### `query` — 发起一次视觉对话

```typescript
interface QueryMessage {
  type: "query";
  request_id: string;    // 客户端生成的 UUID
  image: string;         // Base64 编码的 JPEG 图像（不含 data: 前缀）
  audio: string;         // Base64 编码的音频片段（PCM 16kHz），文本输入时为空字符串
  text?: string;         // 用户手动输入的文本（有值时跳过 STT，直接使用此文本）
  mime_type?: string;    // 音频格式，默认 "audio/pcm"
}
```

#### `config` — 更新会话配置

```typescript
interface ConfigMessage {
  type: "config";
  payload: {
    tts_enabled?: boolean;         // 是否开启语音合成，默认 true
    detail_level?: "low" | "high"; // 图像精度，默认 "low"
    language?: string;             // 交互语言，默认 "zh-CN"
    scenario?: string;             // 场景模式：free_chat / interviewer / english_teacher / debate / interpreter
  };
}
```

#### `interrupt` — 打断当前回复

```typescript
interface InterruptMessage {
  type: "interrupt";
  request_id?: string; // 可选，当前实现不使用此字段，服务端始终取消当前活跃请求
}
```

#### `ping` — 心跳保活

```typescript
interface PingMessage {
  type: "ping";
}
```

### 服务端 → 客户端消息

#### `connected` — 连接建立确认

```typescript
interface ConnectedMessage {
  type: "connected";
  session_id: string;
  conversation_id: string;
  config: {
    tts_enabled: boolean;
    detail_level: "low" | "high";
    language: string;
    scenario: string;
  };
}
```

#### `stt_result` — 语音识别结果

```typescript
interface SttResultMessage {
  type: "stt_result";
  request_id: string;
  text: string;          // 识别出的文本
  is_final: boolean;     // 当前实现始终为 true
}
```

#### `llm_chunk` — LLM 流式响应片段

```typescript
interface LlmChunkMessage {
  type: "llm_chunk";
  request_id: string;
  content: string;       // 当前 token 片段
}
```

#### `llm_done` — LLM 响应完成

```typescript
interface LlmDoneMessage {
  type: "llm_done";
  request_id: string;
  full_text: string;     // 完整响应文本
}
```

#### `tts_audio` — TTS 音频片段

音频流式推送，每个消息携带一个句子的音频数据。

```typescript
interface TtsAudioMessage {
  type: "tts_audio";
  request_id: string;
  audio: string;         // Base64 编码的音频数据
  format: string;        // 音频格式
  sample_rate: number;   // 采样率（Hz）
  sequence: number;      // 句子序号，从 0 开始递增
  is_final: boolean;     // 是否为最后一个句子
}
```

**音频格式约束**：

| 字段 | 值 | 说明 |
|------|-----|------|
| `format` | `"pcm"` | 线性 PCM，小端序 |
| `sample_rate` | `24000` | 24kHz 采样率 |
| 位深度 | 16-bit | 单声道 |
| 句子划分 | 按标点符号（。！？；：）分割 | 服务端按句分割 LLM 响应，并行合成 |

#### `error` — 错误通知

```typescript
interface ErrorMessage {
  type: "error";
  request_id?: string;   // 关联的请求 ID，全局错误时为空
  code: string;          // 错误码，见下文错误码表
  message: string;       // 人类可读的错误描述
  details?: any;         // 可选的详细错误信息
}
```

#### `pong` — 心跳响应

```typescript
interface PongMessage {
  type: "pong";
}
```

### 连接管理

- **心跳机制**：客户端每 30 秒发送 `ping`，服务端回复 `pong`；60 秒无活动则服务端断开连接
- **重连策略**：客户端断线后指数退避重连（1s → 2s → 4s → ... → 最大 30s）
- **并发控制**：同一连接同时只能有一个活跃的 `query` 请求；新请求到来时自动取消旧请求

---

## 二、REST API

所有 REST 端点均使用 JSON 格式。

### 2.1 健康检查

#### `GET /api/health`

检查服务健康状态。

**响应**：
```json
{
  "status": "healthy",
  "timestamp": "2024-01-15T10:30:00Z",
  "dependencies": {
    "database": "healthy",
    "redis": "healthy"
  }
}
```

### 2.2 认证 API

#### `POST /api/auth/register` — 用户注册

**请求**：
```json
{
  "username": "alice",
  "email": "alice@example.com",
  "password": "SecurePass123!"
}
```

**响应**（200 OK）：
```json
{
  "user": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "username": "alice",
    "email": "alice@example.com",
    "created_at": "2024-01-15T10:30:00Z"
  },
  "access_token": "eyJhbGc...",
  "refresh_token": "eyJhbGc...",
  "expires_in": 7200
}
```

**错误**：
- `400 INVALID_INPUT`: 参数验证失败
- `409 USER_EXISTS`: 用户名或邮箱已存在

#### `POST /api/auth/login` — 用户登录

**请求**：
```json
{
  "username": "alice",
  "password": "SecurePass123!"
}
```

**响应**（200 OK）：
```json
{
  "user": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "username": "alice",
    "email": "alice@example.com"
  },
  "access_token": "eyJhbGc...",
  "refresh_token": "eyJhbGc...",
  "expires_in": 7200
}
```

**错误**：
- `400 INVALID_INPUT`: 参数缺失
- `401 INVALID_CREDENTIALS`: 用户名或密码错误

#### `POST /api/auth/refresh` — 刷新 Access Token

**请求头**：
```
Authorization: Bearer <refresh_token>
```

**响应**（200 OK）：
```json
{
  "access_token": "eyJhbGc...",
  "refresh_token": "eyJhbGc...",
  "expires_in": 7200
}
```

**错误**：
- `401 INVALID_TOKEN`: Refresh Token 无效或过期

#### `POST /api/auth/logout` — 用户登出

**请求头**：
```
Authorization: Bearer <access_token>
```

**响应**（200 OK）：
```json
{
  "message": "Logged out successfully"
}
```

### 2.3 对话管理 API

所有端点均需 JWT 认证（`Authorization: Bearer <access_token>`）。

#### `GET /api/conversations` — 获取对话列表

**查询参数**：
- `page`: 页码，从 1 开始，默认 1
- `page_size`: 每页条数，默认 20，最大 100

**响应**（200 OK）：
```json
{
  "conversations": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "title": "关于植物的对话",
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-15T11:45:00Z",
      "message_count": 12
    }
  ],
  "total": 42,
  "page": 1,
  "page_size": 20
}
```

#### `POST /api/conversations` — 创建新对话

**请求**：
```json
{
  "title": "新的对话"
}
```

**响应**（201 Created）：
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "新的对话",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z",
  "message_count": 0
}
```

#### `GET /api/conversations/:id` — 获取对话详情

**响应**（200 OK）：
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "关于植物的对话",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T11:45:00Z",
  "message_count": 12
}
```

**错误**：
- `404 NOT_FOUND`: 对话不存在或无权访问

#### `PATCH /api/conversations/:id` — 更新对话

**请求**：
```json
{
  "title": "修改后的标题"
}
```

**响应**（200 OK）：
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "修改后的标题",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T12:00:00Z",
  "message_count": 12
}
```

#### `DELETE /api/conversations/:id` — 删除对话

**响应**（204 No Content）：无响应体

**错误**：
- `404 NOT_FOUND`: 对话不存在或无权访问

#### `GET /api/conversations/:id/messages` — 获取对话消息

**查询参数**：
- `page`: 页码，从 1 开始，默认 1
- `page_size`: 每页条数，默认 50，最大 100

**响应**（200 OK）：
```json
{
  "messages": [
    {
      "id": "660e8400-e29b-41d4-a716-446655440000",
      "conversation_id": "550e8400-e29b-41d4-a716-446655440000",
      "role": "user",
      "content": "这是什么植物？",
      "image_url": "/api/images/abc123.jpg",
      "created_at": "2024-01-15T10:30:00Z"
    },
    {
      "id": "770e8400-e29b-41d4-a716-446655440000",
      "conversation_id": "550e8400-e29b-41d4-a716-446655440000",
      "role": "assistant",
      "content": "这是一株向日葵...",
      "created_at": "2024-01-15T10:30:15Z"
    }
  ],
  "total": 12,
  "page": 1,
  "page_size": 50
}
```

**消息字段说明**：
- `role`: `"user"` 或 `"assistant"`
- `image_url`: 仅 `user` 消息可能包含，指向存储的图像
- `content`: 消息文本内容

---

## 三、错误码表

所有错误均使用以下格式：

```json
{
  "code": "ERROR_CODE",
  "message": "Human-readable error description",
  "details": {}
}
```

### WebSocket 错误码

| 错误码 | 说明 | HTTP 状态码（若适用）|
|--------|------|---------------------|
| `INVALID_MESSAGE` | 消息格式错误或缺少必填字段 | - |
| `SESSION_NOT_FOUND` | 会话不存在 | - |
| `RATE_LIMITED` | 请求频率过高 | 429 |
| `IMAGE_TOO_LARGE` | 图像超过大小限制（5MB）| - |
| `AUDIO_TOO_LARGE` | 音频超过大小限制（10MB）| - |
| `STT_ERROR` | 语音识别服务错误 | - |
| `LLM_ERROR` | LLM 服务错误 | - |
| `LLM_TIMEOUT` | LLM 响应超时（60 秒）| - |
| `TTS_ERROR` | 语音合成服务错误 | - |
| `CONCURRENT_REQUEST` | 同一连接已有进行中的请求 | - |
| `INTERNAL_ERROR` | 服务器内部错误 | 500 |

### REST API 错误码

| 错误码 | 说明 | HTTP 状态码 |
|--------|------|-------------|
| `INVALID_INPUT` | 请求参数验证失败 | 400 |
| `INVALID_TOKEN` | JWT Token 无效或过期 | 401 |
| `INVALID_CREDENTIALS` | 用户名或密码错误 | 401 |
| `UNAUTHORIZED` | 未认证或认证失败 | 401 |
| `FORBIDDEN` | 无权访问资源 | 403 |
| `NOT_FOUND` | 资源不存在 | 404 |
| `USER_EXISTS` | 用户名或邮箱已存在 | 409 |
| `RATE_LIMITED` | 请求频率过高 | 429 |
| `INTERNAL_ERROR` | 服务器内部错误 | 500 |
| `SERVICE_UNAVAILABLE` | 依赖服务不可用 | 503 |

---

## 四、数据模型

### 用户（User）

```typescript
interface User {
  id: string;              // UUID
  username: string;        // 用户名，唯一
  email: string;           // 邮箱，唯一
  created_at: string;      // ISO 8601 时间戳
  updated_at: string;      // ISO 8601 时间戳
}
```

### 对话（Conversation）

```typescript
interface Conversation {
  id: string;              // UUID
  user_id: string;         // 所属用户 ID
  title: string;           // 对话标题
  created_at: string;      // ISO 8601 时间戳
  updated_at: string;      // ISO 8601 时间戳
  message_count: number;   // 消息数量
}
```

### 消息（Message）

```typescript
interface Message {
  id: string;              // UUID
  conversation_id: string; // 所属对话 ID
  role: "user" | "assistant";
  content: string;         // 消息文本内容
  image_url?: string;      // 可选，用户消息的关联图像 URL
  created_at: string;      // ISO 8601 时间戳
}
```

### JWT Token 载荷

**Access Token**（有效期 120 分钟）：
```json
{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "username": "alice",
  "type": "access",
  "exp": 1705318200,
  "iat": 1705311000
}
```

**Refresh Token**（有效期 7 天）：
```json
{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "refresh",
  "exp": 1705915800,
  "iat": 1705311000
}
```

---

## 附录：版本历史

- **v1.0**（2024-01-15）：初始版本，定义 WebSocket 协议和 REST API
- **v1.1**（2024-01-20）：新增文本输入模式（`query.text` 字段）
- **v1.2**（2024-01-25）：新增场景模式配置（`config.scenario` 字段）
- **v2.0**（2026-06-21）：重构为纯接口契约规范，移除实现细节
