|
|
@@ -11,6 +11,7 @@ import (
|
|
|
tea "github.com/charmbracelet/bubbletea/v2"
|
|
|
"github.com/charmbracelet/lipgloss/v2"
|
|
|
"github.com/sst/opencode/internal/app"
|
|
|
+ "github.com/sst/opencode/internal/components/list"
|
|
|
"github.com/sst/opencode/internal/components/modal"
|
|
|
"github.com/sst/opencode/internal/layout"
|
|
|
"github.com/sst/opencode/internal/styles"
|
|
|
@@ -33,20 +34,15 @@ type modelDialog struct {
|
|
|
app *app.App
|
|
|
availableProviders []client.ProviderInfo
|
|
|
provider client.ProviderInfo
|
|
|
-
|
|
|
- selectedIdx int
|
|
|
- width int
|
|
|
- height int
|
|
|
- scrollOffset int
|
|
|
- hScrollOffset int
|
|
|
- hScrollPossible bool
|
|
|
-
|
|
|
- modal *modal.Modal
|
|
|
+ width int
|
|
|
+ height int
|
|
|
+ hScrollOffset int
|
|
|
+ hScrollPossible bool
|
|
|
+ modal *modal.Modal
|
|
|
+ modelList list.List[list.StringItem]
|
|
|
}
|
|
|
|
|
|
type modelKeyMap struct {
|
|
|
- Up key.Binding
|
|
|
- Down key.Binding
|
|
|
Left key.Binding
|
|
|
Right key.Binding
|
|
|
Enter key.Binding
|
|
|
@@ -54,14 +50,6 @@ type modelKeyMap struct {
|
|
|
}
|
|
|
|
|
|
var modelKeys = modelKeyMap{
|
|
|
- Up: key.NewBinding(
|
|
|
- key.WithKeys("up", "k"),
|
|
|
- key.WithHelp("↑", "previous model"),
|
|
|
- ),
|
|
|
- Down: key.NewBinding(
|
|
|
- key.WithKeys("down", "j"),
|
|
|
- key.WithHelp("↓", "next model"),
|
|
|
- ),
|
|
|
Left: key.NewBinding(
|
|
|
key.WithKeys("left", "h"),
|
|
|
key.WithHelp("←", "scroll left"),
|
|
|
@@ -81,15 +69,7 @@ var modelKeys = modelKeyMap{
|
|
|
}
|
|
|
|
|
|
func (m *modelDialog) Init() tea.Cmd {
|
|
|
- // cfg := config.Get()
|
|
|
- // modelInfo := GetSelectedModel(cfg)
|
|
|
- // m.availableProviders = getEnabledProviders(cfg)
|
|
|
- // m.hScrollPossible = len(m.availableProviders) > 1
|
|
|
-
|
|
|
- // m.provider = modelInfo.Provider
|
|
|
- // m.hScrollOffset = findProviderIndex(m.availableProviders, m.provider)
|
|
|
-
|
|
|
- // m.setupModelsForProvider(m.provider)
|
|
|
+ m.setupModelsForProvider(m.provider.Id)
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
@@ -97,26 +77,32 @@ func (m *modelDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
switch msg := msg.(type) {
|
|
|
case tea.KeyMsg:
|
|
|
switch {
|
|
|
- case key.Matches(msg, modelKeys.Up):
|
|
|
- m.moveSelectionUp()
|
|
|
- case key.Matches(msg, modelKeys.Down):
|
|
|
- m.moveSelectionDown()
|
|
|
case key.Matches(msg, modelKeys.Left):
|
|
|
if m.hScrollPossible {
|
|
|
m.switchProvider(-1)
|
|
|
}
|
|
|
+ return m, nil
|
|
|
case key.Matches(msg, modelKeys.Right):
|
|
|
if m.hScrollPossible {
|
|
|
m.switchProvider(1)
|
|
|
}
|
|
|
+ return m, nil
|
|
|
case key.Matches(msg, modelKeys.Enter):
|
|
|
+ selectedItem, _ := m.modelList.GetSelectedItem()
|
|
|
models := m.models()
|
|
|
+ var selectedModel client.ModelInfo
|
|
|
+ for _, model := range models {
|
|
|
+ if model.Name == string(selectedItem) {
|
|
|
+ selectedModel = model
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
return m, tea.Sequence(
|
|
|
util.CmdHandler(modal.CloseModalMsg{}),
|
|
|
util.CmdHandler(
|
|
|
app.ModelSelectedMsg{
|
|
|
Provider: m.provider,
|
|
|
- Model: models[m.selectedIdx],
|
|
|
+ Model: selectedModel,
|
|
|
}),
|
|
|
)
|
|
|
case key.Matches(msg, modelKeys.Escape):
|
|
|
@@ -127,7 +113,10 @@ func (m *modelDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
m.height = msg.Height
|
|
|
}
|
|
|
|
|
|
- return m, nil
|
|
|
+ // Update the list component
|
|
|
+ updatedList, cmd := m.modelList.Update(msg)
|
|
|
+ m.modelList = updatedList.(list.List[list.StringItem])
|
|
|
+ return m, cmd
|
|
|
}
|
|
|
|
|
|
func (m *modelDialog) models() []client.ModelInfo {
|
|
|
@@ -137,40 +126,9 @@ func (m *modelDialog) models() []client.ModelInfo {
|
|
|
return models
|
|
|
}
|
|
|
|
|
|
-// moveSelectionUp moves the selection up or wraps to bottom
|
|
|
-func (m *modelDialog) moveSelectionUp() {
|
|
|
- if m.selectedIdx > 0 {
|
|
|
- m.selectedIdx--
|
|
|
- } else {
|
|
|
- m.selectedIdx = len(m.provider.Models) - 1
|
|
|
- m.scrollOffset = max(0, len(m.provider.Models)-numVisibleModels)
|
|
|
- }
|
|
|
-
|
|
|
- // Keep selection visible
|
|
|
- if m.selectedIdx < m.scrollOffset {
|
|
|
- m.scrollOffset = m.selectedIdx
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// moveSelectionDown moves the selection down or wraps to top
|
|
|
-func (m *modelDialog) moveSelectionDown() {
|
|
|
- if m.selectedIdx < len(m.provider.Models)-1 {
|
|
|
- m.selectedIdx++
|
|
|
- } else {
|
|
|
- m.selectedIdx = 0
|
|
|
- m.scrollOffset = 0
|
|
|
- }
|
|
|
-
|
|
|
- // Keep selection visible
|
|
|
- if m.selectedIdx >= m.scrollOffset+numVisibleModels {
|
|
|
- m.scrollOffset = m.selectedIdx - (numVisibleModels - 1)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
func (m *modelDialog) switchProvider(offset int) {
|
|
|
newOffset := m.hScrollOffset + offset
|
|
|
|
|
|
- // Ensure we stay within bounds
|
|
|
if newOffset < 0 {
|
|
|
newOffset = len(m.availableProviders) - 1
|
|
|
}
|
|
|
@@ -185,105 +143,46 @@ func (m *modelDialog) switchProvider(offset int) {
|
|
|
}
|
|
|
|
|
|
func (m *modelDialog) View() string {
|
|
|
- t := theme.CurrentTheme()
|
|
|
- baseStyle := lipgloss.NewStyle().
|
|
|
- Background(t.BackgroundElement()).
|
|
|
- Foreground(t.Text())
|
|
|
-
|
|
|
- // Render visible models
|
|
|
- endIdx := min(m.scrollOffset+numVisibleModels, len(m.provider.Models))
|
|
|
- modelItems := make([]string, 0, endIdx-m.scrollOffset)
|
|
|
-
|
|
|
- models := m.models()
|
|
|
- for i := m.scrollOffset; i < endIdx; i++ {
|
|
|
- itemStyle := baseStyle.Width(maxDialogWidth)
|
|
|
- if i == m.selectedIdx {
|
|
|
- itemStyle = itemStyle.
|
|
|
- Background(t.Primary()).
|
|
|
- Foreground(t.BackgroundElement()).
|
|
|
- Bold(true)
|
|
|
- }
|
|
|
- modelItems = append(modelItems, itemStyle.Render(models[i].Name))
|
|
|
- }
|
|
|
-
|
|
|
+ listView := m.modelList.View()
|
|
|
scrollIndicator := m.getScrollIndicators(maxDialogWidth)
|
|
|
-
|
|
|
- content := lipgloss.JoinVertical(
|
|
|
- lipgloss.Left,
|
|
|
- baseStyle.
|
|
|
- Width(maxDialogWidth).
|
|
|
- Render(lipgloss.JoinVertical(lipgloss.Left, modelItems...)),
|
|
|
- scrollIndicator,
|
|
|
- )
|
|
|
-
|
|
|
- return content
|
|
|
+ return strings.Join([]string{listView, scrollIndicator}, "\n")
|
|
|
}
|
|
|
|
|
|
func (m *modelDialog) getScrollIndicators(maxWidth int) string {
|
|
|
var indicator string
|
|
|
-
|
|
|
- if len(m.provider.Models) > numVisibleModels {
|
|
|
- if m.scrollOffset > 0 {
|
|
|
- indicator += "↑ "
|
|
|
- }
|
|
|
- if m.scrollOffset+numVisibleModels < len(m.provider.Models) {
|
|
|
- indicator += "↓ "
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
if m.hScrollPossible {
|
|
|
- indicator = "← " + indicator + "→"
|
|
|
+ indicator = "← → (switch provider) "
|
|
|
}
|
|
|
-
|
|
|
if indicator == "" {
|
|
|
return ""
|
|
|
}
|
|
|
|
|
|
t := theme.CurrentTheme()
|
|
|
- baseStyle := styles.BaseStyle()
|
|
|
-
|
|
|
- return baseStyle.
|
|
|
- Foreground(t.Primary()).
|
|
|
+ return styles.BaseStyle().
|
|
|
+ Foreground(t.TextMuted()).
|
|
|
Width(maxWidth).
|
|
|
Align(lipgloss.Right).
|
|
|
- Bold(true).
|
|
|
Render(indicator)
|
|
|
}
|
|
|
|
|
|
-// findProviderIndex returns the index of the provider in the list, or -1 if not found
|
|
|
-// func findProviderIndex(providers []string, provider string) int {
|
|
|
-// for i, p := range providers {
|
|
|
-// if p == provider {
|
|
|
-// return i
|
|
|
-// }
|
|
|
-// }
|
|
|
-// return -1
|
|
|
-// }
|
|
|
-
|
|
|
-func (m *modelDialog) setupModelsForProvider(_ string) {
|
|
|
- m.selectedIdx = 0
|
|
|
- m.scrollOffset = 0
|
|
|
-
|
|
|
- // cfg := config.Get()
|
|
|
- // agentCfg := cfg.Agents[config.AgentPrimary]
|
|
|
- // selectedModelId := agentCfg.Model
|
|
|
+func (m *modelDialog) setupModelsForProvider(providerId string) {
|
|
|
+ models := m.models()
|
|
|
+ modelNames := make([]string, len(models))
|
|
|
+ for i, model := range models {
|
|
|
+ modelNames[i] = model.Name
|
|
|
+ }
|
|
|
|
|
|
- // m.provider = provider
|
|
|
- // m.models = getModelsForProvider(provider)
|
|
|
+ m.modelList = list.NewStringList(modelNames, numVisibleModels, "No models available", true)
|
|
|
+ m.modelList.SetMaxWidth(maxDialogWidth)
|
|
|
|
|
|
- // Try to select the current model if it belongs to this provider
|
|
|
- // if provider == models.SupportedModels[selectedModelId].Provider {
|
|
|
- // for i, model := range m.models {
|
|
|
- // if model.ID == selectedModelId {
|
|
|
- // m.selectedIdx = i
|
|
|
- // // Adjust scroll position to keep selected model visible
|
|
|
- // if m.selectedIdx >= numVisibleModels {
|
|
|
- // m.scrollOffset = m.selectedIdx - (numVisibleModels - 1)
|
|
|
- // }
|
|
|
- // break
|
|
|
- // }
|
|
|
- // }
|
|
|
- // }
|
|
|
+ if m.app.Provider != nil && m.app.Model != nil && m.app.Provider.Id == providerId {
|
|
|
+ for i, model := range models {
|
|
|
+ if model.Id == m.app.Model.Id {
|
|
|
+ m.modelList.SetSelectedIndex(i)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
func (m *modelDialog) Render(background string) string {
|
|
|
@@ -297,11 +196,30 @@ func (s *modelDialog) Close() tea.Cmd {
|
|
|
func NewModelDialog(app *app.App) ModelDialog {
|
|
|
availableProviders, _ := app.ListProviders(context.Background())
|
|
|
|
|
|
- return &modelDialog{
|
|
|
+ currentProvider := availableProviders[0]
|
|
|
+ hScrollOffset := 0
|
|
|
+ if app.Provider != nil {
|
|
|
+ for i, provider := range availableProviders {
|
|
|
+ if provider.Id == app.Provider.Id {
|
|
|
+ currentProvider = provider
|
|
|
+ hScrollOffset = i
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ dialog := &modelDialog{
|
|
|
+ app: app,
|
|
|
availableProviders: availableProviders,
|
|
|
- hScrollOffset: 0,
|
|
|
+ hScrollOffset: hScrollOffset,
|
|
|
hScrollPossible: len(availableProviders) > 1,
|
|
|
- provider: availableProviders[0],
|
|
|
- modal: modal.New(modal.WithTitle(fmt.Sprintf("Select %s Model", availableProviders[0].Name))),
|
|
|
+ provider: currentProvider,
|
|
|
+ modal: modal.New(
|
|
|
+ modal.WithTitle(fmt.Sprintf("Select %s Model", currentProvider.Name)),
|
|
|
+ modal.WithMaxWidth(maxDialogWidth+4),
|
|
|
+ ),
|
|
|
}
|
|
|
+
|
|
|
+ dialog.setupModelsForProvider(currentProvider.Id)
|
|
|
+ return dialog
|
|
|
}
|