324 lines
9.1 KiB
Markdown
324 lines
9.1 KiB
Markdown
# 前后端边界梳理修改记录
|
||
|
||
更新时间: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 配置同步。
|