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:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user