status.go

 1package core
 2
 3import (
 4	"time"
 5
 6	tea "github.com/charmbracelet/bubbletea"
 7	"github.com/charmbracelet/lipgloss"
 8	"github.com/kujtimiihoxha/termai/internal/config"
 9	"github.com/kujtimiihoxha/termai/internal/llm/models"
10	"github.com/kujtimiihoxha/termai/internal/pubsub"
11	"github.com/kujtimiihoxha/termai/internal/tui/styles"
12	"github.com/kujtimiihoxha/termai/internal/tui/util"
13	"github.com/kujtimiihoxha/termai/internal/version"
14)
15
16type statusCmp struct {
17	info       *util.InfoMsg
18	width      int
19	messageTTL time.Duration
20}
21
22// clearMessageCmd is a command that clears status messages after a timeout
23func (m statusCmp) clearMessageCmd() tea.Cmd {
24	return tea.Tick(m.messageTTL, func(time.Time) tea.Msg {
25		return util.ClearStatusMsg{}
26	})
27}
28
29func (m statusCmp) Init() tea.Cmd {
30	return nil
31}
32
33func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
34	switch msg := msg.(type) {
35	case tea.WindowSizeMsg:
36		m.width = msg.Width
37		return m, m.clearMessageCmd()
38	case pubsub.Event[util.InfoMsg]:
39		m.info = &msg.Payload
40		return m, m.clearMessageCmd()
41	case util.InfoMsg:
42		m.info = &msg
43		return m, m.clearMessageCmd()
44	case util.ClearStatusMsg:
45		m.info = nil
46	}
47	return m, nil
48}
49
50var (
51	versionWidget = styles.Padded.Background(styles.DarkGrey).Foreground(styles.Text).Render(version.Version)
52	helpWidget    = styles.Padded.Background(styles.Grey).Foreground(styles.Text).Render("? help")
53)
54
55func (m statusCmp) View() string {
56	status := styles.Padded.Background(styles.Grey).Foreground(styles.Text).Render("? help")
57	if m.info != nil {
58		infoStyle := styles.Padded.
59			Foreground(styles.Base).
60			Width(m.availableFooterMsgWidth())
61		switch m.info.Type {
62		case util.InfoTypeInfo:
63			infoStyle = infoStyle.Background(styles.Blue)
64		case util.InfoTypeWarn:
65			infoStyle = infoStyle.Background(styles.Peach)
66		case util.InfoTypeError:
67			infoStyle = infoStyle.Background(styles.Red)
68		}
69		status += infoStyle.Render(m.info.Msg)
70	} else {
71		status += styles.Padded.
72			Foreground(styles.Base).
73			Background(styles.LightGrey).
74			Width(m.availableFooterMsgWidth()).
75			Render("")
76	}
77	status += m.model()
78	status += versionWidget
79	return status
80}
81
82func (m statusCmp) availableFooterMsgWidth() int {
83	// -2 to accommodate padding
84	return max(0, m.width-lipgloss.Width(helpWidget)-lipgloss.Width(versionWidget)-lipgloss.Width(m.model()))
85}
86
87func (m statusCmp) model() string {
88	model := models.SupportedModels[config.Get().Model.Coder]
89	return styles.Padded.Background(styles.Grey).Foreground(styles.Text).Render(model.Name)
90}
91
92func NewStatusCmp() tea.Model {
93	return &statusCmp{
94		messageTTL: 15 * time.Second,
95	}
96}