Files
ZacharyZhang-NY d60d53ef90 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.
2026-10-12 12:03:44 -04:00

214 lines
7.7 KiB
Go

package gallery
import (
"fmt"
"slices"
"time"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/agent"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
var agentEntries = []Entry{
{451, "AgentCard", "智能体卡", "agent", "A party card. The context window is the HP bar: the more it reads, the less it has left.", demoAgentCard},
{452, "ToolCall", "工具调用", "agent", "Every tool call is a spell: CAST, a moment of glowing, then OK or FAILED.", demoToolCall},
{453, "PlanQuest", "计划任务", "agent", "The plan as quest objectives. Finish them all for a fanfare.", demoPlanQuest},
{454, "ThinkingOrb", "思考水晶球", "agent", "A crystal ball that swirls while the agent thinks.", demoThinkingOrb},
{455, "TokenMana", "令牌魔力", "agent", "The token budget as MP. Spend it all and the well runs dry.", demoTokenMana},
{456, "ApprovalPrompt", "授权提示", "agent", "ALLOW SPELL? The agent asks before it runs a command; risky ones come in red.", demoApprovalPrompt},
{457, "AgentLog", "智能体日志", "agent", "The transcript as a battle log. The newest line types itself out.", demoAgentLog},
{458, "SubAgentParty", "子智能体", "agent", "Agents summon helpers. The tree shows who summoned whom, and how each is doing.", demoSubAgentParty},
}
type agentDemo struct {
used, done, mana, next int
cast time.Time
told, thinking bool
log []agent.LogStep
party []agent.Member
}
func agentState(c *ui.Context) *agentDemo {
return ui.Local(c.Root(), demoKey("agent"), func() agentDemo {
return agentDemo{used: 64_000, done: 1, mana: 28_000, told: true, next: 3, log: slices.Clone(story[:3]),
party: []agent.Member{{Name: "LEAD", Status: agent.Working, Summoned: []agent.Member{
{Name: "SCOUT", Status: agent.Done}, {Name: "SMITH", Status: agent.Working, Summoned: []agent.Member{{Name: "IMP", Status: agent.Blocked}}},
}}}}
})
}
func demoAgentCard(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := agentState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
agent.AgentCard(c, agent.Agent{Name: "ROBO", Class: "CODER", Level: 12, Used: s.used, Window: 200_000}, agent.AgentCardOptions{})
agent.AgentCard(c, agent.Agent{Name: "SAGE", Class: "REVIEWER", Level: 30, Used: 182_000, Window: 200_000}, agent.AgentCardOptions{})
ui.Row(c).Gap(k.D(6)).Children(func() {
if input.Button(c, "READ A BIG FILE", input.ButtonOptions{Size: input.Small}).Clicked {
s.used = min(200_000, s.used+24_000)
log(fmt.Sprintf("ROBO used %d tokens", s.used))
}
// Compacting is a night at the inn: HP comes back.
if input.Button(c, "COMPACT", input.ButtonOptions{Size: input.Small}).Clicked {
s.used = 12_000
log("ROBO compacted its context")
}
})
})
}
func demoToolCall(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := agentState(c)
grep := agent.OK
if done := s.cast.Add(1500 * time.Millisecond); k.Now().Before(done) {
grep = agent.Casting
k.At(c, done)
} else if !s.told {
s.told = true
log("grep: OK")
}
ui.Column(c).Gap(k.D(4)).Children(func() {
agent.ToolCall(c, "read_file", agent.OK, agent.ToolCallOptions{Args: "pixel/canvas.go"})
agent.ToolCall(c, "grep", grep, agent.ToolCallOptions{Args: `"TODO" in 512 files`})
agent.ToolCall(c, "bash", agent.Failed, agent.ToolCallOptions{Args: "npm test: 2 tests fainted"})
if input.Button(c, "CAST GREP", input.ButtonOptions{Size: input.Small, Disabled: grep == agent.Casting}).Clicked {
s.cast, s.told = k.Now(), false
log("casting grep")
}
})
}
func demoPlanQuest(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := agentState(c)
steps := []agent.PlanStep{{Text: "Reproduce the save bug"}, {Text: "Write a failing test"}, {Text: "Fix the root cause"}, {Text: "Run every test"}, {Text: "Ship it"}}
for i := range s.done {
steps[i].Done = true
}
ui.Column(c).Gap(k.D(6)).Children(func() {
agent.PlanQuest(c, "The Lost Save", steps, agent.PlanQuestOptions{})
if s.done < len(steps) {
if input.Button(c, "COMPLETE STEP", input.ButtonOptions{Size: input.Small}).Clicked {
s.done++
log(fmt.Sprintf("objective %d done", s.done))
}
return
}
if input.Button(c, "NEW QUEST", input.ButtonOptions{Size: input.Small}).Clicked {
s.done = 0
log("a new quest begins")
}
})
}
func demoThinkingOrb(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := agentState(c)
ui.Row(c).Gap(k.D(8)).AlignItems(ui.Center).Children(func() {
agent.ThinkingOrb(c, "Mind", s.thinking, agent.ThinkingOrbOptions{})
label := "PONDER"
if s.thinking {
label = "SNAP OUT OF IT"
}
if input.Button(c, label, input.ButtonOptions{Size: input.Small}).Clicked {
s.thinking = !s.thinking
log(fmt.Sprintf("thinking %v", s.thinking))
}
})
}
func demoTokenMana(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := agentState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
agent.TokenMana(c, "Token budget", s.mana, 100_000, agent.TokenManaOptions{Width: 160})
ui.Row(c).Gap(k.D(6)).Children(func() {
if input.Button(c, "CAST (12K)", input.ButtonOptions{Size: input.Small}).Clicked {
s.mana += 12_000
log(fmt.Sprintf("%d tokens spent", s.mana))
}
if input.Button(c, "DRINK ETHER", input.ButtonOptions{Size: input.Small}).Clicked {
s.mana = 0
log("MP restored")
}
})
})
}
func demoApprovalPrompt(c *ui.Context, log func(string)) {
k := pixel.K(c)
ui.Column(c).Gap(k.D(8)).Children(func() {
for _, cmd := range []struct {
line string
danger bool
}{{"go test ./...", false}, {"rm -rf ~/saves", true}} {
r := agent.ApprovalPrompt(c, cmd.line, cmd.line, agent.ApprovalPromptOptions{Danger: cmd.danger})
if r.Allowed {
log("allowed: " + cmd.line)
}
if r.Denied {
log("denied: " + cmd.line)
}
}
})
}
// story is what the log demo's agent does, in turn.
var story = []agent.LogStep{
{Kind: agent.Thought, Text: "the save file looks corrupt"}, {Kind: agent.Cast, Text: "read_file"},
{Kind: agent.Hit, Text: "a stray byte at 0x2A"}, {Kind: agent.Cast, Text: "edit_file"},
{Kind: agent.Miss, Text: "one test still fails"}, {Kind: agent.Cast, Text: "edit_file"},
{Kind: agent.Hit, Text: "all 64 tests pass"}, {Kind: agent.Said, Text: "Fixed! Shall I ship it?"},
}
func demoAgentLog(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := agentState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
agent.AgentLog(c, "Transcript", "ROBO", s.log, agent.AgentLogOptions{Width: 260})
if input.Button(c, "NEXT STEP", input.ButtonOptions{Size: input.Small}).Clicked {
step := story[s.next%len(story)]
s.log, s.next = append(s.log, step), s.next+1
log(fmt.Sprintf("step %d: %s", s.next, step.Text))
}
})
}
func demoSubAgentParty(c *ui.Context, log func(string)) {
k := pixel.K(c)
s := agentState(c)
ui.Column(c).Gap(k.D(6)).Children(func() {
agent.SubAgentParty(c, "Party", s.party, agent.SubAgentPartyOptions{})
ui.Row(c).Gap(k.D(6)).Children(func() {
lead := &s.party[0]
if input.Button(c, "SUMMON", input.ButtonOptions{Size: input.Small, Disabled: len(lead.Summoned) == 4}).Clicked {
name := []string{"GOLEM", "WISP"}[len(lead.Summoned)-2]
lead.Summoned = append(lead.Summoned, agent.Member{Name: name, Status: agent.Working})
log("LEAD summons " + name)
}
// A turn passes: the blocked wake, the busy finish.
if input.Button(c, "NEXT TURN", input.ButtonOptions{Size: input.Small}).Clicked {
advance(lead.Summoned)
log("a turn passes")
}
})
})
}
// advance moves each summoned member on by a turn.
func advance(ms []agent.Member) {
for i := range ms {
switch ms[i].Status {
case agent.Blocked, agent.Idle:
ms[i].Status = agent.Working
case agent.Working:
ms[i].Status = agent.Done
}
advance(ms[i].Summoned)
}
}