9.4 KiB
前后端边界梳理修改记录
更新时间:2026-06-23
当前目标
本轮重构的目标是把浏览器、Speakr 后端、算法服务之间的处理边界理顺:
- 浏览器只负责音频采集、状态展示、用户操作。
- Speakr 后端统一负责和 FunASR、ASR HTTP、LLM、投屏/外部服务通信。
- 算法服务地址不再通过
getUserConfig暴露给前端。 - 草稿完成后由后端直接进入正式 Recording 处理流,不再由前端下载 blob 后二次上传。
已完成修改
后端实时 ASR WebSocket 代理
新增 src/api/asr_ws.py,提供后端 WebSocket 入口:
/ws/asr/live
经 nginx 暴露为:
/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.pysrc/flask_ext.pysrc/api/__init__.pyrequirements.txt
新增依赖:
flask-sock
websocket-client
前端实时 ASR 连接收口
修改 static/js/wsconnecter.js 和 static/js/app.js。
旧模式:
wss://${wssBaseUrl.FUNASR_WEBSOCKET_IP}/websocket_offline
新模式:
/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 新增:
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的请求改为后端代理。
新增后端代理路径:
/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
新增依赖:
requests
部署配置调整
修改:
Dockerfiledeployment/setup.shdocs/getting-started/installation.md
Gunicorn 增加:
--threads 8
用于支持后端 WebSocket 长连接。
当前运行状态
主流程
实时转写主流程当前可用,后端无明显异常,前端 Network 中主要连接 /tool/speakr/ws/asr/live。
已知遗留问题:停止阶段 Invalid frame header
停止录音时前端仍可能打印:
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 矫正请求
停止录音时可能伴随:
LLM矫正出错: TypeError: Failed to fetch
来源:
/tool/speakr/api/asr/correct
可能原因:
- offline final 到达后触发 LLM 矫正,但录音已经进入停止/收尾阶段。
- 请求被页面状态、CSRF refresh、网络或后端瞬时状态影响。
后续建议:
- 结束录音后禁止新发起 LLM 矫正,只等待已发出的请求。
- 给
getJsonMessage2增加停止态判断。 - 后端为
/api/asr/correct增加更明确日志。
需要手动同步的非 git 配置
下面两类改动非常重要,但通常不会随 git diff 自动同步给其他成员。
1. .env 必须同步
后端代理模式下,FUNASR_WEBSOCKET_IP 不能再指向 nginx 外部入口,例如:
FUNASR_WEBSOCKET_IP=localhost:8083
这个旧值在新架构下会让 Speakr 后端连回 nginx,而不是直接连 FunASR。
应改为真实 FunASR WebSocket 服务地址。按当前环境,建议:
FUNASR_WEBSOCKET_IP=ws://10.100.3.22:10095/websocket_offline
或:
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 前面:
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 直连代理:
location /websocket { ... }
location /websocket_offline { ... }
新前端理论上不再需要。可以先保留,待新链路稳定后清理,避免其他页面或缓存仍依赖旧路径。
修改 nginx 后需要:
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:
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 配置同步。