Merge branch 'worktree-agent-ac0c9e7144c57cf07'

This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 14:38:04 -04:00
commit 959d19ec19
106 files changed
+6122 -1

No files matched your search

+184 -1
View File
@@ -1,3 +1,186 @@
package main
var chatsessionPages = []page{}
const chatsessionCategory = "Chat Sessions"
var chatsessionPages = []page{
{ID: "241", Name: "ConversationList", ZhName: "会话列表", Category: chatsessionCategory, Demo: chatDemoConversationList,
Keys: "Up/Down choose a conversation, skipping group headers; typing jumps; Enter opens.", Code: `var state mujica.ConversationListState
convs := []mujica.ChatConversation{
{ID: "1", Title: "Court etiquette", Updated: time.Now()},
{ID: "2", Title: "Royal letters", Updated: time.Now().AddDate(0, 0, -30), Pinned: true},
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ConversationList(c, &state, convs, mujica.ConversationListOptions{Label: "History"}, nil).Changed() {
log.Println("open", state.Selected)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "242", Name: "ConversationItem", ZhName: "会话条目", Category: chatsessionCategory, Demo: chatDemoConversationItem,
Keys: "Enter opens; F2 renames in place (Enter keeps, Escape cancels); Delete asks to delete; the ellipsis opens the menu.", Code: `conv := mujica.ChatConversation{ID: "1", Title: "Court etiquette", Updated: time.Now()}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
v := mujica.ConversationItem(c, &conv, mujica.ConversationItemOptions{})
if v.Deleted {
log.Println("delete", conv.ID)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "243", Name: "ConversationSearch", ZhName: "会话搜索", Category: chatsessionCategory, Demo: chatDemoConversationSearch,
Keys: "Type to search; Down moves into the results; Up/Down move; Enter opens; Escape clears.", Code: `query := "seat"
hits := []mujica.ConversationSearchHit{{ID: "1", Title: "Ballroom seating plan"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
v := mujica.ConversationSearch(c, &query, hits, mujica.ConversationSearchOptions{})
if v.QueryChanged {
log.Println("search", query)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "244", Name: "ProjectList", ZhName: "项目列表", Category: chatsessionCategory, Demo: chatDemoProjectList,
Keys: "Up/Down move through folders, projects and conversations; Right/Left show or hide a project's conversations; the ellipsis menu moves a conversation.", Code: `var state mujica.ProjectListState
projects := []mujica.ChatProject{{ID: "p1", Name: "Coronation", Folder: "Court"}}
convs := func(id string) []mujica.ChatConversation {
return []mujica.ChatConversation{{ID: "1", Title: "Menu"}}
}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.ProjectList(c, &state, projects, convs, mujica.ProjectListOptions{}); v.Moved {
log.Println("move", v.MoveID, "to", v.MoveTo)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "245", Name: "ProjectKnowledgePanel", ZhName: "项目知识面板", Category: chatsessionCategory, Demo: chatDemoProjectKnowledgePanel,
Keys: "Tab reaches Add files and the file list; Up/Down move between remove buttons; Enter removes.", Code: `files := []mujica.KnowledgeFile{{ID: "1", Name: "etiquette.pdf", Tokens: 12000}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
v := mujica.ProjectKnowledgePanel(c, files, mujica.ProjectKnowledgePanelOptions{Capacity: 200000})
if v.Add {
log.Println("pick files")
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "246", Name: "WelcomeScreen", ZhName: "欢迎页", Category: chatsessionCategory, Demo: chatDemoWelcomeScreen,
Keys: "Tab moves through the cards' examples and the prompt slot; Enter picks.", Code: `cards := []mujica.CapabilityCard{{Title: "Write", Examples: []string{"Draft a decree"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.WelcomeScreen(c, mujica.WelcomeScreenOptions{Greeting: "Good evening", Cards: cards}); v.Example != "" {
log.Println("ask", v.Example)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "247", Name: "SuggestionChips", ZhName: "建议标签", Category: chatsessionCategory, Demo: chatDemoSuggestionChips,
Keys: "Tab enters the chips; Left/Right move; Enter or Space picks.", Code: `input := ""
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.SuggestionChips(c, &input, []string{"Plan a ball", "Write a sonnet"}, mujica.SuggestionChipsOptions{})
}
ui.Render(view, 400, 300, 1)`},
{ID: "248", Name: "PromptLibrary", ZhName: "提示模板库", Category: chatsessionCategory, Demo: chatDemoPromptLibrary,
Keys: "Tab moves between categories, search, templates and fields; Up/Down move in a column; Enter picks.", Code: `var state mujica.PromptLibraryState
templates := []mujica.PromptTemplate{{ID: "1", Category: "Letters", Title: "Invitation", Body: "Invite {{guest}}."}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.PromptLibrary(c, &state, templates, mujica.PromptLibraryOptions{}); v.Inserted != "" {
log.Println("insert", v.Inserted)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "249", Name: "CapabilityCards", ZhName: "能力卡片", Category: chatsessionCategory, Demo: chatDemoCapabilityCards,
Keys: "Tab moves between cards; Up/Down move among a card's examples; Enter picks.", Code: `cards := []mujica.CapabilityCard{{Title: "Plan", Icon: icons.Must("calendar"), Examples: []string{"Plan a ball"}}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.CapabilityCards(c, cards, mujica.CapabilityCardsOptions{}); v.Example != "" {
log.Println("ask", v.Example)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "250", Name: "FeedbackForm", ZhName: "反馈表单", Category: chatsessionCategory, Demo: chatDemoFeedbackForm,
Keys: "Left/Right move among reasons, Space toggles; Escape cancels.", Code: `var value mujica.FeedbackValue
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.FeedbackForm(c, &value, mujica.FeedbackFormOptions{Rating: mujica.FeedbackDown}).Submitted {
log.Println("reasons", value.Reasons)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "251", Name: "DateSeparator", ZhName: "日期分隔", Category: chatsessionCategory, Demo: chatDemoDateSeparator,
Keys: "Not focusable; read as the date.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.DateSeparator(c, mujica.DateOf(time.Now()), mujica.DateSeparatorOptions{})
}
ui.Render(view, 400, 300, 1)`},
{ID: "252", Name: "ThinkingIndicator", ZhName: "思考指示", Category: chatsessionCategory, Demo: chatDemoThinkingIndicator,
Keys: "Not focusable; read as a status.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.ThinkingIndicator(c, mujica.ThinkingIndicatorOptions{Phase: "Searching the archives"})
}
ui.Render(view, 400, 300, 1)`},
{ID: "253", Name: "StopGeneratingButton", ZhName: "停止生成按钮", Category: chatsessionCategory, Demo: chatDemoStopGeneratingButton,
Keys: "Enter or Space stops; Cmd+. (Ctrl+. elsewhere) stops from anywhere.", Code: `generating := true
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.StopGeneratingButton(c, mujica.StopGeneratingButtonOptions{Generating: generating}).Changed() {
generating = false
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "254", Name: "SystemPromptEditor", ZhName: "系统提示编辑器", Category: chatsessionCategory, Demo: chatDemoSystemPromptEditor,
Keys: "Cmd+S (Ctrl+S elsewhere) saves.", Code: `prompt, saved := "You are helpful.", "You are helpful."
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SystemPromptEditor(c, &prompt, mujica.SystemPromptEditorOptions{Default: "You are helpful.", Saved: saved}).Save {
saved = prompt
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "255", Name: "CostEstimator", ZhName: "费用估算", Category: chatsessionCategory, Demo: chatDemoCostEstimator,
Keys: "Not focusable; read as the estimated cost.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.CostEstimator(c, mujica.CostEstimatorOptions{InputTokens: 12000, OutputTokens: 3400, InputPrice: 3, OutputPrice: 15})
}
ui.Render(view, 400, 300, 1)`},
{ID: "256", Name: "SharedConversationView", ZhName: "分享会话视图", Category: chatsessionCategory, Demo: chatDemoSharedConversationView,
Keys: "Tab reaches Copy link and the messages; message text is selectable.", Code: `msgs := []mujica.SessionMessage{{Role: mujica.SessionUser, Text: "Hello"}, {Role: mujica.SessionAssistant, Text: "Good day."}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.SharedConversationView(c, msgs, mujica.SharedConversationViewOptions{Title: "Greeting", SharedBy: "Ann"}).Changed() {
c.WriteClipboard("https://example.com/share/1")
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "257", Name: "ConversationExport", ZhName: "会话导出", Category: chatsessionCategory, Demo: chatDemoConversationExport,
Keys: "Arrows choose a format and range; Tab reaches Export.", Code: `var state mujica.ConversationExportState
msgs := []mujica.SessionMessage{{Role: mujica.SessionUser, Text: "Hello"}}
view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.ConversationExport(c, &state, msgs, mujica.ConversationExportOptions{Title: "Greeting"}); v.Exported {
log.Println(len(v.Content), "bytes to save")
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "258", Name: "RegenerateMenu", ZhName: "重新生成菜单", Category: chatsessionCategory, Demo: chatDemoRegenerateMenu,
Keys: "Enter, Space or Down open; Up/Down move; Right opens the model list; Escape closes.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if v := mujica.RegenerateMenu(c, mujica.RegenerateMenuOptions{Models: []string{"Opus", "Sonnet"}, Current: "Opus"}); v.Chosen {
log.Println("retry", v.Choice.Kind, v.Choice.Model, v.Choice.Variant)
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "259", Name: "ArtifactCard", ZhName: "产物卡片", Category: chatsessionCategory, Demo: chatDemoArtifactCard,
Keys: "Enter or Space opens the artifact panel.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ArtifactCard(c, mujica.ArtifactCardOptions{Title: "seating.go", Detail: "Go · 120 lines"}).Changed() {
log.Println("open artifact panel")
}
}
ui.Render(view, 400, 300, 1)`},
{ID: "260", Name: "ChatEmptyState", ZhName: "会话空状态", Category: chatsessionCategory, Demo: chatDemoChatEmptyState,
Keys: "Tab reaches the action; Enter runs it.", Code: `view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
if mujica.ChatEmptyState(c, mujica.ChatEmptyStateOptions{Kind: mujica.ChatEmptyOffline}).Action {
log.Println("reconnect")
}
}
ui.Render(view, 400, 300, 1)`},
}
+249
View File
@@ -0,0 +1,249 @@
package main
import (
"fmt"
"strings"
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
"mujicaui/icons"
)
// chatHistory is the gallery's sample conversations, dated from now.
func chatHistory(now time.Time) []mujica.ChatConversation {
day := 24 * time.Hour
return []mujica.ChatConversation{
{ID: "a", Title: "Court etiquette notes", Updated: now.Add(-2 * time.Hour)},
{ID: "b", Title: "Ballroom seating plan", Updated: now.Add(-day)},
{ID: "c", Title: "Harpsichord repair", Updated: now.Add(-4 * day)},
{ID: "d", Title: "Coronation menu", Updated: now.Add(-40 * day)},
{ID: "e", Title: "Royal letters", Updated: now.Add(-90 * day), Pinned: true},
{ID: "f", Title: "Garden maze", Updated: now.Add(-time.Hour)},
}
}
func chatSampleMessages(now time.Time) []mujica.SessionMessage {
return []mujica.SessionMessage{
{Role: mujica.SessionUser, Text: "How should guests be seated at the banquet?", Time: now.Add(-10 * time.Minute)},
{Role: mujica.SessionAssistant, Text: "Seat by rank: the host at the head, honored guests to the right.", Time: now.Add(-9 * time.Minute)},
{Role: mujica.SessionUser, Author: "Lady Ann", Text: "And the musicians?", Time: now.Add(-5 * time.Minute)},
{Role: mujica.SessionAssistant, Text: "Place the quartet behind a screen near the gallery.", Time: now.Add(-4 * time.Minute)},
}
}
func chatSampleCards() []mujica.CapabilityCard {
return []mujica.CapabilityCard{
{Title: "Write", Description: "Letters, speeches and verse.", Icon: icons.Must("file-text"), Examples: []string{"Draft a royal decree", "Write a sonnet"}},
{Title: "Plan", Description: "Banquets, seating and schedules.", Icon: icons.Must("calendar"), Examples: []string{"Plan a ball for 200 guests"}},
{Title: "Research", Description: "History, heraldry and music.", Icon: icons.Must("search"), Examples: []string{"Explain the minuet"}},
}
}
func chatDemoConversationList(c *ui.Context, log func(string)) {
type state struct {
list mujica.ConversationListState
items []mujica.ChatConversation
many, rich bool
disabled bool
}
s := *ui.Local(c.Root(), "241", func() *state { return &state{items: chatHistory(time.Now())} })
choiceDemo(c, func() {
if mujica.Switch(c, &s.many, "100,000 conversations", mujica.SwitchOptions{}).Changed() {
s.items = chatHistory(time.Now())
if s.many {
now := time.Now()
for i := range 100000 {
s.items = append(s.items, mujica.ChatConversation{ID: fmt.Sprint("n", i), Title: fmt.Sprintf("Archive %d", i), Updated: now.Add(-time.Duration(i) * 7 * time.Minute)})
}
}
log(fmt.Sprintf("conversations = %d", len(s.items)))
}
choiceFlag(c, &s.rich, "ConversationItem rows", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
var row func(conv mujica.ChatConversation)
if s.rich {
row = func(conv mujica.ChatConversation) {
v := mujica.ConversationItem(c, &conv, mujica.ConversationItemOptions{Selected: conv.ID == s.list.Selected})
if v.Opened {
s.list.Selected = conv.ID
log("open " + conv.Title)
}
}
}
v := mujica.ConversationList(c, &s.list, s.items, mujica.ConversationListOptions{Label: "History", Disabled: s.disabled}, row)
v.Element.Width(380).Height(380)
if v.Changed() {
log("select " + s.list.Selected)
}
if v.Submitted() {
log("open " + s.list.Selected)
}
})
}
func chatDemoConversationItem(c *ui.Context, log func(string)) {
type state struct {
convs []mujica.ChatConversation
selected, disabled bool
}
s := *ui.Local(c.Root(), "242", func() *state { return &state{convs: chatHistory(time.Now())[:3]} })
choiceDemo(c, func() {
choiceFlag(c, &s.selected, "Selected", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
ui.Column(c).Width(420).Gap(4).Children(func() {
for i := range s.convs {
conv := &s.convs[i]
ui.Row(c).Key(conv.ID).Children(func() {
v := mujica.ConversationItem(c, conv, mujica.ConversationItemOptions{Selected: s.selected && i == 0, Disabled: s.disabled})
switch {
case v.Opened:
log("open " + conv.Title)
case v.Renamed:
log("rename to " + conv.Title)
case v.PinToggled:
conv.Pinned = !conv.Pinned
log(fmt.Sprintf("pinned %s = %v", conv.Title, conv.Pinned))
case v.Exported:
log("export " + conv.Title)
case v.Deleted:
log("delete " + conv.Title)
}
})
}
})
})
}
func chatDemoConversationSearch(c *ui.Context, log func(string)) {
type state struct {
query, asked string
at time.Time
hits []mujica.ConversationSearchHit
fail, slow bool
disabled bool
}
s := *ui.Local(c.Root(), "243", func() *state { return &state{} })
choiceDemo(c, func() {
choiceFlag(c, &s.slow, "Slow server", log)
choiceFlag(c, &s.fail, "Fail searches", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
loading := s.asked != s.query
if loading && (!s.slow || choiceDue(c, s.at)) {
s.asked, s.hits, loading = s.query, nil, false
for _, conv := range chatHistory(time.Now()) {
if q := strings.ToLower(s.query); q != "" && strings.Contains(strings.ToLower(conv.Title), q) {
s.hits = append(s.hits, mujica.ConversationSearchHit{ID: conv.ID, Title: conv.Title, Snippet: "…mentions " + strings.ToLower(conv.Title) + "…"})
}
}
log(fmt.Sprintf("results for %q: %d", s.query, len(s.hits)))
}
errText := ""
if s.fail && s.query != "" {
errText = "The archive did not answer."
}
ui.Column(c).Width(420).Children(func() {
v := mujica.ConversationSearch(c, &s.query, s.hits, mujica.ConversationSearchOptions{Loading: loading, Error: errText, Disabled: s.disabled, Label: "Search conversations"})
if v.QueryChanged {
s.at = c.Now()
log(fmt.Sprintf("query %q", s.query))
}
if v.Chosen != "" {
log("open " + v.Chosen)
}
if v.Retry {
s.fail, s.asked = false, ""
log("retry")
}
})
})
}
func chatDemoProjectList(c *ui.Context, log func(string)) {
type state struct {
list mujica.ProjectListState
owner map[string]string
disabled bool
}
s := *ui.Local(c.Root(), "244", func() *state {
return &state{owner: map[string]string{"a": "p1", "b": "p1", "c": "p3", "f": "p2"}}
})
projects := []mujica.ChatProject{{ID: "p1", Name: "Coronation", Folder: "Court"}, {ID: "p2", Name: "Gardens", Folder: "Court"}, {ID: "p3", Name: "Music"}}
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
ui.Row(c).Gap(24).AlignItems(ui.Start).Children(func() {
ui.Column(c).Width(380).Children(func() {
convs := func(id string) []mujica.ChatConversation {
var out []mujica.ChatConversation
for _, conv := range chatHistory(time.Now()) {
if s.owner[conv.ID] == id {
out = append(out, conv)
}
}
return out
}
v := mujica.ProjectList(c, &s.list, projects, convs, mujica.ProjectListOptions{Label: "Projects", Disabled: s.disabled})
switch {
case v.Moved:
s.owner[v.MoveID] = v.MoveTo
log(fmt.Sprintf("move %s to %s", v.MoveID, v.MoveTo))
case v.Changed:
log("project " + s.list.Selected)
case v.Opened != "":
log("open " + v.Opened)
}
})
choiceLabeled(c, "Drag onto a project", func() {
conv := chatHistory(time.Now())[3]
v := mujica.ConversationItem(c, &conv, mujica.ConversationItemOptions{Draggable: true})
v.Element.Width(300)
if v.Opened {
log("open " + conv.Title)
}
})
})
})
}
func chatDemoProjectKnowledgePanel(c *ui.Context, log func(string)) {
type state struct {
files []mujica.KnowledgeFile
next int
small bool
disabled bool
}
s := *ui.Local(c.Root(), "245", func() *state {
return &state{files: []mujica.KnowledgeFile{{ID: "1", Name: "etiquette.pdf", Tokens: 120000}, {ID: "2", Name: "menu.md", Tokens: 30000}}}
})
choiceDemo(c, func() {
choiceFlag(c, &s.small, "Capacity 150,000", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
capacity := 400000
if s.small {
capacity = 150000
}
ui.Column(c).Width(440).Children(func() {
v := mujica.ProjectKnowledgePanel(c, s.files, mujica.ProjectKnowledgePanelOptions{Capacity: capacity, Disabled: s.disabled})
if v.Add {
s.next++
s.files = append(s.files, mujica.KnowledgeFile{ID: fmt.Sprint("n", s.next), Name: fmt.Sprintf("notes-%d.txt", s.next), Tokens: 15000})
log("add (the demo invents a file)")
}
if v.Removed != "" {
for i, f := range s.files {
if f.ID == v.Removed {
s.files = append(s.files[:i:i], s.files[i+1:]...)
log("remove " + f.Name)
break
}
}
}
})
})
}
+341
View File
@@ -0,0 +1,341 @@
package main
import (
"fmt"
"time"
"github.com/egoist/mygo/ui"
mujica "mujicaui"
)
func chatDemoWelcomeScreen(c *ui.Context, log func(string)) {
type state struct {
input string
cards, disabled bool
}
s := *ui.Local(c.Root(), "246", func() *state { return &state{cards: true} })
choiceDemo(c, func() {
choiceFlag(c, &s.cards, "Capability cards", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
var cards []mujica.CapabilityCard
if s.cards {
cards = chatSampleCards()
}
ui.Column(c).Width(860).Children(func() {
v := mujica.WelcomeScreen(c, mujica.WelcomeScreenOptions{Greeting: "Good afternoon", Subtitle: "How may I serve the court?", Cards: cards, Disabled: s.disabled,
Prompts: func() {
if mujica.SuggestionChips(c, &s.input, []string{"Plan a ball", "Write a sonnet"}, mujica.SuggestionChipsOptions{Disabled: s.disabled}).Chosen != "" {
log(fmt.Sprintf("fill %q", s.input))
}
}})
if v.Example != "" {
log(fmt.Sprintf("ask %q", v.Example))
}
})
})
}
func chatDemoSuggestionChips(c *ui.Context, log func(string)) {
type state struct {
input string
send, disabled bool
}
s := *ui.Local(c.Root(), "247", func() *state { return &state{} })
choiceDemo(c, func() {
choiceFlag(c, &s.send, "Send directly", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
v := mujica.SuggestionChips(c, &s.input, []string{"Plan a ball", "Write a sonnet", "Explain the minuet"},
mujica.SuggestionChipsOptions{Send: s.send, Label: "Suggestions", Disabled: s.disabled})
switch {
case v.Sent:
log(fmt.Sprintf("send %q", v.Chosen))
case v.Chosen != "":
log(fmt.Sprintf("fill %q", v.Chosen))
}
ui.Column(c).Width(420).Children(func() {
mujica.TextInput(c, &s.input, mujica.TextInputOptions{Label: "Message", Placeholder: "Message"})
})
})
}
func chatDemoPromptLibrary(c *ui.Context, log func(string)) {
type state struct {
lib mujica.PromptLibraryState
disabled bool
}
s := *ui.Local(c.Root(), "248", func() *state { return &state{} })
templates := []mujica.PromptTemplate{
{ID: "t1", Category: "Letters", Title: "Invitation", Body: "Write an invitation to {{guest}} for the {{event}}."},
{ID: "t2", Category: "Letters", Title: "Thank-you note", Body: "Thank {{host}} for the evening."},
{ID: "t3", Category: "Music", Title: "Recital program", Body: "List pieces for a {{instrument}} recital."},
{ID: "t4", Category: "Planning", Title: "Seating chart", Body: "Seat {{count}} guests by rank."},
}
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
before := s.lib.Selected
v := mujica.PromptLibrary(c, &s.lib, templates, mujica.PromptLibraryOptions{Label: "Prompt library", Disabled: s.disabled})
if s.lib.Selected != before {
log("preview " + s.lib.Selected)
}
if v.Inserted != "" {
log(fmt.Sprintf("insert %q", v.Inserted))
}
})
}
func chatDemoCapabilityCards(c *ui.Context, log func(string)) {
type state struct {
one, disabled bool
}
s := *ui.Local(c.Root(), "249", func() *state { return &state{} })
choiceDemo(c, func() {
choiceFlag(c, &s.one, "One column", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
cols := 0
if s.one {
cols = 1
}
ui.Column(c).Width(860).Children(func() {
if v := mujica.CapabilityCards(c, chatSampleCards(), mujica.CapabilityCardsOptions{Columns: cols, Disabled: s.disabled}); v.Example != "" {
log(fmt.Sprintf("ask %q", v.Example))
}
})
})
}
func chatDemoFeedbackForm(c *ui.Context, log func(string)) {
type state struct {
value mujica.FeedbackValue
down, submitting bool
disabled bool
at time.Time
}
s := *ui.Local(c.Root(), "250", func() *state { return &state{down: true} })
choiceDemo(c, func() {
choiceFlag(c, &s.down, "Thumbs down", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
rating := mujica.FeedbackUp
if s.down {
rating = mujica.FeedbackDown
}
if s.submitting && choiceDue(c, s.at) {
s.submitting, s.value = false, mujica.FeedbackValue{}
log("feedback sent")
}
ui.Column(c).Width(480).Children(func() {
v := mujica.FeedbackForm(c, &s.value, mujica.FeedbackFormOptions{Rating: rating, Submitting: s.submitting, Disabled: s.disabled})
if v.Submitted {
s.submitting, s.at = true, c.Now()
log(fmt.Sprintf("submit reasons %q, %d characters", s.value.Reasons, len(s.value.Comment)))
}
if v.Cancelled {
s.value = mujica.FeedbackValue{}
log("cancel")
}
})
})
}
func chatDemoDateSeparator(c *ui.Context, log func(string)) {
today := mujica.DateOf(time.Now())
choiceDemo(c, func() {
ui.Text(c, "No parameters; the language follows the window.")
}, func() {
ui.Column(c).Width(520).Children(func() {
for _, d := range []int{0, -1, -12} {
mujica.DateSeparator(c, today.AddDays(d), mujica.DateSeparatorOptions{})
}
})
})
}
func chatDemoThinkingIndicator(c *ui.Context, log func(string)) {
type state struct{ phase int }
s := *ui.Local(c.Root(), "252", func() *state { return &state{} })
phases := []string{"", "Searching the archives", "Reading 3 letters", "Composing a reply"}
choiceDemo(c, func() {
if mujica.Button(c, "Next phase", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
s.phase = (s.phase + 1) % len(phases)
log(fmt.Sprintf("phase %q", phases[s.phase]))
}
}, func() {
mujica.ThinkingIndicator(c, mujica.ThinkingIndicatorOptions{Phase: phases[s.phase]})
})
}
func chatDemoStopGeneratingButton(c *ui.Context, log func(string)) {
type state struct {
generating, disabled bool
at time.Time
}
s := *ui.Local(c.Root(), "253", func() *state { return &state{} })
choiceDemo(c, func() {
if mujica.Button(c, "Generate", mujica.ButtonOptions{Disabled: s.generating}).Clicked() {
s.generating, s.at = true, c.Now()
log("generate")
}
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
if s.generating && c.Now().Sub(s.at) > 8*time.Second {
s.generating = false
log("finished")
} else if s.generating {
c.After(time.Second)
}
if mujica.StopGeneratingButton(c, mujica.StopGeneratingButtonOptions{Generating: s.generating, Disabled: s.disabled}).Changed() {
s.generating = false
log("stop")
}
if !s.generating {
ui.Text(c, "Idle: the button is hidden.").TextColor(mujica.Tokens(c).TextMuted)
}
})
}
func chatDemoSystemPromptEditor(c *ui.Context, log func(string)) {
type state struct {
value, saved string
limit, disabled bool
saving bool
at time.Time
}
s := *ui.Local(c.Root(), "254", func() *state {
return &state{value: "You are a courtly assistant.", saved: "You are a courtly assistant."}
})
choiceDemo(c, func() {
choiceFlag(c, &s.limit, "Limit 60", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
if s.saving && choiceDue(c, s.at) {
s.saving, s.saved = false, s.value
log("saved")
}
limit := 0
if s.limit {
limit = 60
}
ui.Column(c).Width(520).Children(func() {
v := mujica.SystemPromptEditor(c, &s.value, mujica.SystemPromptEditorOptions{Default: "You are helpful.", Saved: s.saved,
MaxLength: limit, Saving: s.saving, Disabled: s.disabled})
if v.Save {
s.saving, s.at = true, c.Now()
log(fmt.Sprintf("save %d characters", len(s.value)))
}
if v.Restored {
log("restore default")
}
})
})
}
func chatDemoCostEstimator(c *ui.Context, log func(string)) {
type state struct{ in, out, inPrice, outPrice float64 }
s := *ui.Local(c.Root(), "255", func() *state { return &state{in: 12000, out: 3400, inPrice: 3, outPrice: 15} })
choiceDemo(c, func() {
for _, f := range []struct {
v *float64
label string
}{{&s.in, "Input tokens"}, {&s.out, "Output tokens"}, {&s.inPrice, "Input $/M"}, {&s.outPrice, "Output $/M"}} {
choiceLabeled(c, f.label, func() {
r := mujica.NumberInput(c, f.v, mujica.NumberInputOptions{Min: 0, Max: 1e7, Step: 100, Label: f.label})
r.Element.Width(150)
if r.Changed() {
log(fmt.Sprintf("%s = %v", f.label, *f.v))
}
})
}
}, func() {
ui.Column(c).Width(420).Children(func() {
mujica.CostEstimator(c, mujica.CostEstimatorOptions{InputTokens: int(s.in), OutputTokens: int(s.out), InputPrice: s.inPrice, OutputPrice: s.outPrice})
})
})
}
func chatDemoSharedConversationView(c *ui.Context, log func(string)) {
type state struct{ disabled bool }
s := *ui.Local(c.Root(), "256", func() *state { return &state{} })
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
ui.Column(c).Width(600).Height(420).Children(func() {
if mujica.SharedConversationView(c, chatSampleMessages(time.Now()), mujica.SharedConversationViewOptions{Title: "Banquet seating",
SharedBy: "Lady Ann", SharedAt: time.Now(), Disabled: s.disabled}).Changed() {
c.WriteClipboard("https://example.com/share/banquet")
log("copy link")
}
})
})
}
func chatDemoConversationExport(c *ui.Context, log func(string)) {
type state struct {
export mujica.ConversationExportState
disabled bool
}
s := *ui.Local(c.Root(), "257", func() *state { return &state{} })
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
ui.Column(c).Width(580).Children(func() {
v := mujica.ConversationExport(c, &s.export, chatSampleMessages(time.Now()), mujica.ConversationExportOptions{Title: "Banquet seating",
Selected: []int{1, 3}, Disabled: s.disabled})
if v.Exported {
log(fmt.Sprintf("export %d bytes as format %d (the caller writes the file)", len(v.Content), v.Format))
}
})
})
}
func chatDemoRegenerateMenu(c *ui.Context, log func(string)) {
type state struct{ disabled bool }
s := *ui.Local(c.Root(), "258", func() *state { return &state{} })
choiceDemo(c, func() {
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
v := mujica.RegenerateMenu(c, mujica.RegenerateMenuOptions{Models: []string{"Opus", "Sonnet", "Haiku"}, Current: "Opus", Disabled: s.disabled})
if v.Chosen {
log(fmt.Sprintf("retry kind %d model %q variant %q", v.Choice.Kind, v.Choice.Model, v.Choice.Variant))
}
})
}
func chatDemoArtifactCard(c *ui.Context, log func(string)) {
type state struct{ streaming, disabled bool }
s := *ui.Local(c.Root(), "259", func() *state { return &state{} })
choiceDemo(c, func() {
choiceFlag(c, &s.streaming, "Streaming", log)
choiceFlag(c, &s.disabled, "Disabled", log)
}, func() {
for _, a := range []mujica.ArtifactCardOptions{
{Kind: mujica.ArtifactCode, Title: "seating.go", Detail: "Go · 120 lines"},
{Kind: mujica.ArtifactDocument, Title: "Royal decree", Detail: "2 pages"},
{Kind: mujica.ArtifactWeb, Title: "Ball invitation"},
} {
a.Streaming, a.Disabled = s.streaming, s.disabled
if mujica.ArtifactCard(c, a).Changed() {
log("open " + a.Title)
}
}
})
}
func chatDemoChatEmptyState(c *ui.Context, log func(string)) {
type state struct{ kind int }
s := *ui.Local(c.Root(), "260", func() *state { return &state{} })
choiceDemo(c, func() {
mujica.RadioGroup(c, &s.kind, []mujica.RadioItem[int]{{Value: 0, Label: "New"}, {Value: 1, Label: "No results"}, {Value: 2, Label: "Offline"}},
mujica.RadioGroupOptions{Horizontal: true, Label: "Kind"})
}, func() {
ui.Column(c).Width(420).Children(func() {
if mujica.ChatEmptyState(c, mujica.ChatEmptyStateOptions{Kind: mujica.ChatEmptyKind(s.kind), Query: "minuet"}).Action {
log(fmt.Sprintf("action for kind %d", s.kind))
}
})
})
}