diff --git a/.github/images.png b/.github/images.png new file mode 100644 index 0000000..abcea92 Binary files /dev/null and b/.github/images.png differ diff --git a/chat.go b/chat.go index adc1875..177be39 100644 --- a/chat.go +++ b/chat.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "net/http" - "regexp" "strings" "github.com/revrost/go-openrouter" @@ -436,78 +435,3 @@ func RunCompletion(ctx context.Context, response *Stream, request *openrouter.Ch return tool, buf.String(), nil } - -func SplitImagePairs(text string) []openrouter.ChatMessagePart { - rgx := regexp.MustCompile(`(?m)!\[[^\]]*]\((\S+?)\)`) - - var ( - index int - parts []openrouter.ChatMessagePart - ) - - push := func(str, end int) { - if str > end { - return - } - - rest := text[str:end] - - if rest == "" { - return - } - - total := len(parts) - - if total > 0 && parts[total-1].Type == openrouter.ChatMessagePartTypeText { - parts[total-1].Text += rest - - return - } - - parts = append(parts, openrouter.ChatMessagePart{ - Type: openrouter.ChatMessagePartTypeText, - Text: rest, - }) - } - - for { - location := rgx.FindStringSubmatchIndex(text[index:]) - if location == nil { - push(index, len(text)-1) - - break - } - - start := index + location[0] - end := index + location[1] - - urlStart := index + location[2] - urlEnd := index + location[3] - - url := text[urlStart:urlEnd] - - if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") { - push(index, end) - - index = end - - continue - } - - if start > index { - push(index, start) - } - - parts = append(parts, openrouter.ChatMessagePart{ - Type: openrouter.ChatMessagePartTypeImageURL, - ImageURL: &openrouter.ChatMessageImageURL{ - Detail: openrouter.ImageURLDetailAuto, - URL: url, - }, - }) - - index = end - } - - return parts -} diff --git a/markdown.go b/markdown.go new file mode 100644 index 0000000..27ea19f --- /dev/null +++ b/markdown.go @@ -0,0 +1,132 @@ +package main + +import ( + "regexp" + "strings" + + "github.com/revrost/go-openrouter" +) + +type CodeRegion struct { + Start int + End int +} + +func FindMarkdownCodeRegions(text string) []CodeRegion { + var regions []CodeRegion + + inline := regexp.MustCompile(`\x60[^\x60\n]+?\x60`) + + for _, match := range inline.FindAllStringIndex(text, -1) { + regions = append(regions, CodeRegion{ + Start: match[0], + End: match[1], + }) + } + + fenced := regexp.MustCompile(`(?m)^\x60\x60\x60[^\n]*\n(.*?\n)^\x60\x60\x60\s*$`) + + for _, match := range fenced.FindAllStringIndex(text, -1) { + regions = append(regions, CodeRegion{ + Start: match[0], + End: match[1], + }) + } + + return regions +} + +func IsInsideCodeBlock(pos int, regions []CodeRegion) bool { + for _, region := range regions { + if pos >= region.Start && pos < region.End { + return true + } + } + + return false +} + +func SplitImagePairs(text string) []openrouter.ChatMessagePart { + code := FindMarkdownCodeRegions(text) + + rgx := regexp.MustCompile(`(?m)!\[[^\]]*]\((\S+?)\)`) + + var ( + index int + parts []openrouter.ChatMessagePart + ) + + push := func(str, end int) { + if str > end { + return + } + + rest := text[str:end] + + if rest == "" { + return + } + + total := len(parts) + + if total > 0 && parts[total-1].Type == openrouter.ChatMessagePartTypeText { + parts[total-1].Text += rest + + return + } + + parts = append(parts, openrouter.ChatMessagePart{ + Type: openrouter.ChatMessagePartTypeText, + Text: rest, + }) + } + + for { + location := rgx.FindStringSubmatchIndex(text[index:]) + if location == nil { + push(index, len(text)-1) + + break + } + + start := index + location[0] + end := index + location[1] + + if IsInsideCodeBlock(start, code) { + push(index, end) + + index = end + + continue + } + + urlStart := index + location[2] + urlEnd := index + location[3] + + url := text[urlStart:urlEnd] + + if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") { + push(index, end) + + index = end + + continue + } + + if start > index { + push(index, start) + } + + parts = append(parts, openrouter.ChatMessagePart{ + Type: openrouter.ChatMessagePartTypeImageURL, + ImageURL: &openrouter.ChatMessageImageURL{ + Detail: openrouter.ImageURLDetailAuto, + URL: url, + }, + }) + + index = end + } + + return parts +} diff --git a/static/js/chat.js b/static/js/chat.js index b574044..050ccd5 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -174,7 +174,7 @@ this.#build(collapsed); this.#render(); - if (tool) { + if (tool?.name) { this.setTool(tool); } @@ -592,6 +592,8 @@ this.#_message.classList.toggle("has-tool", !!this.#tool); + this.#updateToolHeight(); + noScroll || scroll(); updateScrollButton(); @@ -631,6 +633,8 @@ if (!only || only === "reasoning") { this.#patch("reasoning", this.#_reasoning, this.#reasoning, () => { + this.#updateReasoningHeight(); + noScroll || scroll(); updateScrollButton();