tui: Chat view footer rewrite

Rewrote footer handling to better handle truncation, and use
`ActiveModel` to return the (stylized) active model
This commit is contained in:
Matt Low 2024-09-26 18:31:04 +00:00
parent 69cdc0a5aa
commit 2fed682969
2 changed files with 74 additions and 26 deletions

View File

@ -54,6 +54,13 @@ func Height(str string) int {
return strings.Count(str, "\n") + 1 return strings.Count(str, "\n") + 1
} }
func Width(str string) int {
if str == "" {
return 0
}
return ansi.PrintableRuneWidth(str)
}
// truncate a string until its rendered cell width + the provided tail fits // truncate a string until its rendered cell width + the provided tail fits
// within the given width // within the given width
func TruncateToCellWidth(str string, width int, tail string) string { func TruncateToCellWidth(str string, width int, tail string) string {

View File

@ -239,7 +239,7 @@ func (m *Model) Content(width, height int) string {
input := m.input.View() input := m.input.View()
// remaining height towards content // remaining height towards content
m.content.Width, m.content.Height = width, height - tuiutil.Height(input) m.content.Width, m.content.Height = width, height-tuiutil.Height(input)
content := m.content.View() content := m.content.View()
return lipgloss.JoinVertical(lipgloss.Left, content, input) return lipgloss.JoinVertical(lipgloss.Left, content, input)
} }
@ -261,6 +261,10 @@ func (m *Model) Footer(width int) string {
segmentStyle := lipgloss.NewStyle().PaddingLeft(1).PaddingRight(1).Faint(true) segmentStyle := lipgloss.NewStyle().PaddingLeft(1).PaddingRight(1).Faint(true)
segmentSeparator := "|" segmentSeparator := "|"
// Left segments
leftSegments := []string{}
savingStyle := segmentStyle.Bold(true) savingStyle := segmentStyle.Bold(true)
saving := "" saving := ""
if m.persistence { if m.persistence {
@ -268,6 +272,20 @@ func (m *Model) Footer(width int) string {
} else { } else {
saving = savingStyle.Foreground(lipgloss.Color("1")).Render("❌💾") saving = savingStyle.Foreground(lipgloss.Color("1")).Render("❌💾")
} }
leftSegments = append(leftSegments, saving)
// Right segments
rightSegments := []string{}
if m.elapsed > 0 && m.tokenCount > 0 {
throughput := fmt.Sprintf("%.0f t/sec", float64(m.tokenCount)/m.elapsed.Seconds())
rightSegments = append(rightSegments, segmentStyle.Render(throughput))
}
model := segmentStyle.Render(m.App.ActiveModel(lipgloss.NewStyle()))
rightSegments = append(rightSegments, model)
// Status
var status string var status string
switch m.state { switch m.state {
@ -277,39 +295,62 @@ func (m *Model) Footer(width int) string {
status = "Press ctrl+s to send" status = "Press ctrl+s to send"
} }
leftSegments := []string{ return m.layoutFooter(width, leftSegments, status, rightSegments, segmentStyle, segmentSeparator)
saving, }
segmentStyle.Render(status),
}
rightSegments := []string{}
if m.elapsed > 0 && m.tokenCount > 0 { func (m *Model) layoutFooter(
throughput := fmt.Sprintf("%.0f t/sec", float64(m.tokenCount)/m.elapsed.Seconds()) width int,
rightSegments = append(rightSegments, segmentStyle.Render(throughput)) leftSegments []string,
status string,
rightSegments []string,
segmentStyle lipgloss.Style,
segmentSeparator string,
) string {
truncate := func(s string, w int) string {
return tuiutil.TruncateToCellWidth(s, w, "...")
} }
padWidth := segmentStyle.GetHorizontalPadding()
if m.App.ProviderName != "" {
provider := fmt.Sprintf("Provider: %s", m.App.ProviderName)
rightSegments = append(rightSegments, segmentStyle.Render(provider))
}
model := fmt.Sprintf("Model: %s", m.App.Model)
rightSegments = append(rightSegments, segmentStyle.Render(model))
left := strings.Join(leftSegments, segmentSeparator) left := strings.Join(leftSegments, segmentSeparator)
right := strings.Join(rightSegments, segmentSeparator) right := strings.Join(rightSegments, segmentSeparator)
totalWidth := lipgloss.Width(left) + lipgloss.Width(right) leftWidth := tuiutil.Width(left)
remaining := width - totalWidth rightWidth := tuiutil.Width(right)
var padding string availableWidth := width - leftWidth - rightWidth - tuiutil.Width(segmentSeparator)
if remaining > 0 {
padding = strings.Repeat(" ", remaining) statusWidth := tuiutil.Width(status)
if availableWidth >= statusWidth+padWidth {
// Everything fits
availableWidth -= statusWidth + padWidth
padding := ""
if availableWidth > 0 {
padding = strings.Repeat(" ", availableWidth)
}
return footerStyle.Render(left + segmentSeparator + segmentStyle.Render(status) + padding + right)
} }
footer := left + padding + right if availableWidth > 4 {
if remaining < 0 { // There is some space left for a truncated status
footer = tuiutil.TruncateToCellWidth(footer, width, "...") truncatedStatus := truncate(status, availableWidth-padWidth)
return footerStyle.Width(width).Render(left + segmentSeparator + segmentStyle.Render(truncatedStatus) + right)
} }
return footerStyle.Width(width).Render(footer)
if availableWidth >= 0 {
// Draw some dots...
dots := ""
if availableWidth == 1 {
dots = " "
} else if availableWidth > 1 {
dots = " " + strings.Repeat(".", availableWidth-1)
dots = lipgloss.NewStyle().Faint(true).Render(dots)
}
return footerStyle.Width(width).Render(left + segmentSeparator + dots + right)
}
// Trucate right segment so it fits
right = truncate(right, tuiutil.Width(right)+availableWidth-1)
padding := ""
return footerStyle.Width(width).Render(left + segmentSeparator + padding + right)
} }