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 { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { import {
Accordion, Accordion,
@@ -11,6 +11,8 @@ import { VertexConfiguration } from "./VertexConfiguration";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { UserSettings } from "@/lib/schemas"; 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) // Helper function to mask ENV API keys (move or duplicate if needed elsewhere)
const maskEnvApiKey = (key: string | undefined): string => { const maskEnvApiKey = (key: string | undefined): string => {
@@ -29,7 +31,7 @@ interface ApiKeyConfigurationProps {
saveError: string | null; saveError: string | null;
apiKeyInput: string; apiKeyInput: string;
onApiKeyInputChange: (value: string) => void; onApiKeyInputChange: (value: string) => void;
onSaveKey: () => Promise<void>; onSaveKey: (value: string) => Promise<void>;
onDeleteKey: () => Promise<void>; onDeleteKey: () => Promise<void>;
isDyad: boolean; isDyad: boolean;
updateSettings: (settings: Partial<UserSettings>) => Promise<UserSettings>; updateSettings: (settings: Partial<UserSettings>) => Promise<UserSettings>;
@@ -144,7 +146,36 @@ export function ApiKeyConfiguration({
placeholder={`Enter new ${providerDisplayName} API Key here`} placeholder={`Enter new ${providerDisplayName} API Key here`}
className={`flex-grow ${saveError ? "border-red-500" : ""}`} 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"} {isSaving ? "Saving..." : "Save Key"}
</Button> </Button>
</div> </div>

View File

@@ -1,14 +1,20 @@
import { import {
ArrowLeft, ArrowLeft,
ArrowUp,
Circle, Circle,
ExternalLink, ExternalLink,
GiftIcon, GiftIcon,
KeyRound, KeyRound,
Settings as SettingsIcon,
} from "lucide-react"; } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {} from "react";
interface ProviderSettingsHeaderProps { interface ProviderSettingsHeaderProps {
providerDisplayName: string; 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 ( return (
<> <>
<Button <Button
@@ -95,21 +112,24 @@ export function ProviderSettingsHeader({
)} )}
</div> </div>
{providerWebsiteUrl && !isLoading && ( {providerWebsiteUrl &&
<Button !isLoading &&
onClick={handleGetApiKeyClick} (!isConfigured ? (
className="mb-4 cursor-pointer py-5 w-full" <Popover defaultOpen>
// variant="primary" <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 ? ( <div className="text-sm font-semibold flex items-center gap-1">
<SettingsIcon className="mr-2 h-4 w-4" /> <ArrowUp /> Create your API key with {providerDisplayName}
</div>
</PopoverContent>
</Popover>
) : ( ) : (
<KeyRound className="mr-2 h-4 w-4" /> ConfigureButton
)} ))}
{getKeyButtonText({ isConfigured, isDyad })}
<ExternalLink className="ml-2 h-4 w-4" />
</Button>
)}
</> </>
); );
} }

View File

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