# 前后端边界梳理修改记录

更新时间：2026-06-23

## 当前目标

本轮重构的目标是把浏览器、Speakr 后端、算法服务之间的处理边界理顺：

- 浏览器只负责音频采集、状态展示、用户操作。
- Speakr 后端统一负责和 FunASR、ASR HTTP、LLM、投屏/外部服务通信。
- 算法服务地址不再通过 `getUserConfig` 暴露给前端。
- 草稿完成后由后端直接进入正式 Recording 处理流，不再由前端下载 blob 后二次上传。

## 已完成修改

### 后端实时 ASR WebSocket 代理

新增 `src/api/asr_ws.py`，提供后端 WebSocket 入口：

```text
/ws/asr/live
```

经 nginx 暴露为：

```text
/tool/speakr/ws/asr/live
```

当前流程：

- 前端只连接 Speakr 后端 WebSocket。
- 后端读取 `FUNASR_WEBSOCKET_IP`，连接 FunASR `/websocket_offline`。
- 前端发送 PCM binary chunk 给后端，后端转发给 FunASR。
- FunASR 返回消息后，后端推回前端，尽量保持原 FunASR 返回格式。
- FunASR 初始化参数由后端生成，包括 `mode`、`chunk_size`、`hotwords` 等。

相关文件：

- `src/api/asr_ws.py`
- `src/flask_ext.py`
- `src/api/__init__.py`
- `requirements.txt`

新增依赖：

```text
flask-sock
websocket-client
```

### 前端实时 ASR 连接收口

修改 `static/js/wsconnecter.js` 和 `static/js/app.js`。

旧模式：

```js
wss://${wssBaseUrl.FUNASR_WEBSOCKET_IP}/websocket_offline
```

新模式：

```text
/tool/speakr/ws/asr/live?mode=online
/tool/speakr/ws/asr/live?mode=offline
```

前端不再读取或使用 `FUNASR_WEBSOCKET_IP` 创建 WebSocket。

保留了现有录音采集、16k PCM 分片、实时展示、`segmentList` 渲染逻辑。

### 草稿 finalize 后端化

修改 `src/api/draft_api.py`、`src/api/recording.py`、`src/api/__init__.py`、`static/js/app.js`。

已完成：

- 注册 `draft_bp` 到 `/api/draft`。
- `POST /tool/speakr/api/draft/<draft_id>/finalize` 不再返回音频 blob。
- 后端合并草稿音频后直接创建正式 `Recording`。
- 后端绑定 notes、tags、ASR 参数。
- 后端启动现有 `transcribe_audio_task`。
- 前端拿到 `{ success: true, recording }` 后进入现有状态轮询。

为复用普通上传链路，`src/api/recording.py` 新增：

```python
create_recording_from_existing_file(...)
```

普通 `/upload` 和草稿 finalize 都走这个函数创建 Recording、绑定标签、启动转写线程。

### 外部算法/服务配置收口

修改 `src/api/main.py`、`static/js/app.js`、`templates/account.html`。

已完成：

- `getUserConfig` 不再返回 `FUNASR_WEBSOCKET_IP`。
- `getUserConfig` 不再返回 `SCREEN_PUSH_IP`。
- 原本前端直连 `SCREEN_PUSH_IP` 的请求改为后端代理。

新增后端代理路径：

```text
/tool/speakr/api/external/summarize_text
/tool/speakr/api/external/submit
/tool/speakr/api/external/prompts/list
/tool/speakr/api/external/recommend_prompts
/tool/speakr/api/external/summarize_text_stream
```

新增依赖：

```text
requests
```

### 部署配置调整

修改：

- `Dockerfile`
- `deployment/setup.sh`
- `docs/getting-started/installation.md`

Gunicorn 增加：

```text
--threads 8
```

用于支持后端 WebSocket 长连接。

## 当前运行状态

### 主流程

实时转写主流程当前可用，后端无明显异常，前端 Network 中主要连接 `/tool/speakr/ws/asr/live`。

### 已知遗留问题：停止阶段 Invalid frame header

停止录音时前端仍可能打印：

```text
WebSocket connection to 'wss://localhost:8083/tool/speakr/ws/asr/live?mode=online' failed: Invalid frame header
WebSocket connection to 'wss://localhost:8083/tool/speakr/ws/asr/live?mode=offline' failed: Invalid frame header
```

当前处理情况：

- 已移除前端消息回调里基于 `is_final.value == true` 的硬关闭。
- 已增加 `wsFinish()`，结束录音时优先发送 stop 并等待后端 closed 状态或短超时。
- 已调整后端 stop 后等待 FunASR final 消息再退出。
- 已撤掉后端显式 `client_ws.close()`，让 Flask-Sock handler return 后自然关闭。

目前判断：

- 问题更像 WebSocket 关闭阶段的代理/框架兼容性问题，而不是主流程架构问题。
- 现阶段不影响正常使用，先记录，后续结合 nginx access/error log、浏览器 Network close code、后端日志继续定位。

后续排查方向：

