Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions internal/application/service/wiki_ingest.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"text/template"
Expand Down Expand Up @@ -3275,7 +3276,12 @@ var (
// e.g. <image url="...">. Capture both double- and single-quoted forms.
imageURLAttrRE = regexp.MustCompile(`(?i)<image\b[^>]*\surl\s*=\s*(?:"([^"]*)"|'([^']*)')`)

imagePlaceholderTokenRE = regexp.MustCompile(`wkimg:[A-Za-z0-9_-]+`)
// Match real URLs as a whole so placeholder-like path/query fragments stay intact.
imagePlaceholderTokenRE = regexp.MustCompile(
`(?i)\b(?:[a-z][a-z0-9+.-]*://[^\s"'<>]+|` +
`wkimg(?:[ \t]*[::/_-][ \t]*[A-Za-z0-9_/-]+|` +
`[ \t]+[0-9][A-Za-z0-9_/-]*|[A-Za-z0-9_/-]+)(?:\.[A-Za-z0-9_/-]+)*)`)
looseImagePlaceholderRE = regexp.MustCompile(`(?i)^wkimg[ \t]*[::/_-]?[ \t]*([0-9]+)$`)
)

func maskTemplateDataImageURLs(data map[string]string) (map[string]string, map[string]string) {
Expand Down Expand Up @@ -3374,14 +3380,14 @@ func unmaskImageURLs(out string, urlMap map[string]string) string {
return match
}
url := strings.TrimSpace(parts[1])
if realURL, ok := urlMap[url]; ok {
if realURL, ok := resolveImagePlaceholder(url, urlMap); ok {
idx := strings.LastIndex(match, "(")
if idx < 0 {
return match
}
return match[:idx+1] + realURL + ")"
}
if strings.HasPrefix(url, "wkimg:") {
if strings.HasPrefix(strings.ToLower(url), "wkimg") {
return ""
}
return match
Expand Down Expand Up @@ -3413,13 +3419,34 @@ func replaceImagePlaceholderTokensOutsideMarkdown(s string, urlMap map[string]st

func replaceImagePlaceholderTokens(s string, urlMap map[string]string) string {
return imagePlaceholderTokenRE.ReplaceAllStringFunc(s, func(token string) string {
if realURL, ok := urlMap[token]; ok {
if !strings.HasPrefix(strings.ToLower(token), "wkimg") {
return token
}
if realURL, ok := resolveImagePlaceholder(token, urlMap); ok {
return realURL
}
return ""
})
}

// resolveImagePlaceholder tolerates formatting changes only when the complete
// numeric identifier maps to an image supplied to this LLM call.
func resolveImagePlaceholder(token string, urlMap map[string]string) (string, bool) {
if realURL, ok := urlMap[token]; ok {
return realURL, true
}
parts := looseImagePlaceholderRE.FindStringSubmatch(strings.TrimSpace(token))
if len(parts) != 2 {
return "", false
}
index, err := strconv.Atoi(parts[1])
if err != nil {
return "", false
}
realURL, ok := urlMap[fmt.Sprintf("wkimg:%04d", index)]
return realURL, ok
}

// stripImageMarkup removes image-only placeholders (Markdown image refs,
// <img> tags, <image_original> redundancy blocks) and unwraps the
// <image>/<image_caption>/<image_ocr> XML wrappers produced by the search
Expand Down
71 changes: 69 additions & 2 deletions internal/application/service/wiki_ingest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package service
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -311,9 +312,75 @@ func TestMaskImageURLs(t *testing.T) {
})
}

func TestUnmaskImageURLsRewrittenPlaceholders(t *testing.T) {
const realURL = "resource://image-10"
urlMap := map[string]string{"wkimg:0010": realURL}
variants := []string{
"wkimg:0010", "wkimg/0010", "wkimg_0010", "wkimg-0010", "wkimg 0010",
"wkimg:0010", "WKIMG/0010", "wkimg : 0010", "wkimg\t0010",
"wkimg0010", "wkimg/10", "wkimg:000010",
}
contexts := []struct {
name string
format string
}{
{"markdown", "Before ![Diagnostics page](%s) after"},
{"attribute", `<image url="%s">caption</image>`},
{"json", `{"details":"Before ![Diagnostics page](%s) after"}`},
{"bare", "Before %s after"},
}
for _, variant := range variants {
for _, context := range contexts {
t.Run(context.name+"/"+variant, func(t *testing.T) {
input := fmt.Sprintf(context.format, variant)
want := fmt.Sprintf(context.format, realURL)
require.Equal(t, want, unmaskImageURLs(input, urlMap))
})
}
}
}

func TestUnmaskImageURLsDropsRewrittenUnknownPlaceholders(t *testing.T) {
urlMap := map[string]string{"wkimg:0010": "resource://image-10"}
variants := []string{
"wkimg/9999", "WKIMG_9999", "wkimg-9999", "wkimg 9999", "wkimg:9999",
"wkimg/0010extra", "wkimg:0010-extra", "wkimg//0010", "wkimg:bad", "wkimg/0010.png",
"wkimg/0000", "wkimg:999999999999999999999999999999",
}
for _, variant := range variants {
t.Run(variant, func(t *testing.T) {
require.Equal(t, "Before after", unmaskImageURLs("Before ![bad]("+variant+") after", urlMap))
require.Equal(t, "Before after", unmaskImageURLs("Before "+variant+" after", urlMap))
})
}
t.Run("no image map", func(t *testing.T) {
require.Equal(t, "Before after", unmaskImageURLs("Before ![bad](wkimg/0010) after", nil))
})
t.Run("unrecognizable image URL", func(t *testing.T) {
require.Equal(t, "Before after", unmaskImageURLs("Before ![bad](WKIMG?0010) after", urlMap))
})
}

func TestUnmaskImageURLsPreservesCaptionsAndRealURLs(t *testing.T) {
urlMap := map[string]string{"wkimg:0010": "resource://image-10", "wkimg:10000": "resource://image-10000"}
input := `![WKIMG/0010](wkimg/0010) ![real](https://example.com/wkimg/0010)
<image url="https://example.com/wkimg:0010">caption</image>
https://example.com/?image=wkimg/0010
resource://wkimg:0010
embeddedwkimg/0010
![large](wkimg/10000)`
want := `![WKIMG/0010](resource://image-10) ![real](https://example.com/wkimg/0010)
<image url="https://example.com/wkimg:0010">caption</image>
https://example.com/?image=wkimg/0010
resource://wkimg:0010
embeddedwkimg/0010
![large](resource://image-10000)`
require.Equal(t, want, unmaskImageURLs(input, urlMap))
}

func TestUnmaskImageURLsDropsUnknownPlaceholders(t *testing.T) {
urlMap := map[string]string{"wkimg:0001": "minio://kb/exports/real.jpg"}
input := `{"details":"keep ![ok](wkimg:0001) drop ![bad](wkimg:001) and wkimg:9999"}`
input := `{"details":"keep ![ok](wkimg:0001) drop ![bad](wkimg:9998) and wkimg:9999"}`
got := unmaskImageURLs(input, urlMap)

if !strings.Contains(got, "![ok](minio://kb/exports/real.jpg)") {
Expand All @@ -327,7 +394,7 @@ func TestUnmaskImageURLsDropsUnknownPlaceholders(t *testing.T) {
func TestGenerateWithTemplateMasksImageURLsBeforeLLM(t *testing.T) {
const realURL = "minio://kb/10000/exports/4135-aaaa-bbbb-cccc/page_1.jpg"
model := &templateCaptureChatModel{
response: `{"details":"Model kept ![caption](wkimg:0001)"}`,
response: `{"details":"Model kept ![caption](wkimg/0001)"}`,
}
service := &wikiIngestService{}

Expand Down
Loading