@@ -51,19 +51,11 @@ const (
51 uiChatCompact
51 uiChatCompact
54−// sessionsLoadedMsg is a message indicating that sessions have been loaded.
54+// listSessionsMsg is a message to list available sessions.
55−type sessionsLoadedMsg struct {
55+type listSessionsMsg struct {
56 sessions []session.Session
56 sessions []session.Session
59−type sessionLoadedMsg struct {
63−type sessionFilesLoadedMsg struct {
67// UI represents the main user interface model.
59// UI represents the main user interface model.
68type UI struct {
60type UI struct {
69 com *common.Common
61 com *common.Common
@@ -176,10 +168,6 @@ func (m *UI) Init() tea.Cmd {
176 return tea.Batch(cmds...)
168 return tea.Batch(cmds...)
179−// sessionLoadedDoneMsg indicates that session loading and message appending is
181−type sessionLoadedDoneMsg struct{}
183// Update handles updates to the UI model.
171// Update handles updates to the UI model.
184func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
172func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
185 var cmds []tea.Cmd
173 var cmds []tea.Cmd
@@ -189,16 +177,19 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
189 if !m.sendProgressBar {
177 if !m.sendProgressBar {
190 m.sendProgressBar = slices.Contains(msg, "WT_SESSION")
178 m.sendProgressBar = slices.Contains(msg, "WT_SESSION")
192− case sessionsLoadedMsg:
180+ case listSessionsMsg:
193− sessions := dialog.NewSessions(m.com, msg.sessions...)
181+ if cmd := m.openSessionsDialog(msg.sessions); cmd != nil {
194− // TODO: Get. Rid. Of. Magic numbers!
182+ cmds = append(cmds, cmd)
195− sessions.SetSize(min(120, m.width-8), 30)
183+ }
196− m.dialog.OpenDialog(sessions)
184+ case loadSessionMsg:
197− case sessionLoadedMsg:
198 m.state = uiChat
185 m.state = uiChat
199− m.session = &msg.sess
186+ m.session = msg.session
200− // Load the last 20 messages from this session.
187+ m.sessionFiles = msg.files
201− msgs, _ := m.com.App.Messages.List(context.Background(), m.session.ID)
188+ msgs, err := m.com.App.Messages.List(context.Background(), m.session.ID)
190+ cmds = append(cmds, uiutil.ReportError(err))
203 // Build tool result map to link tool calls with their results
194 // Build tool result map to link tool calls with their results
204 msgPtrs := make([]*message.Message, len(msgs))
195 msgPtrs := make([]*message.Message, len(msgs))
@@ -215,17 +206,8 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
216 m.chat.SetMessages(items...)
207 m.chat.SetMessages(items...)
218− // Notify that session loading is done to scroll to bottom. This is
219− // needed because we need to draw the chat list first before we can
220− // scroll to bottom.
221− cmds = append(cmds, func() tea.Msg {
222− return sessionLoadedDoneMsg{}
224− case sessionLoadedDoneMsg:
225 m.chat.ScrollToBottom()
209 m.chat.ScrollToBottom()
226 m.chat.SelectLast()
210 m.chat.SelectLast()
227− case sessionFilesLoadedMsg:
228− m.sessionFiles = msg.files
229 case pubsub.Event[history.File]:
211 case pubsub.Event[history.File]:
230 cmds = append(cmds, m.handleFileEvent(msg.Payload))
212 cmds = append(cmds, m.handleFileEvent(msg.Payload))
231 case pubsub.Event[app.LSPEvent]:
213 case pubsub.Event[app.LSPEvent]:
@@ -344,14 +326,6 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
344 return m, tea.Batch(cmds...)
326 return m, tea.Batch(cmds...)
347−func (m *UI) loadSession(sessionID string) tea.Cmd {
348− return func() tea.Msg {
349− // TODO: handle error
350− session, _ := m.com.App.Sessions.Get(context.Background(), sessionID)
351− return sessionLoadedMsg{session}
355func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) (cmds []tea.Cmd) {
329func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) (cmds []tea.Cmd) {
356 handleQuitKeys := func(msg tea.KeyPressMsg) bool {
330 handleQuitKeys := func(msg tea.KeyPressMsg) bool {
@@ -374,23 +348,10 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) (cmds []tea.Cmd) {
374 m.updateLayoutAndSize()
348 m.updateLayoutAndSize()
375 return true
349 return true
376 case key.Matches(msg, m.keyMap.Commands):
350 case key.Matches(msg, m.keyMap.Commands):
377− if m.dialog.ContainsDialog(dialog.CommandsID) {
351+ if cmd := m.openCommandsDialog(); cmd != nil {
379− m.dialog.BringToFront(dialog.CommandsID)
382− if m.session != nil {
383− sessionID = m.session.ID
385− commands, err := dialog.NewCommands(m.com, sessionID)
387− cmds = append(cmds, uiutil.ReportError(err))
352+ cmds = append(cmds, cmd)
389− // TODO: Get. Rid. Of. Magic numbers!
390− commands.SetSize(min(120, m.width-8), 30)
391− m.dialog.OpenDialog(commands)
394 case key.Matches(msg, m.keyMap.Models):
355 case key.Matches(msg, m.keyMap.Models):
395 // TODO: Implement me
356 // TODO: Implement me
396 case key.Matches(msg, m.keyMap.Sessions):
357 case key.Matches(msg, m.keyMap.Sessions):
@@ -398,13 +359,14 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) (cmds []tea.Cmd) {
398 // Bring to front
359 // Bring to front
399 m.dialog.BringToFront(dialog.SessionsID)
360 m.dialog.BringToFront(dialog.SessionsID)
401− cmds = append(cmds, m.loadSessionsCmd)
362+ cmds = append(cmds, m.listSessions)
403 return true
364 return true
405 return false
366 return false
369+ // Route all messages to dialog if one is open.
408 if m.dialog.HasDialogs() {
370 if m.dialog.HasDialogs() {
409 // Always handle quit keys first
371 // Always handle quit keys first
410 if handleQuitKeys(msg) {
372 if handleQuitKeys(msg) {
@@ -420,19 +382,18 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) (cmds []tea.Cmd) {
420 // Generic dialog messages
382 // Generic dialog messages
421 case dialog.CloseMsg:
383 case dialog.CloseMsg:
422 m.dialog.CloseFrontDialog()
384 m.dialog.CloseFrontDialog()
423 // Session dialog messages
386 // Session dialog messages
424 case dialog.SessionSelectedMsg:
387 case dialog.SessionSelectedMsg:
425 m.dialog.CloseDialog(dialog.SessionsID)
388 m.dialog.CloseDialog(dialog.SessionsID)
427− m.loadSession(msg.Session.ID),
389+ cmds = append(cmds, m.loadSession(msg.Session.ID))
428− m.loadSessionFiles(msg.Session.ID),
390+
430 // Command dialog messages
391 // Command dialog messages
431 case dialog.ToggleYoloModeMsg:
392 case dialog.ToggleYoloModeMsg:
432 m.com.App.Permissions.SetSkipRequests(!m.com.App.Permissions.SkipRequests())
393 m.com.App.Permissions.SetSkipRequests(!m.com.App.Permissions.SkipRequests())
433 m.dialog.CloseDialog(dialog.CommandsID)
394 m.dialog.CloseDialog(dialog.CommandsID)
434 case dialog.SwitchSessionsMsg:
395 case dialog.SwitchSessionsMsg:
435− cmds = append(cmds, m.loadSessionsCmd)
396+ cmds = append(cmds, m.listSessions)
436 m.dialog.CloseDialog(dialog.CommandsID)
397 m.dialog.CloseDialog(dialog.CommandsID)
437 case dialog.CompactMsg:
398 case dialog.CompactMsg:
438 err := m.com.App.AgentCoordinator.Summarize(context.Background(), msg.SessionID)
399 err := m.com.App.AgentCoordinator.Summarize(context.Background(), msg.SessionID)
@@ -1039,11 +1000,52 @@ func (m *UI) renderSidebarLogo(width int) {
1039 m.sidebarLogo = renderLogo(m.com.Styles, true, width)
1000 m.sidebarLogo = renderLogo(m.com.Styles, true, width)
1042−// loadSessionsCmd loads the list of sessions and returns a command that sends
1003+// openCommandsDialog opens the commands dialog.
1043−// a sessionFilesLoadedMsg when done.
1004+func (m *UI) openCommandsDialog() tea.Cmd {
1005+ if m.dialog.ContainsDialog(dialog.CommandsID) {
1007+ m.dialog.BringToFront(dialog.CommandsID)
1012+ if m.session != nil {
1013+ sessionID = m.session.ID
1016+ commands, err := dialog.NewCommands(m.com, sessionID)
1018+ return uiutil.ReportError(err)
1021+ // TODO: Get. Rid. Of. Magic numbers!
1022+ commands.SetSize(min(120, m.width-8), 30)
1023+ m.dialog.OpenDialog(commands)
1028+// openSessionsDialog opens the sessions dialog with the given sessions.
1029+func (m *UI) openSessionsDialog(sessions []session.Session) tea.Cmd {
1030+ if m.dialog.ContainsDialog(dialog.SessionsID) {
1032+ m.dialog.BringToFront(dialog.SessionsID)
1036+ dialog := dialog.NewSessions(m.com, sessions...)
1037+ // TODO: Get. Rid. Of. Magic numbers!
1038+ dialog.SetSize(min(120, m.width-8), 30)
1039+ m.dialog.OpenDialog(dialog)
1044+// listSessions is a [tea.Cmd] that lists all sessions and returns them in a
1045+// [listSessionsMsg].
1044−func (m *UI) loadSessionsCmd() tea.Msg {
1046+func (m *UI) listSessions() tea.Msg {
1045 allSessions, _ := m.com.App.Sessions.List(context.TODO())
1047 allSessions, _ := m.com.App.Sessions.List(context.TODO())
1046− return sessionsLoadedMsg{sessions: allSessions}
1048+ return listSessionsMsg{sessions: allSessions}
1049// renderLogo renders the Crush logo with the given styles and dimensions.
1051// renderLogo renders the Crush logo with the given styles and dimensions.