SmartMeeting/speakr/docs/rework-notes/前后端边界梳理修改.md

9.1 KiB
Raw Permalink Blame History

前后端边界梳理修改记录

更新时间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 初始化参数由后端生成,包括 modechunk_sizehotwords 等。

相关文件:

  • src/api/asr_ws.py
  • src/flask_ext.py
  • src/api/__init__.py
  • requirements.txt

新增依赖:

flask-sock
websocket-client

前端实时 ASR 连接收口

修改 static/js/wsconnecter.jsstatic/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.pysrc/api/recording.pysrc/api/__init__.pystatic/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.pystatic/js/app.jstemplates/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

部署配置调整

修改:

  • Dockerfile
  • deployment/setup.sh
  • docs/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 中的 wssBaseUrlgetBaseUrlFromApi()configwords
  • static/js/wsconnecter.js 中的 getBaseHot()
  • FunASR demo 遗留变量:isfilemodefile_extfile_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.pyas_dict() 仍包含内部地址。只要该 dict 不返回前端,目前可接受;后续建议拆成:

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

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

当前结论

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

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