Skip to content
Open
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
68 changes: 51 additions & 17 deletions packages/app/src/components/dialog-custom-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ function validateCustomProvider(input: ValidateArgs) {

type Props = {
back?: "providers" | "close"
editProviderID?: string
}

export function DialogCustomProvider(props: Props) {
Expand All @@ -167,22 +168,46 @@ export function DialogCustomProvider(props: Props) {
const globalSDK = useGlobalSDK()
const language = useLanguage()

const [form, setForm] = createStore<FormState>({
providerID: "",
name: "",
baseURL: "",
apiKey: "",
models: [{ id: "", name: "" }],
headers: [{ key: "", value: "" }],
saving: false,
})
const isEdit = () => !!props.editProviderID

const initialForm = (): FormState => {
if (!props.editProviderID) {
return {
providerID: "",
name: "",
baseURL: "",
apiKey: "",
models: [{ id: "", name: "" }],
headers: [{ key: "", value: "" }],
saving: false,
}
}
const config = globalSync.data.config.provider?.[props.editProviderID]
const models: ModelRow[] = config?.models
? Object.entries(config.models).map(([id, m]) => ({ id, name: (m as { name: string }).name }))
: [{ id: "", name: "" }]
const headers: HeaderRow[] = config?.options?.headers
? Object.entries(config.options.headers as Record<string, string>).map(([key, value]) => ({ key, value }))
: [{ key: "", value: "" }]
return {
providerID: props.editProviderID,
name: config?.name ?? "",
baseURL: (config?.options?.baseURL as string) ?? "",
apiKey: "",
models: models.length > 0 ? models : [{ id: "", name: "" }],
headers: headers.length > 0 ? headers : [{ key: "", value: "" }],
saving: false,
}
}

const [form, setForm] = createStore<FormState>(initialForm())

const [errors, setErrors] = createStore<FormErrors>({
providerID: undefined,
name: undefined,
baseURL: undefined,
models: [{}],
headers: [{}],
models: form.models.map(() => ({})),
headers: form.headers.map(() => ({})),
})

const goBack = () => {
Expand Down Expand Up @@ -216,11 +241,13 @@ export function DialogCustomProvider(props: Props) {
}

const validate = () => {
const existingIDs = new Set(globalSync.data.provider.all.map((p) => p.id))
if (props.editProviderID) existingIDs.delete(props.editProviderID)
const output = validateCustomProvider({
form,
t: language.t,
disabledProviders: globalSync.data.config.disabled_providers ?? [],
existingProviderIDs: new Set(globalSync.data.provider.all.map((p) => p.id)),
existingProviderIDs: existingIDs,
})
setErrors(output.errors)
return output.result
Expand Down Expand Up @@ -257,8 +284,12 @@ export function DialogCustomProvider(props: Props) {
showToast({
variant: "success",
icon: "circle-check",
title: language.t("provider.connect.toast.connected.title", { provider: result.name }),
description: language.t("provider.connect.toast.connected.description", { provider: result.name }),
title: isEdit()
? language.t("provider.edit.toast.updated.title", { provider: result.name })
: language.t("provider.connect.toast.connected.title", { provider: result.name }),
description: isEdit()
? language.t("provider.edit.toast.updated.description", { provider: result.name })
: language.t("provider.connect.toast.connected.description", { provider: result.name }),
})
})
.catch((err: unknown) => {
Expand Down Expand Up @@ -286,7 +317,9 @@ export function DialogCustomProvider(props: Props) {
<div class="flex flex-col gap-6 px-2.5 pb-3 overflow-y-auto max-h-[60vh]">
<div class="px-2.5 flex gap-4 items-center">
<ProviderIcon id="synthetic" class="size-5 shrink-0 icon-strong-base" />
<div class="text-16-medium text-text-strong">{language.t("provider.custom.title")}</div>
<div class="text-16-medium text-text-strong">
{isEdit() ? language.t("provider.custom.title.edit") : language.t("provider.custom.title")}
</div>
</div>

<form onSubmit={save} class="px-2.5 pb-6 flex flex-col gap-6">
Expand All @@ -300,7 +333,8 @@ export function DialogCustomProvider(props: Props) {

<div class="flex flex-col gap-4">
<TextField
autofocus
autofocus={!isEdit()}
disabled={isEdit()}
label={language.t("provider.custom.field.providerID.label")}
placeholder={language.t("provider.custom.field.providerID.placeholder")}
description={language.t("provider.custom.field.providerID.description")}
Expand Down Expand Up @@ -423,7 +457,7 @@ export function DialogCustomProvider(props: Props) {
</div>

<Button class="w-auto self-start" type="submit" size="large" variant="primary" disabled={form.saving}>
{form.saving ? language.t("common.saving") : language.t("common.submit")}
{form.saving ? language.t("common.saving") : isEdit() ? language.t("common.save") : language.t("common.submit")}
</Button>
</form>
</div>
Expand Down
55 changes: 53 additions & 2 deletions packages/app/src/components/dialog-manage-models.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { Dialog } from "@opencode-ai/ui/dialog"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { List } from "@opencode-ai/ui/list"
import { Switch } from "@opencode-ai/ui/switch"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { Button } from "@opencode-ai/ui/button"
import type { Component } from "solid-js"
import { Show, type Component } from "solid-js"
import { useLocal } from "@/context/local"
import { useGlobalSync } from "@/context/global-sync"
import { popularProviders } from "@/hooks/use-providers"
import { useLanguage } from "@/context/language"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { DialogSelectProvider } from "./dialog-select-provider"
import { DialogCustomProvider } from "./dialog-custom-provider"
import { DialogConnectProvider } from "./dialog-connect-provider"

export const DialogManageModels: Component = () => {
const local = useLocal()
const language = useLanguage()
const dialog = useDialog()
const globalSync = useGlobalSync()

const handleConnectProvider = () => {
dialog.show(() => <DialogSelectProvider />)
Expand All @@ -28,6 +33,38 @@ export const DialogManageModels: Component = () => {
})
}

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 getProviderSource = (providerID: string) => {
const item = globalSync.data.provider.all.find((p) => p.id === providerID)
if (!item) return undefined
if (!("source" in item)) return undefined
const value = (item as { source: string }).source
if (value === "env" || value === "api" || value === "config" || value === "custom") return value
return undefined
}

const isEditable = (providerID: string) => {
const source = getProviderSource(providerID)
if (source === "config" && isConfigCustom(providerID)) return true
if (source === "api") return true
return false
}

const handleEditProvider = (providerID: string) => {
if (isConfigCustom(providerID)) {
dialog.show(() => <DialogCustomProvider editProviderID={providerID} back="close" />)
} else {
dialog.show(() => <DialogConnectProvider provider={providerID} />)
}
}

return (
<Dialog
title={language.t("dialog.model.manage")}
Expand All @@ -50,7 +87,21 @@ export const DialogManageModels: Component = () => {
const provider = group.items[0].provider
return (
<>
<span>{provider.name}</span>
<span class="flex items-center gap-x-1.5">
{provider.name}
<Show when={isEditable(provider.id)}>
<IconButton
icon="pencil-line"
variant="ghost"
class="size-5"
onClick={(e) => {
e.stopPropagation()
handleEditProvider(provider.id)
}}
aria-label={language.t("common.edit")}
/>
</Show>
</span>
<Tooltip
placement="top"
value={language.t("dialog.model.manage.provider.toggle", { provider: provider.name })}
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ export const dict = {
"provider.connect.toast.connected.title": "تم توصيل {{provider}}",
"provider.connect.toast.connected.description": "نماذج {{provider}} متاحة الآن للاستخدام.",
"provider.custom.title": "موفر مخصص",
"provider.custom.title.edit": "تعديل الموفر",
"provider.edit.toast.updated.title": "تم تحديث {{provider}}",
"provider.edit.toast.updated.description": "تم تحديث إعدادات {{provider}}.",
"provider.custom.description.prefix": "تكوين موفر متوافق مع OpenAI. راجع ",
"provider.custom.description.link": "وثائق تكوين الموفر",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ export const dict = {
"provider.connect.toast.connected.title": "{{provider}} conectado",
"provider.connect.toast.connected.description": "Modelos do {{provider}} agora estão disponíveis para uso.",
"provider.custom.title": "Provedor personalizado",
"provider.custom.title.edit": "Editar provedor",
"provider.edit.toast.updated.title": "{{provider}} atualizado",
"provider.edit.toast.updated.description": "A configuração de {{provider}} foi atualizada.",
"provider.custom.description.prefix": "Configure um provedor compatível com OpenAI. Veja a ",
"provider.custom.description.link": "documentação de configuração do provedor",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/bs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ export const dict = {
"provider.connect.toast.connected.description": "{{provider}} modeli su sada dostupni za korištenje.",

"provider.custom.title": "Prilagođeni provajder",
"provider.custom.title.edit": "Uredi provajdera",
"provider.edit.toast.updated.title": "{{provider}} ažuriran",
"provider.edit.toast.updated.description": "Konfiguracija za {{provider}} je ažurirana.",
"provider.custom.description.prefix": "Konfiguriši OpenAI-kompatibilnog provajdera. Pogledaj ",
"provider.custom.description.link": "dokumentaciju za konfiguraciju provajdera",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ export const dict = {
"provider.connect.toast.connected.description": "{{provider}} modeller er nu tilgængelige.",

"provider.custom.title": "Brugerdefineret udbyder",
"provider.custom.title.edit": "Rediger udbyder",
"provider.edit.toast.updated.title": "{{provider}} opdateret",
"provider.edit.toast.updated.description": "Konfigurationen for {{provider}} er blevet opdateret.",
"provider.custom.description.prefix": "Konfigurer en OpenAI-kompatibel udbyder. Se ",
"provider.custom.description.link": "dokumentation for udbyderkonfiguration",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ export const dict = {
"provider.connect.toast.connected.title": "{{provider}} verbunden",
"provider.connect.toast.connected.description": "{{provider}} Modelle sind jetzt verfügbar.",
"provider.custom.title": "Benutzerdefinierter Anbieter",
"provider.custom.title.edit": "Anbieter bearbeiten",
"provider.edit.toast.updated.title": "{{provider}} aktualisiert",
"provider.edit.toast.updated.description": "Die Konfiguration von {{provider}} wurde aktualisiert.",
"provider.custom.description.prefix": "Konfigurieren Sie einen OpenAI-kompatiblen Anbieter. Siehe die ",
"provider.custom.description.link": "Anbieter-Konfigurationsdokumente",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ export const dict = {
"provider.connect.toast.connected.description": "{{provider}} models are now available to use.",

"provider.custom.title": "Custom provider",
"provider.custom.title.edit": "Edit provider",
"provider.edit.toast.updated.title": "{{provider}} updated",
"provider.edit.toast.updated.description": "{{provider}} configuration has been updated.",
"provider.custom.description.prefix": "Configure an OpenAI-compatible provider. See the ",
"provider.custom.description.link": "provider config docs",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ export const dict = {
"provider.connect.toast.connected.description": "Los modelos de {{provider}} ahora están disponibles para usar.",

"provider.custom.title": "Proveedor personalizado",
"provider.custom.title.edit": "Editar proveedor",
"provider.edit.toast.updated.title": "{{provider}} actualizado",
"provider.edit.toast.updated.description": "La configuración de {{provider}} ha sido actualizada.",
"provider.custom.description.prefix": "Configurar un proveedor compatible con OpenAI. Ver la ",
"provider.custom.description.link": "documentación de configuración del proveedor",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ export const dict = {
"provider.connect.toast.connected.title": "{{provider}} connecté",
"provider.connect.toast.connected.description": "Les modèles {{provider}} sont maintenant disponibles.",
"provider.custom.title": "Fournisseur personnalisé",
"provider.custom.title.edit": "Modifier le fournisseur",
"provider.edit.toast.updated.title": "{{provider}} mis à jour",
"provider.edit.toast.updated.description": "La configuration de {{provider}} a été mise à jour.",
"provider.custom.description.prefix": "Configurer un fournisseur compatible OpenAI. Voir la ",
"provider.custom.description.link": "doc de config fournisseur",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ export const dict = {
"provider.connect.toast.connected.title": "{{provider}}が接続されました",
"provider.connect.toast.connected.description": "{{provider}}モデルが使用可能になりました。",
"provider.custom.title": "カスタムプロバイダー",
"provider.custom.title.edit": "プロバイダーを編集",
"provider.edit.toast.updated.title": "{{provider}} を更新しました",
"provider.edit.toast.updated.description": "{{provider}} の設定が更新されました。",
"provider.custom.description.prefix": "OpenAI互換のプロバイダーを設定します。詳細は",
"provider.custom.description.link": "プロバイダー設定ドキュメント",
"provider.custom.description.suffix": "をご覧ください。",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ export const dict = {
"provider.connect.toast.connected.title": "{{provider}} 연결됨",
"provider.connect.toast.connected.description": "이제 {{provider}} 모델을 사용할 수 있습니다.",
"provider.custom.title": "사용자 지정 공급자",
"provider.custom.title.edit": "공급자 편집",
"provider.edit.toast.updated.title": "{{provider}} 업데이트됨",
"provider.edit.toast.updated.description": "{{provider}} 구성이 업데이트되었습니다.",
"provider.custom.description.prefix": "OpenAI 호환 공급자를 구성합니다. ",
"provider.custom.description.link": "공급자 구성 문서",
"provider.custom.description.suffix": "를 참조하세요.",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/no.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ export const dict = {
"provider.connect.toast.connected.description": "{{provider}}-modeller er nå tilgjengelige.",

"provider.custom.title": "Egendefinert leverandør",
"provider.custom.title.edit": "Rediger leverandør",
"provider.edit.toast.updated.title": "{{provider}} oppdatert",
"provider.edit.toast.updated.description": "Konfigurasjonen for {{provider}} har blitt oppdatert.",
"provider.custom.description.prefix": "Konfigurer en OpenAI-kompatibel leverandør. Se ",
"provider.custom.description.link": "dokumentasjon for leverandørkonfigurasjon",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ export const dict = {
"provider.connect.toast.connected.title": "Połączono {{provider}}",
"provider.connect.toast.connected.description": "Modele {{provider}} są teraz dostępne do użycia.",
"provider.custom.title": "Dostawca niestandardowy",
"provider.custom.title.edit": "Edytuj dostawcę",
"provider.edit.toast.updated.title": "{{provider}} zaktualizowany",
"provider.edit.toast.updated.description": "Konfiguracja {{provider}} została zaktualizowana.",
"provider.custom.description.prefix": "Skonfiguruj dostawcę zgodnego z OpenAI. Zobacz ",
"provider.custom.description.link": "dokumentację konfiguracji dostawcy",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ export const dict = {
"provider.connect.toast.connected.description": "Модели {{provider}} теперь доступны.",

"provider.custom.title": "Пользовательский провайдер",
"provider.custom.title.edit": "Редактировать провайдер",
"provider.edit.toast.updated.title": "{{provider}} обновлён",
"provider.edit.toast.updated.description": "Конфигурация {{provider}} была обновлена.",
"provider.custom.description.prefix": "Настройте OpenAI-совместимого провайдера. См. ",
"provider.custom.description.link": "документацию по настройке провайдера",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/th.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ export const dict = {
"provider.connect.toast.connected.description": "โมเดล {{provider}} พร้อมใช้งานแล้ว",

"provider.custom.title": "ผู้ให้บริการที่กำหนดเอง",
"provider.custom.title.edit": "แก้ไขผู้ให้บริการ",
"provider.edit.toast.updated.title": "อัปเดต {{provider}} แล้ว",
"provider.edit.toast.updated.description": "การกำหนดค่า {{provider}} ได้รับการอัปเดตแล้ว",
"provider.custom.description.prefix": "กำหนดค่าผู้ให้บริการที่เข้ากันได้กับ OpenAI ดู ",
"provider.custom.description.link": "เอกสารการกำหนดค่าผู้ให้บริการ",
"provider.custom.description.suffix": ".",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ export const dict = {
"provider.connect.toast.connected.description": "现在可以使用 {{provider}} 模型了。",

"provider.custom.title": "自定义提供商",
"provider.custom.title.edit": "编辑提供商",
"provider.edit.toast.updated.title": "{{provider}} 已更新",
"provider.edit.toast.updated.description": "{{provider}} 配置已更新。",
"provider.custom.description.prefix": "配置与 OpenAI 兼容的提供商。请查看",
"provider.custom.description.link": "提供商配置文档",
"provider.custom.description.suffix": "。",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/zht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ export const dict = {
"provider.connect.toast.connected.description": "現在可以使用 {{provider}} 模型了。",

"provider.custom.title": "自訂提供商",
"provider.custom.title.edit": "編輯提供商",
"provider.edit.toast.updated.title": "{{provider}} 已更新",
"provider.edit.toast.updated.description": "{{provider}} 設定已更新。",
"provider.custom.description.prefix": "設定與 OpenAI 相容的提供商。請參閱",
"provider.custom.description.link": "提供商設定文件",
"provider.custom.description.suffix": "。",
Expand Down
Loading