feat(channels,voice): IM 各渠道语音消息可被读到(全渠道云端 ASR + 临时熔断)
采集面:mediafetch 新增 inline_voice.go(音频白名单,与 documents.ExtAllowed 互不污染; 复用既有来源白名单 / 逐跳校验 / 边读边限 / 超时);四个适配器零改动。 消费面:channelbridge 在流水线最前端把「语音字节 → 文本」归一并写回 msg.Content; inbound.go 的丢空判据放宽为「无文本且无可用媒体」。 微信优先平台自带转写(Content 非空 ⇒ 不调 ASR,不重复付费)。 降级(主人定的两层):新增 voice/outcome.go 的 STTOutcome 识破静默档位; errors.go 分类鉴权失败/欠费/限流(CircuitWorthy);voice_breaker.go 临时熔断 + 退避(TTL 恢复); 无论是否熔断,消息面都落一行中文诚实提示(绝不静默、绝不留空消息)。 独立短超时:VOICE_STT_TIMEOUT_SEC(默认 20s,同步 .env.example)。 kook 不再把 CDN 地址写进 Content(地址仍在 Media[].URL)。 设计先行:design/06 §1.1/§1.2、design/08 §4.1、design/24 §1.3/§4.2、开源对照清单。 变异 5 条(静默档位不带 Cause / Transcribed 误判 / 熔断恒放行 / ChatText 退化 / 平台文字判据取反)。 诚实边界:实施时官方音频规格尚未抓到 ⇒ MaxVoiceBytes 自定 5 MiB(保守)、采样率仍 16 kHz; 官方规格随后已抓到(10 MB / 5 min;格式含 amr、无 silk),对齐留待下次改动。
This commit is contained in:
1 parent
679cee04f7
commit
a918819878
20 files changed
+1685
-61
No files matched your search
@@ -14,8 +14,9 @@ package channelbridge
|
||||
// 带来源的记录(只记 name/mime/size,**绝不记正文**)。
|
||||
//
|
||||
// 四条边界:
|
||||
// - **图片/语音不走这里**(design/24 §4.2 末:图片走既有视觉链路),本切片只补
|
||||
// 文件类;
|
||||
// - **图片不走这里**(design/24 §4.2 末:图片走既有视觉链路);**语音也不走这里** ——
|
||||
// 入站语音由 `channelbridge/inbound_voice.go` 在流水线最前端归一成文本写回
|
||||
// `Content`(切片 IM-ASR;design/06 §1.1),既不落文档存储也不进文档预算;
|
||||
// - 入站附件是**用户内容** ⇒ 文档部件只挂在 `model.RoleUser` 消息上;
|
||||
// - 摄取发生在**闸门之后**(会话解析、黑白名单、最小打扰判定都通过之后),
|
||||
// 被拒的消息因此绝不会在盘上留下文件;
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/mediafetch"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/documents"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
@@ -126,6 +128,7 @@ func (b *Bridge) inboundDocumentColumn(msg adapter.UnifiedMessage, attach inboun
|
||||
}
|
||||
|
||||
// hasFileMedia 报告消息是否携带文件类媒体(决定「无文本消息」是否仍要跑流水线)。
|
||||
// 语音那一半见 inbound_voice.go 的 hasVoiceMedia / hasUsableMedia。
|
||||
func hasFileMedia(msg adapter.UnifiedMessage) bool {
|
||||
for _, m := range msg.Media {
|
||||
if isFileMedia(msg, m) {
|
||||
@@ -138,11 +141,12 @@ func hasFileMedia(msg adapter.UnifiedMessage) bool {
|
||||
// isFileMedia 报告一条媒体是不是**文件类**。部分适配器只给内容类型而不给媒体
|
||||
// Kind(`ContentType == file`,附件就在 Media 里),那种消息也按文件处理,否则
|
||||
// 附件会被静默忽略 —— 静默忽略正是本切片要消灭的行为。
|
||||
//
|
||||
// 判定本身只有一份、在 `mediafetch.IsFileMedia`:桥接器的入站摄取与适配器的字节内联
|
||||
// 必须对「哪些附件该被处理」给出同一个答案,否则会出现「内联了但没人消费」(切片
|
||||
// IM-ASR 顺手去重,两处不再是两份实现)。
|
||||
func isFileMedia(msg adapter.UnifiedMessage, m adapter.Media) bool {
|
||||
if m.Kind == adapter.MediaFile {
|
||||
return true
|
||||
}
|
||||
return m.Kind == "" && msg.ContentType == adapter.ContentFile
|
||||
return mediafetch.IsFileMedia(&msg, m)
|
||||
}
|
||||
|
||||
// fileMedia 返回消息里全部文件类媒体(保序)。
|
||||
|
||||
Reference in new issue
Block a user