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,213 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user