|
|
@@ -1,14 +1,18 @@
|
|
|
package dialog
|
|
|
|
|
|
import (
|
|
|
+ "context"
|
|
|
+ "fmt"
|
|
|
+
|
|
|
"github.com/charmbracelet/bubbles/key"
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
|
- "github.com/sst/opencode/internal/config"
|
|
|
+ "github.com/sst/opencode/internal/tui/app"
|
|
|
"github.com/sst/opencode/internal/tui/layout"
|
|
|
"github.com/sst/opencode/internal/tui/styles"
|
|
|
"github.com/sst/opencode/internal/tui/theme"
|
|
|
"github.com/sst/opencode/internal/tui/util"
|
|
|
+ "github.com/sst/opencode/pkg/client"
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
@@ -16,24 +20,25 @@ const (
|
|
|
maxDialogWidth = 40
|
|
|
)
|
|
|
|
|
|
-// ModelSelectedMsg is sent when a model is selected
|
|
|
-type ModelSelectedMsg struct {
|
|
|
- // Model models.Model
|
|
|
-}
|
|
|
-
|
|
|
// CloseModelDialogMsg is sent when a model is selected
|
|
|
-type CloseModelDialogMsg struct{}
|
|
|
+type CloseModelDialogMsg struct {
|
|
|
+ Provider *client.ProviderInfo
|
|
|
+ Model *client.ProviderModel
|
|
|
+}
|
|
|
|
|
|
// ModelDialog interface for the model selection dialog
|
|
|
type ModelDialog interface {
|
|
|
tea.Model
|
|
|
layout.Bindings
|
|
|
+
|
|
|
+ SetProviders(providers []client.ProviderInfo)
|
|
|
}
|
|
|
|
|
|
type modelDialogCmp struct {
|
|
|
- // models []models.Model
|
|
|
- // provider models.ModelProvider
|
|
|
- // availableProviders []models.ModelProvider
|
|
|
+ app *app.App
|
|
|
+ availableProviders []client.ProviderInfo
|
|
|
+ provider client.ProviderInfo
|
|
|
+ model *client.ProviderModel
|
|
|
|
|
|
selectedIdx int
|
|
|
width int
|
|
|
@@ -100,10 +105,28 @@ var modelKeys = modelKeyMap{
|
|
|
}
|
|
|
|
|
|
func (m *modelDialogCmp) Init() tea.Cmd {
|
|
|
- m.setupModels()
|
|
|
+ // 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.availableProviders, _ = m.app.ListProviders(context.Background())
|
|
|
+ m.hScrollOffset = 0
|
|
|
+ m.hScrollPossible = len(m.availableProviders) > 1
|
|
|
+ m.provider = m.availableProviders[m.hScrollOffset]
|
|
|
+
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func (m *modelDialogCmp) SetProviders(providers []client.ProviderInfo) {
|
|
|
+ m.availableProviders = providers
|
|
|
+}
|
|
|
+
|
|
|
func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
switch msg := msg.(type) {
|
|
|
case tea.KeyMsg:
|
|
|
@@ -121,7 +144,7 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
m.switchProvider(1)
|
|
|
}
|
|
|
case key.Matches(msg, modelKeys.Enter):
|
|
|
- // return m, util.CmdHandler(ModelSelectedMsg{Model: m.models[m.selectedIdx]})
|
|
|
+ return m, util.CmdHandler(CloseModelDialogMsg{Provider: &m.provider, Model: &m.provider.Models[m.selectedIdx]})
|
|
|
case key.Matches(msg, modelKeys.Escape):
|
|
|
return m, util.CmdHandler(CloseModelDialogMsg{})
|
|
|
}
|
|
|
@@ -138,8 +161,8 @@ func (m *modelDialogCmp) moveSelectionUp() {
|
|
|
if m.selectedIdx > 0 {
|
|
|
m.selectedIdx--
|
|
|
} else {
|
|
|
- // m.selectedIdx = len(m.models) - 1
|
|
|
- // m.scrollOffset = max(0, len(m.models)-numVisibleModels)
|
|
|
+ m.selectedIdx = len(m.provider.Models) - 1
|
|
|
+ m.scrollOffset = max(0, len(m.provider.Models)-numVisibleModels)
|
|
|
}
|
|
|
|
|
|
// Keep selection visible
|
|
|
@@ -150,12 +173,12 @@ func (m *modelDialogCmp) moveSelectionUp() {
|
|
|
|
|
|
// moveSelectionDown moves the selection down or wraps to top
|
|
|
func (m *modelDialogCmp) moveSelectionDown() {
|
|
|
- // if m.selectedIdx < len(m.models)-1 {
|
|
|
- // m.selectedIdx++
|
|
|
- // } else {
|
|
|
- // m.selectedIdx = 0
|
|
|
- // m.scrollOffset = 0
|
|
|
- // }
|
|
|
+ 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 {
|
|
|
@@ -167,16 +190,16 @@ func (m *modelDialogCmp) switchProvider(offset int) {
|
|
|
newOffset := m.hScrollOffset + offset
|
|
|
|
|
|
// Ensure we stay within bounds
|
|
|
- // if newOffset < 0 {
|
|
|
- // newOffset = len(m.availableProviders) - 1
|
|
|
- // }
|
|
|
- // if newOffset >= len(m.availableProviders) {
|
|
|
- // newOffset = 0
|
|
|
- // }
|
|
|
+ if newOffset < 0 {
|
|
|
+ newOffset = len(m.availableProviders) - 1
|
|
|
+ }
|
|
|
+ if newOffset >= len(m.availableProviders) {
|
|
|
+ newOffset = 0
|
|
|
+ }
|
|
|
|
|
|
m.hScrollOffset = newOffset
|
|
|
- // m.provider = m.availableProviders[m.hScrollOffset]
|
|
|
- // m.setupModelsForProvider(m.provider)
|
|
|
+ m.provider = m.availableProviders[m.hScrollOffset]
|
|
|
+ m.setupModelsForProvider(m.provider.Id)
|
|
|
}
|
|
|
|
|
|
func (m *modelDialogCmp) View() string {
|
|
|
@@ -184,33 +207,32 @@ func (m *modelDialogCmp) View() string {
|
|
|
baseStyle := styles.BaseStyle()
|
|
|
|
|
|
// Capitalize first letter of provider name
|
|
|
- // providerName := strings.ToUpper(string(m.provider)[:1]) + string(m.provider[1:])
|
|
|
- // title := baseStyle.
|
|
|
- // Foreground(t.Primary()).
|
|
|
- // Bold(true).
|
|
|
- // Width(maxDialogWidth).
|
|
|
- // Padding(0, 0, 1).
|
|
|
- // Render(fmt.Sprintf("Select %s Model", providerName))
|
|
|
+ title := baseStyle.
|
|
|
+ Foreground(t.Primary()).
|
|
|
+ Bold(true).
|
|
|
+ Width(maxDialogWidth).
|
|
|
+ Padding(0, 0, 1).
|
|
|
+ Render(fmt.Sprintf("Select %s Model", m.provider.Name))
|
|
|
|
|
|
// Render visible models
|
|
|
- // endIdx := min(m.scrollOffset+numVisibleModels, len(m.models))
|
|
|
- // modelItems := make([]string, 0, endIdx-m.scrollOffset)
|
|
|
- //
|
|
|
- // for i := m.scrollOffset; i < endIdx; i++ {
|
|
|
- // itemStyle := baseStyle.Width(maxDialogWidth)
|
|
|
- // if i == m.selectedIdx {
|
|
|
- // itemStyle = itemStyle.Background(t.Primary()).
|
|
|
- // Foreground(t.Background()).Bold(true)
|
|
|
- // }
|
|
|
- // modelItems = append(modelItems, itemStyle.Render(m.models[i].Name))
|
|
|
- // }
|
|
|
+ endIdx := min(m.scrollOffset+numVisibleModels, len(m.provider.Models))
|
|
|
+ modelItems := make([]string, 0, endIdx-m.scrollOffset)
|
|
|
+
|
|
|
+ for i := m.scrollOffset; i < endIdx; i++ {
|
|
|
+ itemStyle := baseStyle.Width(maxDialogWidth)
|
|
|
+ if i == m.selectedIdx {
|
|
|
+ itemStyle = itemStyle.Background(t.Primary()).
|
|
|
+ Foreground(t.Background()).Bold(true)
|
|
|
+ }
|
|
|
+ modelItems = append(modelItems, itemStyle.Render(*m.provider.Models[i].Name))
|
|
|
+ }
|
|
|
|
|
|
scrollIndicator := m.getScrollIndicators(maxDialogWidth)
|
|
|
|
|
|
content := lipgloss.JoinVertical(
|
|
|
lipgloss.Left,
|
|
|
- // title,
|
|
|
- // baseStyle.Width(maxDialogWidth).Render(lipgloss.JoinVertical(lipgloss.Left, modelItems...)),
|
|
|
+ title,
|
|
|
+ baseStyle.Width(maxDialogWidth).Render(lipgloss.JoinVertical(lipgloss.Left, modelItems...)),
|
|
|
scrollIndicator,
|
|
|
)
|
|
|
|
|
|
@@ -225,22 +247,22 @@ func (m *modelDialogCmp) View() string {
|
|
|
func (m *modelDialogCmp) getScrollIndicators(maxWidth int) string {
|
|
|
var indicator string
|
|
|
|
|
|
- // if len(m.models) > numVisibleModels {
|
|
|
- // if m.scrollOffset > 0 {
|
|
|
- // indicator += "↑ "
|
|
|
- // }
|
|
|
- // if m.scrollOffset+numVisibleModels < len(m.models) {
|
|
|
- // indicator += "↓ "
|
|
|
- // }
|
|
|
- // }
|
|
|
+ if len(m.provider.Models) > numVisibleModels {
|
|
|
+ if m.scrollOffset > 0 {
|
|
|
+ indicator += "↑ "
|
|
|
+ }
|
|
|
+ if m.scrollOffset+numVisibleModels < len(m.provider.Models) {
|
|
|
+ indicator += "↓ "
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
if m.hScrollPossible {
|
|
|
if m.hScrollOffset > 0 {
|
|
|
indicator = "← " + indicator
|
|
|
}
|
|
|
- // if m.hScrollOffset < len(m.availableProviders)-1 {
|
|
|
- // indicator += "→"
|
|
|
- // }
|
|
|
+ if m.hScrollOffset < len(m.availableProviders)-1 {
|
|
|
+ indicator += "→"
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if indicator == "" {
|
|
|
@@ -262,70 +284,26 @@ func (m *modelDialogCmp) BindingKeys() []key.Binding {
|
|
|
return layout.KeyMapToSlice(modelKeys)
|
|
|
}
|
|
|
|
|
|
-func (m *modelDialogCmp) setupModels() {
|
|
|
- // 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)
|
|
|
-}
|
|
|
-
|
|
|
-func GetSelectedModel(cfg *config.Config) string {
|
|
|
- return "Claude Sonnet 4"
|
|
|
- // agentCfg := cfg.Agents[config.AgentPrimary]
|
|
|
- // selectedModelId := agentCfg.Model
|
|
|
- // return models.SupportedModels[selectedModelId]
|
|
|
-}
|
|
|
-
|
|
|
-func getEnabledProviders(cfg *config.Config) []string {
|
|
|
- return []string{"anthropic", "openai", "google"}
|
|
|
- // var providers []models.ModelProvider
|
|
|
- // for providerId, provider := range cfg.Providers {
|
|
|
- // if !provider.Disabled {
|
|
|
- // providers = append(providers, providerId)
|
|
|
- // }
|
|
|
- // }
|
|
|
- //
|
|
|
- // // Sort by provider popularity
|
|
|
- // slices.SortFunc(providers, func(a, b models.ModelProvider) int {
|
|
|
- // rA := models.ProviderPopularity[a]
|
|
|
- // rB := models.ProviderPopularity[b]
|
|
|
- //
|
|
|
- // // models not included in popularity ranking default to last
|
|
|
- // if rA == 0 {
|
|
|
- // rA = 999
|
|
|
- // }
|
|
|
- // if rB == 0 {
|
|
|
- // rB = 999
|
|
|
- // }
|
|
|
- // return rA - rB
|
|
|
- // })
|
|
|
- // return providers
|
|
|
-}
|
|
|
-
|
|
|
// 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 findProviderIndex(providers []string, provider string) int {
|
|
|
+// for i, p := range providers {
|
|
|
+// if p == provider {
|
|
|
+// return i
|
|
|
+// }
|
|
|
+// }
|
|
|
+// return -1
|
|
|
+// }
|
|
|
+
|
|
|
+func (m *modelDialogCmp) setupModelsForProvider(_ string) {
|
|
|
+ m.selectedIdx = 0
|
|
|
+ m.scrollOffset = 0
|
|
|
|
|
|
-func (m *modelDialogCmp) setupModelsForProvider(provider string) {
|
|
|
// cfg := config.Get()
|
|
|
// agentCfg := cfg.Agents[config.AgentPrimary]
|
|
|
// selectedModelId := agentCfg.Model
|
|
|
|
|
|
// m.provider = provider
|
|
|
// m.models = getModelsForProvider(provider)
|
|
|
- m.selectedIdx = 0
|
|
|
- m.scrollOffset = 0
|
|
|
|
|
|
// Try to select the current model if it belongs to this provider
|
|
|
// if provider == models.SupportedModels[selectedModelId].Provider {
|
|
|
@@ -342,28 +320,8 @@ func (m *modelDialogCmp) setupModelsForProvider(provider string) {
|
|
|
// }
|
|
|
}
|
|
|
|
|
|
-func getModelsForProvider(provider string) []string {
|
|
|
- return []string{"Claude Sonnet 4"}
|
|
|
- // var providerModels []models.Model
|
|
|
- // for _, model := range models.SupportedModels {
|
|
|
- // if model.Provider == provider {
|
|
|
- // providerModels = append(providerModels, model)
|
|
|
- // }
|
|
|
- // }
|
|
|
-
|
|
|
- // reverse alphabetical order (if llm naming was consistent latest would appear first)
|
|
|
- // slices.SortFunc(providerModels, func(a, b models.Model) int {
|
|
|
- // if a.Name > b.Name {
|
|
|
- // return -1
|
|
|
- // } else if a.Name < b.Name {
|
|
|
- // return 1
|
|
|
- // }
|
|
|
- // return 0
|
|
|
- // })
|
|
|
-
|
|
|
- // return providerModels
|
|
|
-}
|
|
|
-
|
|
|
-func NewModelDialogCmp() ModelDialog {
|
|
|
- return &modelDialogCmp{}
|
|
|
+func NewModelDialogCmp(app *app.App) ModelDialog {
|
|
|
+ return &modelDialogCmp{
|
|
|
+ app: app,
|
|
|
+ }
|
|
|
}
|