diff options
| author | adamelmore <[email protected]> | 2026-01-27 10:54:10 -0600 |
|---|---|---|
| committer | adamelmore <[email protected]> | 2026-01-28 07:28:02 -0600 |
| commit | bdfd8f8b0feb5f40b4af7def8c9068d9c1937970 (patch) | |
| tree | 581674256dae58d1e0081a2e599f9da6d8dcff3d /packages/app/src/components/settings-providers.tsx | |
| parent | 2f35c40bb59e3b6628247801f9678f5023fd659c (diff) | |
| download | opencode-bdfd8f8b0feb5f40b4af7def8c9068d9c1937970.tar.gz opencode-bdfd8f8b0feb5f40b4af7def8c9068d9c1937970.zip | |
feat(app): custom provider
Diffstat (limited to 'packages/app/src/components/settings-providers.tsx')
| -rw-r--r-- | packages/app/src/components/settings-providers.tsx | 76 |
1 files changed, 72 insertions, 4 deletions
diff --git a/packages/app/src/components/settings-providers.tsx b/packages/app/src/components/settings-providers.tsx index 86a393b98..abc2bee77 100644 --- a/packages/app/src/components/settings-providers.tsx +++ b/packages/app/src/components/settings-providers.tsx @@ -3,13 +3,15 @@ import { useDialog } from "@opencode-ai/ui/context/dialog" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { Tag } from "@opencode-ai/ui/tag" import { showToast } from "@opencode-ai/ui/toast" -import type { IconName } from "@opencode-ai/ui/icons/provider" +import { iconNames, type IconName } from "@opencode-ai/ui/icons/provider" import { popularProviders, useProviders } from "@/hooks/use-providers" import { createMemo, type Component, For, Show } from "solid-js" import { useLanguage } from "@/context/language" import { useGlobalSDK } from "@/context/global-sdk" +import { useGlobalSync } from "@/context/global-sync" import { DialogConnectProvider } from "./dialog-connect-provider" import { DialogSelectProvider } from "./dialog-select-provider" +import { DialogCustomProvider } from "./dialog-custom-provider" type ProviderSource = "env" | "api" | "config" | "custom" type ProviderMeta = { source?: ProviderSource } @@ -18,8 +20,14 @@ export const SettingsProviders: Component = () => { const dialog = useDialog() const language = useLanguage() const globalSDK = useGlobalSDK() + const globalSync = useGlobalSync() const providers = useProviders() + const icon = (id: string): IconName => { + if (iconNames.includes(id as IconName)) return id as IconName + return "synthetic" + } + const connected = createMemo(() => { return providers .connected() @@ -42,14 +50,53 @@ export const SettingsProviders: Component = () => { const current = source(item) if (current === "env") return language.t("settings.providers.tag.environment") if (current === "api") return language.t("provider.connect.method.apiKey") - if (current === "config") return language.t("settings.providers.tag.config") + if (current === "config") { + const id = (item as { id?: string }).id + if (id && isConfigCustom(id)) return language.t("settings.providers.tag.custom") + return language.t("settings.providers.tag.config") + } if (current === "custom") return language.t("settings.providers.tag.custom") return language.t("settings.providers.tag.other") } const canDisconnect = (item: unknown) => source(item) !== "env" + const isConfigCustom = (providerID: string) => { + const provider = globalSync.data.config.provider?.[providerID] + if (!provider) return false + if (provider.npm !== "@ai-sdk/openai-compatible") return false + if (!provider.models || Object.keys(provider.models).length === 0) return false + return true + } + + const disableProvider = async (providerID: string, name: string) => { + const before = globalSync.data.config.disabled_providers ?? [] + const next = before.includes(providerID) ? before : [...before, providerID] + globalSync.set("config", "disabled_providers", next) + + await globalSync + .updateConfig({ disabled_providers: next }) + .then(() => { + showToast({ + variant: "success", + icon: "circle-check", + title: language.t("provider.disconnect.toast.disconnected.title", { provider: name }), + description: language.t("provider.disconnect.toast.disconnected.description", { provider: name }), + }) + }) + .catch((err: unknown) => { + globalSync.set("config", "disabled_providers", before) + const message = err instanceof Error ? err.message : String(err) + showToast({ title: language.t("common.requestFailed"), description: message }) + }) + } + const disconnect = async (providerID: string, name: string) => { + if (isConfigCustom(providerID)) { + await globalSDK.client.auth.remove({ providerID }).catch(() => undefined) + await disableProvider(providerID, name) + return + } await globalSDK.client.auth .remove({ providerID }) .then(async () => { @@ -91,7 +138,7 @@ export const SettingsProviders: Component = () => { {(item) => ( <div class="group flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none"> <div class="flex items-center gap-3 min-w-0"> - <ProviderIcon id={item.id as IconName} class="size-5 shrink-0 icon-strong-base" /> + <ProviderIcon id={icon(item.id)} class="size-5 shrink-0 icon-strong-base" /> <span class="text-14-medium text-text-strong truncate">{item.name}</span> <Tag>{type(item)}</Tag> </div> @@ -122,7 +169,7 @@ export const SettingsProviders: Component = () => { <div class="flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none"> <div class="flex flex-col min-w-0"> <div class="flex items-center gap-x-3"> - <ProviderIcon id={item.id as IconName} class="size-5 shrink-0 icon-strong-base" /> + <ProviderIcon id={icon(item.id)} class="size-5 shrink-0 icon-strong-base" /> <span class="text-14-medium text-text-strong">{item.name}</span> <Show when={item.id === "opencode"}> <Tag>{language.t("dialog.provider.tag.recommended")}</Tag> @@ -177,6 +224,27 @@ export const SettingsProviders: Component = () => { </div> )} </For> + + <div class="flex items-center justify-between gap-4 h-16 border-b border-border-weak-base last:border-none"> + <div class="flex flex-col min-w-0"> + <div class="flex items-center gap-x-3"> + <ProviderIcon id={icon("synthetic")} class="size-5 shrink-0 icon-strong-base" /> + <span class="text-14-medium text-text-strong">Custom provider</span> + <Tag>{language.t("settings.providers.tag.custom")}</Tag> + </div> + <span class="text-12-regular text-text-weak pl-8">Add an OpenAI-compatible provider by base URL.</span> + </div> + <Button + size="large" + variant="secondary" + icon="plus-small" + onClick={() => { + dialog.show(() => <DialogCustomProvider back="close" />) + }} + > + {language.t("common.connect")} + </Button> + </div> </div> <Button |
