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}