From 02228d65ac920ce20cc14fe458886f5bac328ffe Mon Sep 17 00:00:00 2001 From: Matt Low Date: Thu, 12 Dec 2024 07:09:58 +0000 Subject: [PATCH] Update tui error handling - Allow each view to position error banners where they choose - Add global 'esc' key handler to dismiss errors --- pkg/tui/shared/shared.go | 2 +- pkg/tui/tui.go | 21 +++++++++++++++----- pkg/tui/views/chat/chat.go | 1 + pkg/tui/views/chat/view.go | 12 +++++++++-- pkg/tui/views/conversations/conversations.go | 8 ++++++-- pkg/tui/views/settings/settings.go | 8 ++++++-- 6 files changed, 40 insertions(+), 12 deletions(-) diff --git a/pkg/tui/shared/shared.go b/pkg/tui/shared/shared.go index 70e8eed..2f2a7dd 100644 --- a/pkg/tui/shared/shared.go +++ b/pkg/tui/shared/shared.go @@ -13,7 +13,7 @@ type ViewModel interface { // View methods Header(width int) string // Render the view's main content into a container of the given dimensions - Content(width, height int) string + Content(width, height int, errors string) string Footer(width int) string } diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index 751193d..23b7183 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -60,6 +60,14 @@ func (m *Model) Init() tea.Cmd { } func (m *Model) handleGlobalInput(msg tea.KeyMsg) tea.Cmd { + switch msg.String() { + case "esc": + if len(m.errs) > 0 { + m.errs = m.errs[1:] + return shared.KeyHandled(msg) + } + } + view, cmd := m.views[m.activeView].Update(msg) m.views[m.activeView] = view if cmd != nil { @@ -108,10 +116,16 @@ func (m *Model) View() string { errBanners := make([]string, len(m.errs)) for idx, err := range m.errs { errBanners[idx] = tuiutil.ErrorBanner(err, m.width) - fixedUIHeight += tuiutil.Height(errBanners[idx]) } + var errors string + if len(errBanners) > 0 { + errors = lipgloss.JoinVertical(lipgloss.Left, errBanners...) + } else { + errors = "" + } + fixedUIHeight += tuiutil.Height(errors) - content := m.views[m.activeView].Content(m.width, m.height-fixedUIHeight) + content := m.views[m.activeView].Content(m.width, m.height-fixedUIHeight, errors) sections := make([]string, 0, 4) if header != "" { @@ -123,9 +137,6 @@ func (m *Model) View() string { 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 5231e04..463dabd 100644 --- a/pkg/tui/views/chat/chat.go +++ b/pkg/tui/views/chat/chat.go @@ -83,6 +83,7 @@ type Model struct { stopSignal chan struct{} chatReplyChunks chan provider.Chunk persistence bool // whether we will save new messages in the conversation + promptCaching bool // whether prompt caching is enabled // UI state focus focusState diff --git a/pkg/tui/views/chat/view.go b/pkg/tui/views/chat/view.go index 337ad0f..8b364a7 100644 --- a/pkg/tui/views/chat/view.go +++ b/pkg/tui/views/chat/view.go @@ -245,7 +245,7 @@ func (m *Model) conversationMessagesView() string { return sb.String() } -func (m *Model) Content(width, height int) string { +func (m *Model) Content(width, height int, errors string) string { // calculate clamped input height to accomodate input text // minimum 4 lines, maximum half of content area inputHeight := max(4, min(height/2, m.input.LineCount())) @@ -255,7 +255,15 @@ func (m *Model) Content(width, height int) string { // remaining height towards content m.content.Width, m.content.Height = width, height-tuiutil.Height(input) content := m.content.View() - return lipgloss.JoinVertical(lipgloss.Left, content, input) + + var sections []string + if errors != "" { + sections = []string{content, errors, input} + } else { + sections = []string{content, input} + } + + return lipgloss.JoinVertical(lipgloss.Left, sections...) } func (m *Model) Header(width int) string { diff --git a/pkg/tui/views/conversations/conversations.go b/pkg/tui/views/conversations/conversations.go index 48af809..7dcaabb 100644 --- a/pkg/tui/views/conversations/conversations.go +++ b/pkg/tui/views/conversations/conversations.go @@ -218,9 +218,13 @@ func (m *Model) Header(width int) string { return styles.Header.Width(width).Render(header) } -func (m *Model) Content(width int, height int) string { +func (m *Model) Content(width int, height int, errors string) string { m.content.Width, m.content.Height = width, height - return m.content.View() + content := m.content.View() + if errors != "" { + content += errors + } + return content } func (m *Model) Footer(width int) string { diff --git a/pkg/tui/views/settings/settings.go b/pkg/tui/views/settings/settings.go index f79f06e..e69a527 100644 --- a/pkg/tui/views/settings/settings.go +++ b/pkg/tui/views/settings/settings.go @@ -121,11 +121,15 @@ func (m *Model) Header(width int) string { return styles.Header.Width(width).Render(header) } -func (m *Model) Content(width, height int) string { +func (m *Model) Content(width, height int, errors string) string { // TODO: see Header() currentModel := " Active model: " + m.App.ActiveModel(lipgloss.NewStyle()) m.modelList.Width, m.modelList.Height = width, height - 2 - return "\n" + currentModel + "\n" + m.modelList.View() + content := "\n" + currentModel + "\n" + m.modelList.View() + if errors != "" { + content += errors + } + return content } func (m *Model) Footer(width int) string {