import React, { useState, useCallback } from "react"; import { Config, readAuthForModel } from "../config.ts"; import { CustomAutofixFlow } from "./add-model-flow.tsx"; import { CenteredBox } from "./centered-box.tsx"; import { KbShortcutPanel } from "./kb-select/kb-shortcut-panel.tsx"; import { Item, ShortcutArray } from "./kb-select/kb-shortcut-select.tsx"; import { SYNTHETIC_PROVIDER, keyFromName } from "../providers.ts"; import { CustomAuthFlow } from "./add-model-flow.tsx"; import { useKeyboard } from "../hooks/use-keyboard.ts"; import { TerminalFlex } from "./terminal-flex.tsx"; export type AutofixModelProps = { config: Config | null; onComplete: (diffApply: Exclude) => any; onOverrideDefaultApiKey: (apiEnvVar: string) => Promise; onCancel: () => any; defaultModel: string; modelNickname: string; children: React.ReactNode; }; export type AutofixWrapperProps = Omit< AutofixModelProps, "defaultModel" | "modelNickname" | "children" >; const SYNTH_KEY = keyFromName(SYNTHETIC_PROVIDER.name); export function AutofixModelMenu({ config, onComplete, onOverrideDefaultApiKey, onCancel, defaultModel, modelNickname, children, }: AutofixModelProps) { const [step, setStep] = useState<"choose" | "custom" | "missing-auth">("choose"); useKeyboard(event => { if (event.key === "Escape") onCancel(); }); const shortcutItems = [ { type: "key" as const, mapping: { e: { label: `Enable ${modelNickname} via Synthetic (recommended)`, value: "synthetic", }, c: { label: "Use a custom diff-apply model...", value: "custom", }, b: { label: "Back", value: "back", }, } as const, }, ] satisfies ShortcutArray<"synthetic" | "custom" | "back">; const onSelect = useCallback( async (item: Item<"synthetic" | "custom" | "back">) => { if (item.value === "synthetic") { const defaultEnvVar = SYNTHETIC_PROVIDER.envVar; // Check if there's an override for the API key const envVar = config?.defaultApiKeyOverrides?.[SYNTH_KEY] || defaultEnvVar; if (process.env[envVar]) { onComplete({ baseUrl: SYNTHETIC_PROVIDER.baseUrl, apiEnvVar: envVar, model: defaultModel, }); } else { const auth = await readAuthForModel( { baseUrl: SYNTHETIC_PROVIDER.baseUrl, }, config, ); if (auth.ok) { onComplete({ baseUrl: SYNTHETIC_PROVIDER.baseUrl, model: defaultModel, }); } else { setStep("missing-auth"); } } return; } if (item.value === "custom") setStep("custom"); else onCancel(); }, [config, onCancel, onComplete], ); if (step === "custom") { return ( { if (model.type === "codex") return; const val: Exclude = { baseUrl: model.baseUrl, model: model.model, }; if (model.auth?.type === "env" || model.auth?.type === "command") val.auth = model.auth; onComplete(val); }} onCancel={() => setStep("choose")} /> ); } if (step === "missing-auth") { return ( setStep("choose")} onComplete={async auth => { if (auth && auth.type === "env") { await onOverrideDefaultApiKey(auth.name); onComplete({ baseUrl: SYNTHETIC_PROVIDER.baseUrl, model: defaultModel, }); } else if (auth?.type === "command") { onComplete({ baseUrl: SYNTHETIC_PROVIDER.baseUrl, model: defaultModel, auth, }); } else { onComplete({ baseUrl: SYNTHETIC_PROVIDER.baseUrl, model: defaultModel, }); } }} /> ); } return ( {children} ); }