Something went wrong. Try again.
small gleam coding and persistent agent daemon with a detachable cli and a canvas ui forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154package tui
import ( "albedo/cli/internal/daemon" "regexp" "strconv" "strings")
const ( // MaxPromptImages is how many images one prompt may attach. MaxPromptImages = 10 // A paste longer than either collapses into a marker. collapseLines = 10 collapseChars = 1000)
var ( imageMarker = regexp.MustCompile(`\[Image #(\d+)\]`) pasteMarker = regexp.MustCompile(`\[Paste #(\d+)(?:, [^\]\n]*)?\]`) // anyMarker is what one backspace or delete removes whole. anyMarker = regexp.MustCompile(`\[(?:Image|Paste) #\d+(?:, [^\]\n]*)?\]`))
// markerKind says how one kind of attachment is marked in the prompt.type markerKind[T any] interface { pattern() *regexp.Regexp marker(n int, item T) string}
// pastedImage is an image in the composer and, when the terminal shows// them, its thumbnail.type pastedImage struct { daemon.ImageAttachment thumb *thumbnail}
// unseen wraps images restored from a sent prompt, whose thumbnails the// terminal may no longer hold.func unseen(images []daemon.ImageAttachment) []pastedImage { pasted := make([]pastedImage, len(images)) for i, image := range images { pasted[i] = pastedImage{ImageAttachment: image} } return pasted}
type imageKind struct{}
func (imageKind) pattern() *regexp.Regexp { return imageMarker }
func (imageKind) marker(n int, _ pastedImage) string { return "[Image #" + strconv.Itoa(n) + "]"}
type pasteKind struct{}
func (pasteKind) pattern() *regexp.Regexp { return pasteMarker }
func (pasteKind) marker(n int, text string) string { if lines := strings.Count(text, "\n") + 1; lines > collapseLines { return "[Paste #" + strconv.Itoa(n) + ", +" + strconv.Itoa(lines) + " lines]" } return "[Paste #" + strconv.Itoa(n) + ", " + strconv.Itoa(len([]rune(text))) + " chars]"}
// attachments holds what was pasted into the composer under the number its// marker carries. Deleting a marker drops its item from the prompt.type attachments[T any, K markerKind[T]] struct { byNumber map[int]T next int kind K}
type ( promptImages = attachments[pastedImage, imageKind] promptPastes = attachments[string, pasteKind])
// collapses reports whether pasted text is long enough to become a marker.func collapses(text string) bool { return strings.Count(text, "\n") >= collapseLines || len([]rune(text)) > collapseChars}
// add keeps item and returns the marker to insert at the cursor.func (a *attachments[T, K]) add(item T) string { if a.byNumber == nil { a.byNumber = make(map[int]T) } a.next++ a.byNumber[a.next] = item return a.kind.marker(a.next, item)}
// restore replaces the items with ones numbered 1..n, as a resolved prompt// marks them.func (a *attachments[T, K]) restore(items []T) { a.byNumber, a.next = nil, 0 for _, item := range items { a.add(item) }}
// referenced returns the numbers of the items text still marks, in reading// order, each once.func (a attachments[T, K]) referenced(text string) []int { var numbers []int seen := make(map[int]bool) for _, match := range a.kind.pattern().FindAllStringSubmatch(text, -1) { n, _ := strconv.Atoi(match[1]) if _, ok := a.byNumber[n]; ok && !seen[n] { seen[n] = true numbers = append(numbers, n) } } return numbers}
// resolve numbers the marked items 1..n in reading order and returns the// text with its markers renumbered to match, and the items in that order.// Markers for numbers never pasted stay as typed.func (a attachments[T, K]) resolve(text string) (string, []T) { numbers := a.referenced(text) renumbered := make(map[int]int, len(numbers)) items := make([]T, len(numbers)) for i, n := range numbers { renumbered[n] = i + 1 items[i] = a.byNumber[n] } pattern := a.kind.pattern() text = pattern.ReplaceAllStringFunc(text, func(marker string) string { n, _ := strconv.Atoi(pattern.FindStringSubmatch(marker)[1]) if to, ok := renumbered[n]; ok { return a.kind.marker(to, a.byNumber[n]) } return marker }) return text, items}
// markerSpan returns the rune span of the marker in line that a backspace at// col (backward) or a delete at col (forward) would cut into.func markerSpan(line []rune, col int, backward bool) (int, int, bool) { text := string(line) for _, span := range anyMarker.FindAllStringIndex(text, -1) { start := len([]rune(text[:span[0]])) end := start + len([]rune(text[span[0]:span[1]])) if backward && start < col && col <= end || !backward && start <= col && col < end { return start, end, true } } return 0, 0, false}