Split oversized renderer components

This commit is contained in:
ZacharyZhang-NY committed 2026-06-07 13:52:42 -04:00
1 parent 91d9b76281
commit 67d2cf5649
5 files changed
+392 -393

No files matched your search

@@ -1,16 +1,16 @@
import { Fragment, useEffect, useMemo, useState, type FormEvent } from "react";
import { useMemo, useState, type FormEvent } from "react";
import {
PROVIDER_AUTH_TYPE,
type AppSnapshot,
type ProviderCatalogEntry,
} from "../../../shared/domain";
import { Button, Field, Icon, IconButton, Pill } from "../shared/ui";
import { ProviderOAuthConnect } from "../shared/ProviderOAuthConnect";
import type { SnapshotActions } from "./useSnapshot";
import { ProviderStep } from "./OnboardingProviderStep";
type Step = "welcome" | "provider" | "company" | "mode" | "mission";
interface Draft {
export interface Draft {
companyName: string;
companyDescription: string;
employeeName: string;
@@ -234,202 +234,6 @@ function WelcomeStep({ onNext }: { onNext: () => void }) {
);
}
function ProviderStep({
providers,
draft,
onChange,
onBack,
onNext,
}: {
providers: ProviderCatalogEntry[];
draft: Draft;
onChange: (patch: Partial<Draft>) => void;
onBack: () => void;
onNext: () => void;
}) {
const selected = providers.find((entry) => entry.key === draft.providerKey) ?? providers[0];
if (!selected) throw new Error("No provider available for onboarding.");
return (
<div className="form-grid">
<h2 className="page-h1">Provider</h2>
<p style={{ color: "var(--text-3)", fontSize: 13 }}>
Pick the runtime for the founding CEO. You can attach more providers in Settings later.
</p>
<div className="option-grid">
{providers.map((provider) => (
<Fragment key={provider.key}>
<button
type="button"
className={`option-card ${draft.providerKey === provider.key ? "active" : ""}`.trim()}
onClick={() => onChange({
providerKey: provider.key,
providerModel: "",
providerSecret: "",
providerBaseUrl: "",
})}
>
<div className="row">
<div className="opt-title" style={{ flex: 1 }}>{provider.label}</div>
<Pill tone="violet">{authBadge(provider.authType)}</Pill>
</div>
<div className="opt-sub">{provider.capabilityTags.join(" · ") || authBadge(provider.authType)}</div>
</button>
{provider.key === selected.key && (
<ProviderCredentialFields selected={selected} draft={draft} onChange={onChange} />
)}
</Fragment>
))}
</div>
<Actions onBack={onBack} onNext={onNext} />
</div>
);
}
function ProviderCredentialFields({
selected,
draft,
onChange,
}: {
selected: ProviderCatalogEntry;
draft: Draft;
onChange: (patch: Partial<Draft>) => void;
}) {
const isOAuthLike = selected.authType === "oauth_pkce" || selected.authType === "account_subscription";
const isCustom = Boolean(selected.customEndpoint);
const live = selected.supportsLiveModels;
const [fetched, setFetched] = useState<ProviderCatalogEntry["models"] | null>(null);
const [loading, setLoading] = useState(false);
const [loadError, setLoadError] = useState<string | null>(null);
const hasCredential = isOAuthLike
? Boolean(draft.providerSecret.trim())
: isCustom
? Boolean(draft.providerBaseUrl.trim() && draft.providerSecret.trim())
: Boolean(draft.providerSecret.trim());
const loadModels = async (secret: string) => {
setLoading(true);
setLoadError(null);
const result = await window.agentCompany.listProviderModels({ providerKey: selected.key, secret });
setLoading(false);
if (!result.ok) {
setLoadError(result.error.message);
return;
}
setFetched(result.data);
if (result.data[0]) onChange({ providerModel: result.data[0].id });
};
const loadTypedModels = () => {
const secret = isCustom
? JSON.stringify({ baseUrl: draft.providerBaseUrl.trim(), secret: draft.providerSecret.trim() })
: draft.providerSecret.trim();
void loadModels(secret);
};
// Curated providers (no live endpoint) show their fixed dropdown once a
// credential is present; seed the selection with the provider default.
const showCuratedList = !live && hasCredential && selected.models.length > 0;
useEffect(() => {
if (showCuratedList && !draft.providerModel) {
onChange({ providerModel: selected.defaultModel ?? selected.models[0].id });
}
}, [showCuratedList, draft.providerModel, selected, onChange]);
return (
<div className="provider-inline-form">
{selected.credentialHelp && (
<div style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5 }}>{selected.credentialHelp}</div>
)}
{isOAuthLike ? (
<ProviderOAuthConnect
key={selected.key}
providerKey={selected.key}
label={selected.label}
buildInput={() => ({ providerKey: selected.key })}
onComplete={(secret) => {
onChange({ providerSecret: secret ?? "" });
if (secret && live) void loadModels(secret);
}}
/>
) : isCustom ? (
<>
<Field label="Base URL">
<input
className="input"
value={draft.providerBaseUrl}
placeholder={selected.customEndpoint === "openai" ? "https://host/v1" : "https://host"}
onChange={(event) => onChange({ providerBaseUrl: event.target.value })}
/>
</Field>
<Field label="API key">
<input
className="input"
type="password"
autoComplete="off"
value={draft.providerSecret}
onChange={(event) => onChange({ providerSecret: event.target.value })}
/>
</Field>
</>
) : (
<Field label="API key">
<input
className="input"
type="password"
autoComplete="off"
value={draft.providerSecret}
onChange={(event) => onChange({ providerSecret: event.target.value, providerModel: "" })}
/>
</Field>
)}
{!isOAuthLike && live && (
<Button tone="ghost" onClick={loadTypedModels} disabled={!hasCredential || loading}>
{loading ? "Getting models…" : fetched ? "Reload models" : "Get models"}
</Button>
)}
{loadError && <div style={{ color: "var(--red)", fontSize: 12 }}>{loadError}</div>}
{(hasCredential || isCustom) && (
<Field label="Model">
{fetched || showCuratedList ? (
<select
className="input"
value={draft.providerModel}
onChange={(event) => onChange({ providerModel: event.target.value })}
>
{(fetched ?? selected.models).map((model) => (
<option key={model.id} value={model.id}>{model.name}</option>
))}
</select>
) : (
<input
className="input"
value={draft.providerModel}
placeholder={modelPlaceholder(live, isOAuthLike, loading)}
onChange={(event) => onChange({ providerModel: event.target.value })}
/>
)}
</Field>
)}
</div>
);
}
function modelPlaceholder(live: boolean, isOAuthLike: boolean, loading: boolean): string {
if (!live) return "Enter the model id";
if (loading || isOAuthLike) return "Loading…";
return "Click “Get models”, or type a model id";
}
function authBadge(authType: string): string {
if (authType === "api_key") return "API key";
if (authType === "oauth_pkce") return "OAuth";
if (authType === "account_subscription") return "Account";
if (authType === "cloud") return "Cloud";
return authType;
}
function CompanyStep({ draft, onChange, onBack, onNext }: StepProps) {
const chooseWorkspace = () => {
void window.agentCompany.pickDirectory().then((result) => {
@@ -0,0 +1,208 @@
import { Fragment, useEffect, useState } from "react";
import type { ProviderCatalogEntry } from "../../../shared/domain";
import { ProviderOAuthConnect } from "../shared/ProviderOAuthConnect";
import { Button, Field, Pill } from "../shared/ui";
import type { Draft } from "./Onboarding";
export function ProviderStep({
providers,
draft,
onChange,
onBack,
onNext,
}: {
providers: ProviderCatalogEntry[];
draft: Draft;
onChange: (patch: Partial<Draft>) => void;
onBack: () => void;
onNext: () => void;
}) {
const selected = providers.find((entry) => entry.key === draft.providerKey) ?? providers[0];
if (!selected) throw new Error("No provider available for onboarding.");
return (
<div className="form-grid">
<h2 className="page-h1">Provider</h2>
<p style={{ color: "var(--text-3)", fontSize: 13 }}>
Pick the runtime for the founding CEO. You can attach more providers in Settings later.
</p>
<div className="option-grid">
{providers.map((provider) => (
<Fragment key={provider.key}>
<button
type="button"
className={`option-card ${draft.providerKey === provider.key ? "active" : ""}`.trim()}
onClick={() => onChange({
providerKey: provider.key,
providerModel: "",
providerSecret: "",
providerBaseUrl: "",
})}
>
<div className="row">
<div className="opt-title" style={{ flex: 1 }}>{provider.label}</div>
<Pill tone="violet">{authBadge(provider.authType)}</Pill>
</div>
<div className="opt-sub">{provider.capabilityTags.join(" · ") || authBadge(provider.authType)}</div>
</button>
{provider.key === selected.key && (
<ProviderCredentialFields selected={selected} draft={draft} onChange={onChange} />
)}
</Fragment>
))}
</div>
<Actions onBack={onBack} onNext={onNext} />
</div>
);
}
function ProviderCredentialFields({
selected,
draft,
onChange,
}: {
selected: ProviderCatalogEntry;
draft: Draft;
onChange: (patch: Partial<Draft>) => void;
}) {
const isOAuthLike = selected.authType === "oauth_pkce" || selected.authType === "account_subscription";
const isCustom = Boolean(selected.customEndpoint);
const live = selected.supportsLiveModels;
const [fetched, setFetched] = useState<ProviderCatalogEntry["models"] | null>(null);
const [loading, setLoading] = useState(false);
const [loadError, setLoadError] = useState<string | null>(null);
const hasCredential = isOAuthLike
? Boolean(draft.providerSecret.trim())
: isCustom
? Boolean(draft.providerBaseUrl.trim() && draft.providerSecret.trim())
: Boolean(draft.providerSecret.trim());
const loadModels = async (secret: string) => {
setLoading(true);
setLoadError(null);
const result = await window.agentCompany.listProviderModels({ providerKey: selected.key, secret });
setLoading(false);
if (!result.ok) {
setLoadError(result.error.message);
return;
}
setFetched(result.data);
if (result.data[0]) onChange({ providerModel: result.data[0].id });
};
const loadTypedModels = () => {
const secret = isCustom
? JSON.stringify({ baseUrl: draft.providerBaseUrl.trim(), secret: draft.providerSecret.trim() })
: draft.providerSecret.trim();
void loadModels(secret);
};
const showCuratedList = !live && hasCredential && selected.models.length > 0;
useEffect(() => {
if (showCuratedList && !draft.providerModel) {
onChange({ providerModel: selected.defaultModel ?? selected.models[0].id });
}
}, [showCuratedList, draft.providerModel, selected, onChange]);
return (
<div className="provider-inline-form">
{selected.credentialHelp && (
<div style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5 }}>{selected.credentialHelp}</div>
)}
{isOAuthLike ? (
<ProviderOAuthConnect
key={selected.key}
providerKey={selected.key}
label={selected.label}
buildInput={() => ({ providerKey: selected.key })}
onComplete={(secret) => {
onChange({ providerSecret: secret ?? "" });
if (secret && live) void loadModels(secret);
}}
/>
) : isCustom ? (
<>
<Field label="Base URL">
<input
className="input"
value={draft.providerBaseUrl}
placeholder={selected.customEndpoint === "openai" ? "https://host/v1" : "https://host"}
onChange={(event) => onChange({ providerBaseUrl: event.target.value })}
/>
</Field>
<Field label="API key">
<input
className="input"
type="password"
autoComplete="off"
value={draft.providerSecret}
onChange={(event) => onChange({ providerSecret: event.target.value })}
/>
</Field>
</>
) : (
<Field label="API key">
<input
className="input"
type="password"
autoComplete="off"
value={draft.providerSecret}
onChange={(event) => onChange({ providerSecret: event.target.value, providerModel: "" })}
/>
</Field>
)}
{!isOAuthLike && live && (
<Button tone="ghost" onClick={loadTypedModels} disabled={!hasCredential || loading}>
{loading ? "Getting models..." : fetched ? "Reload models" : "Get models"}
</Button>
)}
{loadError && <div style={{ color: "var(--red)", fontSize: 12 }}>{loadError}</div>}
{(hasCredential || isCustom) && (
<Field label="Model">
{fetched || showCuratedList ? (
<select
className="input"
value={draft.providerModel}
onChange={(event) => onChange({ providerModel: event.target.value })}
>
{(fetched ?? selected.models).map((model) => (
<option key={model.id} value={model.id}>{model.name}</option>
))}
</select>
) : (
<input
className="input"
value={draft.providerModel}
placeholder={modelPlaceholder(live, isOAuthLike, loading)}
onChange={(event) => onChange({ providerModel: event.target.value })}
/>
)}
</Field>
)}
</div>
);
}
function Actions({ onBack, onNext }: { onBack: () => void; onNext: () => void }) {
return (
<div className="modal-actions" style={{ marginTop: 4 }}>
<Button onClick={onBack}>Back</Button>
<Button tone="primary" onClick={onNext}>Continue</Button>
</div>
);
}
function modelPlaceholder(live: boolean, isOAuthLike: boolean, loading: boolean): string {
if (!live) return "Enter the model id";
if (loading || isOAuthLike) return "Loading...";
return "Click Get models, or type a model id";
}
function authBadge(authType: string): string {
if (authType === "api_key") return "API key";
if (authType === "oauth_pkce") return "OAuth";
if (authType === "account_subscription") return "Account";
if (authType === "cloud") return "Cloud";
return authType;
}
@@ -0,0 +1,164 @@
import { useState } from "react";
import type { ProviderCatalogEntry, ProviderRecord } from "../../../../shared/domain";
import { ProviderOAuthConnect } from "../../shared/ProviderOAuthConnect";
import { Field } from "../../shared/ui";
interface CloudFields {
baseUrl: string;
region: string;
projectId: string;
deployment: string;
}
const CLOUD_DEFAULTS: CloudFields = { baseUrl: "", region: "", projectId: "", deployment: "" };
export function ProviderConnectFields({
provider,
entry,
}: {
provider: ProviderRecord;
entry: ProviderCatalogEntry;
}) {
if (entry.authType === "api_key") {
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="API key"><input className="input" name="secret" type="password" required autoFocus /></Field>
<ModelPicker entry={entry} provider={provider} />
</>
);
}
if (entry.customEndpoint) {
return <CompatFieldsForm entry={entry} provider={provider} />;
}
return <CloudFieldsForm entry={entry} provider={provider} />;
}
export function ProviderOAuthFields({
orgId,
employeeId,
provider,
entry,
onConnected,
}: {
orgId: string;
employeeId: string;
provider: ProviderRecord;
entry: ProviderCatalogEntry;
onConnected: () => void;
}) {
const [model, setModel] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? "");
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="Default model">
{entry.models.length ? (
<select className="input" value={model} onChange={(event) => setModel(event.target.value)}>
{entry.models.map((option) => (
<option key={option.id} value={option.id}>{option.name}</option>
))}
</select>
) : (
<input className="input" value={model} onChange={(event) => setModel(event.target.value)} />
)}
</Field>
<ProviderOAuthConnect
key={entry.key}
providerKey={entry.key}
label={entry.label}
buildInput={() => ({ providerKey: entry.key, orgId, providerId: provider.id, employeeId, model })}
onComplete={() => onConnected()}
/>
</>
);
}
function CompatFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
const [baseUrl, setBaseUrl] = useState("");
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="Base URL">
<input
className="input"
name="cloudBaseUrl"
value={baseUrl}
placeholder={entry.customEndpoint === "openai" ? "https://host/v1" : "https://host"}
onChange={(event) => setBaseUrl(event.target.value)}
/>
</Field>
<Field label="API key"><input className="input" name="secret" type="password" required /></Field>
<ModelPicker entry={entry} provider={provider} />
</>
);
}
function ModelPicker({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
const [fetched, setFetched] = useState<ProviderCatalogEntry["models"] | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [value, setValue] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? "");
const models = fetched ?? entry.models;
const connected = entry.supportsLiveModels && Boolean(provider.credentialId) && provider.enabled && provider.authState === "configured";
const refresh = async () => {
setLoading(true);
setError(null);
const result = await window.agentCompany.listProviderModels({ providerKey: entry.key, orgId: provider.orgId, providerId: provider.id });
setLoading(false);
if (!result.ok) {
setError(result.error.message);
return;
}
setFetched(result.data);
if (result.data[0] && !result.data.some((model) => model.id === value)) setValue(result.data[0].id);
};
return (
<Field label="Default model">
{models.length ? (
<select className="input" name="model" value={value} onChange={(event) => setValue(event.target.value)}>
{models.map((model) => (
<option key={model.id} value={model.id}>{model.name}</option>
))}
</select>
) : (
<input className="input" name="model" value={value} onChange={(event) => setValue(event.target.value)} />
)}
{connected && (
<button type="button" className="button ghost" style={{ marginTop: 6 }} disabled={loading} onClick={refresh}>
{loading ? "Loading models..." : "Refresh models"}
</button>
)}
{error && <div style={{ color: "var(--red)", fontSize: 12, marginTop: 4 }}>{error}</div>}
</Field>
);
}
function CloudFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
const [state, setState] = useState<CloudFields>(CLOUD_DEFAULTS);
const update = (patch: Partial<CloudFields>) => setState((current) => ({ ...current, ...patch }));
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="Base URL">
<input className="input" name="cloudBaseUrl" value={state.baseUrl} onChange={(event) => update({ baseUrl: event.target.value })} />
</Field>
<Field label="Region">
<input className="input" name="cloudRegion" value={state.region} onChange={(event) => update({ region: event.target.value })} />
</Field>
<Field label="Project / resource">
<input className="input" name="cloudProject" value={state.projectId} onChange={(event) => update({ projectId: event.target.value })} />
</Field>
<Field label="Deployment / model id">
<input className="input" name="cloudDeployment" value={state.deployment} onChange={(event) => update({ deployment: event.target.value })} />
</Field>
<Field label="Credential">
<input className="input" name="secret" type="password" required autoFocus />
</Field>
<Field label="Default model">
<input className="input" name="model" defaultValue={provider.model ?? entry.defaultModel ?? ""} />
</Field>
</>
);
}
@@ -1,7 +1,6 @@
import { useState, type FormEvent } from "react";
import { Button, Field, Pill } from "../../shared/ui";
import { Button, Pill } from "../../shared/ui";
import { Dialog } from "../../shared/Dialog";
import { ProviderOAuthConnect } from "../../shared/ProviderOAuthConnect";
import type {
AgentRecord,
ProviderCatalogEntry,
@@ -14,6 +13,8 @@ import type {
import { PROVIDER_DATA_POLICY } from "../../../../shared/domain";
import { ProviderLogo } from "./ProviderLogo";
import { ProviderRequestPanel } from "./ProviderRequestPanel";
import { ProviderConnectFields, ProviderOAuthFields } from "./ProviderConnectionDialog";
import { composeSecret } from "./provider-secret";
import type { SnapshotActions } from "../../app/useSnapshot";
interface ProvidersPanelProps {
@@ -29,14 +30,6 @@ interface ProvidersPanelProps {
actions: SnapshotActions;
}
interface CloudFields {
baseUrl: string;
region: string;
projectId: string;
deployment: string;
}
const CLOUD_DEFAULTS: CloudFields = { baseUrl: "", region: "", projectId: "", deployment: "" };
const POLICY_GRID = "minmax(150px, 1.2fr) minmax(118px, 0.8fr) minmax(140px, 1fr) 112px 124px 92px";
type WorkspaceScope = "organization" | "workspace";
@@ -272,190 +265,6 @@ function ProviderPolicyRow({
);
}
function ProviderConnectFields({
provider,
entry,
}: {
provider: ProviderRecord;
entry: ProviderCatalogEntry;
}) {
if (entry.authType === "api_key") {
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="API key"><input className="input" name="secret" type="password" required autoFocus /></Field>
<ModelPicker entry={entry} provider={provider} />
</>
);
}
if (entry.customEndpoint) {
return <CompatFieldsForm entry={entry} provider={provider} />;
}
return <CloudFieldsForm entry={entry} provider={provider} />;
}
function CompatFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
const [baseUrl, setBaseUrl] = useState("");
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="Base URL">
<input
className="input"
name="cloudBaseUrl"
value={baseUrl}
placeholder={entry.customEndpoint === "openai" ? "https://host/v1" : "https://host"}
onChange={(event) => setBaseUrl(event.target.value)}
/>
</Field>
<Field label="API key"><input className="input" name="secret" type="password" required /></Field>
<ModelPicker entry={entry} provider={provider} />
</>
);
}
function ProviderOAuthFields({
orgId,
employeeId,
provider,
entry,
onConnected,
}: {
orgId: string;
employeeId: string;
provider: ProviderRecord;
entry: ProviderCatalogEntry;
onConnected: () => void;
}) {
const [model, setModel] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? "");
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="Default model">
{entry.models.length ? (
<select className="input" value={model} onChange={(event) => setModel(event.target.value)}>
{entry.models.map((option) => (
<option key={option.id} value={option.id}>{option.name}</option>
))}
</select>
) : (
<input className="input" value={model} onChange={(event) => setModel(event.target.value)} />
)}
</Field>
<ProviderOAuthConnect
key={entry.key}
providerKey={entry.key}
label={entry.label}
buildInput={() => ({ providerKey: entry.key, orgId, providerId: provider.id, employeeId, model })}
onComplete={() => onConnected()}
/>
</>
);
}
function ModelPicker({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
const [fetched, setFetched] = useState<ProviderCatalogEntry["models"] | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [value, setValue] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? "");
const models = fetched ?? entry.models;
const connected = entry.supportsLiveModels && Boolean(provider.credentialId) && provider.enabled && provider.authState === "configured";
const refresh = async () => {
setLoading(true);
setError(null);
const result = await window.agentCompany.listProviderModels({ providerKey: entry.key, orgId: provider.orgId, providerId: provider.id });
setLoading(false);
if (!result.ok) {
setError(result.error.message);
return;
}
setFetched(result.data);
if (result.data[0] && !result.data.some((model) => model.id === value)) setValue(result.data[0].id);
};
return (
<Field label="Default model">
{models.length ? (
<select className="input" name="model" value={value} onChange={(event) => setValue(event.target.value)}>
{models.map((model) => (
<option key={model.id} value={model.id}>{model.name}</option>
))}
</select>
) : (
<input className="input" name="model" value={value} onChange={(event) => setValue(event.target.value)} />
)}
{connected && (
<button type="button" className="button ghost" style={{ marginTop: 6 }} disabled={loading} onClick={refresh}>
{loading ? "Loading models…" : "Refresh models"}
</button>
)}
{error && <div style={{ color: "var(--red)", fontSize: 12, marginTop: 4 }}>{error}</div>}
</Field>
);
}
function CloudFieldsForm({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
const [state, setState] = useState<CloudFields>(CLOUD_DEFAULTS);
const update = (patch: Partial<CloudFields>) => setState((current) => ({ ...current, ...patch }));
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="Base URL">
<input
className="input"
name="cloudBaseUrl"
value={state.baseUrl}
onChange={(event) => update({ baseUrl: event.target.value })}
/>
</Field>
<Field label="Region">
<input
className="input"
name="cloudRegion"
value={state.region}
onChange={(event) => update({ region: event.target.value })}
/>
</Field>
<Field label="Project / resource">
<input
className="input"
name="cloudProject"
value={state.projectId}
onChange={(event) => update({ projectId: event.target.value })}
/>
</Field>
<Field label="Deployment / model id">
<input
className="input"
name="cloudDeployment"
value={state.deployment}
onChange={(event) => update({ deployment: event.target.value })}
/>
</Field>
<Field label="Credential">
<input className="input" name="secret" type="password" required autoFocus />
</Field>
<Field label="Default model">
<input className="input" name="model" defaultValue={provider.model ?? entry.defaultModel ?? ""} />
</Field>
</>
);
}
function composeSecret(entry: ProviderCatalogEntry, form: FormData): string {
const raw = String(form.get("secret") ?? "").trim();
if (entry.authType !== "cloud") return raw;
const payload = {
baseUrl: String(form.get("cloudBaseUrl") ?? "").trim(),
region: String(form.get("cloudRegion") ?? "").trim(),
projectId: String(form.get("cloudProject") ?? "").trim(),
deployment: String(form.get("cloudDeployment") ?? "").trim(),
secret: raw,
};
return JSON.stringify(payload);
}
function ProviderRow({
provider,
authProfile,
@@ -0,0 +1,14 @@
import type { ProviderCatalogEntry } from "../../../../shared/domain";
export function composeSecret(entry: ProviderCatalogEntry, form: FormData): string {
const raw = String(form.get("secret") ?? "").trim();
if (entry.authType !== "cloud") return raw;
const payload = {
baseUrl: String(form.get("cloudBaseUrl") ?? "").trim(),
region: String(form.get("cloudRegion") ?? "").trim(),
projectId: String(form.get("cloudProject") ?? "").trim(),
deployment: String(form.get("cloudDeployment") ?? "").trim(),
secret: raw,
};
return JSON.stringify(payload);
}