@@ -1,8 +1,6 @@
11package models
22
33import (
4− "slices"
5−
64 "github.com/charmbracelet/bubbles/v2/help"
75 "github.com/charmbracelet/bubbles/v2/key"
86 tea "github.com/charmbracelet/bubbletea/v2"
@@ -12,7 +10,6 @@ import (
1210 "github.com/charmbracelet/crush/internal/tui/components/core"
1311 "github.com/charmbracelet/crush/internal/tui/components/core/list"
1412 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
15− "github.com/charmbracelet/crush/internal/tui/components/dialogs/commands"
1613 "github.com/charmbracelet/crush/internal/tui/styles"
1714 "github.com/charmbracelet/crush/internal/tui/util"
1815 "github.com/charmbracelet/lipgloss/v2"
@@ -53,10 +50,9 @@ type modelDialogCmp struct {
5350 wWidth int
5451 wHeight int
5552
56− modelList list.ListModel
53+ modelList *ModelListComponent
5754 keyMap KeyMap
5855 help help.Model
59− modelType int
6056}
6157
6258func NewModelDialogCmp() ModelDialog {
@@ -75,12 +71,7 @@ func NewModelDialogCmp() ModelDialog {
7571
7672 t := styles.CurrentTheme()
7773 inputStyle := t.S().Base.Padding(0, 1, 0, 1)
78− modelList := list.New(
79− list.WithFilterable(true),
80− list.WithKeyMap(listKeyMap),
81− list.WithInputStyle(inputStyle),
82− list.WithWrapNavigation(true),
83− )
74+ modelList := NewModelListComponent(listKeyMap, inputStyle)
8475 help := help.New()
8576 help.Styles = t.S().Help
8677
@@ -89,12 +80,10 @@ func NewModelDialogCmp() ModelDialog {
8980 width: defaultWidth,
9081 keyMap: DefaultKeyMap(),
9182 help: help,
92− modelType: LargeModelType,
9383 }
9484}
9585
9686func (m *modelDialogCmp) Init() tea.Cmd {
97− m.SetModelType(m.modelType)
9887 return m.modelList.Init()
9988}
10089
@@ -103,7 +92,6 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
10392 case tea.WindowSizeMsg:
10493 m.wWidth = msg.Width
10594 m.wHeight = msg.Height
106− m.SetModelType(m.modelType)
10795 return m, m.modelList.SetSize(m.listWidth(), m.listHeight())
10896 case tea.KeyPressMsg:
10997 switch {
@@ -116,8 +104,8 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
116104 selectedItem := items[selectedItemInx].(completions.CompletionItem).Value().(ModelOption)
117105
118106 var modelType config.SelectedModelType
119− if m.modelType == LargeModelType {
120− modelType = config.SelectedModelTypeLarge
107+ if m.modelList.GetModelType() == LargeModelType {
108+ modelType = config.LargeModel
121109 } else {
122110 modelType = config.SelectedModelTypeSmall
123111 }
@@ -133,16 +121,16 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
133121 }),
134122 )
135123 case key.Matches(msg, m.keyMap.Tab):
136− if m.modelType == LargeModelType {
137− return m, m.SetModelType(SmallModelType)
124+ if m.modelList.GetModelType() == LargeModelType {
125+ return m, m.modelList.SetModelType(SmallModelType)
138126 } else {
139− return m, m.SetModelType(LargeModelType)
127+ return m, m.modelList.SetModelType(LargeModelType)
140128 }
141129 case key.Matches(msg, m.keyMap.Close):
142130 return m, util.CmdHandler(dialogs.CloseDialogMsg{})
143131 default:
144132 u, cmd := m.modelList.Update(msg)
145− m.modelList = u.(list.ListModel)
133+ m.modelList = u
146134 return m, cmd
147135 }
148136 }
@@ -181,7 +169,8 @@ func (m *modelDialogCmp) listWidth() int {
181169}
182170
183171func (m *modelDialogCmp) listHeight() int {
184− listHeigh := len(m.modelList.Items()) + 2 + 4 // height based on items + 2 for the input + 4 for the sections
172+ items := m.modelList.Items()
173+ listHeigh := len(items) + 2 + 4
185174 return min(listHeigh, m.wHeight/2)
186175}
187176
@@ -209,115 +198,8 @@ func (m *modelDialogCmp) modelTypeRadio() string {
209198 choices := []string{"Large Task", "Small Task"}
210199 iconSelected := "◉"
211200 iconUnselected := "○"
212− if m.modelType == LargeModelType {
201+ if m.modelList.GetModelType() == LargeModelType {
213202 return t.S().Base.Foreground(t.FgHalfMuted).Render(iconSelected + " " + choices[0] + " " + iconUnselected + " " + choices[1])
214203 }
215204 return t.S().Base.Foreground(t.FgHalfMuted).Render(iconUnselected + " " + choices[0] + " " + iconSelected + " " + choices[1])
216205}
217−
218−func (m *modelDialogCmp) SetModelType(modelType int) tea.Cmd {
219− m.modelType = modelType
220−
221− providers, err := config.Providers()
222− if err != nil {
223− return util.ReportError(err)
224− }
225−
226− modelItems := []util.Model{}
227− selectIndex := 0
228−
229− cfg := config.Get()
230− var currentModel config.SelectedModel
231− if m.modelType == LargeModelType {
232− currentModel = cfg.Models[config.SelectedModelTypeLarge]
233− } else {
234− currentModel = cfg.Models[config.SelectedModelTypeSmall]
235− }
236−
237− // Create a map to track which providers we've already added
238− addedProviders := make(map[string]bool)
239−
240− // First, add any configured providers that are not in the known providers list
241− // These should appear at the top of the list
242− knownProviders := provider.KnownProviders()
243− for providerID, providerConfig := range cfg.Providers {
244− if providerConfig.Disable {
245− continue
246− }
247−
248− // Check if this provider is not in the known providers list
249− if !slices.Contains(knownProviders, provider.InferenceProvider(providerID)) {
250− // Convert config provider to provider.Provider format
251− configProvider := provider.Provider{
252− Name: string(providerID), // Use provider ID as name for unknown providers
253− ID: provider.InferenceProvider(providerID),
254− Models: make([]provider.Model, len(providerConfig.Models)),
255− }
256−
257− // Convert models
258− for i, model := range providerConfig.Models {
259− configProvider.Models[i] = provider.Model{
260− ID: model.ID,
261− Name: model.Name,
262− CostPer1MIn: model.CostPer1MIn,
263− CostPer1MOut: model.CostPer1MOut,
264− CostPer1MInCached: model.CostPer1MInCached,
265− CostPer1MOutCached: model.CostPer1MOutCached,
266− ContextWindow: model.ContextWindow,
267− DefaultMaxTokens: model.DefaultMaxTokens,
268− CanReason: model.CanReason,
269− HasReasoningEffort: model.HasReasoningEffort,
270− DefaultReasoningEffort: model.DefaultReasoningEffort,
271− SupportsImages: model.SupportsImages,
272− }
273− }
274−
275− // Add this unknown provider to the list
276− name := configProvider.Name
277− if name == "" {
278− name = string(configProvider.ID)
279− }
280− modelItems = append(modelItems, commands.NewItemSection(name))
281− for _, model := range configProvider.Models {
282− modelItems = append(modelItems, completions.NewCompletionItem(model.Name, ModelOption{
283− Provider: configProvider,
284− Model: model,
285− }))
286− if model.ID == currentModel.Model && string(configProvider.ID) == currentModel.Provider {
287− selectIndex = len(modelItems) - 1 // Set the selected index to the current model
288− }
289− }
290− addedProviders[providerID] = true
291− }
292− }
293−
294− // Then add the known providers from the predefined list
295− for _, provider := range providers {
296− // Skip if we already added this provider as an unknown provider
297− if addedProviders[string(provider.ID)] {
298− continue
299− }
300−
301− // Check if this provider is configured and not disabled
302− if providerConfig, exists := cfg.Providers[string(provider.ID)]; exists && providerConfig.Disable {
303− continue
304− }
305−
306− name := provider.Name
307− if name == "" {
308− name = string(provider.ID)
309− }
310− modelItems = append(modelItems, commands.NewItemSection(name))
311− for _, model := range provider.Models {
312− modelItems = append(modelItems, completions.NewCompletionItem(model.Name, ModelOption{
313− Provider: provider,
314− Model: model,
315− }))
316− if model.ID == currentModel.Model && string(provider.ID) == currentModel.Provider {
317− selectIndex = len(modelItems) - 1 // Set the selected index to the current model
318− }
319− }
320− }
321−
322− return tea.Sequence(m.modelList.SetItems(modelItems), m.modelList.SetSelected(selectIndex))
323−}