on main help sign in
feat(ui): model dialog: implement model selection handling
90491b4Ayman Bagabas
internal/config/config.go+5
@@ -54,6 +54,11 @@ var defaultContextPaths = []string{
54
54
55type SelectedModelType string
55type SelectedModelType string
56
56
57+// String returns the string representation of the [SelectedModelType].
58+func (s SelectedModelType) String() string {
59+ return string(s)
60+}
61+
57const (
62const (
58 SelectedModelTypeLarge SelectedModelType = "large"
63 SelectedModelTypeLarge SelectedModelType = "large"
59 SelectedModelTypeSmall SelectedModelType = "small"
64 SelectedModelTypeSmall SelectedModelType = "small"
internal/ui/dialog/messages.go+3−3
@@ -2,7 +2,7 @@ package dialog
2
2
3import (
3import (
4 tea "charm.land/bubbletea/v2"
4 tea "charm.land/bubbletea/v2"
5− "github.com/charmbracelet/catwalk/pkg/catwalk"
5+ "github.com/charmbracelet/crush/internal/config"
6 "github.com/charmbracelet/crush/internal/session"
6 "github.com/charmbracelet/crush/internal/session"
7)
7)
8
8
@@ -24,8 +24,8 @@ type SessionSelectedMsg struct {
24
24
25// ModelSelectedMsg is a message indicating a model has been selected.
25// ModelSelectedMsg is a message indicating a model has been selected.
26type ModelSelectedMsg struct {
26type ModelSelectedMsg struct {
27− Provider catwalk.Provider
27+ Model config.SelectedModel
28− Model catwalk.Model
28+ ModelType config.SelectedModelType
29}
29}
30
30
31// Messages for commands
31// Messages for commands
internal/ui/dialog/models.go+31−17
@@ -37,6 +37,30 @@ func (mt ModelType) String() string {
37 }
37 }
38}
38}
39
39
40+// Config returns the corresponding config model type.
41+func (mt ModelType) Config() config.SelectedModelType {
42+ switch mt {
43+ case ModelTypeLarge:
44+ return config.SelectedModelTypeLarge
45+ case ModelTypeSmall:
46+ return config.SelectedModelTypeSmall
47+ default:
48+ return ""
49+ }
50+}
51+
52+// Placeholder returns the input placeholder for the model type.
53+func (mt ModelType) Placeholder() string {
54+ switch mt {
55+ case ModelTypeLarge:
56+ return largeModelInputPlaceholder
57+ case ModelTypeSmall:
58+ return smallModelInputPlaceholder
59+ default:
60+ return ""
61+ }
62+}
63+
40const (
64const (
41 largeModelInputPlaceholder = "Choose a model for large, complex tasks"
65 largeModelInputPlaceholder = "Choose a model for large, complex tasks"
42 smallModelInputPlaceholder = "Choose a model for small, simple tasks"
66 smallModelInputPlaceholder = "Choose a model for small, simple tasks"
@@ -180,8 +204,8 @@ func (m *Models) Update(msg tea.Msg) tea.Msg {
180 }
204 }
181
205
182 return ModelSelectedMsg{
206 return ModelSelectedMsg{
183− Provider: modelItem.prov,
207+ Model: modelItem.SelectedModel(),
184− Model: modelItem.model,
208+ ModelType: modelItem.SelectedModelType(),
185 }
209 }
186 case key.Matches(msg, m.keyMap.Tab):
210 case key.Matches(msg, m.keyMap.Tab):
187 if m.modelType == ModelTypeLarge {
211 if m.modelType == ModelTypeLarge {
@@ -276,14 +300,8 @@ func (m *Models) setProviderItems() error {
276 t := m.com.Styles
300 t := m.com.Styles
277 cfg := m.com.Config()
301 cfg := m.com.Config()
278
302
279− selectedType := config.SelectedModelTypeLarge
280− if m.modelType == ModelTypeLarge {
281− selectedType = config.SelectedModelTypeLarge
282− } else {
283− selectedType = config.SelectedModelTypeSmall
284− }
285−
286 var selectedItemID string
303 var selectedItemID string
304+ selectedType := m.modelType.Config()
287 currentModel := cfg.Models[selectedType]
305 currentModel := cfg.Models[selectedType]
288 recentItems := cfg.RecentModels[selectedType]
306 recentItems := cfg.RecentModels[selectedType]
289
307
@@ -325,7 +343,7 @@ func (m *Models) setProviderItems() error {
325
343
326 group := NewModelGroup(t, name, true)
344 group := NewModelGroup(t, name, true)
327 for _, model := range p.Models {
345 for _, model := range p.Models {
328− item := NewModelItem(t, provider, model, false)
346+ item := NewModelItem(t, provider, model, m.modelType, false)
329 group.AppendItems(item)
347 group.AppendItems(item)
330 itemsMap[item.ID()] = item
348 itemsMap[item.ID()] = item
331 if model.ID == currentModel.Model && string(provider.ID) == currentModel.Provider {
349 if model.ID == currentModel.Model && string(provider.ID) == currentModel.Provider {
@@ -379,7 +397,7 @@ func (m *Models) setProviderItems() error {
379
397
380 group := NewModelGroup(t, name, providerConfigured)
398 group := NewModelGroup(t, name, providerConfigured)
381 for _, model := range displayProvider.Models {
399 for _, model := range displayProvider.Models {
382− item := NewModelItem(t, provider, model, false)
400+ item := NewModelItem(t, provider, model, m.modelType, false)
383 group.AppendItems(item)
401 group.AppendItems(item)
384 itemsMap[item.ID()] = item
402 itemsMap[item.ID()] = item
385 if model.ID == currentModel.Model && string(provider.ID) == currentModel.Provider {
403 if model.ID == currentModel.Model && string(provider.ID) == currentModel.Provider {
@@ -402,7 +420,7 @@ func (m *Models) setProviderItems() error {
402 }
420 }
403
421
404 // Show provider for recent items
422 // Show provider for recent items
405− item = NewModelItem(t, item.prov, item.model, true)
423+ item = NewModelItem(t, item.prov, item.model, m.modelType, true)
406 item.showProvider = true
424 item.showProvider = true
407
425
408 validRecentItems = append(validRecentItems, recent)
426 validRecentItems = append(validRecentItems, recent)
@@ -429,11 +447,7 @@ func (m *Models) setProviderItems() error {
429 m.list.SetSelectedItem(selectedItemID)
447 m.list.SetSelectedItem(selectedItemID)
430
448
431 // Update placeholder based on model type
449 // Update placeholder based on model type
432− if m.modelType == ModelTypeLarge {
450+ m.input.Placeholder = m.modelType.Placeholder()
433− m.input.Placeholder = largeModelInputPlaceholder
434− } else {
435− m.input.Placeholder = smallModelInputPlaceholder
436− }
437
451
438 return nil
452 return nil
439}
453}
internal/ui/dialog/models_item.go+21−3
@@ -3,6 +3,7 @@ package dialog
3import (
3import (
4 "charm.land/lipgloss/v2"
4 "charm.land/lipgloss/v2"
5 "github.com/charmbracelet/catwalk/pkg/catwalk"
5 "github.com/charmbracelet/catwalk/pkg/catwalk"
6+ "github.com/charmbracelet/crush/internal/config"
6 "github.com/charmbracelet/crush/internal/ui/common"
7 "github.com/charmbracelet/crush/internal/ui/common"
7 "github.com/charmbracelet/crush/internal/ui/styles"
8 "github.com/charmbracelet/crush/internal/ui/styles"
8 "github.com/charmbracelet/x/ansi"
9 "github.com/charmbracelet/x/ansi"
@@ -49,8 +50,9 @@ func (m *ModelGroup) Render(width int) string {
49
50
50// ModelItem represents a list item for a model type.
51// ModelItem represents a list item for a model type.
51type ModelItem struct {
52type ModelItem struct {
52− prov catwalk.Provider
53+ prov catwalk.Provider
53− model catwalk.Model
54+ model catwalk.Model
55+ modelType ModelType
54
56
55 cache map[int]string
57 cache map[int]string
56 t *styles.Styles
58 t *styles.Styles
@@ -59,13 +61,29 @@ type ModelItem struct {
59 showProvider bool
61 showProvider bool
60}
62}
61
63
64+// SelectedModel returns this model item as a [config.SelectedModel] instance.
65+func (m *ModelItem) SelectedModel() config.SelectedModel {
66+ return config.SelectedModel{
67+ Model: m.model.ID,
68+ Provider: string(m.prov.ID),
69+ ReasoningEffort: m.model.DefaultReasoningEffort,
70+ MaxTokens: m.model.DefaultMaxTokens,
71+ }
72+}
73+
74+// SelectedModelType returns the type of model represented by this item.
75+func (m *ModelItem) SelectedModelType() config.SelectedModelType {
76+ return m.modelType.Config()
77+}
78+
62var _ ListItem = &ModelItem{}
79var _ ListItem = &ModelItem{}
63
80
64// NewModelItem creates a new ModelItem.
81// NewModelItem creates a new ModelItem.
65−func NewModelItem(t *styles.Styles, prov catwalk.Provider, model catwalk.Model, showProvider bool) *ModelItem {
82+func NewModelItem(t *styles.Styles, prov catwalk.Provider, model catwalk.Model, typ ModelType, showProvider bool) *ModelItem {
66 return &ModelItem{
83 return &ModelItem{
67 prov: prov,
84 prov: prov,
68 model: model,
85 model: model,
86+ modelType: typ,
69 t: t,
87 t: t,
70 cache: make(map[int]string),
88 cache: make(map[int]string),
71 showProvider: showProvider,
89 showProvider: showProvider,
internal/ui/model/ui.go+21−1
@@ -574,7 +574,27 @@ func (m *UI) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
574 case dialog.QuitMsg:
574 case dialog.QuitMsg:
575 cmds = append(cmds, tea.Quit)
575 cmds = append(cmds, tea.Quit)
576 case dialog.ModelSelectedMsg:
576 case dialog.ModelSelectedMsg:
577− // TODO: Handle model switching
577+ if m.com.App.AgentCoordinator.IsBusy() {
578+ cmds = append(cmds, uiutil.ReportWarn("Agent is busy, please wait..."))
579+ break
580+ }
581+
582+ cfg := m.com.Config()
583+ if cfg == nil {
584+ cmds = append(cmds, uiutil.ReportError(errors.New("configuration not found")))
585+ break
586+ }
587+
588+ if err := cfg.UpdatePreferredModel(msg.ModelType, msg.Model); err != nil {
589+ cmds = append(cmds, uiutil.ReportError(err))
590+ }
591+
592+ // XXX: Should this be in a separate goroutine?
593+ go m.com.App.UpdateAgentModel(context.TODO())
594+
595+ modelMsg := fmt.Sprintf("%s model changed to %s", msg.ModelType, msg.Model.Model)
596+ cmds = append(cmds, uiutil.ReportInfo(modelMsg))
597+ m.dialog.CloseDialog(dialog.ModelsID)
578 }
598 }
579
599
580 return tea.Batch(cmds...)
600 return tea.Batch(cmds...)