Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 29 additions & 59 deletions packages/app/src/components/dialog-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,79 +21,49 @@ export const DialogSettings: Component = () => {
<Dialog size="x-large">
<Tabs orientation="vertical" variant="settings" defaultValue="general" class="h-full settings-dialog">
<Tabs.List>
<div
style={{
display: "flex",
"flex-direction": "column",
"justify-content": "space-between",
height: "100%",
width: "100%",
}}
>
<div
style={{
display: "flex",
"flex-direction": "column",
gap: "12px",
width: "100%",
"padding-top": "12px",
}}
>
<Tabs.SectionTitle>{language.t("settings.section.desktop")}</Tabs.SectionTitle>
<div style={{ display: "flex", "flex-direction": "column", gap: "6px", width: "100%" }}>
<Tabs.Trigger value="general">
<Icon name="sliders" />
{language.t("settings.tab.general")}
</Tabs.Trigger>
<Tabs.Trigger value="shortcuts">
<Icon name="keyboard" />
{language.t("settings.tab.shortcuts")}
</Tabs.Trigger>
<div class="flex flex-col justify-between h-full w-full">
<div class="flex flex-col gap-3 w-full pt-3">
<div class="flex flex-col gap-3">
<div class="flex flex-col gap-1.5">
<Tabs.SectionTitle>{language.t("settings.section.desktop")}</Tabs.SectionTitle>
<div class="flex flex-col gap-1.5 w-full">
<Tabs.Trigger value="general">
<Icon name="sliders" />
{language.t("settings.tab.general")}
</Tabs.Trigger>
<Tabs.Trigger value="shortcuts">
<Icon name="keyboard" />
{language.t("settings.tab.shortcuts")}
</Tabs.Trigger>
</div>
</div>

<div class="flex flex-col gap-1.5">
<Tabs.SectionTitle>{language.t("settings.section.server")}</Tabs.SectionTitle>
<div class="flex flex-col gap-1.5 w-full">
<Tabs.Trigger value="providers">
<Icon name="server" />
{language.t("settings.providers.title")}
</Tabs.Trigger>
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-1 pl-1 py-1 text-12-medium text-text-weak">
<span>OpenCode Desktop</span>
<span class="text-11-regular">v{platform.version}</span>
</div>
</div>
{/* <Tabs.SectionTitle>Server</Tabs.SectionTitle> */}
{/* <Tabs.Trigger value="permissions"> */}
{/* <Icon name="checklist" /> */}
{/* Permissions */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="providers"> */}
{/* <Icon name="server" /> */}
{/* Providers */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="models"> */}
{/* <Icon name="brain" /> */}
{/* Models */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="agents"> */}
{/* <Icon name="task" /> */}
{/* Agents */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="commands"> */}
{/* <Icon name="console" /> */}
{/* Commands */}
{/* </Tabs.Trigger> */}
{/* <Tabs.Trigger value="mcp"> */}
{/* <Icon name="mcp" /> */}
{/* MCP */}
{/* </Tabs.Trigger> */}
</Tabs.List>
<Tabs.Content value="general" class="no-scrollbar">
<SettingsGeneral />
</Tabs.Content>
<Tabs.Content value="shortcuts" class="no-scrollbar">
<SettingsKeybinds />
</Tabs.Content>
{/* <Tabs.Content value="permissions" class="no-scrollbar"> */}
{/* <SettingsPermissions /> */}
{/* </Tabs.Content> */}
{/* <Tabs.Content value="providers" class="no-scrollbar"> */}
{/* <SettingsProviders /> */}
{/* </Tabs.Content> */}
<Tabs.Content value="providers" class="no-scrollbar">
<SettingsProviders />
</Tabs.Content>
{/* <Tabs.Content value="models" class="no-scrollbar"> */}
{/* <SettingsModels /> */}
{/* </Tabs.Content> */}
Expand Down
150 changes: 145 additions & 5 deletions packages/app/src/components/settings-providers.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,154 @@
import { Component } from "solid-js"
import { Button } from "@opencode-ai/ui/button"
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 { 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 { DialogConnectProvider } from "./dialog-connect-provider"
import { DialogSelectProvider } from "./dialog-select-provider"

type ProviderSource = "env" | "api" | "config" | "custom"
type ProviderMeta = { source?: ProviderSource }

export const SettingsProviders: Component = () => {
const dialog = useDialog()
const language = useLanguage()
const globalSDK = useGlobalSDK()
const providers = useProviders()

const connected = createMemo(() => providers.connected())
const popular = createMemo(() => {
const connectedIDs = new Set(connected().map((p) => p.id))
const items = providers
.popular()
.filter((p) => !connectedIDs.has(p.id))
.slice()
items.sort((a, b) => popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id))
return items
})

const source = (item: unknown) => (item as ProviderMeta).source

const type = (item: unknown) => {
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 === "custom") return language.t("settings.providers.tag.custom")
return language.t("settings.providers.tag.other")
}

const canDisconnect = (item: unknown) => source(item) !== "env"

const disconnect = async (providerID: string, name: string) => {
await globalSDK.client.auth
.remove({ providerID })
.then(async () => {
await globalSDK.client.global.dispose()
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) => {
const message = err instanceof Error ? err.message : String(err)
showToast({ title: language.t("common.requestFailed"), description: message })
})
}

return (
<div class="flex flex-col h-full overflow-y-auto">
<div class="flex flex-col gap-6 p-6 max-w-[600px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>
<p class="text-14-regular text-text-weak">{language.t("settings.providers.description")}</p>
<div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}>
<div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
<div class="flex flex-col gap-1 pt-6 pb-8 max-w-[720px]">
<h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>
</div>
</div>

<div class="flex flex-col gap-8 max-w-[720px]">
<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.providers.section.connected")}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<Show
when={connected().length > 0}
fallback={
<div class="py-4 text-14-regular text-text-weak">
{language.t("settings.providers.connected.empty")}
</div>
}
>
<For each={connected()}>
{(item) => (
<div class="flex items-center justify-between gap-4 py-3 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" />
<span class="text-14-regular text-text-strong truncate">{item.name}</span>
<Tag>{type(item)}</Tag>
</div>
<Show when={canDisconnect(item)}>
<Button size="small" variant="ghost" onClick={() => void disconnect(item.id, item.name)}>
{language.t("common.disconnect")}
</Button>
</Show>
</div>
)}
</For>
</Show>
</div>
</div>

<div class="flex flex-col gap-1">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.providers.section.popular")}</h3>
<div class="bg-surface-raised-base px-4 rounded-lg">
<For each={popular()}>
{(item) => (
<div class="flex items-center justify-between gap-4 py-3 border-b border-border-weak-base last:border-none">
<div class="flex items-center gap-x-3 min-w-0">
<ProviderIcon id={item.id as IconName} class="size-5 shrink-0 icon-strong-base" />
<span class="text-14-regular text-text-strong">{item.name}</span>
<Show when={item.id === "opencode"}>
<Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
</Show>
<Show when={item.id === "anthropic"}>
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.anthropic.note")}</div>
</Show>
<Show when={item.id === "openai"}>
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.openai.note")}</div>
</Show>
<Show when={item.id.startsWith("github-copilot")}>
<div class="text-14-regular text-text-weak">{language.t("dialog.provider.copilot.note")}</div>
</Show>
</div>
<Button
size="large"
variant="secondary"
icon="plus-small"
onClick={() => {
dialog.show(() => <DialogConnectProvider provider={item.id} />)
}}
>
{language.t("common.connect")}
</Button>
</div>
)}
</For>
</div>

<Button
variant="ghost"
class="px-0 py-0 text-14-medium text-text-strong text-left justify-start hover:bg-transparent active:bg-transparent"
onClick={() => {
dialog.show(() => <DialogSelectProvider />)
}}
>
{language.t("dialog.provider.viewAll")}
</Button>
</div>
</div>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/i18n/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "نماذج مجانية مقدمة من OpenCode",
"dialog.model.unpaid.addMore.title": "إضافة المزيد من النماذج من موفرين مشهورين",

"dialog.provider.viewAll": "عرض جميع الموفرين",
"dialog.provider.viewAll": "عرض المزيد من الموفرين",

"provider.connect.title": "اتصال {{provider}}",
"provider.connect.title.anthropicProMax": "تسجيل الدخول باستخدام Claude Pro/Max",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/i18n/br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "Modelos gratuitos fornecidos pelo OpenCode",
"dialog.model.unpaid.addMore.title": "Adicionar mais modelos de provedores populares",

"dialog.provider.viewAll": "Ver todos os provedores",
"dialog.provider.viewAll": "Ver mais provedores",

"provider.connect.title": "Conectar {{provider}}",
"provider.connect.title.anthropicProMax": "Entrar com Claude Pro/Max",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/i18n/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "Gratis modeller leveret af OpenCode",
"dialog.model.unpaid.addMore.title": "Tilføj flere modeller fra populære udbydere",

"dialog.provider.viewAll": "Vis alle udbydere",
"dialog.provider.viewAll": "Vis flere udbydere",

"provider.connect.title": "Forbind {{provider}}",
"provider.connect.title.anthropicProMax": "Log ind med Claude Pro/Max",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "Kostenlose Modelle von OpenCode",
"dialog.model.unpaid.addMore.title": "Weitere Modelle von beliebten Anbietern hinzufügen",

"dialog.provider.viewAll": "Alle Anbieter anzeigen",
"dialog.provider.viewAll": "Mehr Anbieter anzeigen",

"provider.connect.title": "{{provider}} verbinden",
"provider.connect.title.anthropicProMax": "Mit Claude Pro/Max anmelden",
Expand Down
15 changes: 14 additions & 1 deletion packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "Free models provided by OpenCode",
"dialog.model.unpaid.addMore.title": "Add more models from popular providers",

"dialog.provider.viewAll": "View all providers",
"dialog.provider.viewAll": "Show more providers",

"provider.connect.title": "Connect {{provider}}",
"provider.connect.title.anthropicProMax": "Login with Claude Pro/Max",
Expand Down Expand Up @@ -137,6 +137,9 @@ export const dict = {
"provider.connect.toast.connected.title": "{{provider}} connected",
"provider.connect.toast.connected.description": "{{provider}} models are now available to use.",

"provider.disconnect.toast.disconnected.title": "{{provider}} disconnected",
"provider.disconnect.toast.disconnected.description": "{{provider}} models are no longer available.",

"model.tag.free": "Free",
"model.tag.latest": "Latest",
"model.provider.anthropic": "Anthropic",
Expand All @@ -159,6 +162,8 @@ export const dict = {
"common.loading": "Loading",
"common.loading.ellipsis": "...",
"common.cancel": "Cancel",
"common.connect": "Connect",
"common.disconnect": "Disconnect",
"common.submit": "Submit",
"common.save": "Save",
"common.saving": "Saving...",
Expand Down Expand Up @@ -491,6 +496,7 @@ export const dict = {
"sidebar.project.viewAllSessions": "View all sessions",

"settings.section.desktop": "Desktop",
"settings.section.server": "Server",
"settings.tab.general": "General",
"settings.tab.shortcuts": "Shortcuts",

Expand Down Expand Up @@ -599,6 +605,13 @@ export const dict = {

"settings.providers.title": "Providers",
"settings.providers.description": "Provider settings will be configurable here.",
"settings.providers.section.connected": "Connected providers",
"settings.providers.connected.empty": "No connected providers",
"settings.providers.section.popular": "Popular providers",
"settings.providers.tag.environment": "Environment",
"settings.providers.tag.config": "Config",
"settings.providers.tag.custom": "Custom",
"settings.providers.tag.other": "Other",
"settings.models.title": "Models",
"settings.models.description": "Model settings will be configurable here.",
"settings.agents.title": "Agents",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "Modelos gratuitos proporcionados por OpenCode",
"dialog.model.unpaid.addMore.title": "Añadir más modelos de proveedores populares",

"dialog.provider.viewAll": "Ver todos los proveedores",
"dialog.provider.viewAll": "Ver más proveedores",

"provider.connect.title": "Conectar {{provider}}",
"provider.connect.title.anthropicProMax": "Iniciar sesión con Claude Pro/Max",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "Modèles gratuits fournis par OpenCode",
"dialog.model.unpaid.addMore.title": "Ajouter plus de modèles de fournisseurs populaires",

"dialog.provider.viewAll": "Voir tous les fournisseurs",
"dialog.provider.viewAll": "Voir plus de fournisseurs",

"provider.connect.title": "Connecter {{provider}}",
"provider.connect.title.anthropicProMax": "Connexion avec Claude Pro/Max",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "OpenCodeが提供する無料モデル",
"dialog.model.unpaid.addMore.title": "人気のプロバイダーからモデルを追加",

"dialog.provider.viewAll": "すべてのプロバイダーを表示",
"dialog.provider.viewAll": "さらにプロバイダーを表示",

"provider.connect.title": "{{provider}}を接続",
"provider.connect.title.anthropicProMax": "Claude Pro/Maxでログイン",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "OpenCode에서 제공하는 무료 모델",
"dialog.model.unpaid.addMore.title": "인기 공급자의 모델 추가",

"dialog.provider.viewAll": "모든 공급자 보기",
"dialog.provider.viewAll": "더 많은 공급자 보기",

"provider.connect.title": "{{provider}} 연결",
"provider.connect.title.anthropicProMax": "Claude Pro/Max로 로그인",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/i18n/no.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const dict = {
"dialog.model.unpaid.freeModels.title": "Gratis modeller levert av OpenCode",
"dialog.model.unpaid.addMore.title": "Legg til flere modeller fra populære leverandører",

"dialog.provider.viewAll": "Vis alle leverandører",
"dialog.provider.viewAll": "Vis flere leverandører",

"provider.connect.title": "Koble til {{provider}}",
"provider.connect.title.anthropicProMax": "Logg inn med Claude Pro/Max",
Expand Down
Loading