refactor(channelbridge): 多气泡分割+旁白剥离+随机延迟上移为IM公共出口——sendReply/SendProactive统一(微信主动推送也分割)
This commit is contained in:
1 parent
d09b25071d
commit
bcf7fe5de0
4 files changed
+233
-36
No files matched your search
@@ -169,8 +169,12 @@ type Bridge struct {
|
||||
engage EngageConfig
|
||||
engageFor func(platform, groupID string) *EngageConfig
|
||||
engageRand func() float64
|
||||
engageMu sync.Mutex
|
||||
engageGroups map[string]*engageGroupState
|
||||
// bubbleGapFn is the injectable delay between IM outbound bubbles (0.5-2s),
|
||||
// defaulting to the package bubbleGap. Tests inject a fixed short gap so the
|
||||
// multi-bubble delay is asserted deterministically without sleeping a real 0.5-2s.
|
||||
bubbleGapFn func() time.Duration
|
||||
engageMu sync.Mutex
|
||||
engageGroups map[string]*engageGroupState
|
||||
|
||||
mu sync.RWMutex
|
||||
adapters map[string]adapter.PlatformAdapter
|
||||
@@ -242,6 +246,7 @@ func New(opts Options) (*Bridge, error) {
|
||||
engage: opts.Engage,
|
||||
engageFor: opts.EngageFor,
|
||||
engageRand: engageRand,
|
||||
bubbleGapFn: bubbleGap,
|
||||
engageGroups: make(map[string]*engageGroupState),
|
||||
adapters: make(map[string]adapter.PlatformAdapter),
|
||||
connected: make(map[string]bool),
|
||||
@@ -660,7 +665,8 @@ func (b *Bridge) buildLoopMessages(_ context.Context, text string, env loop.Sess
|
||||
// sendReply delivers the loop's text back to the channel's source conversation
|
||||
// through the account adapter that received the message (multi-account safe,
|
||||
// design/16 §4.6 §5). It mirrors the inbound message's channel targeting fields so
|
||||
// the adapter resolves the same peer the message came from.
|
||||
// the adapter resolves the same peer the message came from, and it exits through the
|
||||
// shared IM bubble dispatcher (strip role-play/narration + <p> split + 0.5-2s gap).
|
||||
func (b *Bridge) sendReply(ctx context.Context, in adapter.UnifiedMessage, a adapter.PlatformAdapter, text string) error {
|
||||
if a == nil {
|
||||
a = b.adapter(in.Platform)
|
||||
@@ -668,38 +674,9 @@ 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)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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
|
||||
return b.sendIMBubbles(ctx, in, func(ctx context.Context, msg adapter.UnifiedMessage) error {
|
||||
return a.SendMessage(ctx, adapter.UnifiedResponse{Messages: []adapter.UnifiedMessage{msg}})
|
||||
}, text)
|
||||
}
|
||||
|
||||
// adapter returns the registered adapter for a platform name, or nil.
|
||||
|
||||
Reference in new issue
Block a user