Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions frontend/src/components/BuiltInModelManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { cn } from '@/lib/utils';
import { Download, RefreshCw, BadgeAlert, Trash2 } from 'lucide-react';
import { toast } from 'sonner';
import { formatSummaryModelSizeLabelFromMb } from '@/lib/onboarding-summary-model';
import { ConfirmationModal } from '@/components/ConfirmationModel/confirmation-modal';

interface ModelInfo {
name: string;
Expand Down Expand Up @@ -46,6 +47,8 @@ export function BuiltInModelManager({
const [downloadProgress, setDownloadProgress] = useState<Record<string, number>>({});
const [downloadProgressInfo, setDownloadProgressInfo] = useState<Record<string, DownloadProgressInfo>>({});
const [downloadingModels, setDownloadingModels] = useState<Set<string>>(new Set());
const [pendingDelete, setPendingDelete] = useState<ModelInfo | null>(null);
const [isDeleting, setIsDeleting] = useState(false);

const fetchModels = async () => {
try {
Expand Down Expand Up @@ -254,6 +257,17 @@ export function BuiltInModelManager({
}
};

const confirmDelete = async () => {
if (!pendingDelete) return;
setIsDeleting(true);
try {
await deleteModel(pendingDelete.name);
} finally {
setIsDeleting(false);
setPendingDelete(null);
}
};

// Don't show loading spinner if we have downloads in progress - show the model list instead
if (isLoading && downloadingModels.size === 0) {
return (
Expand Down Expand Up @@ -410,7 +424,7 @@ export function BuiltInModelManager({
size="sm"
onClick={(e) => {
e.stopPropagation();
deleteModel(model.name);
setPendingDelete(model);
}}
>
<Trash2 className="mr-2 h-4 w-4" />
Expand All @@ -424,7 +438,7 @@ export function BuiltInModelManager({
className="p-2 rounded hover:bg-gray-100 transition-colors text-gray-500 hover:text-red-600"
onClick={(e) => {
e.stopPropagation();
deleteModel(model.name);
setPendingDelete(model);
}}
title="Delete model"
>
Expand Down Expand Up @@ -487,6 +501,15 @@ export function BuiltInModelManager({
);
})}
</div>
<ConfirmationModal
isOpen={pendingDelete !== null}
title="Delete model"
text={pendingDelete ? `Delete ${pendingDelete.display_name} (${formatSummaryModelSizeLabelFromMb(pendingDelete.size_mb)})? You'll need to download it again to use it.` : ''}
confirmLabel="Delete"
isConfirming={isDeleting}
onConfirm={confirmDelete}
onCancel={() => { if (!isDeleting) setPendingDelete(null); }}
/>
</div>
);
}
27 changes: 25 additions & 2 deletions frontend/src/components/ParakeetModelManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getModelDisplayName,
formatFileSize
} from '../lib/parakeet';
import { ConfirmationModal } from '@/components/ConfirmationModel/confirmation-modal';

interface ParakeetModelManagerProps {
selectedModel?: string;
Expand All @@ -30,6 +31,8 @@ export function ParakeetModelManager({
const [error, setError] = useState<string | null>(null);
const [initialized, setInitialized] = useState(false);
const [downloadingModels, setDownloadingModels] = useState<Set<string>>(new Set());
const [pendingDelete, setPendingDelete] = useState<ParakeetModelInfo | null>(null);
const [isDeleting, setIsDeleting] = useState(false);

// Refs for stable callbacks
const onModelSelectRef = useRef(onModelSelect);
Expand Down Expand Up @@ -328,6 +331,17 @@ export function ParakeetModelManager({
}
};

const confirmDelete = async () => {
if (!pendingDelete) return;
setIsDeleting(true);
try {
await deleteModel(pendingDelete.name);
} finally {
setIsDeleting(false);
setPendingDelete(null);
}
};

if (loading) {
return (
<div className={`space-y-3 ${className}`}>
Expand Down Expand Up @@ -370,7 +384,7 @@ export function ParakeetModelManager({
}}
onDownload={() => downloadModel(recommendedModel.name)}
onCancel={() => cancelDownload(recommendedModel.name)}
onDelete={() => deleteModel(recommendedModel.name)}
onDelete={() => setPendingDelete(recommendedModel)}
isDownloading={downloadingModels.has(recommendedModel.name)}
/>
)}
Expand All @@ -391,7 +405,7 @@ export function ParakeetModelManager({
}}
onDownload={() => downloadModel(model.name)}
onCancel={() => cancelDownload(model.name)}
onDelete={() => deleteModel(model.name)}
onDelete={() => setPendingDelete(model)}
isDownloading={downloadingModels.has(model.name)}
/>
))}
Expand All @@ -408,6 +422,15 @@ export function ParakeetModelManager({
Using {getModelDisplayName(selectedModel)} for transcription
</motion.div>
)}
<ConfirmationModal
isOpen={pendingDelete !== null}
title="Delete model"
text={pendingDelete ? `Delete ${getModelDisplayName(pendingDelete.name)} (${formatFileSize(pendingDelete.size_mb)})? You'll need to download it again to use it.` : ''}
confirmLabel="Delete"
isConfirming={isDeleting}
onConfirm={confirmDelete}
onCancel={() => { if (!isDeleting) setPendingDelete(null); }}
/>
</div>
);
}
Expand Down
27 changes: 25 additions & 2 deletions frontend/src/components/WhisperModelManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
WhisperAPI
} from '../lib/whisper';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
import { ConfirmationModal } from '@/components/ConfirmationModel/confirmation-modal';

interface ModelManagerProps {
selectedModel?: string;
Expand All @@ -34,6 +35,8 @@ export function ModelManager({
const [initialized, setInitialized] = useState(false);
const [downloadingModels, setDownloadingModels] = useState<Set<string>>(new Set());
const [hasUserSelection, setHasUserSelection] = useState(false);
const [pendingDelete, setPendingDelete] = useState<ModelInfo | null>(null);
const [isDeleting, setIsDeleting] = useState(false);

// Refs for stable callbacks
const onModelSelectRef = useRef(onModelSelect);
Expand Down Expand Up @@ -373,6 +376,17 @@ export function ModelManager({
}
};

const confirmDelete = async () => {
if (!pendingDelete) return;
setIsDeleting(true);
try {
await deleteModel(pendingDelete.name);
} finally {
setIsDeleting(false);
setPendingDelete(null);
}
};

const getDisplayName = (modelName: string): string => {
const modelNameMapping: { [key: string]: string } = {
"small": "Small",
Expand Down Expand Up @@ -434,7 +448,7 @@ export function ModelManager({
}}
onDownload={() => downloadModel(model.name)}
onCancel={() => cancelDownload(model.name)}
onDelete={() => deleteModel(model.name)}
onDelete={() => setPendingDelete(model)}
isDownloading={downloadingModels.has(model.name)}
displayName={getDisplayName(model.name)}
/>
Expand Down Expand Up @@ -464,7 +478,7 @@ export function ModelManager({
}}
onDownload={() => downloadModel(model.name)}
onCancel={() => cancelDownload(model.name)}
onDelete={() => deleteModel(model.name)}
onDelete={() => setPendingDelete(model)}
isDownloading={downloadingModels.has(model.name)}
displayName={getDisplayName(model.name)}
/>
Expand All @@ -485,6 +499,15 @@ export function ModelManager({
Using {getDisplayName(selectedModel)} for transcription
</motion.div>
)}
<ConfirmationModal
isOpen={pendingDelete !== null}
title="Delete model"
text={pendingDelete ? `Delete ${getDisplayName(pendingDelete.name)} (${formatFileSize(pendingDelete.size_mb)})? You'll need to download it again to use it.` : ''}
confirmLabel="Delete"
isConfirming={isDeleting}
onConfirm={confirmDelete}
onCancel={() => { if (!isDeleting) setPendingDelete(null); }}
/>
</div>
);
}
Expand Down
Loading