cfe3271fa2
The loopback fetch now degrades to the public URL ...
120 lines
3.5 KiB
Go
120 lines
3.5 KiB
Go
package dingtalk
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gitea.zheromiku.top/Weknora/dobotai-platform/internal/reasoningmode"
|
|
)
|
|
|
|
const reasoningBannerMarker = "【智能推理模式】"
|
|
|
|
type cardContentShape struct {
|
|
bytes int
|
|
lines int
|
|
pipeLines int
|
|
leadingPipeLines int
|
|
delimiterLines int
|
|
confirmedTable bool
|
|
confirmedTablePos int
|
|
// Structural split state: where the answer body starts inside this exact
|
|
// payload. bodyPrefixRunes > 0 means the update still carries narration in
|
|
// front of the body (the reasoning text the card is supposed to hide).
|
|
bodyOK bool
|
|
bodyLine int
|
|
bodyPrefixRunes int
|
|
bannerPos int
|
|
firstHeadingLine int
|
|
imageRefs int
|
|
externalImageRefs int
|
|
mediaImageRefs int
|
|
}
|
|
|
|
func inspectCardContent(content string) cardContentShape {
|
|
shape := cardContentShape{
|
|
bytes: len(content),
|
|
confirmedTablePos: -1,
|
|
bodyLine: -1,
|
|
firstHeadingLine: -1,
|
|
bannerPos: -1,
|
|
}
|
|
if content == "" {
|
|
return shape
|
|
}
|
|
shape.lines = strings.Count(content, "\n") + 1
|
|
shape.imageRefs, shape.externalImageRefs, shape.mediaImageRefs = cardImageRefCounts(content)
|
|
|
|
shape.bannerPos = strings.Index(content, reasoningBannerMarker)
|
|
// The streaming path decorates every frame with the reasoning banner, so the
|
|
// prefix in front of the body has to be measured after that banner: only
|
|
// what remains is narration the card is meant to hide.
|
|
bannerSkip := 0
|
|
if shape.bannerPos == 0 {
|
|
if idx := strings.Index(content, "\n"); idx >= 0 {
|
|
bannerSkip = idx + 1
|
|
for bannerSkip < len(content) && content[bannerSkip] == '\n' {
|
|
bannerSkip++
|
|
}
|
|
}
|
|
}
|
|
if offset, ok := reasoningmode.BodyStartOffset(content); ok {
|
|
shape.bodyOK = true
|
|
shape.bodyLine = strings.Count(content[:offset], "\n")
|
|
prefix := content[:offset]
|
|
if bannerSkip > 0 && bannerSkip <= len(prefix) {
|
|
prefix = prefix[bannerSkip:]
|
|
}
|
|
shape.bodyPrefixRunes = len([]rune(prefix))
|
|
}
|
|
|
|
lines := splitMarkdownStreamLines(content)
|
|
for i, line := range lines {
|
|
trimmed := strings.TrimSpace(line.text)
|
|
if shape.firstHeadingLine < 0 && !line.inFence &&
|
|
strings.HasPrefix(trimmed, "#") && strings.TrimLeft(trimmed, "#") != "" {
|
|
shape.firstHeadingLine = i
|
|
}
|
|
if strings.Contains(line.text, "|") {
|
|
shape.pipeLines++
|
|
}
|
|
if isTableHeaderCandidate(line.text) {
|
|
shape.leadingPipeLines++
|
|
}
|
|
if !line.inFence && isCompleteTableDelimiter(line.text) {
|
|
shape.delimiterLines++
|
|
}
|
|
if shape.confirmedTable || i == 0 || line.inFence || lines[i-1].inFence {
|
|
continue
|
|
}
|
|
if isTableHeaderCandidate(lines[i-1].text) && isCompleteTableDelimiter(line.text) {
|
|
shape.confirmedTable = true
|
|
shape.confirmedTablePos = lines[i-1].start
|
|
}
|
|
}
|
|
return shape
|
|
}
|
|
|
|
func formatCardUpdateDiagnostic(content string, isFinalize, isError bool) string {
|
|
shape := inspectCardContent(content)
|
|
return fmt.Sprintf(
|
|
"DingTalk card update shape: finalize=%t error=%t bytes=%d lines=%d pipe_lines=%d leading_pipe_lines=%d delimiter_lines=%d confirmed_table=%t table_start=%d body_ok=%t body_line=%d body_prefix_runes=%d banner_pos=%d first_heading_line=%d image_refs=%d external_image_refs=%d media_image_refs=%d",
|
|
isFinalize,
|
|
isError,
|
|
shape.bytes,
|
|
shape.lines,
|
|
shape.pipeLines,
|
|
shape.leadingPipeLines,
|
|
shape.delimiterLines,
|
|
shape.confirmedTable,
|
|
shape.confirmedTablePos,
|
|
shape.bodyOK,
|
|
shape.bodyLine,
|
|
shape.bodyPrefixRunes,
|
|
shape.bannerPos,
|
|
shape.firstHeadingLine,
|
|
shape.imageRefs,
|
|
shape.externalImageRefs,
|
|
shape.mediaImageRefs,
|
|
)
|
|
}
|