- 抓 nginx 对应时刻 `/tool/speakr/ws/asr/live` 的 access log 和 error log。
- 确认 `/tool/speakr/ws/` location 一定优先于普通 `/tool/speakr/` location。
- 确认 nginx 没有在 WebSocket 关闭阶段返回普通 HTTP 错误页。
- 检查当前 Flask-Sock/simple-websocket 与实际运行服务器的关闭帧兼容性。
- 如仍无法消除，可考虑把实时 ASR 代理独立为 ASGI/WebSocket 服务。

### 已知遗留问题：停止阶段 LLM 矫正请求

停止录音时可能伴随：

```text
LLM矫正出错: TypeError: Failed to fetch
```

来源：

```text
/tool/speakr/api/asr/correct
```

可能原因：

- offline final 到达后触发 LLM 矫正，但录音已经进入停止/收尾阶段。
- 请求被页面状态、CSRF refresh、网络或后端瞬时状态影响。

后续建议：

- 结束录音后禁止新发起 LLM 矫正，只等待已发出的请求。
- 给 `getJsonMessage2` 增加停止态判断。
- 后端为 `/api/asr/correct` 增加更明确日志。

## 需要手动同步的非 git 配置

下面两类改动非常重要，但通常不会随 git diff 自动同步给其他成员。

### 1. `.env` 必须同步

后端代理模式下，`FUNASR_WEBSOCKET_IP` 不能再指向 nginx 外部入口，例如：

```env
FUNASR_WEBSOCKET_IP=localhost:8083
```

这个旧值在新架构下会让 Speakr 后端连回 nginx，而不是直接连 FunASR。

应改为真实 FunASR WebSocket 服务地址。按当前环境，建议：

```env
FUNASR_WEBSOCKET_IP=ws://10.100.3.22:10095/websocket_offline
```

或：

```env
FUNASR_WEBSOCKET_IP=ws://10.100.3.22:10095
```

代码会在没有 path 时自动补 `/websocket_offline`，但推荐写完整路径。

`SCREEN_PUSH_IP` 仍由后端读取，用于 `/api/external/...` 代理。其他成员需要确认自己的 `.env` 中该值指向对应环境的真实服务地址。

注意：修改 `.env` 后必须重启 Speakr 后端。

### 2. nginx 配置必须同步

需要新增 WebSocket 专用 location，并放在普通 `/tool/speakr/` location 前面：

```nginx
location /tool/speakr/ws/ {
    proxy_pass http://localhost:5000/ws/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Script-Name /tool/speakr;
    proxy_redirect off;
    proxy_connect_timeout 60s;
    proxy_send_timeout 3600s;
    proxy_read_timeout 3600s;
    proxy_buffering off;
}
```

如果后端不是 `localhost:5000`，需要替换为实际 Speakr 后端地址。

当前旧 FunASR 直连代理：

```nginx
location /websocket { ... }
location /websocket_offline { ... }
```

新前端理论上不再需要。可以先保留，待新链路稳定后清理，避免其他页面或缓存仍依赖旧路径。

修改 nginx 后需要：

```bash
nginx -t
nginx -s reload
```

## 后续收尾清单

### 清理旧 FunASR 前端遗留

建议后续删除或整理：

- `static/js/wsconnecter.js` 中的 `wssBaseUrl`、`getBaseUrlFromApi()`、`configwords`。
- `static/js/wsconnecter.js` 中的 `getBaseHot()`。
- FunASR demo 遗留变量：`isfilemode`、`file_ext`、`file_sample_rate` 等。
- `getJsonMessageLegacy()`。

### 整理实时 ASR 生命周期

建议抽成明确 API：

```text
startRealtimeAsr()
pauseRealtimeAsr()
finishRealtimeAsr()
disposeRealtimeAsr()
```

减少 `app.js` 中多处直接调用 `wsStop()`、`wsFinish()` 的分散逻辑。

### 草稿链路收尾

- 确认 `/api/draft/<id>/download` 是否仍需要保留用于预览。
- 如果后续不需要草稿预览 blob，可删除该接口。
- 为草稿 finalize 增加自动化测试，覆盖 segment 文件清理和 Recording 创建。

### 测试补充

建议补：

- draft create -> segment -> finalize 返回 `202 + recording`。
- finalize 后数据库生成正式 Recording。
- finalize 后草稿记录和 segment 文件被清理。
- `/upload` 原上传链路不回归。
- `/ws/asr/live` 未登录、FunASR 不可达、FunASR 中途断开场景。
- 前端停止/暂停/恢复不会向旧 WebSocket 继续发 chunk。

### 配置拆分

`src/config.py` 的 `as_dict()` 仍包含内部地址。只要该 dict 不返回前端，目前可接受；后续建议拆成：

- 后端内部配置。
- 前端可见 UI 配置。

避免以后误把内部服务地址再次返回到页面。

## 当前结论

主要边界调整已经完成：前端不再直连 FunASR，不再读取算法服务地址；草稿 finalize 已后端化；外部服务地址也改为后端代理。

当前剩余工作主要是运行收尾和清理：WebSocket 关闭阶段 `Invalid frame header`、停止阶段 LLM 矫正请求时机、旧 demo/旧直连逻辑删除、自动化测试、`.env` 和 nginx 配置同步。
