Gate the model picker behind credentials: fetch on Get models or after OAuth login

This commit is contained in:
ZacharyZhang-NY committed 2026-05-30 01:05:14 -04:00
1 parent ddd8663ecc
commit d4ace283d1
3 files changed
+37 -25

No files matched your search

@@ -60,7 +60,7 @@ export function Onboarding({
providerKey: firstProvider.key,
providerSecret: "",
providerBaseUrl: "",
providerModel: firstProvider.defaultModel ?? firstProvider.models[0]?.id ?? "",
providerModel: "",
goalTitle: "",
goalDescription: "",
});
@@ -159,7 +159,7 @@ export function Onboarding({
<div className="row" style={{ gap: 10 }}>
<Icon name="bot" size={16} />
<strong style={{ fontSize: 13 }}>{selectedProvider.label}</strong>
<Pill tone="violet">{draft.providerModel}</Pill>
{draft.providerModel && <Pill tone="violet">{draft.providerModel}</Pill>}
</div>
<div style={{ marginTop: 10, fontSize: 12, color: "var(--text-3)" }}>
We never embed the key in the build. Credentials encrypt locally via OS-protected storage.
@@ -264,7 +264,7 @@ function ProviderStep({
className={`option-card ${draft.providerKey === provider.key ? "active" : ""}`.trim()}
onClick={() => onChange({
providerKey: provider.key,
providerModel: provider.defaultModel ?? provider.models[0]?.id ?? "",
providerModel: "",
providerSecret: "",
providerBaseUrl: "",
})}
@@ -301,8 +301,11 @@ function ProviderCredentialFields({
const [loading, setLoading] = useState(false);
const [loadError, setLoadError] = useState<string | null>(null);
const models = fetched ?? selected.models;
const canLoad = isCustom ? Boolean(draft.providerBaseUrl.trim() && draft.providerSecret.trim()) : Boolean(draft.providerSecret.trim());
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);
@@ -347,7 +350,7 @@ function ProviderCredentialFields({
className="input"
value={draft.providerBaseUrl}
placeholder={selected.customEndpoint === "openai" ? "https://host/v1" : "https://host"}
onChange={(event) => onChange({ providerBaseUrl: event.target.value })}
onChange={(event) => onChange({ providerBaseUrl: event.target.value, providerModel: "" })}
/>
</Field>
<Field label="API key">
@@ -356,7 +359,7 @@ function ProviderCredentialFields({
type="password"
autoComplete="off"
value={draft.providerSecret}
onChange={(event) => onChange({ providerSecret: event.target.value })}
onChange={(event) => onChange({ providerSecret: event.target.value, providerModel: "" })}
/>
</Field>
</>
@@ -367,31 +370,38 @@ function ProviderCredentialFields({
type="password"
autoComplete="off"
value={draft.providerSecret}
onChange={(event) => onChange({ providerSecret: event.target.value })}
onChange={(event) => onChange({ providerSecret: event.target.value, providerModel: "" })}
/>
</Field>
)}
{!isOAuthLike && (
<Button tone="ghost" onClick={loadTypedModels} disabled={!canLoad || loading}>
{loading ? "Loading models…" : fetched ? "Reload models" : "Load models"}
<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>}
<Field label="Model">
{models.length ? (
<select
className="input"
value={draft.providerModel}
onChange={(event) => onChange({ providerModel: event.target.value })}
>
{models.map((model) => (
<option key={model.id} value={model.id}>{model.name}</option>
))}
</select>
) : (
<input className="input" value={draft.providerModel} onChange={(event) => onChange({ providerModel: event.target.value })} />
)}
</Field>
{hasCredential && (
<Field label="Model">
{fetched ? (
<select
className="input"
value={draft.providerModel}
onChange={(event) => onChange({ providerModel: event.target.value })}
>
{fetched.map((model) => (
<option key={model.id} value={model.id}>{model.name}</option>
))}
</select>
) : (
<input
className="input"
value={draft.providerModel}
placeholder={isOAuthLike ? "Loading…" : "Click “Get models”, or type a model id"}
onChange={(event) => onChange({ providerModel: event.target.value })}
/>
)}
</Field>
)}
</div>
);
}
@@ -22,6 +22,7 @@ test("onboards, hires, and creates work in the Electron shell", async () => {
await expect(page.getByRole("heading", { name: "Welcome" })).toBeVisible();
await page.getByRole("button", { name: "Get started" }).click();
await page.getByLabel("API key").fill("e2e-secret");
await page.getByLabel("Model").fill("gpt-5.4");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Company name").fill("Acme Operations");
await page.getByLabel("Your name").fill("Dana Lee");
@@ -21,6 +21,7 @@ test("captures every page for visual review", async () => {
await page.getByRole("button", { name: "Get started" }).click();
await page.screenshot({ path: join(outDir, "01-onboarding-provider.png"), fullPage: false });
await page.getByLabel("API key").fill("local-dev-key");
await page.getByLabel("Model").fill("gpt-5.4");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Company name").fill("OrbitOps");