8
8
"github.com/charmbracelet/bubbles/textinput"
9
9
"github.com/charmbracelet/bubbles/viewport"
10
10
tea "github.com/charmbracelet/bubbletea"
11
+ "github.com/charmbracelet/glamour"
11
12
"github.com/charmbracelet/lipgloss"
12
13
"github.com/marpit19/charmlama/internal/ollama"
13
14
)
@@ -21,10 +22,18 @@ var (
21
22
Foreground (lipgloss .Color ("#00FFFF" )). // Cyan
22
23
Bold (true )
23
24
24
- inputStyle = lipgloss .NewStyle ().
25
- BorderStyle (lipgloss .NormalBorder ()).
26
- BorderForeground (lipgloss .Color ("#FF1493" )). // Deep Pink
27
- Foreground (lipgloss .Color ("#FFFFFF" )) // White text
25
+ activeInputStyle = lipgloss .NewStyle ().
26
+ BorderStyle (lipgloss .NormalBorder ()).
27
+ BorderForeground (lipgloss .Color ("#FF1493" )). // Deep Pink
28
+ Foreground (lipgloss .Color ("#FFFFFF" )) // White text
29
+
30
+ disabledInputStyle = lipgloss .NewStyle ().
31
+ BorderStyle (lipgloss .NormalBorder ()).
32
+ BorderForeground (lipgloss .Color ("#696969" )). // Dim Gray
33
+ Foreground (lipgloss .Color ("#A9A9A9" )) // Dark Gray text
34
+
35
+ statusStyle = lipgloss .NewStyle ().
36
+ Foreground (lipgloss .Color ("#98FB98" ))
28
37
)
29
38
30
39
type ChatInterface struct {
@@ -39,6 +48,7 @@ type ChatInterface struct {
39
48
spinner spinner.Model
40
49
width int
41
50
height int
51
+ renderer * glamour.TermRenderer
42
52
}
43
53
44
54
func NewChatInterface (model string , manager * ollama.Manager ) * ChatInterface {
@@ -47,20 +57,26 @@ func NewChatInterface(model string, manager *ollama.Manager) *ChatInterface {
47
57
input .Focus ()
48
58
49
59
vp := viewport .New (80 , 20 )
50
- vp .KeyMap .PageDown .SetEnabled (false )
51
- vp .KeyMap .PageUp .SetEnabled (false )
60
+ // vp.KeyMap.PageDown.SetEnabled(false)
61
+ // vp.KeyMap.PageUp.SetEnabled(false)
52
62
53
63
s := spinner .New ()
54
64
s .Spinner = spinner .Dot
55
65
s .Style = lipgloss .NewStyle ().Foreground (lipgloss .Color ("#FFA500" ))
56
66
67
+ renderer , _ := glamour .NewTermRenderer (
68
+ glamour .WithAutoStyle (),
69
+ glamour .WithWordWrap (80 ),
70
+ )
71
+
57
72
return & ChatInterface {
58
73
model : model ,
59
74
manager : manager ,
60
75
messages : []string {},
61
76
viewport : vp ,
62
77
input : input ,
63
78
spinner : s ,
79
+ renderer : renderer ,
64
80
}
65
81
}
66
82
@@ -73,6 +89,17 @@ func (c *ChatInterface) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
73
89
74
90
switch msg := msg .(type ) {
75
91
case tea.KeyMsg :
92
+ if c .waiting {
93
+ // Ignore most key presses while waiting
94
+ switch msg .String () {
95
+ case "ctrl+c" :
96
+ c .quitting = true
97
+ return c , tea .Quit
98
+ default :
99
+ return c , nil
100
+ }
101
+ }
102
+
76
103
switch msg .String () {
77
104
case "ctrl+c" , "/exit" :
78
105
c .quitting = true
@@ -99,43 +126,75 @@ func (c *ChatInterface) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
99
126
case userMessageMsg :
100
127
c .addMessage ("You" , string (msg ))
101
128
c .waiting = true
129
+ c .input .Blur ()
102
130
cmds = append (cmds , c .handleUserMessage (msg ), c .spinner .Tick )
103
131
104
132
case aiResponseMsg :
105
133
c .waiting = false
106
134
c .addMessage (c .model , string (msg ))
135
+ c .input .Focus ()
107
136
}
108
137
109
138
if c .waiting {
110
139
var cmd tea.Cmd
111
140
c .spinner , cmd = c .spinner .Update (msg )
112
141
cmds = append (cmds , cmd )
142
+ } else {
143
+ var cmd tea.Cmd
144
+ c .input , cmd = c .input .Update (msg )
145
+ cmds = append (cmds , cmd )
113
146
}
114
147
115
- c .input , _ = c .input .Update (msg )
148
+ // c.input, _ = c.input.Update(msg)
149
+
150
+ var cmd tea.Cmd
151
+ c .viewport , cmd = c .viewport .Update (msg )
152
+ cmds = append (cmds , cmd )
116
153
117
154
return c , tea .Batch (cmds ... )
118
155
}
119
156
157
+ // func (c *ChatInterface) View() string {
158
+ // var status string
159
+ // var inputView string
160
+
161
+ // if c.waiting {
162
+ // status = fmt.Sprintf("%s AI is thinking...", c.spinner.View())
163
+ // inputView = disabledInputStyle.Render(c.input.View())
164
+ // } else {
165
+ // status = "Ready for your message"
166
+ // inputView = activeInputStyle.Render(c.input.View())
167
+ // }
168
+
169
+ // maxInputWidth := c.width - 4 // Adjust this value as needed
170
+ // if len(inputView) > maxInputWidth && maxInputWidth > 0 {
171
+ // inputView = inputView[:maxInputWidth] + "..."
172
+ // }
173
+
174
+ // return fmt.Sprintf(
175
+ // "%s\n%s\n%s",
176
+ // c.viewport.View(),
177
+ // inputView,
178
+ // lipgloss.NewStyle().Foreground(lipgloss.Color("#98FB98")).Render(status),
179
+ // )
180
+ // }
181
+
120
182
func (c * ChatInterface ) View () string {
121
183
var status string
184
+ var inputView string
185
+
122
186
if c .waiting {
123
187
status = fmt .Sprintf ("%s AI is thinking..." , c .spinner .View ())
188
+ inputView = disabledInputStyle .Render (c .input .View ())
124
189
} else {
125
190
status = "Ready for your message"
191
+ inputView = activeInputStyle .Render (c .input .View ())
126
192
}
127
193
128
- inputView := c .input .View ()
129
- maxInputWidth := c .width - 4 // Adjust this value as needed
130
- if len (inputView ) > maxInputWidth && maxInputWidth > 0 {
131
- inputView = inputView [:maxInputWidth ] + "..."
132
- }
133
-
134
- return fmt .Sprintf (
135
- "%s\n %s\n %s" ,
194
+ return lipgloss .JoinVertical (lipgloss .Left ,
136
195
c .viewport .View (),
137
- inputStyle . Render ( inputView ) ,
138
- lipgloss . NewStyle (). Foreground ( lipgloss . Color ( "#98FB98" )) .Render (status ),
196
+ inputView ,
197
+ statusStyle .Render (status ),
139
198
)
140
199
}
141
200
@@ -161,52 +220,65 @@ func (c *ChatInterface) handleUserMessage(msg userMessageMsg) tea.Cmd {
161
220
}
162
221
}
163
222
223
+ // func (c *ChatInterface) addMessage(sender, content string) {
224
+ // style := userStyle
225
+ // if sender != "You" {
226
+ // style = aiStyle
227
+ // }
228
+ // formattedMsg := style.Render(sender+":") + " " + content
229
+ // wrappedMsg := c.wrapText(formattedMsg, c.width)
230
+ // c.messages = append(c.messages, wrappedMsg)
231
+ // c.updateViewportContent()
232
+ // }
233
+
164
234
func (c * ChatInterface ) addMessage (sender , content string ) {
165
- style := userStyle
166
- if sender != "You" {
167
- style = aiStyle
235
+ var formattedMsg string
236
+ if sender == "You" {
237
+ formattedMsg = userStyle .Render (sender + ":" ) + " " + content
238
+ } else {
239
+ rendered , _ := c .renderer .Render (content )
240
+ formattedMsg = aiStyle .Render (sender + ":" ) + "\n " + rendered
168
241
}
169
- formattedMsg := style .Render (sender + ":" ) + " " + content
170
- wrappedMsg := c .wrapText (formattedMsg , c .width )
171
- c .messages = append (c .messages , wrappedMsg )
242
+ c .messages = append (c .messages , formattedMsg )
172
243
c .updateViewportContent ()
173
244
}
174
245
175
246
func (c * ChatInterface ) updateViewportContent () {
176
- c .viewport .SetContent (strings .Join (c .messages , "\n \n " ))
247
+ content := strings .Join (c .messages , "\n \n " )
248
+ c .viewport .SetContent (content )
177
249
c .viewport .GotoBottom ()
178
250
}
179
251
180
- func (c * ChatInterface ) wrapText (text string , width int ) string {
181
- if width <= 0 {
182
- return text
183
- }
184
- words := strings .Fields (text )
185
- if len (words ) == 0 {
186
- return text
187
- }
188
-
189
- var lines []string
190
- var currentLine string
191
-
192
- for _ , word := range words {
193
- if len (currentLine )+ len (word )+ 1 > width {
194
- lines = append (lines , strings .TrimSpace (currentLine ))
195
- currentLine = word
196
- } else {
197
- if currentLine != "" {
198
- currentLine += " "
199
- }
200
- currentLine += word
201
- }
202
- }
203
-
204
- if currentLine != "" {
205
- lines = append (lines , strings .TrimSpace (currentLine ))
206
- }
207
-
208
- return strings .Join (lines , "\n " )
209
- }
252
+ // func (c *ChatInterface) wrapText(text string, width int) string {
253
+ // if width <= 0 {
254
+ // return text
255
+ // }
256
+ // words := strings.Fields(text)
257
+ // if len(words) == 0 {
258
+ // return text
259
+ // }
260
+
261
+ // var lines []string
262
+ // var currentLine string
263
+
264
+ // for _, word := range words {
265
+ // if len(currentLine)+len(word)+1 > width {
266
+ // lines = append(lines, strings.TrimSpace(currentLine))
267
+ // currentLine = word
268
+ // } else {
269
+ // if currentLine != "" {
270
+ // currentLine += " "
271
+ // }
272
+ // currentLine += word
273
+ // }
274
+ // }
275
+
276
+ // if currentLine != "" {
277
+ // lines = append(lines, strings.TrimSpace(currentLine))
278
+ // }
279
+
280
+ // return strings.Join(lines, "\n")
281
+ // }
210
282
211
283
func (c * ChatInterface ) Run () (bool , error ) {
212
284
p := tea .NewProgram (c , tea .WithAltScreen ())
0 commit comments