1package repl
2
3import (
4 "strings"
5
6 "github.com/charmbracelet/bubbles/key"
7 tea "github.com/charmbracelet/bubbletea"
8 "github.com/cloudwego/eino/schema"
9 "github.com/kujtimiihoxha/termai/internal/app"
10 "github.com/kujtimiihoxha/termai/internal/tui/layout"
11 "github.com/kujtimiihoxha/vimtea"
12)
13
14type EditorCmp interface {
15 tea.Model
16 layout.Focusable
17 layout.Sizeable
18 layout.Bordered
19 layout.Bindings
20}
21
22type editorCmp struct {
23 app *app.App
24 editor vimtea.Editor
25 editorMode vimtea.EditorMode
26 sessionID string
27 focused bool
28 width int
29 height int
30}
31
32type editorKeyMap struct {
33 SendMessage key.Binding
34 SendMessageI key.Binding
35 InsertMode key.Binding
36 NormaMode key.Binding
37 VisualMode key.Binding
38 VisualLineMode key.Binding
39}
40
41var editorKeyMapValue = editorKeyMap{
42 SendMessage: key.NewBinding(
43 key.WithKeys("enter"),
44 key.WithHelp("enter", "send message normal mode"),
45 ),
46 SendMessageI: key.NewBinding(
47 key.WithKeys("ctrl+s"),
48 key.WithHelp("ctrl+s", "send message insert mode"),
49 ),
50 InsertMode: key.NewBinding(
51 key.WithKeys("i"),
52 key.WithHelp("i", "insert mode"),
53 ),
54 NormaMode: key.NewBinding(
55 key.WithKeys("esc"),
56 key.WithHelp("esc", "normal mode"),
57 ),
58 VisualMode: key.NewBinding(
59 key.WithKeys("v"),
60 key.WithHelp("v", "visual mode"),
61 ),
62 VisualLineMode: key.NewBinding(
63 key.WithKeys("V"),
64 key.WithHelp("V", "visual line mode"),
65 ),
66}
67
68func (m *editorCmp) Init() tea.Cmd {
69 return m.editor.Init()
70}
71
72func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
73 switch msg := msg.(type) {
74 case vimtea.EditorModeMsg:
75 m.editorMode = msg.Mode
76 case SelectedSessionMsg:
77 if msg.SessionID != m.sessionID {
78 m.sessionID = msg.SessionID
79 }
80 }
81 if m.IsFocused() {
82 switch msg := msg.(type) {
83 case tea.KeyMsg:
84 switch {
85 case key.Matches(msg, editorKeyMapValue.SendMessage):
86 if m.editorMode == vimtea.ModeNormal {
87 return m, m.Send()
88 }
89 case key.Matches(msg, editorKeyMapValue.SendMessageI):
90 if m.editorMode == vimtea.ModeInsert {
91 return m, m.Send()
92 }
93 }
94 }
95 u, cmd := m.editor.Update(msg)
96 m.editor = u.(vimtea.Editor)
97 return m, cmd
98 }
99 return m, nil
100}
101
102func (m *editorCmp) Blur() tea.Cmd {
103 m.focused = false
104 return nil
105}
106
107func (m *editorCmp) BorderText() map[layout.BorderPosition]string {
108 return map[layout.BorderPosition]string{
109 layout.TopLeftBorder: "New Message",
110 }
111}
112
113func (m *editorCmp) Focus() tea.Cmd {
114 m.focused = true
115 return m.editor.Tick()
116}
117
118func (m *editorCmp) GetSize() (int, int) {
119 return m.width, m.height
120}
121
122func (m *editorCmp) IsFocused() bool {
123 return m.focused
124}
125
126func (m *editorCmp) SetSize(width int, height int) {
127 m.width = width
128 m.height = height
129 m.editor.SetSize(width, height)
130}
131
132func (m *editorCmp) Send() tea.Cmd {
133 return func() tea.Msg {
134 content := strings.Join(m.editor.GetBuffer().Lines(), "\n")
135 m.app.Messages.Create(m.sessionID, *schema.UserMessage(content))
136 m.app.LLM.SendRequest(m.sessionID, content)
137 return m.editor.Reset()
138 }
139}
140
141func (m *editorCmp) View() string {
142 return m.editor.View()
143}
144
145func (m *editorCmp) BindingKeys() []key.Binding {
146 return layout.KeyMapToSlice(editorKeyMapValue)
147}
148
149func NewEditorCmp(app *app.App) EditorCmp {
150 return &editorCmp{
151 app: app,
152 editor: vimtea.NewEditor(),
153 }
154}