on main help sign in
chore: implement large/small model switching
2a10ac9Kujtim Hoxha
go.mod+1−1
@@ -24,6 +24,7 @@ require (
24 github.com/fsnotify/fsnotify v1.8.0
24 github.com/fsnotify/fsnotify v1.8.0
25 github.com/go-logfmt/logfmt v0.6.0
25 github.com/go-logfmt/logfmt v0.6.0
26 github.com/google/uuid v1.6.0
26 github.com/google/uuid v1.6.0
27+ github.com/invopop/jsonschema v0.13.0
27 github.com/mark3labs/mcp-go v0.17.0
28 github.com/mark3labs/mcp-go v0.17.0
28 github.com/muesli/termenv v0.16.0
29 github.com/muesli/termenv v0.16.0
29 github.com/ncruces/go-sqlite3 v0.25.0
30 github.com/ncruces/go-sqlite3 v0.25.0
@@ -42,7 +43,6 @@ require (
42require (
43require (
43 github.com/bahlo/generic-list-go v0.2.0 // indirect
44 github.com/bahlo/generic-list-go v0.2.0 // indirect
44 github.com/buger/jsonparser v1.1.1 // indirect
45 github.com/buger/jsonparser v1.1.1 // indirect
45− github.com/invopop/jsonschema v0.13.0 // indirect
46 github.com/mailru/easyjson v0.7.7 // indirect
46 github.com/mailru/easyjson v0.7.7 // indirect
47 github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
47 github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
48)
48)
internal/tui/components/dialogs/models/keys.go+8−2
@@ -8,13 +8,14 @@ type KeyMap struct {
8 Select,
8 Select,
9 Next,
9 Next,
10 Previous,
10 Previous,
11+ Tab,
11 Close key.Binding
12 Close key.Binding
12}
13}
13
14
14func DefaultKeyMap() KeyMap {
15func DefaultKeyMap() KeyMap {
15 return KeyMap{
16 return KeyMap{
16 Select: key.NewBinding(
17 Select: key.NewBinding(
17− key.WithKeys("enter", "tab", "ctrl+y"),
18+ key.WithKeys("enter", "ctrl+y"),
18 key.WithHelp("enter", "confirm"),
19 key.WithHelp("enter", "confirm"),
19 ),
20 ),
20 Next: key.NewBinding(
21 Next: key.NewBinding(
@@ -25,6 +26,10 @@ func DefaultKeyMap() KeyMap {
25 key.WithKeys("up", "ctrl+p"),
26 key.WithKeys("up", "ctrl+p"),
26 key.WithHelp("↑", "previous item"),
27 key.WithHelp("↑", "previous item"),
27 ),
28 ),
29+ Tab: key.NewBinding(
30+ key.WithKeys("tab"),
31+ key.WithHelp("tab", "toggle model type"),
32+ ),
28 Close: key.NewBinding(
33 Close: key.NewBinding(
29 key.WithKeys("esc"),
34 key.WithKeys("esc"),
30 key.WithHelp("esc", "cancel"),
35 key.WithHelp("esc", "cancel"),
@@ -38,6 +43,7 @@ func (k KeyMap) KeyBindings() []key.Binding {
38 k.Select,
43 k.Select,
39 k.Next,
44 k.Next,
40 k.Previous,
45 k.Previous,
46+ k.Tab,
41 k.Close,
47 k.Close,
42 }
48 }
43}
49}
@@ -57,10 +63,10 @@ func (k KeyMap) FullHelp() [][]key.Binding {
57func (k KeyMap) ShortHelp() []key.Binding {
63func (k KeyMap) ShortHelp() []key.Binding {
58 return []key.Binding{
64 return []key.Binding{
59 key.NewBinding(
65 key.NewBinding(
60−
61 key.WithKeys("down", "up"),
66 key.WithKeys("down", "up"),
62 key.WithHelp("↑↓", "choose"),
67 key.WithHelp("↑↓", "choose"),
63 ),
68 ),
69+ k.Tab,
64 k.Select,
70 k.Select,
65 k.Close,
71 k.Close,
66 }
72 }
internal/tui/components/dialogs/models/models.go+83−33
@@ -22,9 +22,15 @@ const (
22 defaultWidth = 60
22 defaultWidth = 60
23)
23)
24
24
25+const (
26+ LargeModelType int = iota
27+ SmallModelType
28+)
29+
25// ModelSelectedMsg is sent when a model is selected
30// ModelSelectedMsg is sent when a model is selected
26type ModelSelectedMsg struct {
31type ModelSelectedMsg struct {
27− Model config.PreferredModel
32+ Model config.PreferredModel
33+ ModelType config.ModelType
28}
34}
29
35
30// CloseModelDialogMsg is sent when a model is selected
36// CloseModelDialogMsg is sent when a model is selected
@@ -42,12 +48,13 @@ type ModelOption struct {
42
48
43type modelDialogCmp struct {
49type modelDialogCmp struct {
44 width int
50 width int
45− wWidth int // Width of the terminal window
51+ wWidth int
46− wHeight int // Height of the terminal window
52+ wHeight int
47
53
48 modelList list.ListModel
54 modelList list.ListModel
49 keyMap KeyMap
55 keyMap KeyMap
50 help help.Model
56 help help.Model
57+ modelType int
51}
58}
52
59
53func NewModelDialogCmp() ModelDialog {
60func NewModelDialogCmp() ModelDialog {
@@ -80,34 +87,13 @@ func NewModelDialogCmp() ModelDialog {
80 width: defaultWidth,
87 width: defaultWidth,
81 keyMap: DefaultKeyMap(),
88 keyMap: DefaultKeyMap(),
82 help: help,
89 help: help,
90+ modelType: LargeModelType,
83 }
91 }
84}
92}
85
93
86func (m *modelDialogCmp) Init() tea.Cmd {
94func (m *modelDialogCmp) Init() tea.Cmd {
87− providers := config.Providers()
95+ m.SetModelType(m.modelType)
88−
96+ return m.modelList.Init()
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))
111}
97}
112
98
113func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
99func (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) {
115 case tea.WindowSizeMsg:
101 case tea.WindowSizeMsg:
116 m.wWidth = msg.Width
102 m.wWidth = msg.Width
117 m.wHeight = msg.Height
103 m.wHeight = msg.Height
104+ m.SetModelType(m.modelType)
118 return m, m.modelList.SetSize(m.listWidth(), m.listHeight())
105 return m, m.modelList.SetSize(m.listWidth(), m.listHeight())
119 case tea.KeyPressMsg:
106 case tea.KeyPressMsg:
120 switch {
107 switch {
121 case key.Matches(msg, m.keyMap.Select):
108 case key.Matches(msg, m.keyMap.Select):
122 selectedItemInx := m.modelList.SelectedIndex()
109 selectedItemInx := m.modelList.SelectedIndex()
123 if selectedItemInx == list.NoSelection {
110 if selectedItemInx == list.NoSelection {
124− return m, nil // No item selected, do nothing
111+ return m, nil
125 }
112 }
126 items := m.modelList.Items()
113 items := m.modelList.Items()
127 selectedItem := items[selectedItemInx].(completions.CompletionItem).Value().(ModelOption)
114 selectedItem := items[selectedItemInx].(completions.CompletionItem).Value().(ModelOption)
128
115
116+ var modelType config.ModelType
117+ if m.modelType == LargeModelType {
118+ modelType = config.LargeModel
119+ } else {
120+ modelType = config.SmallModel
121+ }
122+
129 return m, tea.Sequence(
123 return m, tea.Sequence(
130 util.CmdHandler(dialogs.CloseDialogMsg{}),
124 util.CmdHandler(dialogs.CloseDialogMsg{}),
131− util.CmdHandler(ModelSelectedMsg{Model: config.PreferredModel{
125+ util.CmdHandler(ModelSelectedMsg{
126+ Model: config.PreferredModel{
132− ModelID: selectedItem.Model.ID,
127+ ModelID: selectedItem.Model.ID,
133− Provider: selectedItem.Provider.ID,
128+ Provider: selectedItem.Provider.ID,
129+ },
130+ ModelType: modelType,
134− }}),
131+ }),
135 )
132 )
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+ }
136 case key.Matches(msg, m.keyMap.Close):
139 case key.Matches(msg, m.keyMap.Close):
137 return m, util.CmdHandler(dialogs.CloseDialogMsg{})
140 return m, util.CmdHandler(dialogs.CloseDialogMsg{})
138 default:
141 default:
@@ -147,9 +150,10 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
147func (m *modelDialogCmp) View() tea.View {
150func (m *modelDialogCmp) View() tea.View {
148 t := styles.CurrentTheme()
151 t := styles.CurrentTheme()
149 listView := m.modelList.View()
152 listView := m.modelList.View()
153+ radio := m.modelTypeRadio()
150 content := lipgloss.JoinVertical(
154 content := lipgloss.JoinVertical(
151 lipgloss.Left,
155 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),
153 listView.String(),
157 listView.String(),
154 "",
158 "",
155 t.S().Base.Width(m.width-2).PaddingLeft(1).AlignHorizontal(lipgloss.Left).Render(m.help.View(m.keyMap)),
159 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 {
197func (m *modelDialogCmp) ID() dialogs.DialogID {
201func (m *modelDialogCmp) ID() dialogs.DialogID {
198 return ModelsDialogID
202 return ModelsDialogID
199}
203}
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) {
172
172
173 // Model Switch
173 // Model Switch
174 case models.ModelSelectedMsg:
174 case models.ModelSelectedMsg:
175− config.UpdatePreferredModel(config.LargeModel, msg.Model)
175+ config.UpdatePreferredModel(msg.ModelType, msg.Model)
176
176
177 // Update the agent with the new model/provider configuration
177 // Update the agent with the new model/provider configuration
178 if err := a.app.UpdateAgentModel(); err != nil {
178 if err := a.app.UpdateAgentModel(); err != nil {
@@ -180,7 +180,11 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
180 return a, util.ReportError(fmt.Errorf("model changed to %s but failed to update agent: %v", msg.Model.ModelID, err))
180 return a, util.ReportError(fmt.Errorf("model changed to %s but failed to update agent: %v", msg.Model.ModelID, err))
181 }
181 }
182
182
183+ modelTypeName := "large"
184+ if msg.ModelType == config.SmallModel {
185+ modelTypeName = "small"
186+ }
183− return a, util.ReportInfo(fmt.Sprintf("Model changed to %s", msg.Model.ModelID))
187+ return a, util.ReportInfo(fmt.Sprintf("%s model changed to %s", modelTypeName, msg.Model.ModelID))
184
188
185 // File Picker
189 // File Picker
186 case chat.OpenFilePickerMsg:
190 case chat.OpenFilePickerMsg: