sessions.go

  1package repl
  2
  3import (
  4	"fmt"
  5
  6	"github.com/charmbracelet/bubbles/key"
  7	"github.com/charmbracelet/bubbles/list"
  8	tea "github.com/charmbracelet/bubbletea"
  9	"github.com/kujtimiihoxha/termai/internal/app"
 10	"github.com/kujtimiihoxha/termai/internal/session"
 11	"github.com/kujtimiihoxha/termai/internal/tui/layout"
 12	"github.com/kujtimiihoxha/termai/internal/tui/styles"
 13	"github.com/kujtimiihoxha/termai/internal/tui/util"
 14)
 15
 16type SessionsCmp interface {
 17	tea.Model
 18	layout.Sizeable
 19	layout.Focusable
 20	layout.Bordered
 21	layout.Bindings
 22}
 23type sessionsCmp struct {
 24	app     *app.App
 25	list    list.Model
 26	focused bool
 27}
 28
 29type listItem struct {
 30	id, title, desc string
 31}
 32
 33func (i listItem) Title() string       { return i.title }
 34func (i listItem) Description() string { return i.desc }
 35func (i listItem) FilterValue() string { return i.title }
 36
 37type InsertSessionsMsg struct {
 38	sessions []session.Session
 39}
 40
 41type SelectedSessionMsg struct {
 42	SessionID string
 43}
 44
 45func (i *sessionsCmp) Init() tea.Cmd {
 46	existing, err := i.app.Sessions.List()
 47	if err != nil {
 48		return util.ReportError(err)
 49	}
 50	if len(existing) == 0 || existing[0].MessageCount > 0 {
 51		session, err := i.app.Sessions.Create(
 52			"New Session",
 53		)
 54		if err != nil {
 55			return util.ReportError(err)
 56		}
 57		existing = append(existing, session)
 58	}
 59	return tea.Batch(
 60		util.CmdHandler(InsertSessionsMsg{existing}),
 61		util.CmdHandler(SelectedSessionMsg{existing[0].ID}),
 62	)
 63}
 64
 65func (i *sessionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 66	switch msg := msg.(type) {
 67	case InsertSessionsMsg:
 68		items := make([]list.Item, len(msg.sessions))
 69		for i, s := range msg.sessions {
 70			items[i] = listItem{
 71				id:    s.ID,
 72				title: s.Title,
 73				desc:  fmt.Sprintf("Tokens: %d, Cost: %.2f", s.Tokens, s.Cost),
 74			}
 75		}
 76		return i, i.list.SetItems(items)
 77	}
 78	if i.focused {
 79		u, cmd := i.list.Update(msg)
 80		i.list = u
 81		return i, cmd
 82	}
 83	return i, nil
 84}
 85
 86func (i *sessionsCmp) View() string {
 87	return i.list.View()
 88}
 89
 90func (i *sessionsCmp) Blur() tea.Cmd {
 91	i.focused = false
 92	return nil
 93}
 94
 95func (i *sessionsCmp) Focus() tea.Cmd {
 96	i.focused = true
 97	return nil
 98}
 99
100func (i *sessionsCmp) GetSize() (int, int) {
101	return i.list.Width(), i.list.Height()
102}
103
104func (i *sessionsCmp) IsFocused() bool {
105	return i.focused
106}
107
108func (i *sessionsCmp) SetSize(width int, height int) {
109	i.list.SetSize(width, height)
110}
111
112func (i *sessionsCmp) BorderText() map[layout.BorderPosition]string {
113	totalCount := len(i.list.Items())
114	itemsPerPage := i.list.Paginator.PerPage
115	currentPage := i.list.Paginator.Page
116
117	current := min(currentPage*itemsPerPage+itemsPerPage, totalCount)
118
119	pageInfo := fmt.Sprintf(
120		"%d-%d of %d",
121		currentPage*itemsPerPage+1,
122		current,
123		totalCount,
124	)
125	return map[layout.BorderPosition]string{
126		layout.TopMiddleBorder:    "Sessions",
127		layout.BottomMiddleBorder: pageInfo,
128	}
129}
130
131func (i *sessionsCmp) BindingKeys() []key.Binding {
132	return layout.KeyMapToSlice(i.list.KeyMap)
133}
134
135func NewSessionsCmp(app *app.App) SessionsCmp {
136	listDelegate := list.NewDefaultDelegate()
137	defaultItemStyle := list.NewDefaultItemStyles()
138	defaultItemStyle.SelectedTitle = defaultItemStyle.SelectedTitle.BorderForeground(styles.Secondary).Foreground(styles.Primary)
139	defaultItemStyle.SelectedDesc = defaultItemStyle.SelectedDesc.BorderForeground(styles.Secondary).Foreground(styles.Primary)
140
141	defaultStyle := list.DefaultStyles()
142	defaultStyle.FilterPrompt = defaultStyle.FilterPrompt.Foreground(styles.Secondary)
143	defaultStyle.FilterCursor = defaultStyle.FilterCursor.Foreground(styles.Flamingo)
144
145	listDelegate.Styles = defaultItemStyle
146
147	listComponent := list.New([]list.Item{}, listDelegate, 0, 0)
148	listComponent.FilterInput.PromptStyle = defaultStyle.FilterPrompt
149	listComponent.FilterInput.Cursor.Style = defaultStyle.FilterCursor
150	listComponent.SetShowTitle(false)
151	listComponent.SetShowPagination(false)
152	listComponent.SetShowHelp(false)
153	listComponent.SetShowStatusBar(false)
154	listComponent.DisableQuitKeybindings()
155
156	return &sessionsCmp{
157		app:     app,
158		list:    listComponent,
159		focused: false,
160	}
161}