Q169: saved cloud and custom endpoints refill their form and keep the saved key
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5cd413ffcf
commit
a8fbc08df6
13 files changed
+160
-23
No files matched your search
@@ -680,6 +680,9 @@ export const zh: Record<string, string> = {
|
||||
"{percent}% of cap": "上限的 {percent}%",
|
||||
"Default model for {label}": "{label} 的默认模型",
|
||||
"Change model": "更换模型",
|
||||
"Saved. Leave blank to keep it.": "已保存。留空即保留。",
|
||||
"The saved settings could not be read: {error}": "无法读取已保存的设置:{error}",
|
||||
"Loading saved settings…": "正在读取已保存的设置…",
|
||||
"Leave the model blank to use the provider's default. A changed model applies from the agent's next session.": "模型留空即使用 Provider 的默认模型。修改后的模型从该 Agent 的下一次会话起生效。",
|
||||
"{model} (provider default)": "{model}(Provider 默认)",
|
||||
"Chosen when the session starts": "会话开始时确定",
|
||||
|
||||
@@ -1,18 +1,38 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { ProviderCatalogEntry, ProviderRecord } from "../../../../shared/domain";
|
||||
import { ProviderOAuthConnect } from "../../shared/ProviderOAuthConnect";
|
||||
import { Field } from "../../shared/ui";
|
||||
import { t } from "../../i18n/i18n";
|
||||
import type { ModelCheckResult } from "../../../../shared/desktop-api";
|
||||
import type { ProviderEndpoint } from "../../../../shared/commands";
|
||||
|
||||
interface CloudFields {
|
||||
baseUrl: string;
|
||||
region: string;
|
||||
projectId: string;
|
||||
deployment: string;
|
||||
const CLOUD_DEFAULTS: ProviderEndpoint = { baseUrl: "", region: "", projectId: "", deployment: "" };
|
||||
|
||||
/** The saved endpoint of a connected cloud or custom provider: undefined while loading, null when nothing is saved. */
|
||||
function useSavedEndpoint(provider: ProviderRecord, entry: ProviderCatalogEntry): { endpoint: ProviderEndpoint | null | undefined; error: string | null } {
|
||||
const saved = entry.authType === "cloud" && Boolean(provider.credentialId);
|
||||
const [state, setState] = useState<{ endpoint: ProviderEndpoint | null | undefined; error: string | null }>({ endpoint: saved ? undefined : null, error: null });
|
||||
useEffect(() => {
|
||||
if (!saved) return;
|
||||
let live = true;
|
||||
void window.agentCompany.providerEndpoint({ orgId: provider.orgId, providerId: provider.id }).then((result) => {
|
||||
if (!live) return;
|
||||
setState(result.ok ? { endpoint: result.data, error: null } : { endpoint: null, error: result.error.message });
|
||||
});
|
||||
return () => { live = false; };
|
||||
}, [saved, provider.orgId, provider.id]);
|
||||
return state;
|
||||
}
|
||||
|
||||
const CLOUD_DEFAULTS: CloudFields = { baseUrl: "", region: "", projectId: "", deployment: "" };
|
||||
/** With a saved key the field may stay blank, which keeps it. */
|
||||
function SecretField({ label, saved, required }: { label: string; saved: boolean; required: boolean }) {
|
||||
return (
|
||||
<Field label={label}>
|
||||
<input className="input" name="secret" type="password" required={required && !saved} placeholder={saved ? t("Saved. Leave blank to keep it.") : undefined} autoFocus />
|
||||
{saved && <input type="hidden" name="keepSavedSecret" value="1" />}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProviderConnectFields({
|
||||
provider,
|
||||
@@ -30,10 +50,16 @@ export function ProviderConnectFields({
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (entry.customEndpoint) {
|
||||
return <CompatFieldsForm entry={entry} provider={provider} />;
|
||||
}
|
||||
return <CloudFieldsForm entry={entry} provider={provider} />;
|
||||
return <SavedEndpointForm entry={entry} provider={provider} />;
|
||||
}
|
||||
|
||||
function SavedEndpointForm({ provider, entry }: { provider: ProviderRecord; entry: ProviderCatalogEntry }) {
|
||||
const { endpoint, error } = useSavedEndpoint(provider, entry);
|
||||
if (error) return <div className="form-error" role="alert">{t("The saved settings could not be read: {error}", { error })}</div>;
|
||||
if (endpoint === undefined) return <p style={{ fontSize: 12, color: "var(--text-3)", margin: 0 }}>{t("Loading saved settings…")}</p>;
|
||||
return entry.customEndpoint
|
||||
? <CompatFieldsForm entry={entry} provider={provider} saved={endpoint} />
|
||||
: <CloudFieldsForm entry={entry} provider={provider} saved={endpoint} />;
|
||||
}
|
||||
|
||||
export function ProviderOAuthFields({
|
||||
@@ -75,8 +101,8 @@ export function ProviderOAuthFields({
|
||||
);
|
||||
}
|
||||
|
||||
function CompatFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
|
||||
const [baseUrl, setBaseUrl] = useState("");
|
||||
function CompatFieldsForm({ entry, provider, saved }: { entry: ProviderCatalogEntry; provider: ProviderRecord; saved: ProviderEndpoint | null }) {
|
||||
const [baseUrl, setBaseUrl] = useState(saved?.baseUrl ?? "");
|
||||
return (
|
||||
<>
|
||||
<p style={{ fontSize: 12, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
||||
@@ -88,7 +114,7 @@ function CompatFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; pr
|
||||
onChange={(event) => setBaseUrl(event.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field label={t("API key (if the server asks for one)")}><input className="input" name="secret" type="password" /></Field>
|
||||
<SecretField label={t("API key (if the server asks for one)")} saved={saved !== null} required={false} />
|
||||
<ModelPicker entry={entry} provider={provider} />
|
||||
</>
|
||||
);
|
||||
@@ -159,9 +185,9 @@ export function ModelPicker({ entry, provider }: { entry: ProviderCatalogEntry;
|
||||
);
|
||||
}
|
||||
|
||||
function CloudFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
|
||||
const [state, setState] = useState<CloudFields>(CLOUD_DEFAULTS);
|
||||
const update = (patch: Partial<CloudFields>) => setState((current) => ({ ...current, ...patch }));
|
||||
function CloudFieldsForm({ entry, provider, saved }: { entry: ProviderCatalogEntry; provider: ProviderRecord; saved: ProviderEndpoint | null }) {
|
||||
const [state, setState] = useState<ProviderEndpoint>(saved ?? CLOUD_DEFAULTS);
|
||||
const update = (patch: Partial<ProviderEndpoint>) => setState((current) => ({ ...current, ...patch }));
|
||||
return (
|
||||
<>
|
||||
<p style={{ fontSize: 12, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
|
||||
@@ -177,9 +203,7 @@ function CloudFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; pro
|
||||
<Field label={t("Deployment / model id")}>
|
||||
<input className="input" name="cloudDeployment" value={state.deployment} onChange={(event) => update({ deployment: event.target.value })} />
|
||||
</Field>
|
||||
<Field label={t("Credential")}>
|
||||
<input className="input" name="secret" type="password" required autoFocus />
|
||||
</Field>
|
||||
<SecretField label={t("Credential")} saved={saved !== null} required />
|
||||
<Field label={t("Default model")}>
|
||||
<input className="input" name="model" defaultValue={provider.model ?? entry.defaultModel ?? ""} />
|
||||
</Field>
|
||||
|
||||
@@ -9,6 +9,7 @@ export function composeSecret(entry: ProviderCatalogEntry, form: FormData): stri
|
||||
projectId: String(form.get("cloudProject") ?? "").trim(),
|
||||
deployment: String(form.get("cloudDeployment") ?? "").trim(),
|
||||
secret: raw,
|
||||
...(raw === "" && form.get("keepSavedSecret") === "1" ? { keepSavedSecret: true } : {}),
|
||||
};
|
||||
return JSON.stringify(payload);
|
||||
}
|
||||
Reference in new issue
Block a user