Better model download management

This commit is contained in:
Lucas Winther committed 2026-07-14 20:35:03 +02:00
1 parent 42fe27d2dd
commit 8464f0e492
6 files changed
+86 -7

No files matched your search

+17 -4
View File
@@ -283,16 +283,27 @@ export function App() {
setPartialText("");
setTranscribeProgress(null);
setResult(null);
startLoad(nextModelId, devicePreference);
// Selection never downloads (D-020): the load starts when a transcription
// needs it (file drop / live start on the browser engine) or when the
// user explicitly clicks Download. Reset the load state so the next load
// reports fresh progress for this model.
setLoadStarted(false);
setLoadProgress(initialLoadProgress);
}
function handleDevicePreference(preference: DevicePreference) {
if (busy || liveStatus !== "idle" || preference === devicePreference) return;
setDevicePreference(preference);
setActiveDevice(null);
// Only reload eagerly if a load was already in motion; otherwise the next
// file drop picks up the new preference.
if (loadStarted) startLoad(modelId, preference);
// No eager reload (D-020) — the next load picks up the new preference.
setLoadStarted(false);
setLoadProgress(initialLoadProgress);
}
/** Explicit model download (D-020) — pre-caches weights for offline use. */
function handleDownloadModel() {
if (busy || liveStatus !== "idle" || loadStarted) return;
startLoad(modelId, devicePreference);
}
/**
@@ -631,6 +642,8 @@ export function App() {
downloaded={downloaded}
offlineReady={swControlled}
disabled={busy || liveActive}
onDownloadSelected={handleDownloadModel}
downloading={loadStarted && !modelReady}
/>
{model?.multilingual && (
+18 -1
View File
@@ -19,6 +19,10 @@ interface ModelPickerProps {
offlineReady: boolean;
/** True while a transcription is running — switching is disabled then. */
disabled?: boolean;
/** Explicitly download the selected model's weights (D-020). */
onDownloadSelected: () => void;
/** True while a model load/download is in flight. */
downloading: boolean;
}
const DEVICE_OPTIONS: readonly { value: DevicePreference; label: string }[] = [
@@ -50,6 +54,8 @@ export function ModelPicker({
downloaded,
offlineReady,
disabled = false,
onDownloadSelected,
downloading,
}: ModelPickerProps) {
// Size estimates depend on the dtype the worker will pick, which depends on
// the device. Until the worker has reported one, assume WASM unless the
@@ -116,7 +122,7 @@ export function ModelPicker({
{offlineReady
? downloaded.size > 0
? "Offline ready — the app and the models marked “available offline” work without a network connection."
: "The app is cached for offline use. Load a model once to make it available offline too."
: "The app is cached for offline use. Download a model (or run one on-device transcription) to make it available offline too."
: "Offline support is not active yet (the service worker is still installing, or this browser does not support it)."}
</p>
{groups.map((group) => (
@@ -153,6 +159,17 @@ export function ModelPicker({
</span>
)}
<span className="model-option-size">~{size}</span>
{isSelected && !isDownloaded && (
<button
type="button"
className="model-download-button"
disabled={disabled || downloading}
title="Download this model now so it is available offline. Otherwise it downloads automatically when an on-device transcription starts."
onClick={onDownloadSelected}
>
{downloading ? "Downloading…" : "Download"}
</button>
)}
</span>
</label>
</li>
+23
View File
@@ -306,6 +306,29 @@ body {
white-space: nowrap;
}
/* Explicit weight download for the selected model (D-020). */
.model-download-button {
border: 1px solid var(--border);
background: transparent;
color: inherit;
border-radius: 999px;
padding: 0.1rem 0.7rem;
font-size: 0.75rem;
white-space: nowrap;
cursor: pointer;
transition: border-color 0.15s, color 0.15s;
}
.model-download-button:hover:enabled {
border-color: #3d6b4f;
color: #9ad6ae;
}
.model-download-button:disabled {
opacity: 0.6;
cursor: default;
}
/* --- PWA / offline (M3) --- */
.offline-status {
margin: 0.5rem 0 0.75rem;