mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 16:35:27 +08:00
169 lines
3.3 KiB
Go
169 lines
3.3 KiB
Go
package sse
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type citationLinkCollector struct {
|
|
ordered []string
|
|
explicitRaw map[int]string
|
|
hasZeroIdx bool
|
|
}
|
|
|
|
func newCitationLinkCollector() *citationLinkCollector {
|
|
return &citationLinkCollector{
|
|
explicitRaw: map[int]string{},
|
|
}
|
|
}
|
|
|
|
func (c *citationLinkCollector) ingestChunk(chunk map[string]any) {
|
|
if c == nil || len(chunk) == 0 {
|
|
return
|
|
}
|
|
c.walkValue(chunk)
|
|
}
|
|
|
|
func (c *citationLinkCollector) build() map[int]string {
|
|
out := make(map[int]string, len(c.explicitRaw)+len(c.ordered))
|
|
for idx, u := range c.buildNormalizedExplicit() {
|
|
out[idx] = u
|
|
}
|
|
for i, u := range c.ordered {
|
|
idx := i + 1
|
|
if _, exists := out[idx]; !exists {
|
|
out[idx] = u
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (c *citationLinkCollector) buildNormalizedExplicit() map[int]string {
|
|
out := make(map[int]string, len(c.explicitRaw))
|
|
|
|
// Default behavior keeps positive indices as-is (one-based payloads).
|
|
for idx, u := range c.explicitRaw {
|
|
if idx <= 0 || strings.TrimSpace(u) == "" {
|
|
continue
|
|
}
|
|
out[idx] = u
|
|
}
|
|
|
|
if !c.hasZeroIdx {
|
|
return out
|
|
}
|
|
|
|
// If zero index appears, upstream may be using zero-based indices.
|
|
// Add shifted candidates and resolve conflicts using ordered appearance,
|
|
// which matches visible citation marker order in response text.
|
|
for rawIdx, u := range c.explicitRaw {
|
|
if rawIdx < 0 || strings.TrimSpace(u) == "" {
|
|
continue
|
|
}
|
|
normalized := rawIdx + 1
|
|
existing, exists := out[normalized]
|
|
if !exists {
|
|
out[normalized] = u
|
|
continue
|
|
}
|
|
if c.preferURLForIndex(normalized, existing, u) == u {
|
|
out[normalized] = u
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func (c *citationLinkCollector) preferURLForIndex(idx int, current, candidate string) string {
|
|
if idx <= 0 || idx > len(c.ordered) {
|
|
return current
|
|
}
|
|
expected := c.ordered[idx-1]
|
|
switch {
|
|
case strings.TrimSpace(expected) == "":
|
|
return current
|
|
case candidate == expected && current != expected:
|
|
return candidate
|
|
default:
|
|
return current
|
|
}
|
|
}
|
|
|
|
func (c *citationLinkCollector) walkValue(v any) {
|
|
switch x := v.(type) {
|
|
case []any:
|
|
for _, item := range x {
|
|
c.walkValue(item)
|
|
}
|
|
case map[string]any:
|
|
c.captureURLAndIndex(x)
|
|
for _, vv := range x {
|
|
c.walkValue(vv)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *citationLinkCollector) captureURLAndIndex(m map[string]any) {
|
|
url := strings.TrimSpace(asString(m["url"]))
|
|
if !isWebURL(url) {
|
|
return
|
|
}
|
|
c.addOrdered(url)
|
|
|
|
idx, hasIdx := citationIndexFromAny(m["cite_index"])
|
|
if !hasIdx {
|
|
return
|
|
}
|
|
if idx < 0 {
|
|
return
|
|
}
|
|
if idx == 0 {
|
|
c.hasZeroIdx = true
|
|
}
|
|
if existing, ok := c.explicitRaw[idx]; ok && strings.TrimSpace(existing) != "" {
|
|
return
|
|
}
|
|
c.explicitRaw[idx] = url
|
|
}
|
|
|
|
func (c *citationLinkCollector) addOrdered(url string) {
|
|
c.ordered = append(c.ordered, url)
|
|
}
|
|
|
|
func citationIndexFromAny(v any) (int, bool) {
|
|
switch x := v.(type) {
|
|
case int:
|
|
return x, true
|
|
case int32:
|
|
return int(x), true
|
|
case int64:
|
|
return int(x), true
|
|
case float32:
|
|
return int(x), true
|
|
case float64:
|
|
return int(x), true
|
|
case string:
|
|
s := strings.TrimSpace(x)
|
|
if s == "" {
|
|
return 0, false
|
|
}
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return 0, false
|
|
}
|
|
return n, true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func isWebURL(v string) bool {
|
|
v = strings.ToLower(strings.TrimSpace(v))
|
|
return strings.HasPrefix(v, "http://") || strings.HasPrefix(v, "https://")
|
|
}
|
|
|
|
func asString(v any) string {
|
|
s, _ := v.(string)
|
|
return s
|
|
}
|