b6927247da
- protocol.go: 新增 ServerReply("reply") 消息——Agent 回答增量文本
- voice_tts.go: token 一到即转发客户端(打字机),同时攒句喂 TTS(音频随后)
- sentence_buffer.go: 本轮首句用低阈值(5 rune)抢首字延迟,之后回常规 12
- 桌面端 voice.ts onReply + VoiceDock 对话气泡(我说的 + JARVIS 打字机回答,思考态光标)
管线已最优:文字在 LLM 首 token 即刻上屏、音频紧随。剩余时延=大模型 TTFT(deepseek-v4-pro
4-7s 且波动大,疑似推理模型),这是模型的账、非管线——真要"马上响应"需换快模型。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
2.5 KiB
Go
57 lines
2.5 KiB
Go
package voice
|
||
|
||
// 客户端 ↔ 网关的单条 WebSocket 消息协议(设计 VOICE_DESIGN.md D3):
|
||
// 一条连接同时承载上行音频、下行转写、下行 TTS 音频,用「帧类型 + JSON 消息类型」区分。
|
||
//
|
||
// - 二进制帧(BinaryMessage):纯音频 PCM
|
||
// · 上行 = 用户麦克风音频(喂 ASR)
|
||
// · 下行 = Agent 回答的 TTS 音频(客户端播放)
|
||
// - 文本帧(TextMessage, JSON):控制与事件(下方 ClientMsg / ServerMsg)
|
||
|
||
// ClientMsg 是客户端发来的控制消息(文本帧)。音频走二进制帧,不在此。
|
||
type ClientMsg struct {
|
||
Type string `json:"type"`
|
||
// start:一轮语音开始(可带当前画布编排图,用它跑而非默认 DSL)
|
||
Graph string `json:"graph,omitempty"`
|
||
// 其它类型无额外字段:end(用户说完)、barge_in(打断,用户又开口)、bye(结束会话)
|
||
}
|
||
|
||
// 客户端消息类型。
|
||
const (
|
||
ClientStart = "start" // 一轮语音开始
|
||
ClientEnd = "end" // 用户说完(静音检测或手动结束)→ 触发任务
|
||
ClientBargeIn = "barge_in" // 打断:用户在 Agent 说话时又开口 → 停 TTS
|
||
ClientBye = "bye" // 结束整个语音会话
|
||
)
|
||
|
||
// ServerMsg 是网关下发的控制/事件消息(文本帧)。TTS 音频走二进制帧,不在此。
|
||
type ServerMsg struct {
|
||
Type string `json:"type"`
|
||
// transcript:ASR 转写(Final=false 为实时部分结果,true 为最终)
|
||
// reply:Agent 回答的增量文本(打字机效果,逐 token 下发,早于音频)
|
||
Text string `json:"text,omitempty"`
|
||
Final bool `json:"final,omitempty"`
|
||
// task:转写完成、任务已提交,带 task_id 供客户端切运行视图
|
||
TaskID string `json:"task_id,omitempty"`
|
||
// error:出错文案
|
||
Msg string `json:"msg,omitempty"`
|
||
}
|
||
|
||
// 服务端消息类型。
|
||
const (
|
||
ServerReady = "ready" // 会话就绪,可以开始说话
|
||
ServerTranscript = "transcript" // ASR 转写结果(部分/最终)
|
||
ServerTask = "task" // 任务已提交(带 task_id)
|
||
ServerReply = "reply" // Agent 回答增量文本(打字机;逐 token,早于音频)
|
||
ServerSpeaking = "speaking" // Agent 开始出声(首段 TTS 音频将至)
|
||
ServerTTSEnd = "tts_end" // 本轮 TTS 播放完毕
|
||
ServerError = "error" // 出错
|
||
)
|
||
|
||
// 音频格式(与火山 ASR/TTS 约定,客户端按此采集/播放)。
|
||
const (
|
||
AudioSampleRate = 16000 // 上行 ASR:16kHz
|
||
AudioBits = 16 // 16bit
|
||
AudioChannels = 1 // 单声道
|
||
)
|