feat: provider labels in login/logout, graceful unknown provider fallback, model picker filtering
Login and logout dialogs show descriptive labels (Anthropic Claude Pro/Max, OpenAI ChatGPT Plus/Pro). Unknown saved providers fall back to an available one instead of crashing. Model picker only shows models from logged-in providers.
- commit
- 205e362ed42d1238bbe1b9941fdcf74378f9bc7d
- author
- patriceckhart · · 2026-04-25 17:25 UTC
- parent
- d53fbf4
- signature
- unsigned
- refs
- tag: v0.1.14
5 files · +77 -15
M internal/agent/build.go +13 -1
| @@ -129,7 +129,19 @@ func Resolve(args Args, requireCred bool) (Resolved, error) { | |||
| 129 | 129 | // User-requested provider (explicit > config > default). | |
| 130 | 130 | provName := firstNonEmpty(args.Provider, cfg.Provider, "anthropic") | |
| 131 | 131 | if provName != "anthropic" && provName != "openai" && provName != "ollama" { | |
| 132 | - | return Resolved{}, fmt.Errorf("provider must be anthropic, openai, or ollama (got %q)", provName) | |
| 132 | + | // Unknown provider (maybe removed or renamed). Fall back to | |
| 133 | + | // the first provider that has credentials, or anthropic. | |
| 134 | + | provName = "anthropic" | |
| 135 | + | if _, _, _, err := ResolveCredentialFull("openai", ""); err == nil { | |
| 136 | + | provName = "openai" | |
| 137 | + | } | |
| 138 | + | if _, _, _, err := ResolveCredentialFull("anthropic", ""); err == nil { | |
| 139 | + | provName = "anthropic" | |
| 140 | + | } | |
| 141 | + | // Reset the saved config so this doesn't keep happening. | |
| 142 | + | cfg.Provider = provName | |
| 143 | + | cfg.Model = "" | |
| 144 | + | _ = SaveConfig(cfg) | |
| 133 | 145 | } | |
| 134 | 146 | ||
| 135 | 147 | var ( | |
| @@ -129,7 +129,19 @@ func Resolve(args Args, requireCred bool) (Resolved, error) { | |||||
| 129 | // User-requested provider (explicit > config > default). | 129 | // User-requested provider (explicit > config > default). | ||
| 130 | provName := firstNonEmpty(args.Provider, cfg.Provider, "anthropic") | 130 | provName := firstNonEmpty(args.Provider, cfg.Provider, "anthropic") | ||
| 131 | if provName != "anthropic" && provName != "openai" && provName != "ollama" { | 131 | if provName != "anthropic" && provName != "openai" && provName != "ollama" { | ||
| 132 | - | return Resolved{}, fmt.Errorf("provider must be anthropic, openai, or ollama (got %q)", provName) | 132 | + | // Unknown provider (maybe removed or renamed). Fall back to |
| 133 | + | // the first provider that has credentials, or anthropic. | |||
| 134 | + | provName = "anthropic" | |||
| 135 | + | if _, _, _, err := ResolveCredentialFull("openai", ""); err == nil { | |||
| 136 | + | provName = "openai" | |||
| 137 | + | } | |||
| 138 | + | if _, _, _, err := ResolveCredentialFull("anthropic", ""); err == nil { | |||
| 139 | + | provName = "anthropic" | |||
| 140 | + | } | |||
| 141 | + | // Reset the saved config so this doesn't keep happening. | |||
| 142 | + | cfg.Provider = provName | |||
| 143 | + | cfg.Model = "" | |||
| 144 | + | _ = SaveConfig(cfg) | |||
| 133 | } | 145 | } | ||
| 134 | 146 | ||||
| 135 | var ( | 147 | var ( | ||
M internal/agent/cli.go +12 -1
| @@ -587,7 +587,18 @@ func runInteractive(ctx context.Context, args Args, version string) error { | |||
| 587 | 587 | AuthManager: mgr, | |
| 588 | 588 | BuildAgent: buildAgent, | |
| 589 | 589 | BuildAgentFor: buildAgentFor, | |
| 590 | - | LoadSession: loadSession, | |
| 590 | + | LoggedInProviders: func() []string { | |
| 591 | + | var out []string | |
| 592 | + | for _, p := range []string{"anthropic", "openai"} { | |
| 593 | + | if _, _, err := ResolveCredential(p, ""); err == nil { | |
| 594 | + | out = append(out, p) | |
| 595 | + | } | |
| 596 | + | } | |
| 597 | + | // Ollama models are always available (no auth needed). | |
| 598 | + | out = append(out, "ollama") | |
| 599 | + | return out | |
| 600 | + | }, | |
| 601 | + | LoadSession: loadSession, | |
| 591 | 602 | CurrentSessionPath: func() string { | |
| 592 | 603 | if sess == nil { | |
| 593 | 604 | return "" | |
| @@ -587,7 +587,18 @@ func runInteractive(ctx context.Context, args Args, version string) error { | |||||
| 587 | AuthManager: mgr, | 587 | AuthManager: mgr, | ||
| 588 | BuildAgent: buildAgent, | 588 | BuildAgent: buildAgent, | ||
| 589 | BuildAgentFor: buildAgentFor, | 589 | BuildAgentFor: buildAgentFor, | ||
| 590 | - | LoadSession: loadSession, | 590 | + | LoggedInProviders: func() []string { |
| 591 | + | var out []string | |||
| 592 | + | for _, p := range []string{"anthropic", "openai"} { | |||
| 593 | + | if _, _, err := ResolveCredential(p, ""); err == nil { | |||
| 594 | + | out = append(out, p) | |||
| 595 | + | } | |||
| 596 | + | } | |||
| 597 | + | // Ollama models are always available (no auth needed). | |||
| 598 | + | out = append(out, "ollama") | |||
| 599 | + | return out | |||
| 600 | + | }, | |||
| 601 | + | LoadSession: loadSession, | |||
| 591 | CurrentSessionPath: func() string { | 602 | CurrentSessionPath: func() string { | ||
| 592 | if sess == nil { | 603 | if sess == nil { | ||
| 593 | return "" | 604 | return "" | ||
M internal/agent/modes/interactive.go +16 -3
| @@ -53,6 +53,11 @@ type InteractiveConfig struct { | |||
| 53 | 53 | // If providerOverride is empty, the current provider is kept. | |
| 54 | 54 | BuildAgentFor func(providerOverride, modelOverride string) (*core.Agent, string, string, error) | |
| 55 | 55 | ||
| 56 | + | // LoggedInProviders returns the list of provider names that | |
| 57 | + | // currently have credentials. Used by /model to filter the | |
| 58 | + | // picker to only show reachable models. | |
| 59 | + | LoggedInProviders func() []string | |
| 60 | + | ||
| 56 | 61 | // ZotHome is the root directory for sessions/, used by /sessions | |
| 57 | 62 | // and the update-check cache. | |
| 58 | 63 | ZotHome string | |
| @@ -1764,7 +1769,11 @@ func (i *Interactive) runSlash(ctx context.Context, cmd string) (done bool) { | |||
| 1764 | 1769 | if len(parts) >= 2 { | |
| 1765 | 1770 | i.applyModelSelection("", parts[1]) | |
| 1766 | 1771 | } else { | |
| 1767 | - | i.modelDialog.Open(i.cfg.Model) | |
| 1772 | + | var loggedIn []string | |
| 1773 | + | if i.cfg.LoggedInProviders != nil { | |
| 1774 | + | loggedIn = i.cfg.LoggedInProviders() | |
| 1775 | + | } | |
| 1776 | + | i.modelDialog.Open(i.cfg.Model, loggedIn) | |
| 1768 | 1777 | } | |
| 1769 | 1778 | case "/sessions": | |
| 1770 | 1779 | i.sessionDialog.Open(i.cfg.ZotHome, i.cfg.CWD) | |
| @@ -1892,10 +1901,14 @@ func (i *Interactive) openLogoutDialog() { | |||
| 1892 | 1901 | var items []logoutItem | |
| 1893 | 1902 | for _, p := range []string{"anthropic", "openai"} { | |
| 1894 | 1903 | if creds.Has(p) { | |
| 1904 | + | method := creds.Method(p) | |
| 1905 | + | if method == "oauth" { | |
| 1906 | + | method = "subscription" | |
| 1907 | + | } | |
| 1895 | 1908 | items = append(items, logoutItem{ | |
| 1896 | - | label: p, | |
| 1909 | + | label: providerLabel(p), | |
| 1897 | 1910 | target: p, | |
| 1898 | - | method: creds.Method(p), | |
| 1911 | + | method: method, | |
| 1899 | 1912 | }) | |
| 1900 | 1913 | } | |
| 1901 | 1914 | } | |
| @@ -53,6 +53,11 @@ type InteractiveConfig struct { | |||||
| 53 | // If providerOverride is empty, the current provider is kept. | 53 | // If providerOverride is empty, the current provider is kept. | ||
| 54 | BuildAgentFor func(providerOverride, modelOverride string) (*core.Agent, string, string, error) | 54 | BuildAgentFor func(providerOverride, modelOverride string) (*core.Agent, string, string, error) | ||
| 55 | 55 | ||||
| 56 | + | // LoggedInProviders returns the list of provider names that | |||
| 57 | + | // currently have credentials. Used by /model to filter the | |||
| 58 | + | // picker to only show reachable models. | |||
| 59 | + | LoggedInProviders func() []string | |||
| 60 | + | ||||
| 56 | // ZotHome is the root directory for sessions/, used by /sessions | 61 | // ZotHome is the root directory for sessions/, used by /sessions | ||
| 57 | // and the update-check cache. | 62 | // and the update-check cache. | ||
| 58 | ZotHome string | 63 | ZotHome string | ||
| @@ -1764,7 +1769,11 @@ func (i *Interactive) runSlash(ctx context.Context, cmd string) (done bool) { | |||||
| 1764 | if len(parts) >= 2 { | 1769 | if len(parts) >= 2 { | ||
| 1765 | i.applyModelSelection("", parts[1]) | 1770 | i.applyModelSelection("", parts[1]) | ||
| 1766 | } else { | 1771 | } else { | ||
| 1767 | - | i.modelDialog.Open(i.cfg.Model) | 1772 | + | var loggedIn []string |
| 1773 | + | if i.cfg.LoggedInProviders != nil { | |||
| 1774 | + | loggedIn = i.cfg.LoggedInProviders() | |||
| 1775 | + | } | |||
| 1776 | + | i.modelDialog.Open(i.cfg.Model, loggedIn) | |||
| 1768 | } | 1777 | } | ||
| 1769 | case "/sessions": | 1778 | case "/sessions": | ||
| 1770 | i.sessionDialog.Open(i.cfg.ZotHome, i.cfg.CWD) | 1779 | i.sessionDialog.Open(i.cfg.ZotHome, i.cfg.CWD) | ||
| @@ -1892,10 +1901,14 @@ func (i *Interactive) openLogoutDialog() { | |||||
| 1892 | var items []logoutItem | 1901 | var items []logoutItem | ||
| 1893 | for _, p := range []string{"anthropic", "openai"} { | 1902 | for _, p := range []string{"anthropic", "openai"} { | ||
| 1894 | if creds.Has(p) { | 1903 | if creds.Has(p) { | ||
| 1904 | + | method := creds.Method(p) | |||
| 1905 | + | if method == "oauth" { | |||
| 1906 | + | method = "subscription" | |||
| 1907 | + | } | |||
| 1895 | items = append(items, logoutItem{ | 1908 | items = append(items, logoutItem{ | ||
| 1896 | - | label: p, | 1909 | + | label: providerLabel(p), |
| 1897 | target: p, | 1910 | target: p, | ||
| 1898 | - | method: creds.Method(p), | 1911 | + | method: method, |
| 1899 | }) | 1912 | }) | ||
| 1900 | } | 1913 | } | ||
| 1901 | } | 1914 | } | ||
M internal/agent/modes/login_dialog.go +20 -8
| @@ -127,7 +127,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||
| 127 | 127 | case "oauth": | |
| 128 | 128 | tag = " (subscription)" | |
| 129 | 129 | } | |
| 130 | - | plain := " " + o + tag | |
| 130 | + | plain := " " + providerLabel(o) + tag | |
| 131 | 131 | if i == d.cursor { | |
| 132 | 132 | lines = append(lines, th.PadHighlight(plain, width)) | |
| 133 | 133 | } else { | |
| @@ -136,7 +136,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||
| 136 | 136 | } | |
| 137 | 137 | lines = append(lines, frameRule(th, width)) | |
| 138 | 138 | case loginStepWaiting: | |
| 139 | - | lines = append(lines, frameHeader(th, "login - "+d.method+" - "+d.provider, width)) | |
| 139 | + | lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider), width)) | |
| 140 | 140 | lines = append(lines, th.FG256(th.Muted, "open this URL in a browser:")) | |
| 141 | 141 | wrapW := width - 2 | |
| 142 | 142 | if wrapW < 20 { | |
| @@ -158,7 +158,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||
| 158 | 158 | lines = append(lines, th.FG256(th.Muted, "enter submits - esc cancels - waiting for browser callback in background")) | |
| 159 | 159 | lines = append(lines, frameRule(th, width)) | |
| 160 | 160 | case loginStepPasteCode: | |
| 161 | - | lines = append(lines, frameHeader(th, "login - "+d.method+" - "+d.provider+" - paste code", width)) | |
| 161 | + | lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider)+" - paste code", width)) | |
| 162 | 162 | lines = append(lines, th.FG256(th.Muted, "open this URL in any browser:")) | |
| 163 | 163 | wrapW := width - 2 | |
| 164 | 164 | if wrapW < 20 { | |
| @@ -184,7 +184,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||
| 184 | 184 | body := th.FG256(th.Error, d.message) | |
| 185 | 185 | if d.success { | |
| 186 | 186 | title = "login - success" | |
| 187 | - | body = th.FG256(th.Tool, fmt.Sprintf("logged in to %s via %s", d.provider, d.method)) | |
| 187 | + | body = th.FG256(th.Tool, fmt.Sprintf("logged in to %s via %s", providerLabel(d.provider), d.method)) | |
| 188 | 188 | } | |
| 189 | 189 | lines = append(lines, frameHeader(th, title, width)) | |
| 190 | 190 | lines = append(lines, body) | |
| @@ -194,6 +194,17 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||
| 194 | 194 | return lines | |
| 195 | 195 | } | |
| 196 | 196 | ||
| 197 | + | // providerLabel returns the user-facing label for a provider id. | |
| 198 | + | func providerLabel(id string) string { | |
| 199 | + | switch id { | |
| 200 | + | case "anthropic": | |
| 201 | + | return "Anthropic (Claude Pro/Max)" | |
| 202 | + | case "openai": | |
| 203 | + | return "OpenAI (ChatGPT Plus/Pro)" | |
| 204 | + | } | |
| 205 | + | return id | |
| 206 | + | } | |
| 207 | + | ||
| 197 | 208 | // renderStatusLines returns an overview of the current login | |
| 198 | 209 | // state for each provider, one row per provider, suitable to | |
| 199 | 210 | // insert between the frame header and the picker body. Logged- | |
| @@ -210,18 +221,19 @@ func (d *loginDialog) renderStatusLines(th tui.Theme) []string { | |||
| 210 | 221 | if anth == "" && op == "" { | |
| 211 | 222 | return nil | |
| 212 | 223 | } | |
| 213 | - | row := func(name, method string) string { | |
| 224 | + | row := func(id, method string) string { | |
| 225 | + | label := providerLabel(id) | |
| 214 | 226 | var mark, body string | |
| 215 | 227 | switch method { | |
| 216 | 228 | case "apikey": | |
| 217 | 229 | mark = th.FG256(th.Tool, "✓") | |
| 218 | - | body = th.FG256(th.Muted, name+": api key") | |
| 230 | + | body = th.FG256(th.Muted, label+": api key") | |
| 219 | 231 | case "oauth": | |
| 220 | 232 | mark = th.FG256(th.Tool, "✓") | |
| 221 | - | body = th.FG256(th.Muted, name+": subscription") | |
| 233 | + | body = th.FG256(th.Muted, label+": subscription") | |
| 222 | 234 | default: | |
| 223 | 235 | mark = th.FG256(th.Muted, "–") | |
| 224 | - | body = th.FG256(th.Muted, name+": not logged in") | |
| 236 | + | body = th.FG256(th.Muted, label+": not logged in") | |
| 225 | 237 | } | |
| 226 | 238 | return " " + mark + " " + body | |
| 227 | 239 | } | |
| @@ -127,7 +127,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||||
| 127 | case "oauth": | 127 | case "oauth": | ||
| 128 | tag = " (subscription)" | 128 | tag = " (subscription)" | ||
| 129 | } | 129 | } | ||
| 130 | - | plain := " " + o + tag | 130 | + | plain := " " + providerLabel(o) + tag |
| 131 | if i == d.cursor { | 131 | if i == d.cursor { | ||
| 132 | lines = append(lines, th.PadHighlight(plain, width)) | 132 | lines = append(lines, th.PadHighlight(plain, width)) | ||
| 133 | } else { | 133 | } else { | ||
| @@ -136,7 +136,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||||
| 136 | } | 136 | } | ||
| 137 | lines = append(lines, frameRule(th, width)) | 137 | lines = append(lines, frameRule(th, width)) | ||
| 138 | case loginStepWaiting: | 138 | case loginStepWaiting: | ||
| 139 | - | lines = append(lines, frameHeader(th, "login - "+d.method+" - "+d.provider, width)) | 139 | + | lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider), width)) |
| 140 | lines = append(lines, th.FG256(th.Muted, "open this URL in a browser:")) | 140 | lines = append(lines, th.FG256(th.Muted, "open this URL in a browser:")) | ||
| 141 | wrapW := width - 2 | 141 | wrapW := width - 2 | ||
| 142 | if wrapW < 20 { | 142 | if wrapW < 20 { | ||
| @@ -158,7 +158,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||||
| 158 | lines = append(lines, th.FG256(th.Muted, "enter submits - esc cancels - waiting for browser callback in background")) | 158 | lines = append(lines, th.FG256(th.Muted, "enter submits - esc cancels - waiting for browser callback in background")) | ||
| 159 | lines = append(lines, frameRule(th, width)) | 159 | lines = append(lines, frameRule(th, width)) | ||
| 160 | case loginStepPasteCode: | 160 | case loginStepPasteCode: | ||
| 161 | - | lines = append(lines, frameHeader(th, "login - "+d.method+" - "+d.provider+" - paste code", width)) | 161 | + | lines = append(lines, frameHeader(th, "login - "+d.method+" - "+providerLabel(d.provider)+" - paste code", width)) |
| 162 | lines = append(lines, th.FG256(th.Muted, "open this URL in any browser:")) | 162 | lines = append(lines, th.FG256(th.Muted, "open this URL in any browser:")) | ||
| 163 | wrapW := width - 2 | 163 | wrapW := width - 2 | ||
| 164 | if wrapW < 20 { | 164 | if wrapW < 20 { | ||
| @@ -184,7 +184,7 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||||
| 184 | body := th.FG256(th.Error, d.message) | 184 | body := th.FG256(th.Error, d.message) | ||
| 185 | if d.success { | 185 | if d.success { | ||
| 186 | title = "login - success" | 186 | title = "login - success" | ||
| 187 | - | body = th.FG256(th.Tool, fmt.Sprintf("logged in to %s via %s", d.provider, d.method)) | 187 | + | body = th.FG256(th.Tool, fmt.Sprintf("logged in to %s via %s", providerLabel(d.provider), d.method)) |
| 188 | } | 188 | } | ||
| 189 | lines = append(lines, frameHeader(th, title, width)) | 189 | lines = append(lines, frameHeader(th, title, width)) | ||
| 190 | lines = append(lines, body) | 190 | lines = append(lines, body) | ||
| @@ -194,6 +194,17 @@ func (d *loginDialog) Render(th tui.Theme, width int) []string { | |||||
| 194 | return lines | 194 | return lines | ||
| 195 | } | 195 | } | ||
| 196 | 196 | ||||
| 197 | + | // providerLabel returns the user-facing label for a provider id. | |||
| 198 | + | func providerLabel(id string) string { | |||
| 199 | + | switch id { | |||
| 200 | + | case "anthropic": | |||
| 201 | + | return "Anthropic (Claude Pro/Max)" | |||
| 202 | + | case "openai": | |||
| 203 | + | return "OpenAI (ChatGPT Plus/Pro)" | |||
| 204 | + | } | |||
| 205 | + | return id | |||
| 206 | + | } | |||
| 207 | + | ||||
| 197 | // renderStatusLines returns an overview of the current login | 208 | // renderStatusLines returns an overview of the current login | ||
| 198 | // state for each provider, one row per provider, suitable to | 209 | // state for each provider, one row per provider, suitable to | ||
| 199 | // insert between the frame header and the picker body. Logged- | 210 | // insert between the frame header and the picker body. Logged- | ||
| @@ -210,18 +221,19 @@ func (d *loginDialog) renderStatusLines(th tui.Theme) []string { | |||||
| 210 | if anth == "" && op == "" { | 221 | if anth == "" && op == "" { | ||
| 211 | return nil | 222 | return nil | ||
| 212 | } | 223 | } | ||
| 213 | - | row := func(name, method string) string { | 224 | + | row := func(id, method string) string { |
| 225 | + | label := providerLabel(id) | |||
| 214 | var mark, body string | 226 | var mark, body string | ||
| 215 | switch method { | 227 | switch method { | ||
| 216 | case "apikey": | 228 | case "apikey": | ||
| 217 | mark = th.FG256(th.Tool, "✓") | 229 | mark = th.FG256(th.Tool, "✓") | ||
| 218 | - | body = th.FG256(th.Muted, name+": api key") | 230 | + | body = th.FG256(th.Muted, label+": api key") |
| 219 | case "oauth": | 231 | case "oauth": | ||
| 220 | mark = th.FG256(th.Tool, "✓") | 232 | mark = th.FG256(th.Tool, "✓") | ||
| 221 | - | body = th.FG256(th.Muted, name+": subscription") | 233 | + | body = th.FG256(th.Muted, label+": subscription") |
| 222 | default: | 234 | default: | ||
| 223 | mark = th.FG256(th.Muted, "–") | 235 | mark = th.FG256(th.Muted, "–") | ||
| 224 | - | body = th.FG256(th.Muted, name+": not logged in") | 236 | + | body = th.FG256(th.Muted, label+": not logged in") |
| 225 | } | 237 | } | ||
| 226 | return " " + mark + " " + body | 238 | return " " + mark + " " + body | ||
| 227 | } | 239 | } | ||
M internal/agent/modes/model_dialog.go +16 -2
| @@ -38,9 +38,23 @@ func newModelDialog() *modelDialog { | |||
| 38 | 38 | ||
| 39 | 39 | // Open shows the dialog. current is the currently active model id so | |
| 40 | 40 | // it can be pre-selected. | |
| 41 | - | func (d *modelDialog) Open(current string) { | |
| 41 | + | func (d *modelDialog) Open(current string, loggedInProviders []string) { | |
| 42 | 42 | d.active = true | |
| 43 | - | d.all = sortedModels(provider.Active()) | |
| 43 | + | all := provider.Active() | |
| 44 | + | if len(loggedInProviders) > 0 { | |
| 45 | + | provSet := map[string]bool{} | |
| 46 | + | for _, p := range loggedInProviders { | |
| 47 | + | provSet[p] = true | |
| 48 | + | } | |
| 49 | + | var filtered []provider.Model | |
| 50 | + | for _, m := range all { | |
| 51 | + | if provSet[m.Provider] { | |
| 52 | + | filtered = append(filtered, m) | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | all = filtered | |
| 56 | + | } | |
| 57 | + | d.all = sortedModels(all) | |
| 44 | 58 | d.current = current | |
| 45 | 59 | d.query = "" | |
| 46 | 60 | d.refilter() | |
| @@ -38,9 +38,23 @@ func newModelDialog() *modelDialog { | |||||
| 38 | 38 | ||||
| 39 | // Open shows the dialog. current is the currently active model id so | 39 | // Open shows the dialog. current is the currently active model id so | ||
| 40 | // it can be pre-selected. | 40 | // it can be pre-selected. | ||
| 41 | - | func (d *modelDialog) Open(current string) { | 41 | + | func (d *modelDialog) Open(current string, loggedInProviders []string) { |
| 42 | d.active = true | 42 | d.active = true | ||
| 43 | - | d.all = sortedModels(provider.Active()) | 43 | + | all := provider.Active() |
| 44 | + | if len(loggedInProviders) > 0 { | |||
| 45 | + | provSet := map[string]bool{} | |||
| 46 | + | for _, p := range loggedInProviders { | |||
| 47 | + | provSet[p] = true | |||
| 48 | + | } | |||
| 49 | + | var filtered []provider.Model | |||
| 50 | + | for _, m := range all { | |||
| 51 | + | if provSet[m.Provider] { | |||
| 52 | + | filtered = append(filtered, m) | |||
| 53 | + | } | |||
| 54 | + | } | |||
| 55 | + | all = filtered | |||
| 56 | + | } | |||
| 57 | + | d.all = sortedModels(all) | |||
| 44 | d.current = current | 58 | d.current = current | ||
| 45 | d.query = "" | 59 | d.query = "" | ||
| 46 | d.refilter() | 60 | d.refilter() | ||