Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | import type { AIProviderName } from "@/lib/ai-provider";
import { isProductionLike } from "@/lib/runtime-env";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Select } from "@/components/ui/select";
import { Text } from "@/components/ui/text";
import {
DEFAULT_PROVIDER_OPTIONS,
PROVIDER_OPTION_LABEL,
PROVIDER_PANEL_COPY,
} from "@/constants/provider";
import type { UseProviderSelectionResult } from "@/types/provider";
type ProviderSelectorProps = {
provider: UseProviderSelectionResult;
allowedProviders?: AIProviderName[];
withContainer?: boolean;
};
export function ProviderSelector({
provider,
allowedProviders = DEFAULT_PROVIDER_OPTIONS,
withContainer = true,
}: ProviderSelectorProps) {
const isProviderSelectDisabled = allowedProviders.length <= 1;
const content = (
<div className="flex flex-col gap-3">
<div className="space-y-1">
<Text as="label" variant="sectionTitle">
{PROVIDER_PANEL_COPY.label}
</Text>
<Text variant="captionStrong">
{PROVIDER_PANEL_COPY.description}
</Text>
</div>
<Select
value={provider.selectedProvider}
onChange={(event) =>
provider.selectProvider(event.target.value as AIProviderName)
}
disabled={isProviderSelectDisabled}
fullWidth
controlSize="md"
variant="dark"
>
{allowedProviders.includes("ollama") ? (
<option value="ollama">{PROVIDER_OPTION_LABEL.ollama}</option>
) : null}
{allowedProviders.includes("openai") ? (
<option value="openai">{PROVIDER_OPTION_LABEL.openai}</option>
) : null}
</Select>
<Text variant="captionStrong" className="min-h-4" aria-live="polite">
{provider.providerStatus}
</Text>
{provider.isOpenAISelected ? (
<div className="flex flex-col gap-2">
<Input
type="password"
value={provider.openaiApiKeyInput}
onChange={(event) => provider.updateOpenAIApiKeyInput(event.target.value)}
placeholder={PROVIDER_PANEL_COPY.openaiApiKeyPlaceholder}
fullWidth
controlSize="md"
variant="dark"
/>
<Button
type="button"
onClick={provider.verifyOpenAIKey}
isLoading={provider.isValidatingKey}
disabled={provider.isOpenAIKeyVerified}
variant="primary"
size="md"
fullWidth
>
{provider.isValidatingKey
? PROVIDER_PANEL_COPY.verifyActionLoadingLabel
: PROVIDER_PANEL_COPY.verifyOpenAIButtonLabel}
</Button>
</div>
) : (
<div className="flex flex-col gap-2">
<Input
type="url"
value={provider.ollamaBaseUrlInput}
onChange={(event) => provider.updateOllamaBaseUrlInput(event.target.value)}
placeholder={PROVIDER_PANEL_COPY.ollamaBaseUrlPlaceholder}
fullWidth
controlSize="md"
variant="dark"
/>
<Button
type="button"
onClick={provider.verifyOllamaBaseUrl}
isLoading={provider.isValidatingOllamaBaseUrl}
disabled={provider.isOllamaUrlVerified}
variant="ghost"
size="md"
fullWidth
className="font-dm-sans border border-white/12 text-white/60 hover:bg-white/8"
>
{provider.isValidatingOllamaBaseUrl
? PROVIDER_PANEL_COPY.verifyActionLoadingLabel
: PROVIDER_PANEL_COPY.verifyOllamaButtonLabel}
</Button>
<Text variant="captionMuted">
{isProductionLike()
? PROVIDER_PANEL_COPY.productionOllamaHint
: PROVIDER_PANEL_COPY.localOllamaHint}
</Text>
</div>
)}
</div>
);
if (!withContainer) return content;
return (
<Card variant="panel" className="p-4">
{content}
</Card>
);
}
|