Add support for environment API key in onboarding

Enhanced IBM onboarding to allow using an API key from environment configuration, with UI controls for toggling and validation feedback. Also added support for pre-filling endpoint and project ID for IBM and Ollama providers based on existing settings.
This commit is contained in:
Edwin Jose 2025-12-03 00:39:12 -05:00
parent a93da15ae2
commit b0a29415bd
3 changed files with 103 additions and 25 deletions

View file

@ -3,6 +3,12 @@ import { useEffect, useState } from "react";
import { LabelInput } from "@/components/label-input"; import { LabelInput } from "@/components/label-input";
import { LabelWrapper } from "@/components/label-wrapper"; import { LabelWrapper } from "@/components/label-wrapper";
import IBMLogo from "@/components/icons/ibm-logo"; import IBMLogo from "@/components/icons/ibm-logo";
import { Switch } from "@/components/ui/switch";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useDebouncedValue } from "@/lib/debounce"; import { useDebouncedValue } from "@/lib/debounce";
import type { OnboardingVariables } from "../../api/mutations/useOnboardingMutation"; import type { OnboardingVariables } from "../../api/mutations/useOnboardingMutation";
import { useGetIBMModelsQuery } from "../../api/queries/useGetModelsQuery"; import { useGetIBMModelsQuery } from "../../api/queries/useGetModelsQuery";
@ -18,6 +24,9 @@ export function IBMOnboarding({
setSampleDataset, setSampleDataset,
setIsLoadingModels, setIsLoadingModels,
alreadyConfigured = false, alreadyConfigured = false,
existingEndpoint,
existingProjectId,
hasEnvApiKey = false,
}: { }: {
isEmbedding?: boolean; isEmbedding?: boolean;
setSettings: Dispatch<SetStateAction<OnboardingVariables>>; setSettings: Dispatch<SetStateAction<OnboardingVariables>>;
@ -25,12 +34,20 @@ export function IBMOnboarding({
setSampleDataset: (dataset: boolean) => void; setSampleDataset: (dataset: boolean) => void;
setIsLoadingModels?: (isLoading: boolean) => void; setIsLoadingModels?: (isLoading: boolean) => void;
alreadyConfigured?: boolean; alreadyConfigured?: boolean;
existingEndpoint?: string;
existingProjectId?: string;
hasEnvApiKey?: boolean;
}) { }) {
const [endpoint, setEndpoint] = useState( const [endpoint, setEndpoint] = useState(
alreadyConfigured ? "" : "https://us-south.ml.cloud.ibm.com", alreadyConfigured ? "" : (existingEndpoint || "https://us-south.ml.cloud.ibm.com"),
); );
const [apiKey, setApiKey] = useState(""); const [apiKey, setApiKey] = useState("");
const [projectId, setProjectId] = useState(""); const [getFromEnv, setGetFromEnv] = useState(
hasEnvApiKey && !alreadyConfigured,
);
const [projectId, setProjectId] = useState(
alreadyConfigured ? "" : (existingProjectId || ""),
);
const options = [ const options = [
{ {
@ -76,7 +93,7 @@ export function IBMOnboarding({
} = useGetIBMModelsQuery( } = useGetIBMModelsQuery(
{ {
endpoint: debouncedEndpoint ? debouncedEndpoint : undefined, endpoint: debouncedEndpoint ? debouncedEndpoint : undefined,
apiKey: debouncedApiKey ? debouncedApiKey : undefined, apiKey: getFromEnv ? "" : (debouncedApiKey ? debouncedApiKey : undefined),
projectId: debouncedProjectId ? debouncedProjectId : undefined, projectId: debouncedProjectId ? debouncedProjectId : undefined,
}, },
{ {
@ -84,6 +101,7 @@ export function IBMOnboarding({
!!debouncedEndpoint || !!debouncedEndpoint ||
!!debouncedApiKey || !!debouncedApiKey ||
!!debouncedProjectId || !!debouncedProjectId ||
getFromEnv ||
alreadyConfigured, alreadyConfigured,
}, },
); );
@ -97,6 +115,16 @@ export function IBMOnboarding({
languageModels, languageModels,
embeddingModels, embeddingModels,
} = useModelSelection(modelsData, isEmbedding); } = useModelSelection(modelsData, isEmbedding);
const handleGetFromEnvChange = (fromEnv: boolean) => {
setGetFromEnv(fromEnv);
if (fromEnv) {
setApiKey("");
}
setEmbeddingModel?.("");
setLanguageModel?.("");
};
const handleSampleDatasetChange = (dataset: boolean) => { const handleSampleDatasetChange = (dataset: boolean) => {
setSampleDataset(dataset); setSampleDataset(dataset);
}; };
@ -169,34 +197,78 @@ export function IBMOnboarding({
</p> </p>
)} )}
</div> </div>
<div className="space-y-1"> <LabelWrapper
<LabelInput label="Use environment watsonx API key"
label="watsonx API key" id="get-api-key"
helperText="API key to access watsonx.ai" description="Reuse the key from your environment config. Turn off to enter a different key."
id="api-key" flex
type="password" >
required <Tooltip>
placeholder={ <TooltipTrigger asChild>
alreadyConfigured <div>
? "•••••••••••••••••••••••••••••••••••••••••" <Switch
: "your-api-key" checked={getFromEnv}
} onCheckedChange={handleGetFromEnvChange}
value={apiKey} disabled={!hasEnvApiKey || alreadyConfigured}
onChange={(e) => setApiKey(e.target.value)} />
disabled={alreadyConfigured} </div>
/> </TooltipTrigger>
{alreadyConfigured && ( {!hasEnvApiKey && !alreadyConfigured && (
<TooltipContent>
watsonx API key not detected in the environment.
</TooltipContent>
)}
</Tooltip>
</LabelWrapper>
{!getFromEnv && !alreadyConfigured && (
<div className="space-y-1">
<LabelInput
label="watsonx API key"
helperText="API key to access watsonx.ai"
className={modelsError ? "!border-destructive" : ""}
id="api-key"
type="password"
required
placeholder="your-api-key"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
{isLoadingModels && (
<p className="text-mmd text-muted-foreground">
Validating API key...
</p>
)}
{modelsError && (
<p className="text-mmd text-destructive">
Invalid watsonx API key. Verify or replace the key.
</p>
)}
</div>
)}
{alreadyConfigured && (
<div className="space-y-1">
<LabelInput
label="watsonx API key"
helperText="API key to access watsonx.ai"
id="api-key"
type="password"
required
placeholder="•••••••••••••••••••••••••••••••••••••••••"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
disabled={true}
/>
<p className="text-mmd text-muted-foreground"> <p className="text-mmd text-muted-foreground">
Reusing API key from model provider selection. Reusing API key from model provider selection.
</p> </p>
)} </div>
</div> )}
{isLoadingModels && ( {getFromEnv && isLoadingModels && (
<p className="text-mmd text-muted-foreground"> <p className="text-mmd text-muted-foreground">
Validating configuration... Validating configuration...
</p> </p>
)} )}
{modelsError && ( {getFromEnv && modelsError && (
<p className="text-mmd text-accent-amber-foreground"> <p className="text-mmd text-accent-amber-foreground">
Connection failed. Check your configuration. Connection failed. Check your configuration.
</p> </p>

View file

@ -17,6 +17,7 @@ export function OllamaOnboarding({
setIsLoadingModels, setIsLoadingModels,
isEmbedding = false, isEmbedding = false,
alreadyConfigured = false, alreadyConfigured = false,
existingEndpoint,
}: { }: {
setSettings: Dispatch<SetStateAction<OnboardingVariables>>; setSettings: Dispatch<SetStateAction<OnboardingVariables>>;
sampleDataset: boolean; sampleDataset: boolean;
@ -24,9 +25,10 @@ export function OllamaOnboarding({
setIsLoadingModels?: (isLoading: boolean) => void; setIsLoadingModels?: (isLoading: boolean) => void;
isEmbedding?: boolean; isEmbedding?: boolean;
alreadyConfigured?: boolean; alreadyConfigured?: boolean;
existingEndpoint?: string;
}) { }) {
const [endpoint, setEndpoint] = useState( const [endpoint, setEndpoint] = useState(
alreadyConfigured ? undefined : `http://localhost:11434`, alreadyConfigured ? undefined : (existingEndpoint || `http://localhost:11434`),
); );
const [showConnecting, setShowConnecting] = useState(false); const [showConnecting, setShowConnecting] = useState(false);
const debouncedEndpoint = useDebouncedValue(endpoint, 500); const debouncedEndpoint = useDebouncedValue(endpoint, 500);

View file

@ -518,6 +518,9 @@ const OnboardingCard = ({
setIsLoadingModels={setIsLoadingModels} setIsLoadingModels={setIsLoadingModels}
isEmbedding={isEmbedding} isEmbedding={isEmbedding}
alreadyConfigured={providerAlreadyConfigured} alreadyConfigured={providerAlreadyConfigured}
existingEndpoint={currentSettings?.providers?.watsonx?.endpoint}
existingProjectId={currentSettings?.providers?.watsonx?.project_id}
hasEnvApiKey={currentSettings?.providers?.watsonx?.has_api_key === true}
/> />
</TabsContent> </TabsContent>
<TabsContent value="ollama"> <TabsContent value="ollama">
@ -528,6 +531,7 @@ const OnboardingCard = ({
setIsLoadingModels={setIsLoadingModels} setIsLoadingModels={setIsLoadingModels}
isEmbedding={isEmbedding} isEmbedding={isEmbedding}
alreadyConfigured={providerAlreadyConfigured} alreadyConfigured={providerAlreadyConfigured}
existingEndpoint={currentSettings?.providers?.ollama?.endpoint}
/> />
</TabsContent> </TabsContent>
</Tabs> </Tabs>