import React from "react"; import { ModelInfo } from "../../lib/types"; import { ProgressBar, ProgressData } from "../shared"; interface DownloadProgress { model_id: string; downloaded: number; total: number; percentage: number; } interface ModelDropdownProps { models: ModelInfo[]; currentModelId: string; downloadProgress: Map; onModelSelect: (modelId: string) => void; onModelDownload: (modelId: string) => void; onModelDelete: (modelId: string) => Promise; onError?: (error: string) => void; } const ModelDropdown: React.FC = ({ models, currentModelId, downloadProgress, onModelSelect, onModelDownload, onModelDelete, onError, }) => { const availableModels = models.filter((m) => m.is_downloaded); const downloadableModels = models.filter((m) => !m.is_downloaded); const isFirstRun = availableModels.length === 0 && models.length > 0; const handleDeleteClick = async (e: React.MouseEvent, modelId: string) => { e.preventDefault(); e.stopPropagation(); try { await onModelDelete(modelId); } catch (err) { const errorMsg = `Failed to delete model: ${err}`; onError?.(errorMsg); } }; const handleModelClick = (modelId: string) => { if (downloadProgress.has(modelId)) { return; // Don't allow interaction while downloading } onModelSelect(modelId); }; const handleDownloadClick = (modelId: string) => { if (downloadProgress.has(modelId)) { return; // Don't allow interaction while downloading } onModelDownload(modelId); }; return (
{/* First Run Welcome */} {isFirstRun && (
Welcome to Handy!
Download a model below to get started with transcription.
)} {/* Available Models */} {availableModels.length > 0 && (
Available Models
{availableModels.map((model) => (
handleModelClick(model.id)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleModelClick(model.id); } }} tabIndex={0} role="button" className={`w-full px-3 py-2 text-left hover:bg-mid-gray/10 transition-colors cursor-pointer focus:outline-none ${ currentModelId === model.id ? "bg-logo-primary/10 text-logo-primary" : "" }`} >
{model.name}
{model.description}
{currentModelId === model.id && (
Active
)} {currentModelId !== model.id && ( )}
))}
)} {/* Downloadable Models */} {downloadableModels.length > 0 && (
{(availableModels.length > 0 || isFirstRun) && (
)}
{isFirstRun ? "Choose a Model" : "Download Models"}
{downloadableModels.map((model) => { const isDownloading = downloadProgress.has(model.id); const progress = downloadProgress.get(model.id); return (
handleDownloadClick(model.id)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleDownloadClick(model.id); } }} tabIndex={0} role="button" aria-disabled={isDownloading} className={`w-full px-3 py-2 text-left hover:bg-mid-gray/10 transition-colors cursor-pointer focus:outline-none ${ isDownloading ? "opacity-50 cursor-not-allowed hover:bg-transparent" : "" }`} >
{model.name} {model.id === "small" && isFirstRun && ( Recommended )}
{model.description}
{isDownloading && progress ? ( `${Math.max(0, Math.min(100, Math.round(progress.percentage)))}%` ) : ( "Download" )}
{isDownloading && progress && (
)}
); })}
)} {/* No Models Available */} {availableModels.length === 0 && downloadableModels.length === 0 && (
No models available
)}
); }; export default ModelDropdown;