d146e45e2f
Add a Bun script to apply and normalize AGPL copyright headers across the default frontend source files. The script keeps headers idempotent, upgrades existing headers to the 2023-2026 QuantumNous range, and is exposed through `bun run copyright` for future maintenance.
216 lines
6.1 KiB
TypeScript
Vendored
216 lines
6.1 KiB
TypeScript
Vendored
/*
|
|
Copyright (C) 2023-2026 QuantumNous
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
For commercial licensing, please contact support@quantumnous.com
|
|
*/
|
|
import { useState, useCallback } from 'react'
|
|
import { Check, Copy, Loader2 } from 'lucide-react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { copyToClipboard } from '@/lib/copy-to-clipboard'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from '@/components/ui/popover'
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from '@/components/ui/tooltip'
|
|
import { StatusBadge } from '@/components/status-badge'
|
|
import { type ApiKey } from '../types'
|
|
import { useApiKeys } from './api-keys-provider'
|
|
|
|
export function ApiKeyCell({ apiKey }: { apiKey: ApiKey }) {
|
|
const { t } = useTranslation()
|
|
const {
|
|
resolveRealKey,
|
|
resolvedKeys,
|
|
loadingKeys,
|
|
copiedKeyId,
|
|
markKeyCopied,
|
|
} = useApiKeys()
|
|
const [popoverOpen, setPopoverOpen] = useState(false)
|
|
|
|
const isLoading = !!loadingKeys[apiKey.id]
|
|
const resolvedFullKey = resolvedKeys[apiKey.id]
|
|
const isCopied = copiedKeyId === apiKey.id
|
|
const maskedKey = `sk-${apiKey.key}`
|
|
|
|
const handlePopoverOpen = useCallback(
|
|
(open: boolean) => {
|
|
setPopoverOpen(open)
|
|
if (open && !resolvedFullKey) {
|
|
resolveRealKey(apiKey.id)
|
|
}
|
|
},
|
|
[resolvedFullKey, resolveRealKey, apiKey.id]
|
|
)
|
|
|
|
const handleCopy = useCallback(async () => {
|
|
const realKey = resolvedFullKey || (await resolveRealKey(apiKey.id))
|
|
if (realKey) {
|
|
const ok = await copyToClipboard(realKey)
|
|
if (ok) markKeyCopied(apiKey.id)
|
|
}
|
|
}, [resolvedFullKey, resolveRealKey, apiKey.id, markKeyCopied])
|
|
|
|
return (
|
|
<div className='flex items-center'>
|
|
<Popover open={popoverOpen} onOpenChange={handlePopoverOpen}>
|
|
<PopoverTrigger
|
|
render={
|
|
<Button
|
|
variant='ghost'
|
|
size='sm'
|
|
className='text-muted-foreground h-7 font-mono text-xs'
|
|
/>
|
|
}
|
|
>
|
|
{maskedKey}
|
|
</PopoverTrigger>
|
|
<PopoverContent
|
|
className='w-auto max-w-[min(90vw,28rem)]'
|
|
align='start'
|
|
>
|
|
<div className='space-y-2'>
|
|
<p className='text-muted-foreground text-xs'>{t('Full API Key')}</p>
|
|
{isLoading ? (
|
|
<div className='flex items-center gap-2 py-2'>
|
|
<Loader2 className='size-3.5 animate-spin' />
|
|
<span className='text-muted-foreground text-xs'>
|
|
{t('Loading...')}
|
|
</span>
|
|
</div>
|
|
) : (
|
|
<input
|
|
readOnly
|
|
value={resolvedFullKey || maskedKey}
|
|
autoFocus
|
|
onFocus={(e) => e.target.select()}
|
|
className='bg-muted/50 w-full min-w-[280px] rounded-md border px-3 py-2 font-mono text-xs outline-none'
|
|
/>
|
|
)}
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
<Tooltip>
|
|
<TooltipTrigger
|
|
render={
|
|
<Button
|
|
variant='ghost'
|
|
size='icon'
|
|
className='size-7 shrink-0'
|
|
onClick={handleCopy}
|
|
disabled={isLoading}
|
|
/>
|
|
}
|
|
>
|
|
{isLoading ? (
|
|
<Loader2 className='size-3.5 animate-spin' />
|
|
) : isCopied ? (
|
|
<Check className='size-3.5 text-green-600' />
|
|
) : (
|
|
<Copy className='size-3.5' />
|
|
)}
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
{isLoading
|
|
? t('Loading...')
|
|
: isCopied
|
|
? t('Copied!')
|
|
: t('Copy API key')}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function ModelLimitsCell({ apiKey }: { apiKey: ApiKey }) {
|
|
const { t } = useTranslation()
|
|
|
|
if (!apiKey.model_limits_enabled || !apiKey.model_limits) {
|
|
return (
|
|
<StatusBadge label={t('Unlimited')} variant='neutral' copyable={false} />
|
|
)
|
|
}
|
|
|
|
const models = apiKey.model_limits.split(',').filter(Boolean)
|
|
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger render={<span />}>
|
|
<StatusBadge
|
|
label={t('{{count}} model(s)', { count: models.length })}
|
|
variant='neutral'
|
|
copyable={false}
|
|
/>
|
|
</TooltipTrigger>
|
|
<TooltipContent side='top' className='max-w-xs'>
|
|
<div className='max-h-[200px] space-y-0.5 overflow-y-auto text-xs'>
|
|
{models.map((m) => (
|
|
<div key={m} className='font-mono'>
|
|
{m}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
)
|
|
}
|
|
|
|
export function IpRestrictionsCell({ apiKey }: { apiKey: ApiKey }) {
|
|
const { t } = useTranslation()
|
|
const allowIps = apiKey.allow_ips?.trim()
|
|
|
|
if (!allowIps) {
|
|
return (
|
|
<StatusBadge
|
|
label={t('No restriction')}
|
|
variant='neutral'
|
|
copyable={false}
|
|
/>
|
|
)
|
|
}
|
|
|
|
const ips = allowIps
|
|
.split('\n')
|
|
.map((ip) => ip.trim())
|
|
.filter(Boolean)
|
|
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger render={<span />}>
|
|
<StatusBadge
|
|
label={t('{{count}} IP(s)', { count: ips.length })}
|
|
variant='neutral'
|
|
copyable={false}
|
|
/>
|
|
</TooltipTrigger>
|
|
<TooltipContent side='top' className='max-w-xs'>
|
|
<div className='max-h-[200px] space-y-0.5 overflow-y-auto text-xs'>
|
|
{ips.map((ip) => (
|
|
<div key={ip} className='font-mono'>
|
|
{ip}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
)
|
|
}
|