Enhance release workflow and update provider settings

- Added macOS support for PyTorch CPU providers in the release workflow.
- Updated the ProviderSettings component to handle macOS-specific conditions and improve UI interactions.
- Refactored the radio group component styles for better accessibility and visual consistency.
- Improved provider management logic to ensure proper handling of available providers across different platforms.
This commit is contained in:
Jamie Pine
2026-02-01 00:00:47 -08:00
parent 6dd5bb2311
commit 3b14f81741
6 changed files with 173 additions and 182 deletions
@@ -1,6 +1,6 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Delete01Icon, Download01Icon, Loading01Icon } from '@hugeicons/core-free-icons';
import { HugeiconsIcon } from '@hugeicons/react';
import { Download01Icon, Loading01Icon, Delete01Icon } from '@hugeicons/core-free-icons';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback, useState } from 'react';
import {
AlertDialog,
@@ -23,12 +23,18 @@ import { useModelDownloadToast } from '@/lib/hooks/useModelDownloadToast';
const isMacOS = () => navigator.platform.toLowerCase().includes('mac');
type ProviderType = 'auto' | 'bundled-mlx' | 'bundled-pytorch' | 'pytorch-cpu' | 'pytorch-cuda' | 'remote' | 'openai';
type ProviderType =
| 'auto'
| 'bundled-mlx'
| 'bundled-pytorch'
| 'pytorch-cpu'
| 'pytorch-cuda'
| 'remote'
| 'openai';
export function ProviderSettings() {
const { toast } = useToast();
const queryClient = useQueryClient();
const [selectedProvider, setSelectedProvider] = useState<ProviderType>('auto');
const [downloadingProvider, setDownloadingProvider] = useState<string | null>(null);
const { data: providersData, isLoading } = useQuery({
@@ -167,6 +173,7 @@ export function ProviderSettings() {
// Determine current active provider
const currentProvider = activeProvider?.provider || 'auto';
const selectedProvider = currentProvider as ProviderType;
return (
<>
@@ -176,41 +183,25 @@ export function ProviderSettings() {
<CardDescription>Choose how Voicebox generates speech</CardDescription>
</CardHeader>
<CardContent>
<RadioGroup
value={selectedProvider}
onValueChange={(value) => setSelectedProvider(value as ProviderType)}
>
{/* Auto-detect */}
<div className="flex items-center space-x-2 py-2">
<RadioGroupItem value="auto" id="auto" />
<Label htmlFor="auto" className="flex-1 cursor-pointer">
<div className="font-medium">Auto-detect (Recommended)</div>
<div className="text-sm text-muted-foreground">
Automatically choose the best available provider
</div>
</Label>
{currentProvider === 'auto' && (
<Badge variant="outline" className="ml-2">
Active
</Badge>
)}
</div>
<RadioGroup value={selectedProvider} onValueChange={(value) => handleStart(value)}>
{/* PyTorch CUDA */}
<div className="flex items-center justify-between py-2">
<div className="flex items-center space-x-2 flex-1">
<RadioGroupItem value="pytorch-cuda" id="cuda" />
<Label htmlFor="cuda" className="flex-1 cursor-pointer">
<div
className={`flex items-center justify-between py-2 ${isMacOS() ? 'opacity-50' : ''}`}
>
<div className="flex items-center space-x-3 flex-1">
<RadioGroupItem value="pytorch-cuda" id="cuda" disabled={isMacOS()} />
<Label
htmlFor="cuda"
className={`flex-1 ${isMacOS() ? 'cursor-not-allowed' : 'cursor-pointer'}`}
>
<div className="font-medium">PyTorch CUDA (NVIDIA GPU)</div>
<div className="text-sm text-muted-foreground">
4-5x faster inference on NVIDIA GPUs
{isMacOS() ? 'Not available on macOS' : '4-5x faster inference on NVIDIA GPUs'}
</div>
</Label>
</div>
<div className="flex items-center gap-2">
{currentProvider === 'pytorch-cuda' && (
<Badge variant="outline">Active</Badge>
)}
{currentProvider === 'pytorch-cuda' && <Badge variant="outline">Active</Badge>}
{!installedProviders.includes('pytorch-cuda') && (
<Button
onClick={() => handleDownload('pytorch-cuda')}
@@ -218,7 +209,11 @@ export function ProviderSettings() {
disabled={downloadingProvider === 'pytorch-cuda'}
>
{downloadingProvider === 'pytorch-cuda' ? (
<HugeiconsIcon icon={Loading01Icon} size={16} className="h-4 w-4 animate-spin" />
<HugeiconsIcon
icon={Loading01Icon}
size={16}
className="h-4 w-4 animate-spin"
/>
) : (
<>
<HugeiconsIcon icon={Download01Icon} size={16} className="h-4 w-4 mr-1" />
@@ -227,148 +222,109 @@ export function ProviderSettings() {
)}
</Button>
)}
{installedProviders.includes('pytorch-cuda') && selectedProvider !== 'pytorch-cuda' && (
<Button
onClick={() => handleStart('pytorch-cuda')}
size="sm"
variant="outline"
>
Start
</Button>
)}
{installedProviders.includes('pytorch-cuda') &&
selectedProvider !== 'pytorch-cuda' && (
<Button onClick={() => handleStart('pytorch-cuda')} size="sm" variant="outline">
Start
</Button>
)}
{installedProviders.includes('pytorch-cuda') && (
<Button
onClick={() => handleDelete('pytorch-cuda')}
size="sm"
variant="ghost"
>
<Button onClick={() => handleDelete('pytorch-cuda')} size="sm" variant="ghost">
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" />
</Button>
)}
</div>
</div>
{/* PyTorch CPU (Windows/Linux only) */}
{!isMacOS() && (
<div className="flex items-center justify-between py-2">
<div className="flex items-center space-x-2 flex-1">
<RadioGroupItem value="pytorch-cpu" id="cpu" />
<Label htmlFor="cpu" className="flex-1 cursor-pointer">
<div className="font-medium">PyTorch CPU</div>
<div className="text-sm text-muted-foreground">
Works on any system, slower inference
</div>
</Label>
</div>
<div className="flex items-center gap-2">
{currentProvider === 'pytorch-cpu' && (
<Badge variant="outline">Active</Badge>
)}
{!installedProviders.includes('pytorch-cpu') && (
<Button
onClick={() => handleDownload('pytorch-cpu')}
size="sm"
disabled={downloadingProvider === 'pytorch-cpu'}
>
{downloadingProvider === 'pytorch-cpu' ? (
<HugeiconsIcon icon={Loading01Icon} size={16} 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
onClick={() => handleStart('pytorch-cpu')}
size="sm"
variant="outline"
>
{/* PyTorch CPU */}
<div className="flex items-center justify-between py-2">
<div className="flex items-center space-x-3 flex-1">
<RadioGroupItem value="pytorch-cpu" id="cpu" />
<Label htmlFor="cpu" className="flex-1 cursor-pointer">
<div className="font-medium">PyTorch CPU</div>
<div className="text-sm text-muted-foreground">
Works on any system, slower inference
</div>
</Label>
</div>
<div className="flex items-center gap-2">
{currentProvider === 'pytorch-cpu' && <Badge variant="outline">Active</Badge>}
{!installedProviders.includes('pytorch-cpu') && (
<Button
onClick={() => handleDownload('pytorch-cpu')}
size="sm"
disabled={downloadingProvider === 'pytorch-cpu'}
>
{downloadingProvider === 'pytorch-cpu' ? (
<HugeiconsIcon
icon={Loading01Icon}
size={16}
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 onClick={() => handleStart('pytorch-cpu')} size="sm" variant="outline">
Start
</Button>
)}
{installedProviders.includes('pytorch-cpu') && (
<Button
onClick={() => handleDelete('pytorch-cpu')}
size="sm"
variant="ghost"
>
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" />
</Button>
)}
</div>
{installedProviders.includes('pytorch-cpu') && (
<Button onClick={() => handleDelete('pytorch-cpu')} size="sm" variant="ghost">
<HugeiconsIcon icon={Delete01Icon} size={16} className="h-4 w-4" />
</Button>
)}
</div>
)}
</div>
{/* MLX bundled (macOS only) */}
{/* MLX bundled (macOS Apple Silicon only) */}
{isMacOS() && (
<div className="p-3 bg-muted rounded-md">
<div className="text-sm">
<div className="font-medium flex items-center gap-2">
MLX (Apple Silicon)
{currentProvider === 'bundled-mlx' && (
<Badge variant="outline">Active</Badge>
)}
<div className="flex items-center space-x-3 py-2">
<RadioGroupItem value="bundled-mlx" id="mlx" />
<Label htmlFor="mlx" className="flex-1 cursor-pointer">
<div className="font-medium">MLX (Apple Silicon)</div>
<div className="text-sm text-muted-foreground">
Bundled with the app - optimized for M-series chips
</div>
<div className="text-muted-foreground mt-1">
Bundled with the app - optimized for M1/M2/M3 chips
</div>
</div>
</Label>
{currentProvider === 'bundled-mlx' && (
<Badge variant="outline" className="ml-2">
Active
</Badge>
)}
</div>
)}
{/* Remote */}
<div className="space-y-2 py-2">
<div className="flex items-center space-x-2">
<RadioGroupItem value="remote" id="remote" />
<Label htmlFor="remote" className="flex-1 cursor-pointer">
<div className="space-y-2 py-2 opacity-50">
<div className="flex items-center space-x-3">
<RadioGroupItem value="remote" id="remote" disabled />
<Label htmlFor="remote" className="flex-1 cursor-not-allowed">
<div className="font-medium">Remote Server</div>
<div className="text-sm text-muted-foreground">
Connect to your own TTS server
Connect to your own TTS server (coming soon)
</div>
</Label>
</div>
{selectedProvider === 'remote' && (
<div className="ml-6">
<input
type="text"
placeholder="http://your-server:8000"
className="w-full px-3 py-2 border rounded-md"
disabled
/>
<div className="text-xs text-muted-foreground mt-1">
Remote provider support coming soon
</div>
</div>
)}
</div>
{/* OpenAI */}
<div className="space-y-2 py-2">
<div className="flex items-center space-x-2">
<RadioGroupItem value="openai" id="openai" />
<Label htmlFor="openai" className="flex-1 cursor-pointer">
<div className="space-y-2 py-2 opacity-50">
<div className="flex items-center space-x-3">
<RadioGroupItem value="openai" id="openai" disabled />
<Label htmlFor="openai" className="flex-1 cursor-not-allowed">
<div className="font-medium">OpenAI API</div>
<div className="text-sm text-muted-foreground">
Use OpenAI's TTS API (requires API key)
Use OpenAI's TTS API (coming soon)
</div>
</Label>
</div>
{selectedProvider === 'openai' && (
<div className="ml-6">
<input
type="password"
placeholder="sk-..."
className="w-full px-3 py-2 border rounded-md"
disabled
/>
<div className="text-xs text-muted-foreground mt-1">
OpenAI provider support coming soon
</div>
</div>
)}
</div>
</RadioGroup>
</CardContent>
@@ -385,7 +341,10 @@ export function ProviderSettings() {
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} className="bg-destructive text-destructive-foreground">
<AlertDialogAction
onClick={confirmDelete}
className="bg-destructive text-destructive-foreground"
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
+1 -1
View File
@@ -29,7 +29,7 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"aspect-square h-4 w-4 rounded-full border border-accent text-accent ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}