99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
import { AlertCircle } from 'lucide-react'
|
|
import type { ApiSuccessOf } from '../lib/api-client'
|
|
import { Button } from './ui/button'
|
|
|
|
type HfAuthStatus = ApiSuccessOf<'getHuggingFaceAuthStatus'>['status']
|
|
type ModelAccessMap = ApiSuccessOf<'checkModelAccess'>['access']
|
|
|
|
interface HfModelAccessGateProps {
|
|
accessMap: ModelAccessMap
|
|
allAuthorized: boolean
|
|
hfAuthStatus: HfAuthStatus
|
|
hfAuthPolling: boolean
|
|
startHuggingFaceLogin: () => void
|
|
/** When the access check itself failed (network/backend), distinct from unauthorized. */
|
|
checkError?: string | null
|
|
onRetryCheck?: () => void
|
|
className?: string
|
|
}
|
|
|
|
export function HfModelAccessGate({
|
|
accessMap,
|
|
allAuthorized,
|
|
hfAuthStatus,
|
|
hfAuthPolling,
|
|
startHuggingFaceLogin,
|
|
checkError = null,
|
|
onRetryCheck,
|
|
className,
|
|
}: HfModelAccessGateProps) {
|
|
if (allAuthorized) return null
|
|
|
|
if (checkError) {
|
|
return (
|
|
<div className={className ?? 'space-y-2'}>
|
|
<div className="flex items-start gap-2 text-xs text-amber-400">
|
|
<AlertCircle className="h-3.5 w-3.5 flex-shrink-0 mt-0.5" />
|
|
<span>Couldn't verify Hugging Face access: {checkError}</span>
|
|
</div>
|
|
{onRetryCheck && (
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={onRetryCheck}
|
|
className="text-xs"
|
|
>
|
|
Retry
|
|
</Button>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const unauthorizedRepos = Object.entries(accessMap).filter(([, status]) => status === 'not_authorized')
|
|
if (unauthorizedRepos.length === 0) return null
|
|
|
|
if (hfAuthStatus !== 'authenticated') {
|
|
return (
|
|
<div className={className ?? 'space-y-2'}>
|
|
<div className="flex items-start gap-2 text-xs text-amber-400">
|
|
<AlertCircle className="h-3.5 w-3.5 flex-shrink-0 mt-0.5" />
|
|
<span>
|
|
This model is gated on Hugging Face. Sign in, then accept the license to download.
|
|
</span>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
onClick={startHuggingFaceLogin}
|
|
disabled={hfAuthPolling}
|
|
className="bg-indigo-600 hover:bg-indigo-500 text-white text-xs"
|
|
>
|
|
{hfAuthPolling ? 'Waiting for sign in…' : 'Sign in with Hugging Face'}
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className={className ?? 'space-y-1.5'}>
|
|
<p className="text-xs text-amber-400">
|
|
Accept the Hugging Face license for this model, then download.
|
|
</p>
|
|
{unauthorizedRepos.map(([repoId]) => (
|
|
<div key={repoId} className="flex items-center justify-between gap-2 bg-zinc-900 rounded px-2 py-1.5">
|
|
<span className="text-[10px] text-zinc-400 font-mono truncate">{repoId}</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
void window.electronAPI.openHuggingFaceRepo({ repoId })
|
|
}}
|
|
className="text-[10px] text-indigo-400 hover:text-indigo-300 font-medium flex-shrink-0"
|
|
>
|
|
Request access
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|