123 lines
4.2 KiB
Go
123 lines
4.2 KiB
Go
package mujica
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"mujicaui/icons"
|
|
"mujicaui/internal"
|
|
"mujicaui/theme"
|
|
)
|
|
|
|
// KnowledgeFile is a project knowledge file, sized in tokens.
|
|
type KnowledgeFile struct {
|
|
ID, Name string
|
|
Tokens int
|
|
}
|
|
|
|
// ProjectKnowledgePanelOptions configure a ProjectKnowledgePanel. Capacity is required.
|
|
type ProjectKnowledgePanelOptions struct {
|
|
// Capacity is how many tokens the project's knowledge holds.
|
|
Capacity int
|
|
// Title replaces the built-in "Project knowledge".
|
|
Title string
|
|
Disabled bool
|
|
}
|
|
|
|
// ProjectKnowledgePanelView is a built panel and its requests.
|
|
type ProjectKnowledgePanelView struct {
|
|
Element *ui.Element
|
|
// Add asks the caller to pick files to add.
|
|
Add bool
|
|
// Removed is the ID of a file to remove.
|
|
Removed string
|
|
}
|
|
|
|
// ProjectKnowledgePanel lists knowledge files with add, remove and usage.
|
|
func ProjectKnowledgePanel(c *ui.Context, files []KnowledgeFile, opts ProjectKnowledgePanelOptions) ProjectKnowledgePanelView {
|
|
if opts.Capacity <= 0 {
|
|
panic("mujica: ProjectKnowledgePanel needs a positive Capacity")
|
|
}
|
|
used := 0
|
|
for _, f := range files {
|
|
if f.Tokens < 0 {
|
|
panic("mujica: KnowledgeFile with negative Tokens")
|
|
}
|
|
used += f.Tokens
|
|
}
|
|
k := Tokens(c)
|
|
d := Density(c)
|
|
col := ui.Column(c).Gap(d.Unit()*2).Padding(d.Unit()*3).Background(k.Surface).Border(theme.BorderWidth, k.Border).
|
|
Radius(theme.CardRadius).Disabled(opts.Disabled)
|
|
v := ProjectKnowledgePanelView{Element: col}
|
|
col.Children(func() {
|
|
ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Center).Children(func() {
|
|
title := opts.Title
|
|
if title == "" {
|
|
title = msg(c, "project_knowledge_panel.title")
|
|
}
|
|
ui.Text(c, title).Font(theme.SerifFont).FontSize(FontSize(c, theme.H3Size)).TextColor(k.Text).Grow(1)
|
|
v.Add = Button(c, msg(c, "project_knowledge_panel.add"), ButtonOptions{Variant: Secondary, Icon: icons.Must("plus"), Disabled: opts.Disabled}).Clicked()
|
|
})
|
|
ui.Box(c).FillWidth().Height(9).Draw(func(p *ui.Painter, r ui.Rect) {
|
|
internal.RuleDiamond(p, r, 6, 5, k.Ornament)
|
|
})
|
|
projectKnowledgeMeter(c, k, used, opts.Capacity)
|
|
if len(files) == 0 {
|
|
ui.Text(c, msg(c, "project_knowledge_panel.empty")).TextColor(k.TextMuted).Padding(d.Unit()*2, 0)
|
|
return
|
|
}
|
|
ui.Column(c).Gap(2).FocusGroup(ui.Vertical).Children(func() {
|
|
for _, f := range files {
|
|
ui.Row(c).Key(f.ID).MinHeight(d.ControlHeight()).Gap(d.Unit()*2).AlignItems(ui.Center).Padding(0, d.Unit()).Children(func() {
|
|
ui.Icon(c, icons.Must("file-text")).Size(16, 16).TextColor(k.TextMuted)
|
|
ui.Text(c, f.Name).SingleLine().Grow(1).MinWidth(0)
|
|
ui.Text(c, fmt.Sprintf(msg(c, "project_knowledge_panel.tokens"), f.Tokens)).
|
|
FontSize(FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
|
|
x := iconAction(c, icons.Must("x"), fmt.Sprintf(msg(c, "project_knowledge_panel.remove"), f.Name))
|
|
if x.Clicked() && !x.IsDisabled() {
|
|
v.Removed = f.ID
|
|
}
|
|
})
|
|
}
|
|
})
|
|
})
|
|
if v.Add || v.Removed != "" {
|
|
slog.Debug("mujica: project_knowledge_panel action", "add", v.Add, "remove", v.Removed)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// projectKnowledgeMeter shows usage; limits get words and icons.
|
|
func projectKnowledgeMeter(c *ui.Context, k theme.Tokens, used, capacity int) {
|
|
d := Density(c)
|
|
frac := float64(used) / float64(capacity)
|
|
ink, note, icon := k.AccentText, "", ""
|
|
switch {
|
|
case used > capacity:
|
|
ink, note, icon = k.Danger, msg(c, "project_knowledge_panel.over"), "circle-alert"
|
|
case frac >= 0.8:
|
|
ink, note, icon = k.Warning, msg(c, "project_knowledge_panel.near"), "triangle-alert"
|
|
}
|
|
pct := int(frac * 100)
|
|
label := fmt.Sprintf(msg(c, "project_knowledge_panel.usage"), used, capacity, pct)
|
|
ui.Column(c).Gap(d.Unit()).Role(ui.RoleMeter).Label(label).Children(func() {
|
|
ui.Box(c).FillWidth().Height(6).Draw(func(p *ui.Painter, r ui.Rect) {
|
|
p.Fill(r, k.Border, 3)
|
|
p.Fill(ui.Rect{X: r.X, Y: r.Y, W: r.W * float32(min(frac, 1)), H: r.H}, ink, 3)
|
|
})
|
|
ui.Row(c).Gap(d.Unit()).AlignItems(ui.Center).Children(func() {
|
|
if icon != "" {
|
|
ui.Icon(c, icons.Must(icon)).Size(14, 14).TextColor(ink)
|
|
}
|
|
t := ui.Text(c, label).FontSize(FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
|
|
if note != "" {
|
|
t.TextColor(ink)
|
|
ui.Text(c, note).FontSize(FontSize(c, theme.CaptionSize)).TextColor(ink).Role(ui.RoleStatus)
|
|
}
|
|
})
|
|
})
|
|
}
|