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 i.mu.Lock()
38 text := i.skillPins.rawPrompt(message.Text)
39 i.skillPins.recall(message.Text)
40 i.mu.Unlock()
41 attachments := make([]clipboardImageAttachment, 0, len(message.Images))
42 for index, image := range message.Images {
43 markerIndex := index + 1
44 marker := fmt.Sprintf("[clipboard image #%d]", markerIndex)
45 for strings.Contains(text, marker) {
46 markerIndex++
47 marker = fmt.Sprintf("[clipboard image #%d]", markerIndex)
48 }
49 if text != "" {
50 text += " "
51 }
52 text += marker
53 attachments = append(attachments, clipboardImageAttachment{Marker: marker, Image: image})
54 }
55 i.clipboardImages = attachments
56 i.ed.SetValue(text)
57 i.inputHistoryIndex = -1
58 return true
59}
60
61func removeClipboardMarker(text, marker string) string {
62 for {
63 idx := strings.Index(text, marker)
64 if idx < 0 {
65 return text
66 }
67 end := idx + len(marker)
68 prevInline := idx > 0 && isInlineWhitespace(text[idx-1])
69 nextInline := end < len(text) && isInlineWhitespace(text[end])
70 prevLineBreak := idx == 0 || idx > 0 && isLineBreak(text[idx-1])
71 nextLineBreak := end == len(text) || end < len(text) && isLineBreak(text[end])
72 switch {
73 case prevInline && nextInline:
74 text = text[:idx] + text[end+1:]
75 case prevInline && nextLineBreak:
76 text = text[:idx-1] + text[end:]
77 case prevLineBreak && nextInline:
78 text = text[:idx] + text[end+1:]
79 default:
80 text = text[:idx] + text[end:]
81 }
82 }
83}
84
85func isInlineWhitespace(b byte) bool {
86 return b == ' ' || b == '\t'
87}
88
89func isLineBreak(b byte) bool {
90 return b == '\n' || b == '\r'
91}