Enhance ProviderSettings component for platform-specific functionality

- Added platform detection for macOS and Windows to improve user experience.
- Updated UI to conditionally disable options and provide clearer guidance based on installed providers.
- Refactored button states and labels for PyTorch CUDA and CPU to reflect availability and download status accurately.
This commit is contained in:
Jamie Pine
2026-02-03 06:07:19 -08:00
parent d21954358d
commit 637e0b4585
3 changed files with 94 additions and 82 deletions
@@ -22,6 +22,12 @@ import { apiClient } from '@/lib/api/client';
import { useModelDownloadToast } from '@/lib/hooks/useModelDownloadToast'; import { useModelDownloadToast } from '@/lib/hooks/useModelDownloadToast';
const isMacOS = () => navigator.platform.toLowerCase().includes('mac'); const isMacOS = () => navigator.platform.toLowerCase().includes('mac');
const isWindows = () => navigator.platform.toLowerCase().includes('win');
const getPlatformName = () => {
if (isMacOS()) return 'macOS';
if (isWindows()) return 'Windows';
return 'Linux';
};
type ProviderType = type ProviderType =
| 'auto' | 'auto'
@@ -200,14 +206,12 @@ export function ProviderSettings() {
disabled={isStarting} disabled={isStarting}
> >
{/* PyTorch CUDA */} {/* PyTorch CUDA */}
<div <div className="flex items-center justify-between py-2">
className={`flex items-center justify-between py-2 ${isMacOS() ? 'opacity-50' : ''}`} <div className={`flex items-center space-x-3 flex-1 ${isMacOS() || !installedProviders.includes('pytorch-cuda') ? 'opacity-50' : ''}`}>
> <RadioGroupItem value="pytorch-cuda" id="cuda" disabled={isMacOS() || isStarting || !installedProviders.includes('pytorch-cuda')} />
<div className="flex items-center space-x-3 flex-1">
<RadioGroupItem value="pytorch-cuda" id="cuda" disabled={isMacOS() || isStarting} />
<Label <Label
htmlFor="cuda" htmlFor="cuda"
className={`flex-1 ${isMacOS() || isStarting ? 'cursor-not-allowed' : 'cursor-pointer'}`} className={`flex-1 ${isMacOS() || isStarting || !installedProviders.includes('pytorch-cuda') ? 'cursor-not-allowed' : 'cursor-pointer'}`}
> >
<div className="font-medium">PyTorch CUDA</div> <div className="font-medium">PyTorch CUDA</div>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
@@ -216,41 +220,45 @@ export function ProviderSettings() {
</Label> </Label>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{!installedProviders.includes('pytorch-cuda') && ( {isMacOS() && (
<Button <>
onClick={() => handleDownload('pytorch-cuda')} <span className="text-xs text-muted-foreground">2.4GB</span>
size="sm" <Button size="sm" variant="secondary" disabled>
disabled={downloadingProvider === 'pytorch-cuda' || isStarting} Not Available on macOS
> </Button>
{downloadingProvider === 'pytorch-cuda' ? ( </>
<Icon icon="svg-spinners:ring-resize" className="h-4 w-4 animate-spin" />
) : (
<>
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4 mr-1" />
Download (2.4GB)
</>
)}
</Button>
)} )}
{installedProviders.includes('pytorch-cuda') && {!isMacOS() && !installedProviders.includes('pytorch-cuda') && (
selectedProvider !== 'pytorch-cuda' && ( <>
<span className="text-xs text-muted-foreground">2.4GB</span>
<Button <Button
onClick={() => handleStart('pytorch-cuda')} onClick={() => handleDownload('pytorch-cuda')}
size="sm" size="sm"
variant="outline" variant="outline"
disabled={isStarting} disabled={downloadingProvider === 'pytorch-cuda' || isStarting}
className="shrink-0"
> >
Start {downloadingProvider === 'pytorch-cuda' ? (
<Icon icon="svg-spinners:ring-resize" className="h-4 w-4 animate-spin" />
) : (
<>
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4 mr-2" />
Download
</>
)}
</Button> </Button>
)} </>
)}
{installedProviders.includes('pytorch-cuda') && ( {installedProviders.includes('pytorch-cuda') && (
<Button <Button
onClick={() => handleDelete('pytorch-cuda')} onClick={() => handleDelete('pytorch-cuda')}
size="sm" size="sm"
variant="ghost" variant="outline"
disabled={isStarting} disabled={isStarting}
className="shrink-0"
> >
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" /> <HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4 mr-2" />
Uninstall
</Button> </Button>
)} )}
</div> </div>
@@ -258,11 +266,11 @@ export function ProviderSettings() {
{/* PyTorch CPU */} {/* PyTorch CPU */}
<div className="flex items-center justify-between py-2"> <div className="flex items-center justify-between py-2">
<div className="flex items-center space-x-3 flex-1"> <div className={`flex items-center space-x-3 flex-1 ${!installedProviders.includes('pytorch-cpu') ? 'opacity-50' : ''}`}>
<RadioGroupItem value="pytorch-cpu" id="cpu" disabled={isStarting} /> <RadioGroupItem value="pytorch-cpu" id="cpu" disabled={isStarting || !installedProviders.includes('pytorch-cpu')} />
<Label <Label
htmlFor="cpu" htmlFor="cpu"
className={`flex-1 ${isStarting ? 'cursor-not-allowed' : 'cursor-pointer'}`} className={`flex-1 ${isStarting || !installedProviders.includes('pytorch-cpu') ? 'cursor-not-allowed' : 'cursor-pointer'}`}
> >
<div className="font-medium">PyTorch CPU</div> <div className="font-medium">PyTorch CPU</div>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
@@ -272,64 +280,67 @@ export function ProviderSettings() {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{!installedProviders.includes('pytorch-cpu') && ( {!installedProviders.includes('pytorch-cpu') && (
<Button <>
onClick={() => handleDownload('pytorch-cpu')} <span className="text-xs text-muted-foreground">242MB</span>
size="sm"
disabled={downloadingProvider === 'pytorch-cpu' || isStarting}
>
{downloadingProvider === 'pytorch-cpu' ? (
<Icon icon="svg-spinners:ring-resize" className="h-4 w-4 animate-spin" />
) : (
<>
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4 mr-1" />
Download (300MB)
</>
)}
</Button>
)}
{installedProviders.includes('pytorch-cpu') &&
selectedProvider !== 'pytorch-cpu' && (
<Button <Button
onClick={() => handleStart('pytorch-cpu')} onClick={() => handleDownload('pytorch-cpu')}
size="sm" size="sm"
variant="outline" variant="outline"
disabled={isStarting} disabled={downloadingProvider === 'pytorch-cpu' || isStarting}
className="shrink-0"
> >
Start {downloadingProvider === 'pytorch-cpu' ? (
<Icon icon="svg-spinners:ring-resize" className="h-4 w-4 animate-spin" />
) : (
<>
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4 mr-2" />
Download
</>
)}
</Button> </Button>
)} </>
)}
{installedProviders.includes('pytorch-cpu') && ( {installedProviders.includes('pytorch-cpu') && (
<Button <Button
onClick={() => handleDelete('pytorch-cpu')} onClick={() => handleDelete('pytorch-cpu')}
size="sm" size="sm"
variant="ghost" variant="outline"
disabled={isStarting} disabled={isStarting}
className="shrink-0"
> >
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" /> <HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4 mr-2" />
Uninstall
</Button> </Button>
)} )}
</div> </div>
</div> </div>
{/* MLX bundled (macOS Apple Silicon only) */} {/* MLX bundled (macOS Apple Silicon only) */}
<div className="flex items-center space-x-3 py-2"> <div className="flex items-center justify-between py-2">
<RadioGroupItem value="apple-mlx" id="mlx" disabled={isStarting || !isMacOS()} /> <div className={`flex items-center space-x-3 flex-1 ${!isMacOS() ? 'opacity-50' : ''}`}>
<Label <RadioGroupItem value="apple-mlx" id="mlx" disabled={isStarting || !isMacOS()} />
htmlFor="mlx" <Label
className={`flex-1 ${isStarting ? 'cursor-not-allowed' : 'cursor-pointer'}`} htmlFor="mlx"
> className={`flex-1 ${isStarting || !isMacOS() ? 'cursor-not-allowed' : 'cursor-pointer'}`}
<div className="font-medium">Apple MLX</div> >
<div className="text-sm text-muted-foreground"> <div className="font-medium">Apple MLX</div>
{isMacOS() <div className="text-sm text-muted-foreground">
? 'Bundled with this version, optimized for Apple Silicon' {isMacOS()
: 'Only available on Apple Silicon'} ? 'Bundled with this version, optimized for Apple Silicon'
</div> : 'Only available on Apple Silicon'}
</Label> </div>
</Label>
</div>
{!isMacOS() && (
<Button size="sm" variant="secondary" disabled>
Not Available on {getPlatformName()}
</Button>
)}
</div> </div>
{/* Remote */} {/* Remote */}
<div className="space-y-2 py-2 opacity-50"> <div className="flex items-center justify-between py-2">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3 flex-1 opacity-50">
<RadioGroupItem value="remote" id="remote" disabled /> <RadioGroupItem value="remote" id="remote" disabled />
<Label htmlFor="remote" className="flex-1 cursor-not-allowed"> <Label htmlFor="remote" className="flex-1 cursor-not-allowed">
<div className="font-medium">Remote Server</div> <div className="font-medium">Remote Server</div>
@@ -338,17 +349,23 @@ export function ProviderSettings() {
</div> </div>
</Label> </Label>
</div> </div>
<Button size="sm" variant="secondary" disabled>
Coming Soon
</Button>
</div> </div>
{/* OpenAI */} {/* OpenAI */}
<div className="space-y-2 py-2 opacity-50"> <div className="flex items-center justify-between py-2">
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3 flex-1 opacity-50">
<RadioGroupItem value="openai" id="openai" disabled /> <RadioGroupItem value="openai" id="openai" disabled />
<Label htmlFor="openai" className="flex-1 cursor-not-allowed"> <Label htmlFor="openai" className="flex-1 cursor-not-allowed">
<div className="font-medium">OpenAI API</div> <div className="font-medium">OpenAI API</div>
<div className="text-sm text-muted-foreground">Use OpenAI's TTS API</div> <div className="text-sm text-muted-foreground">Use OpenAI's TTS API</div>
</Label> </Label>
</div> </div>
<Button size="sm" variant="secondary" disabled>
Coming Soon
</Button>
</div> </div>
</RadioGroup> </RadioGroup>
<p className="text-xs text-muted-foreground mt-5"> <p className="text-xs text-muted-foreground mt-5">
+5 -10
View File
@@ -220,16 +220,11 @@ class ProviderManager:
installed.append("pytorch-cpu") installed.append("pytorch-cpu")
# Linux: no bundled provider - users must download # Linux: no bundled provider - users must download
# Check for downloaded providers (Phase 2) # Check for downloaded providers by checking if binary path exists
providers_dir = _get_providers_dir() for provider_type in ["pytorch-cpu", "pytorch-cuda"]:
if providers_dir.exists(): binary_path = get_provider_binary_path(provider_type)
for provider_file in providers_dir.glob("tts-provider-*"): if binary_path and binary_path.exists() and provider_type not in installed:
if provider_file.is_file() and provider_file.stat().st_size > 0: installed.append(provider_type)
name = provider_file.name
if "pytorch-cpu" in name:
installed.append("pytorch-cpu")
elif "pytorch-cuda" in name:
installed.append("pytorch-cuda")
return installed return installed
Binary file not shown.