Skip to content

Commit

Permalink
feat: configure providers grid (#709)
Browse files Browse the repository at this point in the history
  • Loading branch information
lily-de authored Jan 23, 2025
1 parent d230c6e commit 63b239a
Show file tree
Hide file tree
Showing 9 changed files with 489 additions and 266 deletions.
6 changes: 5 additions & 1 deletion ui/desktop/src/components/settings/ProviderSetupModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { Card } from '../ui/card';
import { Lock } from 'lucide-react';
import { Input } from '../ui/input';
import { Button } from '../ui/button';
import { required_keys } from './models/hardcoded_stuff';
// import UnionIcon from "../images/[email protected]";

interface ProviderSetupModalProps {
provider: string;
model: string;
endpoint: string;
title?: string;
onSubmit: (apiKey: string) => void;
onCancel: () => void;
}
Expand All @@ -17,10 +19,12 @@ export function ProviderSetupModal({
provider,
model,
endpoint,
title,
onSubmit,
onCancel,
}: ProviderSetupModalProps) {
const [apiKey, setApiKey] = React.useState('');
const keyName = required_keys[provider]?.[0] || 'API Key';
const headerText = `Setup ${provider}`;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -48,7 +52,7 @@ export function ProviderSetupModal({
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={`Enter API key`}
placeholder={keyName}
className="w-full h-14 px-4 font-regular rounded-lg border shadow-none border-gray-300 bg-white text-lg placeholder:text-gray-400 font-regular text-gray-900"
required
/>
Expand Down
226 changes: 226 additions & 0 deletions ui/desktop/src/components/settings/providers/BaseProviderGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import React from 'react';
import { Check, Plus, Settings, X } from 'lucide-react';
import { Button } from '../../ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/Tooltip';
import { Portal } from '@radix-ui/react-portal';
import { required_keys } from '../models/hardcoded_stuff';

// Common interfaces and helper functions
interface Provider {
id: string;
name: string;
isConfigured: boolean;
description: string;
}

interface BaseProviderCardProps {
name: string;
description: string;
isConfigured: boolean;
isSelected?: boolean;
isSelectable?: boolean;
onSelect?: () => void;
onAddKeys?: () => void;
onConfigure?: () => void;
showSettings?: boolean;
onDelete?: () => void;
showDelete?: boolean;
hasRequiredKeys?: boolean;
}

function getArticle(word: string): string {
return 'aeiouAEIOU'.indexOf(word[0]) >= 0 ? 'an' : 'a';
}

function BaseProviderCard({
name,
description,
isConfigured,
isSelected,
isSelectable,
onSelect,
onAddKeys,
onConfigure,
showSettings,
onDelete,
showDelete = false,
hasRequiredKeys = false,
}: BaseProviderCardProps) {
const numRequiredKeys = required_keys[name]?.length || 0;
const tooltipText = numRequiredKeys === 1 ? `Add ${name} API Key` : `Add ${name} API Keys`;

return (
<div
onClick={() => isSelectable && isConfigured && onSelect?.()}
className={`relative bg-white dark:bg-gray-800 rounded-lg border
${
isSelected
? 'border-blue-500 dark:border-blue-400 shadow-[0_0_0_1px] shadow-blue-500/50'
: 'border-gray-200 dark:border-gray-700'
}
p-3 transition-all duration-200 h-[140px]
${isSelectable && isConfigured ? 'cursor-pointer' : ''}
${!isSelectable ? 'hover:border-gray-300 dark:hover:border-gray-600 hover:shadow-md hover:scale-[1.01]' : ''}
${isSelectable && isConfigured ? 'hover:border-blue-400 dark:hover:border-blue-300 hover:shadow-md' : ''}
`}
>
{isConfigured && (
<div className="absolute top-2 right-2 flex gap-1.5">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center justify-center w-4 h-4 rounded-full bg-green-100 dark:bg-green-900/30 shrink-0">
<Check className="h-2.5 w-2.5 text-green-600 dark:text-green-500" />
</div>
</TooltipTrigger>
<Portal>
<TooltipContent side="top" align="center" className="z-[9999]">
<p>
{hasRequiredKeys
? `You have ${getArticle(name)} ${name} API Key set in your environment`
: `${name} has no required API keys`}
</p>
</TooltipContent>
</Portal>
</Tooltip>
</TooltipProvider>

{showDelete && hasRequiredKeys && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-4 w-4 p-0"
onClick={(e) => {
e.stopPropagation();
onDelete?.();
}}
>
<X className="h-2.5 w-2.5 text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400" />
</Button>
</TooltipTrigger>
<Portal>
<TooltipContent side="top" align="center" className="z-[9999]">
<p>Remove {name} API Key</p>
</TooltipContent>
</Portal>
</Tooltip>
</TooltipProvider>
)}
</div>
)}

<div className="space-y-1 mt-4">
<h3 className="text-base font-semibold text-gray-900 dark:text-gray-100 truncate">
{name}
</h3>
</div>

<p className="text-[10px] text-gray-600 dark:text-gray-400 mt-1.5 mb-3 leading-normal overflow-y-auto max-h-[48px] pr-1">
{description}
</p>

<div className="absolute bottom-2 right-3">
{!isConfigured && onAddKeys && hasRequiredKeys && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="default"
size="sm"
onClick={(e) => {
e.stopPropagation();
onAddKeys();
}}
className="rounded-full h-6 w-6 p-0 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-900 dark:text-gray-100"
>
<Plus className="h-3.5 w-3.5" />
</Button>
</TooltipTrigger>
<Portal>
<TooltipContent side="top" align="center" className="z-[9999]">
<p>{tooltipText}</p>
</TooltipContent>
</Portal>
</Tooltip>
</TooltipProvider>
)}
{isConfigured && showSettings && hasRequiredKeys && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="rounded-full h-6 w-6 p-0"
onClick={(e) => {
e.stopPropagation();
onConfigure?.();
}}
>
<Settings className="h-3.5 w-3.5 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300" />
</Button>
</TooltipTrigger>
<Portal>
<TooltipContent side="top" align="center" className="z-[9999]">
<p>Configure {name} settings</p>
</TooltipContent>
</Portal>
</Tooltip>
</TooltipProvider>
)}
</div>
</div>
);
}

interface BaseProviderGridProps {
providers: Provider[];
isSelectable?: boolean;
showSettings?: boolean;
showDelete?: boolean;
selectedId?: string | null;
onSelect?: (providerId: string) => void;
onAddKeys?: (provider: Provider) => void;
onConfigure?: (provider: Provider) => void;
onDelete?: (provider: Provider) => void;
}

export function BaseProviderGrid({
providers,
isSelectable = false,
showSettings = false,
showDelete = false,
selectedId = null,
onSelect,
onAddKeys,
onConfigure,
onDelete,
}: BaseProviderGridProps) {
return (
<div className="grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-3 auto-rows-fr max-w-full [&_*]:z-20">
{providers.map((provider) => {
const hasRequiredKeys = required_keys[provider.name]?.length > 0;
return (
<BaseProviderCard
key={provider.id}
name={provider.name}
description={provider.description}
isConfigured={provider.isConfigured}
isSelected={selectedId === provider.id}
isSelectable={isSelectable}
onSelect={() => onSelect?.(provider.id)}
onAddKeys={() => onAddKeys?.(provider)}
onConfigure={() => onConfigure?.(provider)}
onDelete={() => onDelete?.(provider)}
showSettings={showSettings}
showDelete={showDelete}
hasRequiredKeys={hasRequiredKeys}
/>
);
})}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,30 @@ import React from 'react';
import { Providers } from './Provider';
import { ScrollArea } from '../../ui/scroll-area';
import BackButton from '../../ui/BackButton';
import { ConfigureProvidersGrid } from './ConfigureProvidersGrid';

export default function ConfigureProviders() {
return (
<div className="h-screen w-full pt-[36px]">
<div className="h-full w-full bg-white dark:bg-gray-800 overflow-hidden p-2 pt-0">
<ScrollArea className="h-full w-full">
<div className="flex min-h-full">
{/* Left Navigation */}
<div className="w-48 border-r border-gray-100 dark:border-gray-700 px-2 pt-2">
{/* Left Navigation - further reduced width */}
<div className="w-24 border-r border-gray-100 dark:border-gray-700 px-2 pt-2">
<div className="sticky top-8">
<BackButton className="mb-4" />
</div>
</div>

{/* Content Area */}
<div className="flex-1 px-16 py-8 pt-[20px]">
<div className="max-w-3xl space-y-12">
<div className="max-w-5xl space-y-12">
<div className="flex items-center gap-4 mb-8">
<h1 className="text-2xl font-semibold tracking-tight">Configure Providers</h1>
</div>
<Providers />
<div className="relative z-10">
<ConfigureProvidersGrid />
</div>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit 63b239a

Please sign in to comment.