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 } } } }) }) }