mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-30 15:48:37 +00:00
225 lines
6.8 KiB
TypeScript
225 lines
6.8 KiB
TypeScript
import type {
|
|
AdminModel,
|
|
ProviderModels,
|
|
UpstreamProvider,
|
|
} from '@/lib/api/services/admin';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@/components/ui/card';
|
|
import {
|
|
ChevronDown,
|
|
ChevronUp,
|
|
Database,
|
|
Pencil,
|
|
Trash2,
|
|
Key,
|
|
} from 'lucide-react';
|
|
import { ProviderBalance } from '@/components/provider-balance';
|
|
import { ProviderModelsPanel } from '@/components/provider-models-panel';
|
|
import { RoutstrCreateKeySection } from '@/components/providers/RoutstrCreateKeySection';
|
|
import { useState } from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
|
|
interface ProviderCardProps {
|
|
provider: UpstreamProvider;
|
|
isExpanded: boolean;
|
|
canShowBalance: boolean;
|
|
platformUrl: string | null;
|
|
isModelsLoading: boolean;
|
|
providerModels: ProviderModels | null;
|
|
isDeletingModel: boolean;
|
|
onToggleExpansion: () => void;
|
|
onEditProvider: () => void;
|
|
onDeleteProvider: () => void;
|
|
onBatchOverride: () => void;
|
|
onAddModel: () => void;
|
|
onEditModel: (model: AdminModel) => void;
|
|
onDeleteModel: (modelId: string) => void;
|
|
onOverrideModel: (model: AdminModel) => void;
|
|
onUpdateApiKey: (newKey: string) => void;
|
|
availableMints: string[];
|
|
}
|
|
|
|
export function ProviderCard({
|
|
provider,
|
|
isExpanded,
|
|
canShowBalance,
|
|
platformUrl,
|
|
isModelsLoading,
|
|
providerModels,
|
|
isDeletingModel,
|
|
onToggleExpansion,
|
|
onEditProvider,
|
|
onDeleteProvider,
|
|
onBatchOverride,
|
|
onAddModel,
|
|
onEditModel,
|
|
onDeleteModel,
|
|
onOverrideModel,
|
|
onUpdateApiKey,
|
|
}: ProviderCardProps) {
|
|
const [isKeyModalOpen, setIsKeyModalOpen] = useState(false);
|
|
const hasDetails = Boolean(provider.api_version) || isExpanded;
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<div className='flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between'>
|
|
<div className='min-w-0 flex-1'>
|
|
<div className='flex flex-col gap-2 sm:flex-row sm:items-center'>
|
|
<CardTitle className='truncate'>
|
|
{provider.provider_type}
|
|
</CardTitle>
|
|
<Badge
|
|
variant={provider.enabled ? 'default' : 'secondary'}
|
|
className='w-fit'
|
|
>
|
|
{provider.enabled ? 'Enabled' : 'Disabled'}
|
|
</Badge>
|
|
</div>
|
|
<CardDescription className='break-all'>
|
|
{provider.base_url}
|
|
</CardDescription>
|
|
</div>
|
|
|
|
<div className='grid w-full grid-cols-2 gap-2 sm:flex sm:w-auto sm:flex-wrap sm:items-center sm:justify-end'>
|
|
{canShowBalance && provider.api_key && (
|
|
<div
|
|
className={cn(
|
|
'col-span-2 sm:col-auto',
|
|
provider.provider_type === 'routstr' && 'col-span-1'
|
|
)}
|
|
>
|
|
<ProviderBalance
|
|
providerId={provider.id}
|
|
platformUrl={platformUrl}
|
|
isRoutstr={provider.provider_type === 'routstr'}
|
|
nodeUrl={provider.base_url}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{provider.provider_type === 'routstr' && (
|
|
<Button
|
|
variant='outline'
|
|
size='sm'
|
|
onClick={() => setIsKeyModalOpen(true)}
|
|
className={cn(
|
|
'justify-center gap-1.5',
|
|
canShowBalance && provider.api_key
|
|
? 'col-span-1'
|
|
: 'col-span-2 sm:col-auto'
|
|
)}
|
|
>
|
|
<Key className='h-4 w-4' />
|
|
<span>New Key</span>
|
|
</Button>
|
|
)}
|
|
|
|
<Button
|
|
variant='outline'
|
|
size='sm'
|
|
onClick={onToggleExpansion}
|
|
className='col-span-2 justify-between sm:col-auto sm:justify-center'
|
|
>
|
|
<span className='inline-flex items-center gap-1.5'>
|
|
<Database className='h-4 w-4' />
|
|
<span>Models</span>
|
|
</span>
|
|
{isExpanded ? (
|
|
<ChevronUp className='h-4 w-4' />
|
|
) : (
|
|
<ChevronDown className='h-4 w-4' />
|
|
)}
|
|
</Button>
|
|
|
|
<Button
|
|
variant='outline'
|
|
size='sm'
|
|
onClick={onEditProvider}
|
|
className='justify-center gap-1.5'
|
|
>
|
|
<Pencil className='h-4 w-4' />
|
|
<span>Edit</span>
|
|
</Button>
|
|
|
|
<Button
|
|
variant='outline'
|
|
size='sm'
|
|
onClick={onDeleteProvider}
|
|
className='text-destructive hover:text-destructive justify-center gap-1.5'
|
|
>
|
|
<Trash2 className='h-4 w-4' />
|
|
<span>Delete</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
|
|
<Dialog open={isKeyModalOpen} onOpenChange={setIsKeyModalOpen}>
|
|
<DialogContent className='max-h-[90vh] overflow-y-auto sm:max-w-[500px]'>
|
|
<DialogHeader>
|
|
<DialogTitle>Generate New Key</DialogTitle>
|
|
<DialogDescription>
|
|
Create a new API key for provider{' '}
|
|
<span className='font-medium'>{provider.provider_type}</span>.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className='py-4'>
|
|
<RoutstrCreateKeySection
|
|
baseUrl={provider.base_url || ''}
|
|
onApiKeyCreated={(newApiKey) => {
|
|
onUpdateApiKey(newApiKey);
|
|
setIsKeyModalOpen(false);
|
|
}}
|
|
/>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{hasDetails ? (
|
|
<CardContent>
|
|
<div className='space-y-3'>
|
|
{provider.api_version && (
|
|
<div className='flex flex-col gap-1 text-sm sm:flex-row sm:items-center sm:justify-between'>
|
|
<span className='text-muted-foreground'>API Version:</span>
|
|
<span className='font-mono break-all'>
|
|
{provider.api_version}
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{isExpanded && (
|
|
<div className='mt-3 border-t pt-3'>
|
|
<ProviderModelsPanel
|
|
isLoading={isModelsLoading}
|
|
providerModels={providerModels}
|
|
onBatchOverride={onBatchOverride}
|
|
onAddModel={onAddModel}
|
|
onEditModel={onEditModel}
|
|
onDeleteModel={onDeleteModel}
|
|
onOverrideModel={onOverrideModel}
|
|
isDeletingModel={isDeletingModel}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
) : null}
|
|
</Card>
|
|
);
|
|
}
|