chore: show configured indicator

Kujtim Hoxha created

Change summary

internal/tui/components/chat/splash/splash.go    |  2 
internal/tui/components/core/helpers.go          | 16 ++++++++++++
internal/tui/components/core/list/list.go        |  5 +++
internal/tui/components/dialogs/commands/item.go | 20 ++++++++++++---
internal/tui/components/dialogs/models/list.go   | 24 +++++++++++++++--
internal/tui/components/dialogs/models/models.go |  7 ++++
6 files changed, 65 insertions(+), 9 deletions(-)

Detailed changes

internal/tui/components/chat/splash/splash.go 🔗

@@ -59,7 +59,7 @@ func New() Splash {
 
 	t := styles.CurrentTheme()
 	inputStyle := t.S().Base.Padding(0, 1, 0, 1)
-	modelList := models.NewModelListComponent(listKeyMap, inputStyle)
+	modelList := models.NewModelListComponent(listKeyMap, inputStyle, "Find your fave")
 	return &splashCmp{
 		width:        0,
 		height:       0,

internal/tui/components/core/helpers.go 🔗

@@ -23,6 +23,22 @@ func Section(text string, width int) string {
 	return text
 }
 
+func SectionWithInfo(text string, width int, info string) string {
+	t := styles.CurrentTheme()
+	char := "─"
+	length := lipgloss.Width(text) + 1
+	remainingWidth := width - length
+
+	if info != "" {
+		remainingWidth -= lipgloss.Width(info) + 1 // 1 for the space before info
+	}
+	lineStyle := t.S().Base.Foreground(t.Border)
+	if remainingWidth > 0 {
+		text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth)) + " " + info
+	}
+	return text
+}
+
 func Title(title string, width int) string {
 	t := styles.CurrentTheme()
 	char := "╱"

internal/tui/components/core/list/list.go 🔗

@@ -41,6 +41,7 @@ type ListModel interface {
 	SelectedIndex() int             // Get the index of the currently selected item
 	SetSelected(int) tea.Cmd        // Set the selected item by index and scroll to it
 	Filter(string) tea.Cmd          // Filter items based on a search term
+	SetFilterPlaceholder(string)    // Set the placeholder text for the filter input
 }
 
 // HasAnim interface identifies items that support animation.
@@ -1355,3 +1356,7 @@ func (m *model) Focus() tea.Cmd {
 func (m *model) IsFocused() bool {
 	return m.isFocused
 }
+
+func (m *model) SetFilterPlaceholder(placeholder string) {
+	m.input.Placeholder = placeholder
+}

internal/tui/components/dialogs/commands/item.go 🔗

@@ -14,11 +14,12 @@ type ItemSection interface {
 	util.Model
 	layout.Sizeable
 	list.SectionHeader
+	SetInfo(info string)
 }
 type itemSectionModel struct {
-	width     int
-	title     string
-	noPadding bool // No padding for the section header
+	width int
+	title string
+	info  string
 }
 
 func NewItemSection(title string) ItemSection {
@@ -40,7 +41,14 @@ func (m *itemSectionModel) View() tea.View {
 	title := ansi.Truncate(m.title, m.width-2, "…")
 	style := t.S().Base.Padding(1, 1, 0, 1)
 	title = t.S().Muted.Render(title)
-	return tea.NewView(style.Render(core.Section(title, m.width-2)))
+	section := ""
+	if m.info != "" {
+		section = core.SectionWithInfo(title, m.width-2, m.info)
+	} else {
+		section = core.Section(title, m.width-2)
+	}
+
+	return tea.NewView(style.Render(section))
 }
 
 func (m *itemSectionModel) GetSize() (int, int) {
@@ -55,3 +63,7 @@ func (m *itemSectionModel) SetSize(width int, height int) tea.Cmd {
 func (m *itemSectionModel) IsSectionHeader() bool {
 	return true
 }
+
+func (m *itemSectionModel) SetInfo(info string) {
+	m.info = info
+}

internal/tui/components/dialogs/models/list.go 🔗

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"fmt"
 	"slices"
 
 	tea "github.com/charmbracelet/bubbletea/v2"
@@ -9,6 +10,7 @@ import (
 	"github.com/charmbracelet/crush/internal/tui/components/completions"
 	"github.com/charmbracelet/crush/internal/tui/components/core/list"
 	"github.com/charmbracelet/crush/internal/tui/components/dialogs/commands"
+	"github.com/charmbracelet/crush/internal/tui/styles"
 	"github.com/charmbracelet/crush/internal/tui/util"
 	"github.com/charmbracelet/lipgloss/v2"
 )
@@ -18,11 +20,12 @@ type ModelListComponent struct {
 	modelType int
 }
 
-func NewModelListComponent(keyMap list.KeyMap, inputStyle lipgloss.Style) *ModelListComponent {
+func NewModelListComponent(keyMap list.KeyMap, inputStyle lipgloss.Style, inputPlaceholder string) *ModelListComponent {
 	modelList := list.New(
 		list.WithFilterable(true),
 		list.WithKeyMap(keyMap),
 		list.WithInputStyle(inputStyle),
+		list.WithFilterPlaceholder(inputPlaceholder),
 		list.WithWrapNavigation(true),
 	)
 
@@ -59,6 +62,7 @@ func (m *ModelListComponent) SelectedIndex() int {
 }
 
 func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
+	t := styles.CurrentTheme()
 	m.modelType = modelType
 
 	providers, err := config.Providers()
@@ -77,6 +81,9 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
 		currentModel = cfg.Models[config.SelectedModelTypeSmall]
 	}
 
+	configuredIcon := t.S().Base.Foreground(t.Success).Render(styles.CheckIcon)
+	configured := fmt.Sprintf("%s %s", configuredIcon, t.S().Subtle.Render("Configured"))
+
 	// Create a map to track which providers we've already added
 	addedProviders := make(map[string]bool)
 
@@ -120,7 +127,9 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
 			if name == "" {
 				name = string(configProvider.ID)
 			}
-			modelItems = append(modelItems, commands.NewItemSection(name))
+			section := commands.NewItemSection(name)
+			section.SetInfo(configured)
+			modelItems = append(modelItems, section)
 			for _, model := range configProvider.Models {
 				modelItems = append(modelItems, completions.NewCompletionItem(model.Name, ModelOption{
 					Provider: configProvider,
@@ -150,7 +159,12 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
 		if name == "" {
 			name = string(provider.ID)
 		}
-		modelItems = append(modelItems, commands.NewItemSection(name))
+
+		section := commands.NewItemSection(name)
+		if _, ok := cfg.Providers[string(provider.ID)]; ok {
+			section.SetInfo(configured)
+		}
+		modelItems = append(modelItems, section)
 		for _, model := range provider.Models {
 			modelItems = append(modelItems, completions.NewCompletionItem(model.Name, ModelOption{
 				Provider: provider,
@@ -169,3 +183,7 @@ func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
 func (m *ModelListComponent) GetModelType() int {
 	return m.modelType
 }
+
+func (m *ModelListComponent) SetInputPlaceholder(placeholder string) {
+	m.list.SetFilterPlaceholder(placeholder)
+}

internal/tui/components/dialogs/models/models.go 🔗

@@ -24,6 +24,9 @@ const (
 const (
 	LargeModelType int = iota
 	SmallModelType
+
+	largeModelInputPlaceholder = "Choose a model for large, complex tasks"
+	smallModelInputPlaceholder = "Choose a model for small, simple tasks"
 )
 
 // ModelSelectedMsg is sent when a model is selected
@@ -71,7 +74,7 @@ func NewModelDialogCmp() ModelDialog {
 
 	t := styles.CurrentTheme()
 	inputStyle := t.S().Base.Padding(0, 1, 0, 1)
-	modelList := NewModelListComponent(listKeyMap, inputStyle)
+	modelList := NewModelListComponent(listKeyMap, inputStyle, "Choose a model for large, complex tasks")
 	help := help.New()
 	help.Styles = t.S().Help
 
@@ -122,8 +125,10 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			)
 		case key.Matches(msg, m.keyMap.Tab):
 			if m.modelList.GetModelType() == LargeModelType {
+				m.modelList.SetInputPlaceholder(smallModelInputPlaceholder)
 				return m, m.modelList.SetModelType(SmallModelType)
 			} else {
+				m.modelList.SetInputPlaceholder(largeModelInputPlaceholder)
 				return m, m.modelList.SetModelType(LargeModelType)
 			}
 		case key.Matches(msg, m.keyMap.Close):