@@ -2,48 +2,6 @@ package chat
22
33import "github.com/charmbracelet/bubbles/v2/key"
44
5−// import (
6−//
7−// "context"
8−// "fmt"
9−// "math"
10−//
11−// "github.com/charmbracelet/bubbles/v2/key"
12−// "github.com/charmbracelet/bubbles/v2/spinner"
13−// "github.com/charmbracelet/bubbles/v2/viewport"
14−// tea "github.com/charmbracelet/bubbletea/v2"
15−// "github.com/charmbracelet/lipgloss/v2"
16−// "github.com/opencode-ai/opencode/internal/app"
17−// "github.com/opencode-ai/opencode/internal/message"
18−// "github.com/opencode-ai/opencode/internal/pubsub"
19−// "github.com/opencode-ai/opencode/internal/session"
20−// "github.com/opencode-ai/opencode/internal/tui/components/dialog"
21−// "github.com/opencode-ai/opencode/internal/tui/styles"
22−// "github.com/opencode-ai/opencode/internal/tui/theme"
23−// "github.com/opencode-ai/opencode/internal/tui/util"
24−//
25−// )
26−//
27−// type cacheItem struct {
28−// width int
29−// content []uiMessage
30−// }
31−//
32−// type messagesCmp struct {
33−// app *app.App
34−// width, height int
35−// viewport viewport.Model
36−// session session.Session
37−// messages []message.Message
38−// uiMessages []uiMessage
39−// currentMsgID string
40−// cachedContent map[string]cacheItem
41−// spinner spinner.Model
42−// rendering bool
43−// attachments viewport.Model
44−// }
45−//
46−// type renderFinishedMsg struct{}
475type MessageKeys struct {
486 PageDown key.Binding
497 PageUp key.Binding
@@ -69,412 +27,3 @@ var messageKeys = MessageKeys{
6927 key.WithHelp("ctrl+d", "½ page down"),
7028 ),
7129}
72−
73−//
74−// func (m *messagesCmp) Init() tea.Cmd {
75−// return tea.Batch(m.viewport.Init(), m.spinner.Tick)
76−// }
77−//
78−// func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
79−// var cmds []tea.Cmd
80−// switch msg := msg.(type) {
81−// case dialog.ThemeChangedMsg:
82−// m.rerender()
83−// return m, nil
84−// case SessionSelectedMsg:
85−// if msg.ID != m.session.ID {
86−// cmd := m.SetSession(msg)
87−// return m, cmd
88−// }
89−// return m, nil
90−// case SessionClearedMsg:
91−// m.session = session.Session{}
92−// m.messages = make([]message.Message, 0)
93−// m.currentMsgID = ""
94−// m.rendering = false
95−// return m, nil
96−//
97−// case tea.KeyMsg:
98−// if key.Matches(msg, messageKeys.PageUp) || key.Matches(msg, messageKeys.PageDown) ||
99−// key.Matches(msg, messageKeys.HalfPageUp) || key.Matches(msg, messageKeys.HalfPageDown) {
100−// u, cmd := m.viewport.Update(msg)
101−// m.viewport = u
102−// cmds = append(cmds, cmd)
103−// }
104−//
105−// case renderFinishedMsg:
106−// m.rendering = false
107−// m.viewport.GotoBottom()
108−// case pubsub.Event[session.Session]:
109−// if msg.Type == pubsub.UpdatedEvent && msg.Payload.ID == m.session.ID {
110−// m.session = msg.Payload
111−// if m.session.SummaryMessageID == m.currentMsgID {
112−// delete(m.cachedContent, m.currentMsgID)
113−// m.renderView()
114−// }
115−// }
116−// case pubsub.Event[message.Message]:
117−// needsRerender := false
118−// if msg.Type == pubsub.CreatedEvent {
119−// if msg.Payload.SessionID == m.session.ID {
120−//
121−// messageExists := false
122−// for _, v := range m.messages {
123−// if v.ID == msg.Payload.ID {
124−// messageExists = true
125−// break
126−// }
127−// }
128−//
129−// if !messageExists {
130−// if len(m.messages) > 0 {
131−// lastMsgID := m.messages[len(m.messages)-1].ID
132−// delete(m.cachedContent, lastMsgID)
133−// }
134−//
135−// m.messages = append(m.messages, msg.Payload)
136−// delete(m.cachedContent, m.currentMsgID)
137−// m.currentMsgID = msg.Payload.ID
138−// needsRerender = true
139−// }
140−// }
141−// // There are tool calls from the child task
142−// for _, v := range m.messages {
143−// for _, c := range v.ToolCalls() {
144−// if c.ID == msg.Payload.SessionID {
145−// delete(m.cachedContent, v.ID)
146−// needsRerender = true
147−// }
148−// }
149−// }
150−// } else if msg.Type == pubsub.UpdatedEvent && msg.Payload.SessionID == m.session.ID {
151−// for i, v := range m.messages {
152−// if v.ID == msg.Payload.ID {
153−// m.messages[i] = msg.Payload
154−// delete(m.cachedContent, msg.Payload.ID)
155−// needsRerender = true
156−// break
157−// }
158−// }
159−// }
160−// if needsRerender {
161−// m.renderView()
162−// if len(m.messages) > 0 {
163−// if (msg.Type == pubsub.CreatedEvent) ||
164−// (msg.Type == pubsub.UpdatedEvent && msg.Payload.ID == m.messages[len(m.messages)-1].ID) {
165−// m.viewport.GotoBottom()
166−// }
167−// }
168−// }
169−// }
170−//
171−// spinner, cmd := m.spinner.Update(msg)
172−// m.spinner = spinner
173−// cmds = append(cmds, cmd)
174−// return m, tea.Batch(cmds...)
175−// }
176−//
177−// func (m *messagesCmp) IsAgentWorking() bool {
178−// return m.app.CoderAgent.IsSessionBusy(m.session.ID)
179−// }
180−//
181−// func formatTimeDifference(unixTime1, unixTime2 int64) string {
182−// diffSeconds := float64(math.Abs(float64(unixTime2 - unixTime1)))
183−//
184−// if diffSeconds < 60 {
185−// return fmt.Sprintf("%.1fs", diffSeconds)
186−// }
187−//
188−// minutes := int(diffSeconds / 60)
189−// seconds := int(diffSeconds) % 60
190−// return fmt.Sprintf("%dm%ds", minutes, seconds)
191−// }
192−//
193−// func (m *messagesCmp) renderView() {
194−// m.uiMessages = make([]uiMessage, 0)
195−// pos := 0
196−// baseStyle := styles.BaseStyle()
197−//
198−// if m.width == 0 {
199−// return
200−// }
201−// for inx, msg := range m.messages {
202−// switch msg.Role {
203−// case message.User:
204−// if cache, ok := m.cachedContent[msg.ID]; ok && cache.width == m.width {
205−// m.uiMessages = append(m.uiMessages, cache.content...)
206−// continue
207−// }
208−// userMsg := renderUserMessage(
209−// msg,
210−// msg.ID == m.currentMsgID,
211−// m.width,
212−// pos,
213−// )
214−// m.uiMessages = append(m.uiMessages, userMsg)
215−// m.cachedContent[msg.ID] = cacheItem{
216−// width: m.width,
217−// content: []uiMessage{userMsg},
218−// }
219−// pos += userMsg.height + 1 // + 1 for spacing
220−// case message.Assistant:
221−// if cache, ok := m.cachedContent[msg.ID]; ok && cache.width == m.width {
222−// m.uiMessages = append(m.uiMessages, cache.content...)
223−// continue
224−// }
225−// isSummary := m.session.SummaryMessageID == msg.ID
226−//
227−// assistantMessages := renderAssistantMessage(
228−// msg,
229−// inx,
230−// m.messages,
231−// m.app.Messages,
232−// m.currentMsgID,
233−// isSummary,
234−// m.width,
235−// pos,
236−// )
237−// for _, msg := range assistantMessages {
238−// m.uiMessages = append(m.uiMessages, msg)
239−// pos += msg.height + 1 // + 1 for spacing
240−// }
241−// m.cachedContent[msg.ID] = cacheItem{
242−// width: m.width,
243−// content: assistantMessages,
244−// }
245−// }
246−// }
247−//
248−// messages := make([]string, 0)
249−// for _, v := range m.uiMessages {
250−// messages = append(messages, lipgloss.JoinVertical(lipgloss.Left, v.content),
251−// baseStyle.
252−// Width(m.width).
253−// Render(
254−// "",
255−// ),
256−// )
257−// }
258−//
259−// m.viewport.SetContent(
260−// baseStyle.
261−// Width(m.width).
262−// Render(
263−// lipgloss.JoinVertical(
264−// lipgloss.Top,
265−// messages...,
266−// ),
267−// ),
268−// )
269−// }
270−//
271−// func (m *messagesCmp) View() string {
272−// baseStyle := styles.BaseStyle()
273−//
274−// if m.rendering {
275−// return baseStyle.
276−// Width(m.width).
277−// Render(
278−// lipgloss.JoinVertical(
279−// lipgloss.Top,
280−// "Loading...",
281−// m.working(),
282−// m.help(),
283−// ),
284−// )
285−// }
286−// if len(m.messages) == 0 {
287−// content := baseStyle.
288−// Width(m.width).
289−// Height(m.height - 1).
290−// Render(
291−// initialScreen(),
292−// )
293−//
294−// return baseStyle.
295−// Width(m.width).
296−// Render(
297−// lipgloss.JoinVertical(
298−// lipgloss.Top,
299−// content,
300−// "",
301−// m.help(),
302−// ),
303−// )
304−// }
305−//
306−// return baseStyle.
307−// Width(m.width).
308−// Render(
309−// lipgloss.JoinVertical(
310−// lipgloss.Top,
311−// m.viewport.View(),
312−// m.working(),
313−// m.help(),
314−// ),
315−// )
316−// }
317−//
318−// func hasToolsWithoutResponse(messages []message.Message) bool {
319−// toolCalls := make([]message.ToolCall, 0)
320−// toolResults := make([]message.ToolResult, 0)
321−// for _, m := range messages {
322−// toolCalls = append(toolCalls, m.ToolCalls()...)
323−// toolResults = append(toolResults, m.ToolResults()...)
324−// }
325−//
326−// for _, v := range toolCalls {
327−// found := false
328−// for _, r := range toolResults {
329−// if v.ID == r.ToolCallID {
330−// found = true
331−// break
332−// }
333−// }
334−// if !found && v.Finished {
335−// return true
336−// }
337−// }
338−// return false
339−// }
340−//
341−// func hasUnfinishedToolCalls(messages []message.Message) bool {
342−// toolCalls := make([]message.ToolCall, 0)
343−// for _, m := range messages {
344−// toolCalls = append(toolCalls, m.ToolCalls()...)
345−// }
346−// for _, v := range toolCalls {
347−// if !v.Finished {
348−// return true
349−// }
350−// }
351−// return false
352−// }
353−//
354−// func (m *messagesCmp) working() string {
355−// text := ""
356−// if m.IsAgentWorking() && len(m.messages) > 0 {
357−// t := theme.CurrentTheme()
358−// baseStyle := styles.BaseStyle()
359−//
360−// task := "Thinking..."
361−// lastMessage := m.messages[len(m.messages)-1]
362−// if hasToolsWithoutResponse(m.messages) {
363−// task = "Waiting for tool response..."
364−// } else if hasUnfinishedToolCalls(m.messages) {
365−// task = "Building tool call..."
366−// } else if !lastMessage.IsFinished() {
367−// task = "Generating..."
368−// }
369−// if task != "" {
370−// text += baseStyle.
371−// Width(m.width).
372−// Foreground(t.Primary()).
373−// Bold(true).
374−// Render(fmt.Sprintf("%s %s ", m.spinner.View(), task))
375−// }
376−// }
377−// return text
378−// }
379−//
380−// func (m *messagesCmp) help() string {
381−// t := theme.CurrentTheme()
382−// baseStyle := styles.BaseStyle()
383−//
384−// text := ""
385−//
386−// if m.app.CoderAgent.IsBusy() {
387−// text += lipgloss.JoinHorizontal(
388−// lipgloss.Left,
389−// baseStyle.Foreground(t.TextMuted()).Bold(true).Render("press "),
390−// baseStyle.Foreground(t.Text()).Bold(true).Render("esc"),
391−// baseStyle.Foreground(t.TextMuted()).Bold(true).Render(" to exit cancel"),
392−// )
393−// } else {
394−// text += lipgloss.JoinHorizontal(
395−// lipgloss.Left,
396−// baseStyle.Foreground(t.TextMuted()).Bold(true).Render("press "),
397−// baseStyle.Foreground(t.Text()).Bold(true).Render("enter"),
398−// baseStyle.Foreground(t.TextMuted()).Bold(true).Render(" to send the message,"),
399−// baseStyle.Foreground(t.TextMuted()).Bold(true).Render(" write"),
400−// baseStyle.Foreground(t.Text()).Bold(true).Render(" \\"),
401−// baseStyle.Foreground(t.TextMuted()).Bold(true).Render(" and enter to add a new line"),
402−// )
403−// }
404−// return baseStyle.
405−// Width(m.width).
406−// Render(text)
407−// }
408−//
409−// func (m *messagesCmp) rerender() {
410−// for _, msg := range m.messages {
411−// delete(m.cachedContent, msg.ID)
412−// }
413−// m.renderView()
414−// }
415−//
416−// func (m *messagesCmp) SetSize(width, height int) tea.Cmd {
417−// if m.width == width && m.height == height {
418−// return nil
419−// }
420−// m.width = width
421−// m.height = height
422−// m.viewport.SetWidth(width)
423−// m.viewport.SetHeight(height - 2)
424−// m.attachments.SetWidth(width + 40)
425−// m.attachments.SetHeight(3)
426−// m.rerender()
427−// return nil
428−// }
429−//
430−// func (m *messagesCmp) GetSize() (int, int) {
431−// return m.width, m.height
432−// }
433−//
434−// func (m *messagesCmp) SetSession(session session.Session) tea.Cmd {
435−// if m.session.ID == session.ID {
436−// return nil
437−// }
438−// m.session = session
439−// messages, err := m.app.Messages.List(context.Background(), session.ID)
440−// if err != nil {
441−// return util.ReportError(err)
442−// }
443−// m.messages = messages
444−// if len(m.messages) > 0 {
445−// m.currentMsgID = m.messages[len(m.messages)-1].ID
446−// }
447−// delete(m.cachedContent, m.currentMsgID)
448−// m.rendering = true
449−// return func() tea.Msg {
450−// m.renderView()
451−// return renderFinishedMsg{}
452−// }
453−// }
454−//
455−// func (m *messagesCmp) BindingKeys() []key.Binding {
456−// return []key.Binding{
457−// m.viewport.KeyMap.PageDown,
458−// m.viewport.KeyMap.PageUp,
459−// m.viewport.KeyMap.HalfPageUp,
460−// m.viewport.KeyMap.HalfPageDown,
461−// }
462−// }
463−//
464−// func NewMessagesCmp(app *app.App) util.Model {
465−// s := spinner.New()
466−// s.Spinner = spinner.Pulse
467−// vp := viewport.New()
468−// attachmets := viewport.New()
469−// vp.KeyMap.PageUp = messageKeys.PageUp
470−// vp.KeyMap.PageDown = messageKeys.PageDown
471−// vp.KeyMap.HalfPageUp = messageKeys.HalfPageUp
472−// vp.KeyMap.HalfPageDown = messageKeys.HalfPageDown
473−// return &messagesCmp{
474−// app: app,
475−// cachedContent: make(map[string]cacheItem),
476−// viewport: vp,
477−// spinner: s,
478−// attachments: attachmets,
479−// }
480−// }