Add agent components 451-465 and icon.Crystal
Fifteen components that dress an AI agent's work as an RPG party: AgentCard, ToolCall, PlanQuest, ThinkingOrb, TokenMana, ApprovalPrompt, AgentLog, SubAgentParty, MemoryCrystals, AgentStatus, DiffScroll, ModelSelect, PromptBox, CostCoins and Checkpoints. The context window is HP and the token budget MP; tool calls are spells that CAST, glow, then land OK or FAILED; the transcript reads as a battle log whose newest line types itself out; models line up like fighters. Busy signs keep moving under Reduce Motion, decoration stops. Commands and prompts never reach the log, and a veiled PromptBox casts nothing. icon.Crystal is the new 12x12 save and memory crystal.
This commit is contained in:
29 files changed
+2489
-2
No files matched your search
@@ -0,0 +1,121 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
// FileDiff is a changed file: lines added and removed.
|
||||
type FileDiff struct {
|
||||
Path string
|
||||
Added, Removed int
|
||||
}
|
||||
|
||||
type DiffScrollOptions struct {
|
||||
// Width is in dots; zero means 220.
|
||||
Width int
|
||||
}
|
||||
|
||||
// DiffScroll unrolls a parchment of changed files, lines counted.
|
||||
func DiffScroll(c *ui.Context, text string, files []FileDiff, o DiffScrollOptions) *ui.Element {
|
||||
k := pixel.K(c)
|
||||
if len(files) == 0 {
|
||||
panic("agent: DiffScroll has no files")
|
||||
}
|
||||
add, del := 0, 0
|
||||
said := make([]string, 0, len(files)+1)
|
||||
for _, f := range files {
|
||||
if f.Path == "" || f.Added < 0 || f.Removed < 0 || f.Added > math.MaxInt-add || f.Removed > math.MaxInt-del {
|
||||
panic(fmt.Sprintf("agent: DiffScroll file %q +%d -%d", f.Path, f.Added, f.Removed))
|
||||
}
|
||||
add, del = add+f.Added, del+f.Removed
|
||||
said = append(said, f.Path+" +"+strconv.Itoa(f.Added)+" -"+strconv.Itoa(f.Removed))
|
||||
}
|
||||
w := pixel.Or("DiffScroll Width", o.Width, 220)
|
||||
total := fmt.Sprintf(k.Msg("files_changed"), len(files))
|
||||
said = append(said, total+" +"+strconv.Itoa(add)+" -"+strconv.Itoa(del))
|
||||
// Count columns fit the totals, the widest of them.
|
||||
aw, dw := pixel.MicroWidth("+"+strconv.Itoa(add)), pixel.MicroWidth("-"+strconv.Itoa(del))
|
||||
bx := w - 8 - 24
|
||||
dx := bx - 6 - dw
|
||||
ax := dx - 4 - aw
|
||||
if ax-12 < 24 || pixel.Measure(total) > ax-12 {
|
||||
panic(fmt.Sprintf("agent: DiffScroll Width %d is too narrow", w))
|
||||
}
|
||||
h := 6 + 4 + (len(files)+1)*pixel.LineHeight + 4 + 6
|
||||
return ui.Box(c).Size(k.D(w), k.D(h)).Role(ui.RoleImage).Label(text).Description(strings.Join(said, "; ")).Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
g := k.Canvas(p, r)
|
||||
g.Rect(4, 6, w-8, h-12, pixel.Lit)
|
||||
g.VLine(4, 6, h-12, pixel.Shade)
|
||||
g.VLine(w-5, 6, h-12, pixel.Shade)
|
||||
rod(g, 0, w)
|
||||
rod(g, h-6, w)
|
||||
row := func(y int, name string, a, d int) {
|
||||
g.Text(8, y, name, pixel.Ink, 1)
|
||||
plus, minus := "+"+strconv.Itoa(a), "-"+strconv.Itoa(d)
|
||||
g.Micro(ax+aw-pixel.MicroWidth(plus), y+6, plus, ink(a, pixel.Good), 1)
|
||||
g.Micro(dx+dw-pixel.MicroWidth(minus), y+6, minus, ink(d, pixel.Bad), 1)
|
||||
blocks(g, bx, y+6, a, d)
|
||||
}
|
||||
y := 10
|
||||
for _, f := range files {
|
||||
row(y, tail(f.Path, ax-12), f.Added, f.Removed)
|
||||
y += pixel.LineHeight
|
||||
}
|
||||
g.Checker(8, y+1, w-16, 1, pixel.Shade)
|
||||
row(y+4, total, add, del)
|
||||
})
|
||||
}
|
||||
|
||||
// ink is s for a count, Faint for none.
|
||||
func ink(n int, s pixel.Slot) pixel.Slot {
|
||||
if n == 0 {
|
||||
return pixel.Faint
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// rod is a scroll's wooden roller, capped past the paper.
|
||||
func rod(g pixel.Canvas, y, w int) {
|
||||
g.Rect(1, y+1, w-2, 4, pixel.Shade)
|
||||
g.HLine(1, y, w-2, pixel.Edge)
|
||||
g.HLine(1, y+5, w-2, pixel.Edge)
|
||||
g.Rect(0, y, 3, 6, pixel.Edge)
|
||||
g.Rect(w-3, y, 3, 6, pixel.Edge)
|
||||
}
|
||||
|
||||
// blocks shares five squares between added and removed.
|
||||
func blocks(g pixel.Canvas, x, y, a, d int) {
|
||||
good := 0
|
||||
if a > 0 || d > 0 {
|
||||
good = int(math.Round(5 * float64(a) / (float64(a) + float64(d))))
|
||||
}
|
||||
for i := range 5 {
|
||||
ink := pixel.Bad
|
||||
switch {
|
||||
case a == 0 && d == 0:
|
||||
ink = pixel.Shade
|
||||
case i < good:
|
||||
ink = pixel.Good
|
||||
}
|
||||
g.Rect(x+i*5, y, 4, 4, ink)
|
||||
}
|
||||
}
|
||||
|
||||
// tail cuts s from the left, so the name shows.
|
||||
func tail(s string, width int) string {
|
||||
if pixel.Measure(s) <= width {
|
||||
return s
|
||||
}
|
||||
rs := []rune(s)
|
||||
for len(rs) > 0 && pixel.Measure("…"+string(rs)) > width {
|
||||
rs = rs[1:]
|
||||
}
|
||||
return "…" + string(rs)
|
||||
}
|
||||
Reference in new issue
Block a user