Update tui error handling

- Allow each view to position error banners where they choose
- Add global 'esc' key handler to dismiss errors
This commit is contained in:
Matt Low 2024-12-12 07:09:58 +00:00
parent d9d1b02ef3
commit 02228d65ac
6 changed files with 40 additions and 12 deletions

View File

@ -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
}

View File

@ -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...)
}

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {