wip chat sessions 2
This commit is contained in:
92 files changed
+3692
-22
No files matched your search
@@ -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)`},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user