on main help sign in
chore: implement large/small model switching
commit2a10ac99f755f4d25f8385b58d5d77e47fa747e4 authorKujtim Hoxha <kujtimii.h@gmail.com> dateMon 30 Jun 2025, 14:06 (1 year ago) parent21719ee
4 files changed+98−38
go.mod+1−1 internal/tui/components/dialogs/models/keys.go+8−2 internal/tui/components/dialogs/models/models.go+83−33 internal/tui/tui.go+6−2
go.mod+1−1
@@ -24,6 +24,7 @@ require (
2424 github.com/fsnotify/fsnotify v1.8.0
2525 github.com/go-logfmt/logfmt v0.6.0
2626 github.com/google/uuid v1.6.0
27+ github.com/invopop/jsonschema v0.13.0
2728 github.com/mark3labs/mcp-go v0.17.0
2829 github.com/muesli/termenv v0.16.0
2930 github.com/ncruces/go-sqlite3 v0.25.0
@@ -42,7 +43,6 @@ require (
4243require (
4344 github.com/bahlo/generic-list-go v0.2.0 // indirect
4445 github.com/buger/jsonparser v1.1.1 // indirect
45− github.com/invopop/jsonschema v0.13.0 // indirect
4646 github.com/mailru/easyjson v0.7.7 // indirect
4747 github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
4848)
internal/tui/components/dialogs/models/keys.go+8−2
@@ -8,13 +8,14 @@ type KeyMap struct {
88 Select,
99 Next,
1010 Previous,
11+ Tab,
1112 Close key.Binding
1213}
1314
1415func DefaultKeyMap() KeyMap {
1516 return KeyMap{
1617 Select: key.NewBinding(
17− key.WithKeys("enter", "tab", "ctrl+y"),
18+ key.WithKeys("enter", "ctrl+y"),
1819 key.WithHelp("enter", "confirm"),
1920 ),
2021 Next: key.NewBinding(
@@ -25,6 +26,10 @@ func DefaultKeyMap() KeyMap {
2526 key.WithKeys("up", "ctrl+p"),
2627 key.WithHelp("↑", "previous item"),
2728 ),
29+ Tab: key.NewBinding(
30+ key.WithKeys("tab"),
31+ key.WithHelp("tab", "toggle model type"),
32+ ),
2833 Close: key.NewBinding(
2934 key.WithKeys("esc"),
3035 key.WithHelp("esc", "cancel"),
@@ -38,6 +43,7 @@ func (k KeyMap) KeyBindings() []key.Binding {
3843 k.Select,
3944 k.Next,
4045 k.Previous,
46+ k.Tab,
4147 k.Close,
4248 }
4349}
@@ -57,10 +63,10 @@ func (k KeyMap) FullHelp() [][]key.Binding {
5763func (k KeyMap) ShortHelp() []key.Binding {
5864 return []key.Binding{
5965 key.NewBinding(
60−
6166 key.WithKeys("down", "up"),
6267 key.WithHelp("↑↓", "choose"),
6368 ),
69+ k.Tab,
6470 k.Select,
6571 k.Close,
6672 }
internal/tui/components/dialogs/models/models.go+83−33
@@ -22,9 +22,15 @@ const (
2222 defaultWidth = 60
2323)
2424
25+const (
26+ LargeModelType int = iota
27+ SmallModelType
28+)
29+
2530// ModelSelectedMsg is sent when a model is selected
2631type ModelSelectedMsg struct {
27− Model config.PreferredModel
32+ Model config.PreferredModel
33+ ModelType config.ModelType
2834}
2935
3036// CloseModelDialogMsg is sent when a model is selected
@@ -42,12 +48,13 @@ type ModelOption struct {
4248
4349type modelDialogCmp struct {
4450 width int
45− wWidth int // Width of the terminal window
46− wHeight int // Height of the terminal window
51+ wWidth int
52+ wHeight int
4753
4854 modelList list.ListModel
4955 keyMap KeyMap
5056 help help.Model
57+ modelType int
5158}
5259
5360func NewModelDialogCmp() ModelDialog {
@@ -80,34 +87,13 @@ func NewModelDialogCmp() ModelDialog {
8087 width: defaultWidth,
8188 keyMap: DefaultKeyMap(),
8289 help: help,
90+ modelType: LargeModelType,
8391 }
8492}
8593
8694func (m *modelDialogCmp) Init() tea.Cmd {
87− providers := config.Providers()
88−
89− modelItems := []util.Model{}
90− selectIndex := 0
91− agentModel := config.GetAgentModel(config.AgentCoder)
92− agentProvider := config.GetAgentProvider(config.AgentCoder)
93− for _, provider := range providers {
94− name := provider.Name
95− if name == "" {
96− name = string(provider.ID)
97− }
98− modelItems = append(modelItems, commands.NewItemSection(name))
99− for _, model := range provider.Models {
100− modelItems = append(modelItems, completions.NewCompletionItem(model.Name, ModelOption{
101− Provider: provider,
102− Model: model,
103− }))
104− if model.ID == agentModel.ID && provider.ID == agentProvider.ID {
105− selectIndex = len(modelItems) - 1 // Set the selected index to the current model
106− }
107− }
108− }
109−
110− return tea.Sequence(m.modelList.Init(), m.modelList.SetItems(modelItems), m.modelList.SetSelected(selectIndex))
95+ m.SetModelType(m.modelType)
96+ return m.modelList.Init()
11197}
11298
11399func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -115,24 +101,41 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
115101 case tea.WindowSizeMsg:
116102 m.wWidth = msg.Width
117103 m.wHeight = msg.Height
104+ m.SetModelType(m.modelType)
118105 return m, m.modelList.SetSize(m.listWidth(), m.listHeight())
119106 case tea.KeyPressMsg:
120107 switch {
121108 case key.Matches(msg, m.keyMap.Select):
122109 selectedItemInx := m.modelList.SelectedIndex()
123110 if selectedItemInx == list.NoSelection {
124− return m, nil // No item selected, do nothing
111+ return m, nil
125112 }
126113 items := m.modelList.Items()
127114 selectedItem := items[selectedItemInx].(completions.CompletionItem).Value().(ModelOption)
128115
116+ var modelType config.ModelType
117+ if m.modelType == LargeModelType {
118+ modelType = config.LargeModel
119+ } else {
120+ modelType = config.SmallModel
121+ }
122+
129123 return m, tea.Sequence(
130124 util.CmdHandler(dialogs.CloseDialogMsg{}),
131− util.CmdHandler(ModelSelectedMsg{Model: config.PreferredModel{
132− ModelID: selectedItem.Model.ID,
133− Provider: selectedItem.Provider.ID,
134− }}),
125+ util.CmdHandler(ModelSelectedMsg{
126+ Model: config.PreferredModel{
127+ ModelID: selectedItem.Model.ID,
128+ Provider: selectedItem.Provider.ID,
129+ },
130+ ModelType: modelType,
131+ }),
135132 )
133+ case key.Matches(msg, m.keyMap.Tab):
134+ if m.modelType == LargeModelType {
135+ return m, m.SetModelType(SmallModelType)
136+ } else {
137+ return m, m.SetModelType(LargeModelType)
138+ }
136139 case key.Matches(msg, m.keyMap.Close):
137140 return m, util.CmdHandler(dialogs.CloseDialogMsg{})
138141 default:
@@ -147,9 +150,10 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
147150func (m *modelDialogCmp) View() tea.View {
148151 t := styles.CurrentTheme()
149152 listView := m.modelList.View()
153+ radio := m.modelTypeRadio()
150154 content := lipgloss.JoinVertical(
151155 lipgloss.Left,
152− t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Switch Model", m.width-4)),
156+ t.S().Base.Padding(0, 1, 1, 1).Render(core.Title("Switch Model", m.width-lipgloss.Width(radio)-5)+" "+radio),
153157 listView.String(),
154158 "",
155159 t.S().Base.Width(m.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(m.help.View(m.keyMap)),
@@ -197,3 +201,49 @@ func (m *modelDialogCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
197201func (m *modelDialogCmp) ID() dialogs.DialogID {
198202 return ModelsDialogID
199203}
204+
205+func (m *modelDialogCmp) modelTypeRadio() string {
206+ t := styles.CurrentTheme()
207+ choices := []string{"Large", "Small"}
208+ iconSelected := "◉"
209+ iconUnselected := "○"
210+ if m.modelType == LargeModelType {
211+ return t.S().Base.Foreground(t.FgHalfMuted).Render(iconSelected + " " + choices[0] + " " + iconUnselected + " " + choices[1])
212+ }
213+ return t.S().Base.Foreground(t.FgHalfMuted).Render(iconUnselected + " " + choices[0] + " " + iconSelected + " " + choices[1])
214+}
215+
216+func (m *modelDialogCmp) SetModelType(modelType int) tea.Cmd {
217+ m.modelType = modelType
218+
219+ providers := config.Providers()
220+ modelItems := []util.Model{}
221+ selectIndex := 0
222+
223+ cfg := config.Get()
224+ var currentModel config.PreferredModel
225+ if m.modelType == LargeModelType {
226+ currentModel = cfg.Models.Large
227+ } else {
228+ currentModel = cfg.Models.Small
229+ }
230+
231+ for _, provider := range providers {
232+ name := provider.Name
233+ if name == "" {
234+ name = string(provider.ID)
235+ }
236+ modelItems = append(modelItems, commands.NewItemSection(name))
237+ for _, model := range provider.Models {
238+ modelItems = append(modelItems, completions.NewCompletionItem(model.Name, ModelOption{
239+ Provider: provider,
240+ Model: model,
241+ }))
242+ if model.ID == currentModel.ModelID && provider.ID == currentModel.Provider {
243+ selectIndex = len(modelItems) - 1 // Set the selected index to the current model
244+ }
245+ }
246+ }
247+
248+ return tea.Sequence(m.modelList.SetItems(modelItems), m.modelList.SetSelected(selectIndex))
249+}
internal/tui/tui.go+6−2
@@ -172,7 +172,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
172172
173173 // Model Switch
174174 case models.ModelSelectedMsg:
175− config.UpdatePreferredModel(config.LargeModel, msg.Model)
175+ config.UpdatePreferredModel(msg.ModelType, msg.Model)
176176
177177 // Update the agent with the new model/provider configuration
178178 if err := a.app.UpdateAgentModel(); err != nil {
@@ -180,7 +180,11 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
180180 return a, util.ReportError(fmt.Errorf("model changed to %s but failed to update agent: %v", msg.Model.ModelID, err))
181181 }
182182
183− return a, util.ReportInfo(fmt.Sprintf("Model changed to %s", msg.Model.ModelID))
183+ modelTypeName := "large"
184+ if msg.ModelType == config.SmallModel {
185+ modelTypeName = "small"
186+ }
187+ return a, util.ReportInfo(fmt.Sprintf("%s model changed to %s", modelTypeName, msg.Model.ModelID))
184188
185189 // File Picker
186190 case chat.OpenFilePickerMsg: