diff --git a/pkg/tui/model/model.go b/pkg/tui/model/model.go index 60645e7..dc4648d 100644 --- a/pkg/tui/model/model.go +++ b/pkg/tui/model/model.go @@ -8,7 +8,6 @@ import ( "git.mlow.ca/mlow/lmcli/pkg/api" cmdutil "git.mlow.ca/mlow/lmcli/pkg/cmd/util" "git.mlow.ca/mlow/lmcli/pkg/lmcli" - "git.mlow.ca/mlow/lmcli/pkg/tui/shared" ) type LoadedConversation struct { @@ -59,7 +58,7 @@ func (m *AppModel) NewConversation() { func (m *AppModel) LoadConversations() (error, []LoadedConversation) { messages, err := m.Ctx.Store.LatestConversationMessages() if err != nil { - return shared.MsgError(fmt.Errorf("Could not load conversations: %v", err)), nil + return fmt.Errorf("Could not load conversations: %v", err), nil } conversations := make([]LoadedConversation, len(messages)) diff --git a/pkg/tui/shared/shared.go b/pkg/tui/shared/shared.go index d757d9d..38600c1 100644 --- a/pkg/tui/shared/shared.go +++ b/pkg/tui/shared/shared.go @@ -32,7 +32,7 @@ type ( // sent to a state when it is entered MsgViewEnter struct{} // sent when a recoverable error occurs (displayed to user) - MsgError error + MsgError struct { Err error } // sent when the view has handled a key input MsgKeyHandled tea.KeyMsg ) @@ -57,6 +57,10 @@ func KeyHandled(key tea.KeyMsg) tea.Cmd { func WrapError(err error) tea.Cmd { return func() tea.Msg { - return MsgError(err) + return MsgError{ Err: err } } } + +func AsMsgError(err error) MsgError { + return MsgError{ Err: err } +} diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index df1fec3..8f3c2b6 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -91,7 +91,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.activeView = shared.View(msg) return m, tea.Batch(tea.WindowSize(), shared.ViewEnter()) case shared.MsgError: - m.errs = append(m.errs, msg) + m.errs = append(m.errs, msg.Err) + return m, nil } view, cmd := m.views[m.activeView].Update(msg) @@ -124,12 +125,12 @@ func (m *Model) View() string { if content != "" { sections = append(sections, content) } - if len(errBanners) > 0 { - sections = append(sections, lipgloss.JoinVertical(lipgloss.Left, errBanners...)) - } if footer != "" { sections = append(sections, footer) } + for _, errBanner := range errBanners { + sections = append(sections, errBanner) + } return lipgloss.JoinVertical(lipgloss.Left, sections...) } diff --git a/pkg/tui/views/chat/chat.go b/pkg/tui/views/chat/chat.go index 8a98f43..3c56fbb 100644 --- a/pkg/tui/views/chat/chat.go +++ b/pkg/tui/views/chat/chat.go @@ -25,11 +25,13 @@ type ( } // sent when a conversation's messages are laoded msgConversationMessagesLoaded struct { - messages []api.Message + messages []api.Message rootMessages []api.Message } // a special case of common.MsgError that stops the response waiting animation - msgChatResponseError error + msgChatResponseError struct { + Err error + } // sent on each chunk received from LLM msgChatResponseChunk api.Chunk // sent on each completed reply @@ -72,9 +74,9 @@ const ( type Model struct { // App state - App *model.AppModel + App *model.AppModel Height int - Width int + Width int // Chat view state state state // current overall status of the view @@ -108,8 +110,8 @@ func Chat(app *model.AppModel) *Model { m := Model{ App: app, - state: idle, - persistence: true, + state: idle, + persistence: true, stopSignal: make(chan struct{}), replyChan: make(chan api.Message), diff --git a/pkg/tui/views/chat/cmds.go b/pkg/tui/views/chat/cmds.go index 4fccbf0..b4b681f 100644 --- a/pkg/tui/views/chat/cmds.go +++ b/pkg/tui/views/chat/cmds.go @@ -19,11 +19,11 @@ func (m *Model) loadConversationMessages() tea.Cmd { return func() tea.Msg { messages, err := m.App.LoadConversationMessages() if err != nil { - return shared.MsgError(err) + return shared.AsMsgError(err) } rootMessages, err := m.App.LoadConversationRootMessages() if err != nil { - return shared.MsgError(err) + return shared.AsMsgError(err) } return msgConversationMessagesLoaded{ messages, rootMessages, @@ -35,7 +35,7 @@ func (m *Model) generateConversationTitle() tea.Cmd { return func() tea.Msg { title, err := m.App.GenerateConversationTitle(m.App.Messages) if err != nil { - return shared.MsgError(err) + return shared.AsMsgError(err) } return msgConversationTitleGenerated(title) } @@ -104,7 +104,7 @@ func (m *Model) persistConversation() tea.Cmd { return func() tea.Msg { conversation, messages, err := m.App.PersistConversation(m.App.Conversation, m.App.Messages) if err != nil { - return shared.MsgError(err) + return shared.AsMsgError(err) } return msgConversationPersisted{conversation.ID == 0, conversation, messages} } @@ -114,7 +114,7 @@ func (m *Model) executeToolCalls(toolCalls []api.ToolCall) tea.Cmd { return func() tea.Msg { results, err := m.App.ExecuteToolCalls(toolCalls) if err != nil { - return shared.MsgError(err) + return shared.AsMsgError(err) } return msgToolResults(results) } @@ -131,7 +131,7 @@ func (m *Model) promptLLM() tea.Cmd { return func() tea.Msg { resp, err := m.App.PromptLLM(m.App.Messages, m.chatReplyChunks, m.stopSignal) if err != nil { - return msgChatResponseError(err) + return msgChatResponseError{ Err: err } } return msgChatResponse(resp) } diff --git a/pkg/tui/views/chat/update.go b/pkg/tui/views/chat/update.go index abacaf3..83d275d 100644 --- a/pkg/tui/views/chat/update.go +++ b/pkg/tui/views/chat/update.go @@ -170,7 +170,7 @@ func (m *Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) { case msgChatResponseError: m.state = idle m.updateContent() - return m, shared.WrapError(msg) + return m, shared.WrapError(msg.Err) case msgToolResults: last := len(m.App.Messages) - 1 if last < 0 { diff --git a/pkg/tui/views/conversations/conversations.go b/pkg/tui/views/conversations/conversations.go index 60af450..1dd7d4d 100644 --- a/pkg/tui/views/conversations/conversations.go +++ b/pkg/tui/views/conversations/conversations.go @@ -182,7 +182,7 @@ func (m *Model) loadConversations() tea.Cmd { return func() tea.Msg { err, conversations := m.App.LoadConversations() if err != nil { - return shared.MsgError(fmt.Errorf("Could not load conversations: %v", err)) + return shared.AsMsgError(fmt.Errorf("Could not load conversations: %v", err)) } return msgConversationsLoaded(conversations) } @@ -192,7 +192,7 @@ func (m *Model) deleteConversation(conv api.Conversation) tea.Cmd { return func() tea.Msg { err := m.App.Ctx.Store.DeleteConversation(&conv) if err != nil { - return shared.MsgError(fmt.Errorf("Could not delete conversation: %v", err)) + return shared.AsMsgError(fmt.Errorf("Could not delete conversation: %v", err)) } return msgConversationDeleted{} }