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}