- JavaScript 47.5%
- HTML 38.7%
- TypeScript 6.8%
- Python 5.9%
- Shell 0.5%
- Other 0.6%
|
|
||
|---|---|---|
| .certs | ||
| .claude/skills | ||
| .superpowers/brainstorm | ||
| asr-server | ||
| design | ||
| desktop | ||
| docs | ||
| feishu-plugin | ||
| logs/meetings | ||
| meeting-app | ||
| meeting-review | ||
| scripts | ||
| voice-api | ||
| web-server | ||
| website | ||
| .env.example | ||
| .gitignore | ||
| ASR_TEST_RESULT.md | ||
| capture-dialog-2.cjs | ||
| capture-dialog.cjs | ||
| capture-figma.cjs | ||
| capture-figma2.cjs | ||
| DEPLOY.md | ||
| deploy.sh | ||
| DEPLOYMENT_QUICK_REF.md | ||
| DEPLOYMENT_SUMMARY.md | ||
| dev-mac.sh | ||
| meeting-app.html.bak | ||
| package-lock.json | ||
| package.json | ||
| PRODUCT.md | ||
| proxy.cjs | ||
| README.md | ||
| TECH_RESEARCH.md | ||
| test-config.cjs | ||
| test-diarization.cjs | ||
| test-speaker.cjs | ||
| TEST_PLAN.md | ||
工作豆包 · Clare 会议助手
定位:不是帮你记会议,而是帮你推动会议从争论走向决策。
最后更新:2026-03-23
目录
一、产品定位与核心价值
解决什么问题
会议中最大的浪费不是时间,而是决策力的流失:
- 讨论了 1 小时,没有结论
- 每个人记的重点不一样
- 会后没有人清楚下一步谁负责什么
核心价值主张
| 传统会议 | Clare 会议助手 |
|---|---|
| 人工记录,容易遗漏 | 实时 AI 转写,全程留档 |
| 靠人感知争议 | AI 自动标注争议点 + 方案对比 |
| 会后才整理纪要 | 会中实时生成决策建议 |
| 文档和会议分离 | 材料直接参与分析 |
| 不知道谁说了什么 | 说话人自动分离标注 |
二、核心功能清单
| 功能 | 状态 | 说明 |
|---|---|---|
| 🎙 实时语音转写 | ✅ | FunASR SenseVoice / 火山引擎豆包,双引擎可切 |
| 👥 说话人自动分离 | ✅ | ERes2NetV2/CAM++ embedding 聚类 |
| 📄 材料上传 | ✅ | 会前+会中上传,支持 txt/md/json/csv/docx/doc |
| 🔗 飞书文档导入 | ✅ | 粘贴飞书链接直接导入 |
| 🤖 AI 争议分析 | ✅ | 争议点 + 建议列表 + 苏格拉底提问 + 方案对比 |
| 📊 方案对比表 | ✅ | 每个争议点自动生成 ≥2 方案的 pros/cons/effort 对比 |
| 💬 会议问答 (Clare) | ✅ | 基于完整转写 + 材料的对话问答 |
| ⚙️ 配置自动加载 | ✅ | meeting-config.json 或 ?config=<url> 自动填充设置 |
| 📱 HTTPS 移动端支持 | ✅ | https://clare.vinex.top 支持手机麦克风 |
三、技术架构
整体架构图(当前生产环境)
┌─────────────────────────────────────────────────────────────┐
│ 用户设备(PC / 手机浏览器) │
│ │
│ https://clare.vinex.top/meeting-app/ │
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌──────────┐ │
│ │ 实时转写 │ │ 材料管理 │ │ AI 争议分析 │ │ Clare 问答│ │
│ │ 说话人标签 │ │ 上传/删除 │ │ 方案对比 │ │ 上下文透明│ │
│ └─────┬─────┘ └────┬─────┘ └──────┬─────┘ └─────┬────┘ │
│ │ wss:// │ https:// │ https:// │ │
└─────────┼──────────────┼───────────────┼──────────────┼───────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ 阿里云服务器 (112.124.30.51) — clare.vinex.top │
│ │
│ ┌─── nginx (443 SSL) ──────────────────────────────────┐ │
│ │ /* → proxy_pass http://127.0.0.1:3001 │ │
│ │ WebSocket upgrade 支持 (map $http_upgrade) │ │
│ └───────────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌─── proxy.cjs (pm2, port 3001) ───────────────────────┐ │
│ │ │ │
│ │ 静态文件 /meeting-app/* → meeting-app/ │ │
│ │ │ │
│ │ LLM 代理 /llm/* → LiteLLM Proxy │ │
│ │ 飞书代理 /feishu/* → open.feishu.cn │ │
│ │ │ │
│ │ ASR 代理 /funasr-asr → ws://127.0.0.1:8765 ──┼── SSH 隧道
│ │ Speaker代理 /funasr-speaker → ws://127.0.0.1:8766 ──┼── SSH 隧道
│ │ │ │
│ │ 豆包 ASR /volcengine-ws → wss://openspeech... │ │
│ │ 豆包 TTS /tts-ws → wss://openspeech... │ │
│ │ │ │
│ │ 文件解析 /parse-docx → mammoth/pdf-parse │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ SSH 隧道端口 (由 GPU 发起的 autossh 反向隧道): │
│ localhost:8765 ← GPU:8765 (FunASR ASR) │
│ localhost:8766 ← GPU:8766 (FunASR Speaker) │
│ localhost:13001 ← GPU:3001 (LLM 备用) │
└─────────────────────────────────────────────────────────────┘
↑ SSH 反向隧道 (autossh, 自动重连)
│
┌─────────────────────────────────────────────────────────────┐
│ GPU 开发机 (192.168.188.93, 内网) │
│ │
│ ┌─── FunASR server.py (port 8765) ─────────────────────┐ │
│ │ │ │
│ │ 音频输入 { type:"audio", data:"<base64 PCM>" } │ │
│ │ ↓ │ │
│ │ fsmn-vad → 语音段边界检测 │ │
│ │ ↓ │ │
│ │ SenseVoice-Small → 中文高精度识别 │ │
│ │ ↓ │ │
│ │ ERes2NetV2 → 说话人 embedding → 聚类 │ │
│ │ ↓ │ │
│ │ 输出 { type:"transcript", text, speaker, is_final } │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌─── FunASR Speaker-only (port 8766) ──────────────────┐ │
│ │ 仅输出说话人时间段,供豆包 ASR 对齐使用 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌─── autossh 持久隧道 (/opt/asr-tunnel.sh) ───────────┐ │
│ │ -R 8765:127.0.0.1:8765 (ASR) │ │
│ │ -R 8766:127.0.0.1:8766 (Speaker) │ │
│ │ ServerAliveInterval=30, 断开自动重连 │ │
│ │ /etc/rc.local + .bashrc 双保险开机自启 │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ 硬件:5x RTX 4090 (24GB) · CUDA 12.1 │
└─────────────────────────────────────────────────────────────┘
数据流向
【材料上传流】
会前: 文件 → _preMeetingDocs[] → startMeeting → 异步读取内容 → state.documents[]
会中: 文件 → handleMaterialUpload → FileReader → state.documents[]
UI: state.documents[] → updateMaterialsChips() → 顶部 chip 条(含 × 删除)
【ASR 流】(FunASR 模式)
麦克风 → Web Audio API → resampleEncode(PCM→base64)
→ JSON { type:"audio", data } → wss://clare.vinex.top/funasr-asr
→ nginx → proxy.cjs → ws://127.0.0.1:8765 → SSH隧道 → GPU FunASR
→ { type:"transcript", text, speaker } → 回传同路径 → 前端转写区
【AI 分析流】
state.transcript + state.documents(selected)
→ 拼接 prompt(含 JSON schema)
→ https://clare.vinex.top/llm/v1/chat/completions
→ proxy.cjs → LiteLLM → Claude/GPT
→ JSON { disputes[], consensus[], nextActions[] }
→ renderAIAnalysis() → 争议卡片 + 方案对比表
技术栈总览
| 层 | 技术 | 说明 |
|---|---|---|
| 前端 | 原生 HTML/JS + Tailwind CSS | 单文件,无构建工具 |
| 音频 | Web Audio API + ScriptProcessor | PCM 16kHz, GainNode 8x 增益 |
| 代理 | Node.js proxy.cjs + pm2 | CORS/WSS 代理,自动重启 |
| Web 服务 | nginx + Let's Encrypt SSL | HTTPS + WebSocket 支持 |
| ASR 引擎 | FunASR (SenseVoice + VAD + Speaker) | GPU 自部署,base64 JSON 协议 |
| ASR 备选 | 火山引擎豆包 bigmodel | 云端流式,二进制帧协议 |
| 网络隧道 | autossh 反向隧道 | GPU→阿里云,自动重连 |
| LLM | Claude Opus 4.6 via LiteLLM | 争议分析 + 问答 |
| 文件解析 | mammoth (docx) + pdf-parse | 服务端解析 |
四、部署架构
服务器清单
| 角色 | 主机 | IP | 端口 | 说明 |
|---|---|---|---|---|
| Web + 代理 | 阿里云 me |
112.124.30.51 | 443(nginx), 3001(proxy) | 公网访问入口 |
| ASR GPU | 内网 dev |
192.168.188.93 | 8765(ASR), 8766(Speaker) | FunASR 推理 |
| LLM 网关 | LiteLLM SG | litellm-sg.mayfair-inc.com | 443 | Claude/GPT API |
进程管理
| 服务 | 管理方式 | 自启动 | 日志 |
|---|---|---|---|
| proxy.cjs | pm2 work-doubao |
✅ pm2 startup | pm2 logs work-doubao |
| nginx | systemd | ✅ | /var/log/nginx/ |
| FunASR server.py | nohup 后台 | 手动 | /opt/work-doubao-asr/asr_server.log |
| SSH 隧道 | autossh + /opt/asr-tunnel.sh |
✅ rc.local + bashrc | /var/log/asr-tunnel.log |
配置文件位置
| 文件 | 位置 | 说明 |
|---|---|---|
| nginx 站点配置 | me:/etc/nginx/sites-enabled/work-doubao |
SSL + WebSocket proxy |
| proxy.cjs | me:/opt/work-doubao/proxy.cjs |
Node 代理服务 |
| 前端应用 | me:/opt/work-doubao/meeting-app/ |
静态文件 |
| 前端配置 | me:/opt/work-doubao/meeting-app/meeting-config.json |
自动加载的默认设置 |
| 隧道脚本 | dev:/opt/asr-tunnel.sh |
autossh 守护 |
| ASR 服务 | dev:/opt/work-doubao-asr/server.py |
FunASR 主服务 |
| ASR 环境 | dev:/opt/work-doubao-asr/venv/ |
Python 虚拟环境 |
核心能力代码索引
1. 实时语音转写 (ASR)
index.html
├─ getFunasrAsrUrl() :1840 构造 WSS 代理 URL
├─ connectAsr() :3576 ASR 连接路由(按 provider 分发)
├─ connectFunAsr() :4136 FunASR WebSocket 连接 + onmessage 转写处理
├─ processor.onaudioprocess :4804 麦克风 PCM 采集 → resampleEncode → 发送
├─ resampleEncode() :4674 Float32 → Int16 PCM 16kHz → base64
├─ buildVolcMsg() / decodeVolcMsg() :3598/3623 豆包二进制帧协议
└─ disconnectAsr() :4295 关闭连接
src/tts-service.js
├─ startClareListening() :320 Clare 独立 ASR 输入(语音提问)
└─ stopClareListening() :424 停止并返回转写文本
链路: 麦克风 → Web Audio ScriptProcessor(4096) → resampleEncode → { type:"audio", data:base64 } → WSS 代理 → GPU FunASR → { type:"transcript", text, speaker } → UI
2. 说话人识别 (Speaker Diarization)
index.html
├─ getFunasrSpeakerUrl() :1852 构造 Speaker WSS URL
├─ connectFunasrDiarization() :3700 说话人分离 WebSocket 连接
├─ funasrSpeakerWs :3564 Speaker-only WebSocket 实例
├─ speakerTimeline :3565 时间轴 [{speaker, absStart, absEnd}]
├─ getSpeakerForRange() :3752 时间区间 → 说话人(多数投票)
└─ 音频帧并行转发 :4838 onaudioprocess 中同时发给 ASR + Speaker
server.py (GPU: /opt/work-doubao-asr/)
├─ 8765 端口: ASR + Speaker 一体
└─ 8766 端口: Speaker-only(ERes2NetV2 embedding 聚类)
链路: 麦克风 PCM 同时发给 ASR(8765) 和 Speaker(8766) → 服务端 ERes2NetV2 提取 embedding → 余弦相似度聚类 → { speaker:"说话人A" } → 前端 speakerTimeline 对齐
3. 语音唤醒 (Wake Word)
index.html
├─ WAKE_PATTERNS :3179 唤醒词列表('clare','克莱尔' 等 10+ 种)
├─ _extractAfterWake() :3192 从转写文本中提取唤醒词后的内容
├─ activateClareByWakeWord() :3213 激活 Clare 监听模式
├─ _resetClareWakeTimeout() :3229 1 秒静音自动提交定时器
├─ finishClareWakeWord() :3238 收集完毕 → 发送给 Clare
└─ 唤醒检测循环 :3905 ASR onmessage 中逐条检查唤醒词
链路: ASR 转写文本 → 逐条 indexOf(pattern) 匹配唤醒词 → 匹配成功 → clareListening=true → 后续语句存入 clareWakeWordBuffer → 1s 静音后 → finishClareWakeWord() → 发送到 Clare 问答
支持的唤醒词: clare, claire, hi clare, 克莱尔, 你好克莱尔 等
4. TTS 语音播报 (Text-to-Speech)
src/tts-service.js
├─ getTtsWsUrl() :25 构造 TTS WSS URL(/tts-ws 代理)
├─ buildTtsFrame() :36 构造火山引擎 TTS 二进制帧
├─ parseTtsResponse() :71 解析 TTS 响应(事件+音频数据)
├─ ttsSpeak(text) :172 主函数:连接 → 发文本 → 接收音频流
├─ playAudioChunk() :119 AudioContext 播放 MP3 音频块
├─ processAudioQueue() :146 音频块队列顺序播放
└─ ttsStop() :285 停止播放并清理
index.html
├─ toggleTtsPlay() :3119 播放/停止切换按钮
├─ _enableTtsInterrupt() :3070 点击/ESC 打断播报
└─ Clare 自动播报 :2958 Clare 回复后自动 TTS
链路: 文本 → ttsSpeak() → WSS /tts-ws → proxy.cjs → 火山引擎 Seed-TTS 2.0 → MP3 音频流 → playAudioChunk() → AudioContext 播放
音频闪避: 录音时 TTS 音量降到 30%(ttsGainNode.gain.setTargetAtTime(0.3))
五、ASR 方案演进
演进时间线
| 版本 | 时间 | 方案 | 结果 |
|---|---|---|---|
| v1 | 03 早期 | Paraformer-zh-streaming + 能量 VAD | 分句不准 |
| v2 | 03-14 | SenseVoice-Small + fsmn-vad + CAM++ | ✅ 说话人分离 |
| v3 | 03-15 | + pyannote 3.1 全局聚类 | ✅ 跨段说话人追踪 |
| v4 | 03-17 | + 火山引擎豆包云端 ASR | ✅ 低延迟流式 |
| v5 | 03-22 | + ERes2NetV2 (替代 CAM++) | ✅ 短片段识别更准 |
当前 ASR 方案对比
| FunASR 自部署 | 火山引擎豆包 | |
|---|---|---|
| 准确率 | 优秀(SenseVoice-Small) | 优秀 |
| 说话人分离 | ✅ ERes2NetV2 服务端聚类 | ⚠️ 本地 Pitch 估算 |
| 延迟 | ~500ms(等语音段结束) | ~200ms(流式) |
| 成本 | 免费(自有 GPU) | 按量计费 |
| 依赖 | GPU + SSH 隧道 | 公网 API |
| 协议 | JSON + base64 | 二进制帧 |
FunASR 服务端协议
客户端 → { "type": "audio", "data": "<base64 PCM 16kHz int16>" }
客户端 → { "type": "end" }
服务端 → { "type": "ready", "models": [...] }
服务端 → { "type": "transcript", "text": "...", "speaker": "说话人A", "is_final": true }
六、AI 分析能力
分析输出 Schema(v2, 2026-03-22)
{
"disputes": [{
"id": "唯一标识",
"title": "争议点名称",
"severity": "高|中|低",
"coreDivergence": "核心分歧一句话总结",
"suggestions": ["建议1", "建议2"],
"keyQuestions": [{"question": "苏格拉底式提问", "hint": "思考方向"}],
"comparison": [{"plan": "方案名", "pros": "优势", "cons": "劣势", "effort": "成本"}],
"status": "open|resolved"
}],
"consensus": ["已达成的共识点"],
"nextActions": [{"action": "行动项", "owner": "负责人"}]
}
与旧版 Schema 对比
| 字段 | 旧版 (03-17) | 新版 (03-22) |
|---|---|---|
| 建议 | suggestion (单字符串) |
suggestions[] (数组) |
| 提问 | socraticQuestion (单字符串) |
keyQuestions[{question,hint}] |
| 方案对比 | ❌ 无 | ✅ comparison[{plan,pros,cons,effort}] |
| 决策路径 | decisionPaths[] |
❌ 移除(合并到 suggestions) |
渲染效果
- 每个争议卡片包含:严重度标签、核心分歧、建议列表、提问区、可折叠方案对比表
- 已解决争议自动折叠
- 二次分析保留已有争议点 ID,支持增量更新
七、快速启动
线上访问(推荐)
直接打开 https://clare.vinex.top/meeting-app/
首次访问会自动从 meeting-config.json 加载默认配置。
本地开发
cd /Users/vinexio/Desktop/dev-projects/work-doubao
node proxy.cjs
# 浏览器打开 http://localhost:3001/meeting-app/
部署更新
# 上传前端改动(即时生效,无需重启)
sshpass -p 'xxx' scp meeting-app/index.html me:/opt/work-doubao/meeting-app/index.html
# 如果改了 proxy.cjs,需重启:
sshpass -p 'xxx' ssh me "pm2 restart work-doubao"
GPU ASR 服务管理
# 检查状态
ssh dev "ps aux | grep server.py | grep -v grep; ss -tlnp | grep 8765"
# 重启 ASR
ssh dev "pkill -f server.py; sleep 2; cd /opt/work-doubao-asr && \
MODELSCOPE_CACHE=/opt/work-doubao-asr/models \
nohup ./venv/bin/python server.py > asr_server.log 2>&1 &"
# 检查隧道
ssh dev "ps aux | grep asr-tunnel | grep -v grep"
# 重启隧道
ssh dev "pkill -f asr-tunnel; nohup /opt/asr-tunnel.sh >> /var/log/asr-tunnel.log 2>&1 &"
应用内设置参考
| 设置项 | 线上值 | 本地值 |
|---|---|---|
| ASR 提供商 | 自部署 FunASR | 自部署 FunASR |
| ASR 地址 | (留空,自动代理) | ws://localhost:8765 |
| LLM Base URL | (留空,自动) | http://localhost:3001/llm |
| 模型 | pub-claude-opus-4-6 |
同左 |
八、已知问题与限制
| 问题 | 严重度 | 说明 |
|---|---|---|
| SSH 隧道依赖 GPU 在线 | 中 | GPU 关机 = ASR 不可用。隧道有 autossh 自动重连 |
| 豆包无话者分离 | 中 | 用本地 Pitch fallback,音色相近时不准 |
| 自签名证书限制 | 低 | GPU nginx:8180 用自签名证书,外部浏览器无法直连 wss |
| 文档截取上限 2000 字 | 低 | 超长文档只用前 2000 字参与分析 |
| WebSocket Blob 兼容 | ✅ 已修复 | 代理转发后 WS 消息变 Blob,需 await blob.text() |
九、更新日志
2026-03-22 ~ 03-23
材料上传流向修复 + AI 分析升级 + 云端部署
材料上传:
- 修复上传流向:会前上传的文件不再进"知识库",而是存入
_preMeetingDocs,startMeeting时异步读取内容 →state.documents - 支持会中上传:材料栏新增上传按钮 + 隐藏 file input
- 材料 chip 支持删除(× 按钮)
- docx/doc 文件通过
/parse-docx服务端解析
AI 分析 Prompt 升级:
- disputes schema 从 v1(suggestion 单字符串)升级到 v2(suggestions 数组 + keyQuestions + comparison)
- 每个争议点强制生成 ≥2 方案对比(plan/pros/cons/effort)
- 移除
decisionPaths,合并到 suggestions - system message 加入格式控制(不要代码块、不要未转义换行)
云端部署(clare.vinex.top):
- 阿里云 nginx 配置优化:
map $http_upgrade条件判断 WebSocket - proxy.cjs 的 FunASR 默认 target 改为
127.0.0.1:8765/8766(通过隧道) - GPU 上部署 autossh 持久反向隧道(
/opt/asr-tunnel.sh),rc.local + bashrc 双保险 - 前端 HTTPS 下自动走
wss://clare.vinex.top/funasr-asr代理 src/app.js引入修复,autoLoadConfig()恢复工作meeting-config.json部署到服务器
WebSocket Blob 兼容修复:
- 通过代理的 WS 消息变成 Blob/ArrayBuffer,4 处
onmessage全部改为支持 string/Blob/ArrayBuffer
2026-03-17
火山引擎豆包流式 ASR 集成
- 新增 ASR 提供商:火山引擎豆包
bigmodel - proxy.cjs 新增
/volcengine-wsWSS 代理 - 实现火山引擎自定义二进制帧协议
- GainNode 8x 放大 + 无抖动显示
2026-03-15
ASR v3:pyannote 说话人分离
- CAM++ → pyannote 3.1 全局聚类
- 支持"说话人回来了"跨段追踪
- 修复 torchaudio/PyTorch 2.6 兼容性(4 处补丁)
2026-03-14
ASR v2:SenseVoice + fsmn-vad + CAM++
- Paraformer → SenseVoice-Small(中文准确率 +10-15%)
- 能量 VAD → fsmn-vad 神经网络 VAD
- 新增服务端说话人分离(CAM++ embedding 聚类)
十、后续规划
近期
- ASR 稳定性:FunASR 测试验证实时转写 + 隧道断线自恢复
- 豆包话者分离:申请 diarization 资源 或 FunASR+豆包混合模式
- 会议纪要导出:Markdown / 飞书文档一键导出
中期
- 说话人命名持久化:跨会议记住说话人
- 飞书双向同步:分析结果 → 飞书文档
- 移动端优化:手机端 UI 适配
长期
- 向量知识库 RAG:大文档语义检索
- 多模型按需切换:摘要用快模型,深度分析用强模型
- 企业知识库对接:Confluence / Notion / 飞书知识库
文档维护者:AI 助手 · 每次重大更新后同步