fix(telegram): improve HTML chunking and preserve word boundaries
This commit is contained in:
parent
86da6a7d56
commit
758a82e219
1 changed files with 63 additions and 6 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue