fix(channelbridge): IM 发送过滤 action/narration + <p>拆多气泡 + 0.5-2s 间隔
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

把回复转发给 IM 适配器(sendReply)时剥掉 <action>/<narration> 动作/内心独白(Web 端
才渲染灰斜体,IM 只能收纯文本),并按 <p> 段落拆成多条气泡分别发送;多气泡发送间隔
随机 0.5~2s(仅 IM,Web 前端不延迟)。新增 sanitize.go stripNarration/splitBubbles/
bubbleGap + 测试。
This commit is contained in:
Aska committed 2026-09-07 17:50:56 +08:00
1 parent dcb0b33954
commit 5a0c1e2fd1
11 files changed
+790 -12

No files matched your search

@@ -597,19 +597,38 @@ func (b *Bridge) sendReply(ctx context.Context, in adapter.UnifiedMessage, a ada
if a == nil {
return fmt.Errorf("channelbridge: no adapter for platform %q", in.Platform)
}
out := adapter.UnifiedMessage{
Platform: in.Platform,
ChannelType: in.ChannelType,
ChannelID: in.ChannelID,
GroupID: in.GroupID,
GroupName: in.GroupName,
SenderID: in.SenderID,
Sender: in.Sender,
Content: text,
ContentType: adapter.ContentText,
Timestamp: time.Now().UTC(),
// IM channels receive the reply as one or more bubbles: role-play/action/narration
// markup (rendered as muted italic on the Web) is stripped, and each <p> paragraph
// becomes its own bubble/message (design/17 §2). A reply that was only an action is
// dropped rather than sent empty. A multi-bubble reply spaces its sends 0.5-2s apart
// so a burst doesn't flood the channel; the Web frontend has no such delay.
bubbles := splitBubbles(text)
if len(bubbles) == 0 {
b.log.Debug("reply has no plain text, skipped", "platform", in.Platform, "channel", in.ChannelID)
return nil
}
return a.SendMessage(ctx, adapter.UnifiedResponse{Messages: []adapter.UnifiedMessage{out}})
for i, content := range bubbles {
if i > 0 {
time.Sleep(bubbleGap())
}
out := adapter.UnifiedMessage{
Platform: in.Platform,
ChannelType: in.ChannelType,
ChannelID: in.ChannelID,
GroupID: in.GroupID,
GroupName: in.GroupName,
SenderID: in.SenderID,
Sender: in.Sender,
Content: content,
ContentType: adapter.ContentText,
Timestamp: time.Now().UTC(),
}
// Each bubble is its own send so the gap lands between messages.
if err := a.SendMessage(ctx, adapter.UnifiedResponse{Messages: []adapter.UnifiedMessage{out}}); err != nil {
return fmt.Errorf("channelbridge: send bubble #%d of %d: %w", i+1, len(bubbles), err)
}
}
return nil
}
// adapter returns the registered adapter for a platform name, or nil.