293 lines
12 KiB
Go
293 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
mujica "mujicaui"
|
|
)
|
|
|
|
var chatmsgActionPages = []page{
|
|
{ID: "211", Name: "AudioMessage", ZhName: "语音消息", Category: chatmsgCategory, Keys: "Tab 到播放按钮与波形;波形上 Left/Right 跳转,Home、End 到首尾。",
|
|
Code: `r := mujica.AudioMessage(c, mujica.AudioMessageOptions{Duration: time.Minute, Position: pos})
|
|
if d, ok := r.Seek(); ok {
|
|
pos = d
|
|
}`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
type state struct {
|
|
pos time.Duration
|
|
playing bool
|
|
at time.Time
|
|
}
|
|
s := ui.Local(c.Root(), "211", func() state { return state{pos: 12 * time.Second} })
|
|
const length = 47 * time.Second
|
|
// The demo plays: it moves the position while playing.
|
|
if s.playing {
|
|
s.pos += c.Now().Sub(s.at)
|
|
if s.pos >= length {
|
|
s.pos, s.playing = length, false
|
|
log("playback ended")
|
|
}
|
|
c.After(100 * time.Millisecond)
|
|
}
|
|
s.at = c.Now()
|
|
inputSection(c, "Live", func() {
|
|
r := mujica.AudioMessage(c, mujica.AudioMessageOptions{Duration: length, Position: s.pos, Playing: s.playing, Waveform: chatmsgWave})
|
|
if r.Toggled() {
|
|
s.playing = !s.playing
|
|
if s.pos >= length {
|
|
s.pos = 0
|
|
}
|
|
log(fmt.Sprintf("playing %v", s.playing))
|
|
}
|
|
if d, ok := r.Seek(); ok {
|
|
s.pos = d
|
|
log(fmt.Sprintf("seek to %v", d.Round(time.Second)))
|
|
}
|
|
})
|
|
inputSection(c, "States", func() {
|
|
ui.Column(c).Disabled(true).Children(func() {
|
|
mujica.AudioMessage(c, mujica.AudioMessageOptions{Duration: length, Waveform: chatmsgWave})
|
|
})
|
|
})
|
|
}},
|
|
{ID: "212", Name: "LinkPreviewCard", ZhName: "链接预览卡片", Category: chatmsgCategory, Keys: "Tab 聚焦卡片,Enter 或 Space 打开。",
|
|
Code: `p := mujica.LinkPreview{URL: "https://example.com", Title: "The Palace", Site: "example.com"}
|
|
if mujica.LinkPreviewCard(c, p).Clicked() {
|
|
fmt.Println("open", p.URL)
|
|
}`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
thumb := ui.Local(c.Root(), "212", func() bool { return true })
|
|
inputSection(c, "Parameters", func() {
|
|
inputFlags(c, log, map[string]*bool{"Thumbnail": thumb}, "Thumbnail")
|
|
})
|
|
p := mujica.LinkPreview{URL: "https://example.com/versailles", Title: "The Palace of Versailles", Site: "example.com",
|
|
Description: "A royal château in the Île-de-France region, seat of the French court from 1682 until the Revolution."}
|
|
if *thumb {
|
|
p.Thumbnail = chatmsgPicture("#AD9164")
|
|
}
|
|
inputSection(c, "Live", func() {
|
|
if mujica.LinkPreviewCard(c, p).Clicked() {
|
|
log("open " + p.URL)
|
|
}
|
|
if mujica.LinkPreviewCard(c, mujica.LinkPreview{URL: "https://example.com/hall", Title: "Hall of Mirrors"}).Clicked() {
|
|
log("open https://example.com/hall")
|
|
}
|
|
})
|
|
}},
|
|
{ID: "213", Name: "QuoteReply", ZhName: "引用回复", Category: chatmsgCategory, Keys: "Tab 聚焦,Enter 或 Space 跳转。",
|
|
Code: `if mujica.QuoteReply(c, "Marie", "Let them eat cake.").Clicked() {
|
|
fmt.Println("jump to the quoted message")
|
|
}`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
inputSection(c, "Live", func() {
|
|
for _, q := range [][2]string{{"Marie", "Let them eat cake."}, {"Herald", chatmsgMarkdown}} {
|
|
if mujica.QuoteReply(c, q[0], q[1]).Clicked() {
|
|
log("jump to " + q[0] + ": " + chatmsgSnippet(q[1], 24))
|
|
}
|
|
}
|
|
})
|
|
}},
|
|
{ID: "214", Name: "MessageActions", ZhName: "消息操作", Category: chatmsgCategory, Keys: "Tab 进入工具栏,Left/Right 移动,Enter 或 Space 触发。",
|
|
Code: `if a, ok := mujica.MessageActions(c, mujica.MessageAssistant, mujica.MessageActionsOptions{Feedback: &rating}).Action(); ok {
|
|
fmt.Println("action", a)
|
|
}`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
type state struct {
|
|
role mujica.MessageRole
|
|
rating mujica.MessageFeedback
|
|
rated bool
|
|
}
|
|
s := ui.Local(c.Root(), "214", func() state { return state{role: mujica.MessageAssistant, rated: true} })
|
|
names := map[mujica.MessageAction]string{mujica.ActionCopy: "copy", mujica.ActionRegenerate: "regenerate", mujica.ActionEdit: "edit",
|
|
mujica.ActionLike: "like", mujica.ActionDislike: "dislike", mujica.ActionSpeak: "speak", mujica.ActionShare: "share"}
|
|
inputSection(c, "Parameters", func() {
|
|
roles := []mujica.RadioItem[mujica.MessageRole]{{Value: mujica.MessageUser, Label: "User"}, {Value: mujica.MessageAssistant, Label: "Assistant"},
|
|
{Value: mujica.MessageSystem, Label: "System"}, {Value: mujica.MessageError, Label: "Error"}}
|
|
if mujica.RadioGroup(c, &s.role, roles, mujica.RadioGroupOptions{Horizontal: true, Label: "Role"}).Changed() {
|
|
log(fmt.Sprintf("role %d", s.role))
|
|
}
|
|
inputFlags(c, log, map[string]*bool{"Rating": &s.rated}, "Rating")
|
|
})
|
|
inputSection(c, "Live", func() {
|
|
opts := mujica.MessageActionsOptions{}
|
|
if s.rated {
|
|
opts.Feedback = &s.rating
|
|
}
|
|
if a, ok := mujica.MessageActions(c, s.role, opts).Action(); ok {
|
|
log(fmt.Sprintf("action %s, rating %d", names[a], s.rating))
|
|
}
|
|
})
|
|
}},
|
|
{ID: "215", Name: "MessageEditor", ZhName: "消息编辑器", Category: chatmsgCategory, Keys: "Cmd/Ctrl+Enter 保存,Escape 取消。",
|
|
Code: `if mujica.MessageEditor(c, &draft, mujica.MessageEditorOptions{Original: "Hello"}).Saved() {
|
|
fmt.Println("new branch from", draft)
|
|
}`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
type state struct {
|
|
original, draft string
|
|
editing bool
|
|
branches []string
|
|
index int
|
|
}
|
|
s := ui.Local(c.Root(), "215", func() state {
|
|
return state{original: "Seat the ambassador.", branches: []string{"Seat the ambassador."}}
|
|
})
|
|
inputSection(c, "Live", func() {
|
|
if !s.editing {
|
|
mujica.MessageBubble(c, mujica.MessageUser, mujica.MessageBubbleOptions{}, func() { ui.Text(c, s.branches[s.index]) })
|
|
ui.Row(c).Gap(8).Justify(ui.End).Children(func() {
|
|
if mujica.BranchNavigator(c, &s.index, len(s.branches)).Changed() {
|
|
log(fmt.Sprintf("branch %d", s.index+1))
|
|
}
|
|
if mujica.Button(c, "Edit", mujica.ButtonOptions{Variant: mujica.Ghost}).Clicked() {
|
|
s.editing, s.draft = true, s.branches[s.index]
|
|
log("editing")
|
|
}
|
|
})
|
|
return
|
|
}
|
|
r := mujica.MessageEditor(c, &s.draft, mujica.MessageEditorOptions{Original: s.branches[s.index]})
|
|
if r.Saved() {
|
|
s.branches = append(s.branches, s.draft)
|
|
s.index, s.editing = len(s.branches)-1, false
|
|
log(fmt.Sprintf("saved as branch %d", len(s.branches)))
|
|
}
|
|
if r.Canceled() {
|
|
s.editing = false
|
|
log("edit canceled")
|
|
}
|
|
})
|
|
}},
|
|
{ID: "216", Name: "BranchNavigator", ZhName: "分支切换", Category: chatmsgCategory, Keys: "Tab 聚焦,Left/Right 切换,Home、End 到首末。",
|
|
Code: `if mujica.BranchNavigator(c, &branch, 3).Changed() {
|
|
fmt.Println("show branch", branch)
|
|
}`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
type state struct {
|
|
index int
|
|
count float64
|
|
disabled bool
|
|
}
|
|
s := ui.Local(c.Root(), "216", func() state { return state{index: 1, count: 3} })
|
|
inputSection(c, "Parameters", func() {
|
|
if mujica.NumberInput(c, &s.count, mujica.NumberInputOptions{Min: 1, Max: 9, StepperOnly: true, Label: "Branches"}).Changed() {
|
|
s.index = min(s.index, int(s.count)-1)
|
|
log(fmt.Sprintf("%v branches", s.count))
|
|
}
|
|
inputFlags(c, log, map[string]*bool{"Disabled": &s.disabled}, "Disabled")
|
|
})
|
|
inputSection(c, "Live", func() {
|
|
ui.Row(c).Disabled(s.disabled).Children(func() {
|
|
if mujica.BranchNavigator(c, &s.index, int(s.count)).Changed() {
|
|
log(fmt.Sprintf("branch %d of %v", s.index+1, s.count))
|
|
}
|
|
})
|
|
})
|
|
}},
|
|
{ID: "217", Name: "ErrorMessage", ZhName: "错误消息", Category: chatmsgCategory, Keys: "Tab 到错误文本(可选择复制)与重试按钮。",
|
|
Code: `opts := mujica.ErrorMessageOptions{RetryAt: time.Now().Add(30 * time.Second)}
|
|
if mujica.ErrorMessage(c, "429 Too Many Requests", opts).Retried() {
|
|
fmt.Println("send again")
|
|
}`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
until := ui.Local(c.Root(), "217", func() time.Time { return time.Time{} })
|
|
inputSection(c, "Parameters", func() {
|
|
ui.Row(c).Children(func() {
|
|
if mujica.Button(c, "Rate limit for 10 s", mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
|
*until = c.Now().Add(10 * time.Second)
|
|
log("rate limited for 10 s")
|
|
}
|
|
})
|
|
})
|
|
inputSection(c, "Live", func() {
|
|
opts := mujica.ErrorMessageOptions{RetryAt: *until}
|
|
if mujica.ErrorMessage(c, `429 Too Many Requests: {"type":"rate_limit_error","message":"Number of requests exceeded"}`, opts).Retried() {
|
|
log("retry")
|
|
}
|
|
if mujica.ErrorMessage(c, "dial tcp 10.0.0.7:443: connection refused", mujica.ErrorMessageOptions{Title: "Could not reach the server"}).Retried() {
|
|
log("retry connection")
|
|
}
|
|
})
|
|
}},
|
|
{ID: "218", Name: "ThinkingBlock", ZhName: "思考过程", Category: chatmsgCategory, Keys: "Tab 聚焦标题,Enter 或 Space 展开与折叠。",
|
|
Code: `mujica.ThinkingBlock(c, &open, mujica.ThinkingBlockOptions{Elapsed: 12 * time.Second}, func() {
|
|
ui.Text(c, "Weigh the guest list.")
|
|
})`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
type state struct {
|
|
open, thinking bool
|
|
started time.Time
|
|
elapsed time.Duration
|
|
}
|
|
s := ui.Local(c.Root(), "218", func() state { return state{elapsed: 14 * time.Second} })
|
|
inputSection(c, "Parameters", func() {
|
|
label := "Start thinking"
|
|
if s.thinking {
|
|
label = "Finish thinking"
|
|
}
|
|
ui.Row(c).Children(func() {
|
|
if mujica.Button(c, label, mujica.ButtonOptions{Variant: mujica.Secondary}).Clicked() {
|
|
if s.thinking {
|
|
s.elapsed = c.Now().Sub(s.started)
|
|
} else {
|
|
s.started = c.Now()
|
|
}
|
|
s.thinking = !s.thinking
|
|
log(fmt.Sprintf("thinking %v", s.thinking))
|
|
}
|
|
})
|
|
})
|
|
inputSection(c, "Live", func() {
|
|
opts := mujica.ThinkingBlockOptions{Thinking: s.thinking, Started: s.started, Elapsed: s.elapsed}
|
|
if mujica.ThinkingBlock(c, &s.open, opts, func() {
|
|
ui.Text(c, "Forty guests, thirty-six seats. The envoys outrank the counts; four counts dine in the antechamber.")
|
|
}).Changed() {
|
|
log(fmt.Sprintf("open %v", s.open))
|
|
}
|
|
})
|
|
}},
|
|
{ID: "219", Name: "ScrollToBottomButton", ZhName: "回到底部按钮", Category: chatmsgCategory, Keys: "Tab 聚焦,Enter 或 Space 回到底部。",
|
|
Code: `ui.Box(c).Fill().Children(func() {
|
|
if mujica.ScrollToBottomButton(c, !list.AtEnd(), 3).Clicked() {
|
|
list.ScrollToEnd()
|
|
}
|
|
})`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
type state struct {
|
|
away bool
|
|
unread float64
|
|
}
|
|
s := ui.Local(c.Root(), "219", func() state { return state{away: true, unread: 3} })
|
|
inputSection(c, "Parameters", func() {
|
|
inputFlags(c, log, map[string]*bool{"Away from bottom": &s.away}, "Away from bottom")
|
|
if mujica.NumberInput(c, &s.unread, mujica.NumberInputOptions{Min: 0, Max: 99, Label: "Unread"}).Changed() {
|
|
log(fmt.Sprintf("unread %v", s.unread))
|
|
}
|
|
})
|
|
inputSection(c, "Live", func() {
|
|
ui.Box(c).Height(120).Background(mujica.Tokens(c).Surface).Border(1, mujica.Tokens(c).Border).Children(func() {
|
|
if mujica.ScrollToBottomButton(c, s.away, int(s.unread)).Clicked() {
|
|
s.away, s.unread = false, 0
|
|
log("back to the bottom")
|
|
}
|
|
})
|
|
})
|
|
}},
|
|
{ID: "220", Name: "TypingIndicator", ZhName: "正在输入", Category: chatmsgCategory, Keys: "不获取焦点;减少动画时显示静态文字。",
|
|
Code: `mujica.TypingIndicator(c, "Herald")`,
|
|
Demo: func(c *ui.Context, log func(string)) {
|
|
typing := ui.Local(c.Root(), "220", func() bool { return true })
|
|
inputSection(c, "Parameters", func() {
|
|
inputFlags(c, log, map[string]*bool{"Typing": typing}, "Typing")
|
|
})
|
|
inputSection(c, "Live", func() {
|
|
if *typing {
|
|
mujica.TypingIndicator(c, "Herald")
|
|
}
|
|
})
|
|
}},
|
|
}
|