## ADDED Requirements

### Requirement: Message status lifecycle
每条对话消息 SHALL 拥有独立的状态字段，使用 3 个状态：
- `loading`: 处理中（发送/转录/流式输出）
- `done`: 完成
- `error`: 失败

#### Scenario: Student message sent successfully
- **WHEN** 学生完成录音并发送消息
- **THEN** 消息状态从 `loading` 变为 `done`，消息内容来自 ASR 转写结果

#### Scenario: AI reply streams in
- **WHEN** 学生消息发送成功后触发 AI 回复
- **THEN** AI 消息进入 `loading` 状态（显示 typing 动画），收到 token 后逐字渲染，流结束后变为 `done`

#### Scenario: Request fails at any stage
- **WHEN** 网络异常或后端错误导致请求失败（不区分 ASR/LLM 阶段）
- **THEN** 出错的消息状态变为 `error`，显示「重试」按钮

### Requirement: Message retry
系统 SHALL 支持对 `error` 状态的消息进行重试操作。重试按钮 SHALL 显示在出错消息的下方。

#### Scenario: Retry a failed student message
- **WHEN** 用户点击学生消息下方的「重试」按钮
- **THEN** 系统用缓存的 audioBlob 重新调用 `/speak`，整个流程重走

#### Scenario: Retry a failed AI message (stream interrupted)
- **WHEN** AI 消息流式中断，用户点击 AI 消息下方的「重新生成」按钮
- **THEN** 系统清空 AI 消息内容，重新调用 `/speak`（后端幂等，不重复存学生消息），重新流式填充

#### Scenario: User chooses to re-record instead of retry
- **WHEN** 学生消息发送失败后，用户直接点击录音按钮录新音频
- **THEN** 系统丢弃失败消息，用新录音发起新的 `/speak` 请求

### Requirement: AudioBlob lifecycle
录音 Blob SHALL 在该轮对话成功完成前保持缓存，以支持重试。

#### Scenario: Retain blob until round completes
- **WHEN** 学生完成录音
- **THEN** audioBlob 保持在内存中，直到该轮 AI 回复 `done` 后才可释放

### Requirement: Round progression
对话引擎 SHALL 按轮次推进对话。每轮结束由后端 SSE `done` 事件触发。

#### Scenario: Normal round progression
- **WHEN** 收到 SSE `done` 事件且 `isComplete` 为 false
- **THEN** 录音按钮恢复可用，等待学生下一轮录音；下一轮的 AI 消息由后端在 `done` 事件中返回

#### Scenario: Dialogue complete
- **WHEN** 收到 SSE `done` 事件且 `isComplete` 为 true
- **THEN** 触发 `complete` 事件，前端转到报告页

#### Scenario: First round initialization
- **WHEN** 调用 `POST /session` 创建对话
- **THEN** 后端返回 AI 开场白，前端显示第一条 AI 消息，学生进入可录音状态

### Requirement: Input locking
系统 SHALL 在消息处理过程中禁用录音按钮，保证一次只能有一条处理中的消息。

#### Scenario: Lock during processing
- **WHEN** 有任何消息处于 `loading` 状态
- **THEN** 录音按钮处于禁用状态

#### Scenario: Unlock after completion or error
- **WHEN** 当前轮所有消息都是 `done` 或 `error` 状态
- **THEN** 录音按钮恢复可用

### Requirement: Auto scroll
消息列表 SHALL 在新内容到达时自动滚动到底部。

#### Scenario: Auto scroll on new content
- **WHEN** 新消息添加或流式输出产生新内容
- **THEN** 自动滚动到底部
