From 68039f19b713da840b7f7e6f4f84e348f09e6347 Mon Sep 17 00:00:00 2001 From: mokuro <82637350+Finkyky@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:44:00 +0800 Subject: [PATCH] feat(app): add edit provider button to manage models dialog --- .../src/components/dialog-custom-provider.tsx | 68 ++++++++++++++----- .../src/components/dialog-manage-models.tsx | 55 ++++++++++++++- packages/app/src/i18n/ar.ts | 3 + packages/app/src/i18n/br.ts | 3 + packages/app/src/i18n/bs.ts | 3 + packages/app/src/i18n/da.ts | 3 + packages/app/src/i18n/de.ts | 3 + packages/app/src/i18n/en.ts | 3 + packages/app/src/i18n/es.ts | 3 + packages/app/src/i18n/fr.ts | 3 + packages/app/src/i18n/ja.ts | 3 + packages/app/src/i18n/ko.ts | 3 + packages/app/src/i18n/no.ts | 3 + packages/app/src/i18n/pl.ts | 3 + packages/app/src/i18n/ru.ts | 3 + packages/app/src/i18n/th.ts | 3 + packages/app/src/i18n/zh.ts | 3 + packages/app/src/i18n/zht.ts | 3 + 18 files changed, 152 insertions(+), 19 deletions(-) diff --git a/packages/app/src/components/dialog-custom-provider.tsx b/packages/app/src/components/dialog-custom-provider.tsx index 017b85a2c99..211885f022b 100644 --- a/packages/app/src/components/dialog-custom-provider.tsx +++ b/packages/app/src/components/dialog-custom-provider.tsx @@ -159,6 +159,7 @@ function validateCustomProvider(input: ValidateArgs) { type Props = { back?: "providers" | "close" + editProviderID?: string } export function DialogCustomProvider(props: Props) { @@ -167,22 +168,46 @@ export function DialogCustomProvider(props: Props) { const globalSDK = useGlobalSDK() const language = useLanguage() - const [form, setForm] = createStore({ - 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).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(initialForm()) const [errors, setErrors] = createStore({ providerID: undefined, name: undefined, baseURL: undefined, - models: [{}], - headers: [{}], + models: form.models.map(() => ({})), + headers: form.headers.map(() => ({})), }) const goBack = () => { @@ -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 @@ -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) => { @@ -286,7 +317,9 @@ export function DialogCustomProvider(props: Props) {
-
{language.t("provider.custom.title")}
+
+ {isEdit() ? language.t("provider.custom.title.edit") : language.t("provider.custom.title")} +
@@ -300,7 +333,8 @@ export function DialogCustomProvider(props: Props) {
diff --git a/packages/app/src/components/dialog-manage-models.tsx b/packages/app/src/components/dialog-manage-models.tsx index ace79e38a7c..b689fe674bb 100644 --- a/packages/app/src/components/dialog-manage-models.tsx +++ b/packages/app/src/components/dialog-manage-models.tsx @@ -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(() => ) @@ -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(() => ) + } else { + dialog.show(() => ) + } + } + return ( { const provider = group.items[0].provider return ( <> - {provider.name} + + {provider.name} + + { + e.stopPropagation() + handleEditProvider(provider.id) + }} + aria-label={language.t("common.edit")} + /> + +