feat(tui): add keymap to remove entries from recently used models (#1019)

This commit is contained in:
Timo Clasen 2025-07-15 18:20:56 +02:00 committed by GitHub
parent 6b98acb7be
commit f707fb3f8d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 1 deletions

View file

@ -23,6 +23,7 @@ const (
numVisibleModels = 10
minDialogWidth = 40
maxDialogWidth = 80
maxRecentModels = 5
)
// ModelDialog interface for the model selection dialog
@ -122,6 +123,17 @@ func (m *modelDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case SearchCancelledMsg:
return m, util.CmdHandler(modal.CloseModalMsg{})
case SearchRemoveItemMsg:
if item, ok := msg.Item.(modelItem); ok {
if m.isModelInRecentSection(item.model, msg.Index) {
m.app.State.RemoveModelFromRecentlyUsed(item.model.Provider.ID, item.model.Model.ID)
m.app.SaveState()
items := m.buildDisplayList(m.searchDialog.GetQuery())
m.searchDialog.SetItems(items)
}
}
return m, nil
case SearchQueryChangedMsg:
// Update the list based on search query
items := m.buildDisplayList(msg.Query)
@ -307,7 +319,7 @@ func (m *modelDialog) buildGroupedResults() []list.Item {
var items []list.Item
// Add Recent section
recentModels := m.getRecentModels(5)
recentModels := m.getRecentModels(maxRecentModels)
if len(recentModels) > 0 {
items = append(items, list.HeaderItem("Recent"))
for _, model := range recentModels {
@ -398,6 +410,28 @@ func (m *modelDialog) getRecentModels(limit int) []ModelWithProvider {
return recentModels
}
func (m *modelDialog) isModelInRecentSection(model ModelWithProvider, index int) bool {
// Only check if we're in grouped mode (no search query)
if m.searchDialog.GetQuery() != "" {
return false
}
recentModels := m.getRecentModels(maxRecentModels)
if len(recentModels) == 0 {
return false
}
// Index 0 is the "Recent" header, so recent models are at indices 1 to len(recentModels)
if index >= 1 && index <= len(recentModels) {
if index-1 < len(recentModels) {
recentModel := recentModels[index-1]
return recentModel.Provider.ID == model.Provider.ID && recentModel.Model.ID == model.Model.ID
}
}
return false
}
func (m *modelDialog) Render(background string) string {
return m.modal.Render(m.View(), background)
}

View file

@ -24,6 +24,12 @@ type SearchSelectionMsg struct {
// SearchCancelledMsg is emitted when the search is cancelled
type SearchCancelledMsg struct{}
// SearchRemoveItemMsg is emitted when Ctrl+X is pressed to remove an item
type SearchRemoveItemMsg struct {
Item any
Index int
}
// SearchDialog is a reusable component that combines a text input with a list
type SearchDialog struct {
textInput textinput.Model
@ -38,6 +44,7 @@ type searchKeyMap struct {
Down key.Binding
Enter key.Binding
Escape key.Binding
Remove key.Binding
}
var searchKeys = searchKeyMap{
@ -57,6 +64,10 @@ var searchKeys = searchKeyMap{
key.WithKeys("esc"),
key.WithHelp("esc", "cancel"),
),
Remove: key.NewBinding(
key.WithKeys("ctrl+x"),
key.WithHelp("ctrl+x", "remove from recent"),
),
}
// NewSearchDialog creates a new SearchDialog
@ -148,6 +159,13 @@ func (s *SearchDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
case key.Matches(msg, searchKeys.Remove):
if selectedItem, idx := s.list.GetSelectedItem(); idx != -1 {
return s, func() tea.Msg {
return SearchRemoveItemMsg{Item: selectedItem, Index: idx}
}
}
case key.Matches(msg, searchKeys.Up):
var cmd tea.Cmd
listModel, cmd := s.list.Update(msg)

View file

@ -69,6 +69,15 @@ func (s *State) UpdateModelUsage(providerID, modelID string) {
}
}
func (s *State) RemoveModelFromRecentlyUsed(providerID, modelID string) {
for i, usage := range s.RecentlyUsedModels {
if usage.ProviderID == providerID && usage.ModelID == modelID {
s.RecentlyUsedModels = append(s.RecentlyUsedModels[:i], s.RecentlyUsedModels[i+1:]...)
return
}
}
}
// SaveState writes the provided Config struct to the specified TOML file.
// It will create the file if it doesn't exist, or overwrite it if it does.
func SaveState(filePath string, state *State) error {