fix(telegram): improve HTML chunking and preserve word boundaries

This commit is contained in:
Badgerbees 2026-03-16 22:51:54 +07:00
parent 86da6a7d56
commit 758a82e219

View file

@ -190,14 +190,71 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
htmlContent := markdownToTelegramHTML(chunk) htmlContent := markdownToTelegramHTML(chunk)
if len([]rune(htmlContent)) > 4096 { if len([]rune(htmlContent)) > 4096 {
ratio := float64(len([]rune(chunk))) / float64(len([]rune(htmlContent))) runeChunk := []rune(chunk)
ratio := float64(len(runeChunk)) / float64(len([]rune(htmlContent)))
smallerLen := int(float64(4096) * ratio * 0.95) // 5% safety margin smallerLen := int(float64(4096) * ratio * 0.95) // 5% safety margin
if smallerLen < 100 {
smallerLen = 100 // Guarantee progress: if estimated length is >= chunk length, force it smaller
if smallerLen >= len(runeChunk) {
smallerLen = len(runeChunk) - 1
} }
// Push sub-chunks back to the front of the queue for
// re-validation instead of sending them blindly. if smallerLen <= 0 {
subChunks := channels.SplitMessage(chunk, smallerLen) if err := c.sendHTMLChunk(ctx, chatID, threadID, htmlContent, chunk, replyToID); err != nil {
return err
}
replyToID = ""
continue
}
// Seek a natural break point (space or newline) to avoid splitting mid-word.
splitIdx := smallerLen
foundBreak := false
// Scan backwards from the target point to find the nearest whitespace.
for i := smallerLen; i >= 0; i-- {
if runeChunk[i] == ' ' || runeChunk[i] == '\n' || runeChunk[i] == '\t' || runeChunk[i] == '\r' {
splitIdx = i
foundBreak = true
break
}
}
// If no space was found behind, scan forward to find the next word boundary.
if !foundBreak {
for i := smallerLen; i < len(runeChunk); i++ {
if runeChunk[i] == ' ' || runeChunk[i] == '\n' || runeChunk[i] == '\t' || runeChunk[i] == '\r' {
splitIdx = i
foundBreak = true
break
}
}
}
// Fall back to a hard split if the entire block is monolithic (no spaces found).
if !foundBreak {
splitIdx = smallerLen
}
if splitIdx <= 0 {
splitIdx = 1
}
// Attempt to split using the determined index, ensuring code block integrity is maintained.
subChunks := channels.SplitMessage(chunk, splitIdx)
// Force a manual split if the message remains monolithic after the attempt.
if len(subChunks) == 1 && subChunks[0] == chunk {
part1 := string(runeChunk[:splitIdx])
nextStart := splitIdx
if foundBreak && nextStart < len(runeChunk) {
nextStart++
}
part2 := string(runeChunk[nextStart:])
subChunks = []string{part1, part2}
}
// Push sub-chunks back to the front of the queue
queue = append(subChunks, queue...) queue = append(subChunks, queue...)
continue continue
} }