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