9 tea "charm.land/bubbletea/v2"
10 "github.com/charmbracelet/catwalk/pkg/catwalk"
11 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/tui/exp/list"
13 "github.com/charmbracelet/crush/internal/tui/styles"
14 "github.com/charmbracelet/crush/internal/tui/util"
17type listModel = list.FilterableGroupList[list.CompletionItem[ModelOption]]
19type ModelListComponent struct {
22 providers []catwalk.Provider
25func modelKey(providerID, modelID string) string {
26 if providerID == "" || modelID == "" {
29 return providerID + ":" + modelID
32func NewModelListComponent(keyMap list.KeyMap, inputPlaceholder string, shouldResize bool) *ModelListComponent {
33 t := styles.CurrentTheme()
34 inputStyle := t.S().Base.PaddingLeft(1).PaddingBottom(1)
35 options := []list.ListOption{
36 list.WithKeyMap(keyMap),
37 list.WithWrapNavigation(),
40 options = append(options, list.WithResizeByList())
42 modelList := list.NewFilterableGroupedList(
43 []list.Group[list.CompletionItem[ModelOption]]{},
44 list.WithFilterInputStyle(inputStyle),
45 list.WithFilterPlaceholder(inputPlaceholder),
46 list.WithFilterListOptions(
51 return &ModelListComponent{
53 modelType: LargeModelType,
57func (m *ModelListComponent) Init() tea.Cmd {
59 if len(m.providers) == 0 {
61 providers, err := config.Providers(cfg)
62 filteredProviders := []catwalk.Provider{}
63 for _, p := range providers {
64 hasAPIKeyEnv := strings.HasPrefix(p.APIKey, "$")
65 if hasAPIKeyEnv && p.ID != catwalk.InferenceProviderAzure {
66 filteredProviders = append(filteredProviders, p)
70 m.providers = filteredProviders
72 cmds = append(cmds, util.ReportError(err))
75 cmds = append(cmds, m.list.Init(), m.SetModelType(m.modelType))
76 return tea.Batch(cmds...)
79func (m *ModelListComponent) Update(msg tea.Msg) (*ModelListComponent, tea.Cmd) {
80 u, cmd := m.list.Update(msg)
81 m.list = u.(listModel)
85func (m *ModelListComponent) View() string {
89func (m *ModelListComponent) Cursor() *tea.Cursor {
90 return m.list.Cursor()
93func (m *ModelListComponent) SetSize(width, height int) tea.Cmd {
94 return m.list.SetSize(width, height)
97func (m *ModelListComponent) SelectedModel() *ModelOption {
98 s := m.list.SelectedItem()
107func (m *ModelListComponent) SetModelType(modelType int) tea.Cmd {
108 t := styles.CurrentTheme()
109 m.modelType = modelType
111 var groups []list.Group[list.CompletionItem[ModelOption]]
112 // first none section
114 itemsByKey := make(map[string]list.CompletionItem[ModelOption])
117 var currentModel config.SelectedModel
118 selectedType := config.SelectedModelTypeLarge
119 if m.modelType == LargeModelType {
120 currentModel = cfg.Models[config.SelectedModelTypeLarge]
121 selectedType = config.SelectedModelTypeLarge
123 currentModel = cfg.Models[config.SelectedModelTypeSmall]
124 selectedType = config.SelectedModelTypeSmall
126 recentItems := cfg.RecentModels[selectedType]
128 configuredIcon := t.S().Base.Foreground(t.Success).Render(styles.CheckIcon)
129 configured := fmt.Sprintf("%s %s", configuredIcon, t.S().Subtle.Render("Configured"))
131 // Create a map to track which providers we've already added
132 addedProviders := make(map[string]bool)
134 // First, add any configured providers that are not in the known providers list
135 // These should appear at the top of the list
136 knownProviders, err := config.Providers(cfg)
138 return util.ReportError(err)
140 for providerID, providerConfig := range cfg.Providers.Seq2() {
141 if providerConfig.Disable {
145 // Check if this provider is not in the known providers list
146 if !slices.ContainsFunc(knownProviders, func(p catwalk.Provider) bool { return p.ID == catwalk.InferenceProvider(providerID) }) ||
147 !slices.ContainsFunc(m.providers, func(p catwalk.Provider) bool { return p.ID == catwalk.InferenceProvider(providerID) }) {
148 // Convert config provider to provider.Provider format
149 configProvider := catwalk.Provider{
150 Name: providerConfig.Name,
151 ID: catwalk.InferenceProvider(providerID),
152 Models: make([]catwalk.Model, len(providerConfig.Models)),
156 for i, model := range providerConfig.Models {
157 configProvider.Models[i] = catwalk.Model{
160 CostPer1MIn: model.CostPer1MIn,
161 CostPer1MOut: model.CostPer1MOut,
162 CostPer1MInCached: model.CostPer1MInCached,
163 CostPer1MOutCached: model.CostPer1MOutCached,
164 ContextWindow: model.ContextWindow,
165 DefaultMaxTokens: model.DefaultMaxTokens,
166 CanReason: model.CanReason,
167 ReasoningLevels: model.ReasoningLevels,
168 DefaultReasoningEffort: model.DefaultReasoningEffort,
169 SupportsImages: model.SupportsImages,
173 // Add this unknown provider to the list
174 name := configProvider.Name
176 name = string(configProvider.ID)
178 section := list.NewItemSection(name)
179 section.SetInfo(configured)
180 group := list.Group[list.CompletionItem[ModelOption]]{
183 for _, model := range configProvider.Models {
184 modelOption := ModelOption{
185 Provider: configProvider,
188 key := modelKey(string(configProvider.ID), model.ID)
189 item := list.NewCompletionItem(
192 list.WithCompletionID(key),
194 itemsByKey[key] = item
196 group.Items = append(group.Items, item)
197 if model.ID == currentModel.Model && string(configProvider.ID) == currentModel.Provider {
198 selectedItemID = item.ID()
201 groups = append(groups, group)
203 addedProviders[providerID] = true
207 // Then add the known providers from the predefined list
208 for _, provider := range m.providers {
209 // Skip if we already added this provider as an unknown provider
210 if addedProviders[string(provider.ID)] {
214 providerConfig, providerConfigured := cfg.Providers.Get(string(provider.ID))
215 if providerConfigured && providerConfig.Disable {
219 displayProvider := provider
220 if providerConfigured {
221 displayProvider.Name = cmp.Or(providerConfig.Name, displayProvider.Name)
222 modelIndex := make(map[string]int, len(displayProvider.Models))
223 for i, model := range displayProvider.Models {
224 modelIndex[model.ID] = i
226 for _, model := range providerConfig.Models {
230 if idx, ok := modelIndex[model.ID]; ok {
231 if model.Name != "" {
232 displayProvider.Models[idx].Name = model.Name
236 if model.Name == "" {
237 model.Name = model.ID
239 displayProvider.Models = append(displayProvider.Models, model)
240 modelIndex[model.ID] = len(displayProvider.Models) - 1
244 name := displayProvider.Name
246 name = string(displayProvider.ID)
249 section := list.NewItemSection(name)
250 if providerConfigured {
251 section.SetInfo(configured)
253 group := list.Group[list.CompletionItem[ModelOption]]{
256 for _, model := range displayProvider.Models {
257 modelOption := ModelOption{
258 Provider: displayProvider,
261 key := modelKey(string(displayProvider.ID), model.ID)
262 item := list.NewCompletionItem(
265 list.WithCompletionID(key),
267 itemsByKey[key] = item
268 group.Items = append(group.Items, item)
269 if model.ID == currentModel.Model && string(displayProvider.ID) == currentModel.Provider {
270 selectedItemID = item.ID()
273 groups = append(groups, group)
276 if len(recentItems) > 0 {
277 recentSection := list.NewItemSection("Recently used")
278 recentGroup := list.Group[list.CompletionItem[ModelOption]]{
279 Section: recentSection,
281 var validRecentItems []config.SelectedModel
282 for _, recent := range recentItems {
283 key := modelKey(recent.Provider, recent.Model)
284 option, ok := itemsByKey[key]
288 validRecentItems = append(validRecentItems, recent)
289 recentID := fmt.Sprintf("recent::%s", key)
290 modelOption := option.Value()
291 providerName := modelOption.Provider.Name
292 if providerName == "" {
293 providerName = string(modelOption.Provider.ID)
295 item := list.NewCompletionItem(
296 modelOption.Model.Name,
298 list.WithCompletionID(recentID),
299 list.WithCompletionShortcut(providerName),
301 recentGroup.Items = append(recentGroup.Items, item)
302 if recent.Model == currentModel.Model && recent.Provider == currentModel.Provider {
303 selectedItemID = recentID
307 if len(validRecentItems) != len(recentItems) {
308 if err := cfg.SetConfigField(fmt.Sprintf("recent_models.%s", selectedType), validRecentItems); err != nil {
309 return util.ReportError(err)
313 if len(recentGroup.Items) > 0 {
314 groups = append([]list.Group[list.CompletionItem[ModelOption]]{recentGroup}, groups...)
320 cmd := m.list.SetGroups(groups)
323 cmds = append(cmds, cmd)
325 cmd = m.list.SetSelected(selectedItemID)
327 cmds = append(cmds, cmd)
330 return tea.Sequence(cmds...)
333// GetModelType returns the current model type
334func (m *ModelListComponent) GetModelType() int {
338func (m *ModelListComponent) SetInputPlaceholder(placeholder string) {
339 m.list.SetInputPlaceholder(placeholder)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list