chat.go

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