chat.go

  1package page
  2
  3import (
  4	"context"
  5
  6	"github.com/charmbracelet/bubbles/key"
  7	tea "github.com/charmbracelet/bubbletea"
  8	"github.com/kujtimiihoxha/termai/internal/app"
  9	"github.com/kujtimiihoxha/termai/internal/session"
 10	"github.com/kujtimiihoxha/termai/internal/tui/components/chat"
 11	"github.com/kujtimiihoxha/termai/internal/tui/layout"
 12	"github.com/kujtimiihoxha/termai/internal/tui/util"
 13)
 14
 15var ChatPage PageID = "chat"
 16
 17type chatPage struct {
 18	app     *app.App
 19	layout  layout.SplitPaneLayout
 20	session session.Session
 21}
 22
 23type ChatKeyMap struct {
 24	NewSession key.Binding
 25	Cancel     key.Binding
 26}
 27
 28var keyMap = ChatKeyMap{
 29	NewSession: key.NewBinding(
 30		key.WithKeys("ctrl+n"),
 31		key.WithHelp("ctrl+n", "new session"),
 32	),
 33	Cancel: key.NewBinding(
 34		key.WithKeys("ctrl+x"),
 35		key.WithHelp("ctrl+x", "cancel"),
 36	),
 37}
 38
 39func (p *chatPage) Init() tea.Cmd {
 40	cmds := []tea.Cmd{
 41		p.layout.Init(),
 42	}
 43
 44	sessions, _ := p.app.Sessions.List(context.Background())
 45	if len(sessions) > 0 {
 46		p.session = sessions[0]
 47		cmd := p.setSidebar()
 48		cmds = append(cmds, util.CmdHandler(chat.SessionSelectedMsg(p.session)), cmd)
 49	}
 50	return tea.Batch(cmds...)
 51}
 52
 53func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 54	switch msg := msg.(type) {
 55	case tea.WindowSizeMsg:
 56		p.layout.SetSize(msg.Width, msg.Height)
 57	case chat.SendMsg:
 58		cmd := p.sendMessage(msg.Text)
 59		if cmd != nil {
 60			return p, cmd
 61		}
 62	case tea.KeyMsg:
 63		switch {
 64		case key.Matches(msg, keyMap.NewSession):
 65			p.session = session.Session{}
 66			p.clearSidebar()
 67			return p, util.CmdHandler(chat.SessionClearedMsg{})
 68		case key.Matches(msg, keyMap.Cancel):
 69			if p.session.ID != "" {
 70				// Cancel the current session's generation process
 71				// This allows users to interrupt long-running operations
 72				p.app.CoderAgent.Cancel(p.session.ID)
 73				return p, nil
 74			}
 75		}
 76	}
 77	u, cmd := p.layout.Update(msg)
 78	p.layout = u.(layout.SplitPaneLayout)
 79	if cmd != nil {
 80		return p, cmd
 81	}
 82	return p, nil
 83}
 84
 85func (p *chatPage) setSidebar() tea.Cmd {
 86	sidebarContainer := layout.NewContainer(
 87		chat.NewSidebarCmp(p.session, p.app.History),
 88		layout.WithPadding(1, 1, 1, 1),
 89	)
 90	p.layout.SetRightPanel(sidebarContainer)
 91	width, height := p.layout.GetSize()
 92	p.layout.SetSize(width, height)
 93	return sidebarContainer.Init()
 94}
 95
 96func (p *chatPage) clearSidebar() {
 97	p.layout.SetRightPanel(nil)
 98	width, height := p.layout.GetSize()
 99	p.layout.SetSize(width, height)
100}
101
102func (p *chatPage) sendMessage(text string) tea.Cmd {
103	var cmds []tea.Cmd
104	if p.session.ID == "" {
105		session, err := p.app.Sessions.Create(context.Background(), "New Session")
106		if err != nil {
107			return util.ReportError(err)
108		}
109
110		p.session = session
111		cmd := p.setSidebar()
112		if cmd != nil {
113			cmds = append(cmds, cmd)
114		}
115		cmds = append(cmds, util.CmdHandler(chat.SessionSelectedMsg(session)))
116	}
117
118	p.app.CoderAgent.Run(context.Background(), p.session.ID, text)
119	return tea.Batch(cmds...)
120}
121
122func (p *chatPage) SetSize(width, height int) {
123	p.layout.SetSize(width, height)
124}
125
126func (p *chatPage) GetSize() (int, int) {
127	return p.layout.GetSize()
128}
129
130func (p *chatPage) View() string {
131	return p.layout.View()
132}
133
134func (p *chatPage) BindingKeys() []key.Binding {
135	bindings := layout.KeyMapToSlice(keyMap)
136	bindings = append(bindings, p.layout.BindingKeys()...)
137	return bindings
138}
139
140func NewChatPage(app *app.App) tea.Model {
141	messagesContainer := layout.NewContainer(
142		chat.NewMessagesCmp(app),
143		layout.WithPadding(1, 1, 0, 1),
144	)
145
146	editorContainer := layout.NewContainer(
147		chat.NewEditorCmp(app),
148		layout.WithBorder(true, false, false, false),
149	)
150	return &chatPage{
151		app: app,
152		layout: layout.NewSplitPane(
153			layout.WithLeftPanel(messagesContainer),
154			layout.WithBottomPanel(editorContainer),
155		),
156	}
157}