feat(desktop): align provider onboarding dialogs (#36733)

Co-authored-by: Brendan Allan <git@brendonovich.dev>
This commit is contained in:
usrnk1
2026-07-17 10:54:20 +02:00
committed by GitHub
parent 9125f80bdd
commit b8dcf269db
13 changed files with 843 additions and 132 deletions
+2
View File
@@ -47,6 +47,8 @@ export default defineMain({
{ find: /^@\/context\/language$/, replacement: path.resolve(mocks, "app/context/language.ts") },
{ find: /^@\/context\/platform$/, replacement: path.resolve(mocks, "app/context/platform.ts") },
{ find: /^@\/context\/global-sync$/, replacement: path.resolve(mocks, "app/context/global-sync.ts") },
{ find: /^@\/context\/server-sync$/, replacement: path.resolve(mocks, "app/context/server-sync.ts") },
{ find: /^@\/context\/server-sdk$/, replacement: path.resolve(mocks, "app/context/server-sdk.ts") },
{ find: /^@\/hooks\/use-providers$/, replacement: path.resolve(mocks, "app/hooks/use-providers.ts") },
{
find: /^@\/components\/dialog-select-model$/,
@@ -22,6 +22,69 @@ const dict: Record<string, string> = {
"prompt.mode.shell": "Shell",
"prompt.mode.normal": "Prompt",
"dialog.model.select.title": "Select model",
"dialog.model.unpaid.freeModels.title": "Free models provided by OpenCode",
"dialog.model.unpaid.addMore.title": "Add more models from popular providers",
"dialog.model.unpaid.viewMoreProviders": "See 70+ more providers",
"dialog.provider.opencode.tagline": "Reliable optimized models",
"dialog.provider.opencodeGo.tagline": "Low cost subscription for everyone",
"dialog.provider.custom.label": "Custom OpenAI-compatible provider",
"dialog.provider.search.placeholder": "Search providers",
"dialog.provider.empty": "No providers found",
"dialog.provider.group.popular": "Popular",
"dialog.provider.group.other": "Other",
"dialog.provider.tag.recommended": "Recommended",
"settings.providers.tag.custom": "Custom",
"command.provider.connect": "Connect provider",
"provider.connect.title": "Connect {{provider}}",
"provider.connect.selectMethod": "Select login method for {{provider}}.",
"provider.connect.method.apiKey": "API key",
"provider.connect.apiKey.description":
"Enter your {{provider}} API key to connect your account and use {{provider}} models in OpenCode.",
"provider.connect.apiKey.label": "{{provider}} API key",
"provider.connect.apiKey.placeholder": "API key",
"provider.connect.apiKey.required": "API key is required",
"provider.connect.opencodeZen.line1":
"OpenCode Zen gives you access to a curated set of reliable optimized models for coding agents.",
"provider.connect.opencodeZen.line2":
"With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more.",
"provider.connect.opencodeZen.visit.prefix": "Visit ",
"provider.connect.opencodeZen.visit.link": "opencode.ai/zen",
"provider.connect.opencodeZen.visit.suffix": " to collect your API key.",
"provider.connect.oauth.code.visit.prefix": "Visit ",
"provider.connect.oauth.code.visit.link": "this link",
"provider.connect.oauth.code.visit.suffix":
" to collect your authorization code to connect your account and use {{provider}} models in OpenCode.",
"provider.connect.oauth.code.label": "{{method}} authorization code",
"provider.connect.oauth.code.placeholder": "Authorization code",
"provider.connect.oauth.code.required": "Authorization code is required",
"provider.connect.oauth.code.invalid": "Invalid authorization code",
"provider.connect.oauth.auto.visit.prefix": "Visit ",
"provider.connect.oauth.auto.visit.link": "this link",
"provider.connect.oauth.auto.visit.suffix":
" and enter the code below to connect your account and use {{provider}} models in OpenCode.",
"provider.connect.oauth.auto.confirmationCode": "Confirmation code",
"provider.connect.status.inProgress": "Authorization in progress...",
"provider.connect.status.waiting": "Waiting for authorization...",
"provider.connect.status.failed": "Authorization failed: {{error}}",
"provider.connect.toast.connected.title": "{{provider}} connected",
"provider.connect.toast.connected.description": "{{provider}} models are now available to use.",
"common.continue": "Continue",
"model.tag.free": "Free",
"model.tag.latest": "Latest",
"model.input.text": "text",
"model.input.image": "image",
"model.input.audio": "audio",
"model.input.video": "video",
"model.input.pdf": "pdf",
"model.tooltip.context.label": "Context",
"model.tooltip.inputs": "Inputs",
"model.tooltip.model": "Model",
"model.tooltip.provider": "Provider",
"model.tooltip.reasoning": "Reasoning",
"model.tooltip.reasoning.allowed": "Allows reasoning",
"model.tooltip.reasoning.none": "No reasoning",
"common.close": "Close",
"common.goBack": "Go back",
"common.default": "Default",
"common.key.esc": "Esc",
"command.category.file": "File",
@@ -73,6 +136,7 @@ function render(template: string, params?: Record<string, unknown>) {
export function useLanguage() {
return {
locale: () => "en" as const,
intl: () => "en-US",
t(key: string, params?: Record<string, unknown>) {
return render(dict[key] ?? key, params)
},
@@ -0,0 +1,47 @@
const providers = [
"opencode",
"opencode-go",
"anthropic",
"openai",
"google",
"openrouter",
"vercel",
"github-copilot",
"302ai",
"abacus",
"abliteration",
"alibaba",
"alibaba-cn",
"alibaba-coding-plan",
]
const client = {
provider: {
auth: async () => ({
data: Object.fromEntries(providers.map((provider) => [provider, [{ type: "api", label: "API key" }]])),
}),
oauth: {
authorize: async (input: { method?: number }) => ({
data: {
url: "https://example.com/oauth",
method: input.method === 1 ? ("code" as const) : ("auto" as const),
instructions: input.method === 1 ? "Paste the authorization code" : "Confirmation code: ABCD-EFGH",
},
}),
callback: async (input: { method?: number }) => {
if (input.method === 0) return new Promise<never>(() => {})
return { data: undefined }
},
},
},
auth: {
set: async () => ({ data: true }),
},
global: {
dispose: async () => ({ data: true }),
},
}
export function useServerSDK() {
return () => ({ client })
}
@@ -0,0 +1,33 @@
import type { ProviderAuthMethod } from "@opencode-ai/sdk/v2/client"
const data = {
provider: {
all: new Map(),
connected: [],
default: {},
},
provider_auth: {} as Record<string, ProviderAuthMethod[]>,
config: { disabled_providers: [] as string[] },
}
export function mockProviderAuth(provider: string, methods: ProviderAuthMethod[]) {
const previous = data.provider_auth[provider]
data.provider_auth[provider] = methods
return () => {
if (previous) {
data.provider_auth[provider] = previous
return
}
delete data.provider_auth[provider]
}
}
export function useServerSync() {
return () => ({
data,
set(key: "provider_auth", value: typeof data.provider_auth) {
data[key] = value
},
updateConfig: async () => {},
})
}
@@ -13,6 +13,7 @@ export const popularProviders = [
const provider = {
id: "anthropic",
name: "Anthropic",
models: {
[model_id]: {
id: model_id,
@@ -23,12 +24,33 @@ const provider = {
},
}
const popular = [
{ id: "opencode", name: "OpenCode Zen", models: {} },
{ id: "opencode-go", name: "OpenCode Go", models: {} },
{ id: "openai", name: "OpenAI", models: {} },
provider,
{ id: "google", name: "Google", models: {} },
{ id: "github-copilot", name: "GitHub Copilot", models: {} },
]
const catalog = [
...popular,
{ id: "openrouter", name: "OpenRouter", models: {} },
{ id: "vercel", name: "Vercel AI Gateway", models: {} },
{ id: "302ai", name: "302.AI", models: {} },
{ id: "abacus", name: "Abacus", models: {} },
{ id: "abliteration", name: "abliteration.ai", models: {} },
{ id: "alibaba", name: "Alibaba", models: {} },
{ id: "alibaba-cn", name: "Alibaba (China)", models: {} },
{ id: "alibaba-coding-plan", name: "Alibaba Coding Plan", models: {} },
]
export function useProviders() {
return {
all: () => [provider],
all: () => new Map(catalog.map((item) => [item.id, item])),
default: () => ({ anthropic: model_id }),
connected: () => [provider],
paid: () => [provider],
popular: () => [provider],
popular: () => popular,
}
}