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