({ file, hardwareInfo, onDownload, downloadProgress, isInstalled = false }: HfModelFileItemProps)
| 10 | } |
| 11 | |
| 12 | export function HfModelFileItem({ file, hardwareInfo, onDownload, downloadProgress, isInstalled = false }: HfModelFileItemProps) { |
| 13 | const isDownloading = !!downloadProgress; |
| 14 | |
| 15 | const compatibilityBadge = hardwareInfo |
| 16 | ? getCompatibilityBadge(file.estimated_ram_gb, hardwareInfo.vram_gb, hardwareInfo.ram_gb) |
| 17 | : null; |
| 18 | |
| 19 | const badgeConfig = { |
| 20 | compatible: { label: 'Best for your GPU', color: 'bg-green-600' }, |
| 21 | marginal: { label: 'CPU compatible', color: 'bg-yellow-600' }, |
| 22 | incompatible: { label: 'May not fit', color: 'bg-red-600' }, |
| 23 | }; |
| 24 | |
| 25 | return ( |
| 26 | <div className="bg-dark-surface border border-dark-border rounded-lg p-4"> |
| 27 | <div className="flex items-start justify-between mb-2"> |
| 28 | <div className="flex-1"> |
| 29 | <h4 className="text-dark-text font-medium mb-1">{file.filename}</h4> |
| 30 | <div className="flex items-center gap-3 text-sm text-dark-text-secondary"> |
| 31 | <span>{formatBytes(file.size)}</span> |
| 32 | {file.quantization && ( |
| 33 | <span className="px-2 py-0.5 bg-dark-bg text-dark-accent text-xs rounded font-mono"> |
| 34 | {file.quantization} |
| 35 | </span> |
| 36 | )} |
| 37 | <span>~{file.estimated_ram_gb.toFixed(1)} GB RAM</span> |
| 38 | </div> |
| 39 | </div> |
| 40 | |
| 41 | {compatibilityBadge && ( |
| 42 | <span className={`px-2 py-1 ${badgeConfig[compatibilityBadge].color} text-white text-xs rounded`}> |
| 43 | {badgeConfig[compatibilityBadge].label} |
| 44 | </span> |
| 45 | )} |
| 46 | </div> |
| 47 | |
| 48 | {isInstalled ? ( |
| 49 | <div className="w-full px-3 py-2 bg-green-600/15 border border-green-600/30 text-green-400 text-sm rounded text-center"> |
| 50 | Installed |
| 51 | </div> |
| 52 | ) : isDownloading ? ( |
| 53 | <div className="space-y-2"> |
| 54 | <div className="w-full bg-dark-bg rounded-full h-2"> |
| 55 | <div |
| 56 | className="bg-dark-accent h-2 rounded-full transition-all" |
| 57 | style={{ width: `${downloadProgress.percentage}%` }} |
| 58 | /> |
| 59 | </div> |
| 60 | <div className="flex items-center justify-between text-xs text-dark-text-secondary"> |
| 61 | <span>{downloadProgress.percentage.toFixed(1)}%</span> |
| 62 | <span>{formatBytes(downloadProgress.bytes_downloaded)} / {formatBytes(downloadProgress.total_bytes)}</span> |
| 63 | <span>{formatSpeed(downloadProgress.speed_mbps)}</span> |
| 64 | <span>ETA: {formatETA(downloadProgress.eta_seconds)}</span> |
| 65 | </div> |
| 66 | <button |
| 67 | onClick={onDownload} |
| 68 | className="w-full px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-sm rounded transition-colors focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 focus:ring-offset-dark-surface" |
| 69 | aria-label="Cancel download" |
nothing calls this directly
no test coverage detected