clipboard_images.go RawHistoryFolder
1package modes
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/bnema/zut/packages/core"
8 "github.com/bnema/zut/packages/provider"
9)
10
11type clipboardImageAttachment struct {
12 Marker string
13 Image provider.ImageBlock
14}
15
16func preparePromptWithClipboardImages(text string, pending []clipboardImageAttachment) (string, []provider.ImageBlock) {
17 if len(pending) == 0 {
18 return text, nil
19 }
20
21 out := text
22 images := make([]provider.ImageBlock, 0, len(pending))
23 for _, item := range pending {
24 if item.Marker == "" || !strings.Contains(out, item.Marker) {
25 continue
26 }
27 out = removeClipboardMarker(out, item.Marker)
28 images = append(images, item.Image)
29 }
30 return strings.TrimSpace(out), images
31}
32
33func (i *Interactive) restoreQueuedMessageToEditor(message core.QueuedMessage) bool {
34 if message.Text == "" && len(message.Images) == 0 {
35 return false
36 }
37 text := message.Text
38 attachments := make([]clipboardImageAttachment, 0, len(message.Images))
39 for index, image := range message.Images {
40 markerIndex := index + 1
41 marker := fmt.Sprintf("[clipboard image #%d]", markerIndex)
42 for strings.Contains(text, marker) {
43 markerIndex++
44 marker = fmt.Sprintf("[clipboard image #%d]", markerIndex)
45 }
46 if text != "" {
47 text += " "
48 }
49 text += marker
50 attachments = append(attachments, clipboardImageAttachment{Marker: marker, Image: image})
51 }
52 i.clipboardImages = attachments
53 i.ed.SetValue(text)
54 i.inputHistoryIndex = -1
55 return true
56}
57
58func removeClipboardMarker(text, marker string) string {
59 for {
60 idx := strings.Index(text, marker)
61 if idx < 0 {
62 return text
63 }
64 end := idx + len(marker)
65 prevInline := idx > 0 && isInlineWhitespace(text[idx-1])
66 nextInline := end < len(text) && isInlineWhitespace(text[end])
67 prevLineBreak := idx == 0 || idx > 0 && isLineBreak(text[idx-1])
68 nextLineBreak := end == len(text) || end < len(text) && isLineBreak(text[end])
69 switch {
70 case prevInline && nextInline:
71 text = text[:idx] + text[end+1:]
72 case prevInline && nextLineBreak:
73 text = text[:idx-1] + text[end:]
74 case prevLineBreak && nextInline:
75 text = text[:idx] + text[end+1:]
76 default:
77 text = text[:idx] + text[end:]
78 }
79 }
80}
81
82func isInlineWhitespace(b byte) bool {
83 return b == ' ' || b == '\t'
84}
85
86func isLineBreak(b byte) bool {
87 return b == '\n' || b == '\r'
88}