Make provider flow more obvious (#1493)

<!-- This is an auto-generated description by cubic. -->

## Summary by cubic
Improved the provider setup flow by adding a clipboard paste-and-save
action and a clearer “Get API key” prompt when a provider isn’t
configured.

- **New Features**
- Added a clipboard “Paste and Save” button with tooltip and error
handling.
- Highlighted the “Get API key” button with a popover prompt and visual
emphasis when not configured.

- **Refactors**
- Changed onSaveKey to accept the key value (string) and updated usage
to save the provided value.

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Will Chen
2025-10-09 14:26:01 -07:00
committed by GitHub
parent 92b657410f
commit 185f0927a0
3 changed files with 73 additions and 22 deletions

View File

@@ -1,4 +1,4 @@
import { Info, KeyRound, Trash2 } from "lucide-react";
import { Info, KeyRound, Trash2, Clipboard } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
Accordion,
@@ -11,6 +11,8 @@ import { VertexConfiguration } from "./VertexConfiguration";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { UserSettings } from "@/lib/schemas";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
import { showError } from "@/lib/toast";
// Helper function to mask ENV API keys (move or duplicate if needed elsewhere)
const maskEnvApiKey = (key: string | undefined): string => {
@@ -29,7 +31,7 @@ interface ApiKeyConfigurationProps {
saveError: string | null;
apiKeyInput: string;
onApiKeyInputChange: (value: string) => void;
onSaveKey: () => Promise<void>;
onSaveKey: (value: string) => Promise<void>;
onDeleteKey: () => Promise<void>;
isDyad: boolean;
updateSettings: (settings: Partial<UserSettings>) => Promise<UserSettings>;
@@ -144,7 +146,36 @@ export function ApiKeyConfiguration({
placeholder={`Enter new ${providerDisplayName} API Key here`}
className={`flex-grow ${saveError ? "border-red-500" : ""}`}
/>
<Button onClick={onSaveKey} disabled={isSaving || !apiKeyInput}>
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={async () => {
try {
const text = await navigator.clipboard.readText();
if (text) {
onSaveKey(text);
}
} catch (error) {
showError("Failed to paste from clipboard");
console.error("Failed to paste from clipboard", error);
}
}}
disabled={isSaving}
variant="outline"
size="icon"
title="Paste from clipboard and save"
aria-label="Paste from clipboard and save"
>
<Clipboard className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Paste from clipboard and save</TooltipContent>
</Tooltip>
<Button
onClick={() => onSaveKey(apiKeyInput)}
disabled={isSaving || !apiKeyInput}
>
{isSaving ? "Saving..." : "Save Key"}
</Button>
</div>

View File

@@ -1,14 +1,20 @@
import {
ArrowLeft,
ArrowUp,
Circle,
ExternalLink,
GiftIcon,
KeyRound,
Settings as SettingsIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { IpcClient } from "@/ipc/ipc_client";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {} from "react";
interface ProviderSettingsHeaderProps {
providerDisplayName: string;
@@ -51,6 +57,17 @@ export function ProviderSettingsHeader({
}
};
const ConfigureButton = (
<Button
onClick={handleGetApiKeyClick}
className="mb-4 cursor-pointer py-5 w-full ring-4 ring-primary/60 shadow-lg shadow-primary/30 border-primary/60"
>
<KeyRound className="mr-2 h-4 w-4" />
{getKeyButtonText({ isConfigured, isDyad })}
<ExternalLink className="ml-2 h-4 w-4" />
</Button>
);
return (
<>
<Button
@@ -95,21 +112,24 @@ export function ProviderSettingsHeader({
)}
</div>
{providerWebsiteUrl && !isLoading && (
<Button
onClick={handleGetApiKeyClick}
className="mb-4 cursor-pointer py-5 w-full"
// variant="primary"
{providerWebsiteUrl &&
!isLoading &&
(!isConfigured ? (
<Popover defaultOpen>
<PopoverTrigger asChild>{ConfigureButton}</PopoverTrigger>
<PopoverContent
side="bottom"
align="center"
className="w-fit py-2 px-3 bg-background text-primary shadow-lg ring-1 ring-primary/40"
>
{isConfigured ? (
<SettingsIcon className="mr-2 h-4 w-4" />
<div className="text-sm font-semibold flex items-center gap-1">
<ArrowUp /> Create your API key with {providerDisplayName}
</div>
</PopoverContent>
</Popover>
) : (
<KeyRound className="mr-2 h-4 w-4" />
)}
{getKeyButtonText({ isConfigured, isDyad })}
<ExternalLink className="ml-2 h-4 w-4" />
</Button>
)}
ConfigureButton
))}
</>
);
}

View File

@@ -118,8 +118,8 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
: isValidUserKey || hasEnvKey; // Configured if either is set
// --- Save Handler ---
const handleSaveKey = async () => {
if (!apiKeyInput) {
const handleSaveKey = async (value: string) => {
if (!value.trim()) {
setSaveError("API Key cannot be empty.");
return;
}
@@ -132,7 +132,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
[provider]: {
...settings?.providerSettings?.[provider],
apiKey: {
value: apiKeyInput,
value,
},
},
},