106 lines
3.5 KiB
Svelte
106 lines
3.5 KiB
Svelte
<script>
|
|
import { onMount, onDestroy } from "svelte";
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { listen } from "@tauri-apps/api/event";
|
|
import Card from "$lib/components/Card.svelte";
|
|
|
|
let { modelSize = "base", onComplete = () => {} } = $props();
|
|
|
|
let progress = $state(0);
|
|
let downloading = $state(false);
|
|
let downloaded = $state(0);
|
|
let total = $state(0);
|
|
let error = $state("");
|
|
let unlisten = null;
|
|
|
|
const modelInfo = {
|
|
tiny: { size: "~75 MB", accuracy: "Basic" },
|
|
base: { size: "~142 MB", accuracy: "Good" },
|
|
small: { size: "~466 MB", accuracy: "Better" },
|
|
medium: { size: "~1.5 GB", accuracy: "Best" },
|
|
};
|
|
|
|
onMount(async () => {
|
|
unlisten = await listen("model-download-progress", (event) => {
|
|
const data = event.payload;
|
|
progress = data.progress;
|
|
downloaded = data.downloaded;
|
|
total = data.total;
|
|
});
|
|
});
|
|
|
|
onDestroy(() => {
|
|
if (unlisten) unlisten();
|
|
});
|
|
|
|
async function startDownload() {
|
|
downloading = true;
|
|
error = "";
|
|
progress = 0;
|
|
try {
|
|
await invoke("download_model", { size: modelSize });
|
|
onComplete();
|
|
} catch (err) {
|
|
error = typeof err === "string" ? err : err.message || "Download failed";
|
|
downloading = false;
|
|
}
|
|
}
|
|
|
|
function formatBytes(bytes) {
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
if (bytes < 1073741824) return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
return `${(bytes / 1073741824).toFixed(2)} GB`;
|
|
}
|
|
</script>
|
|
|
|
<div class="flex flex-col items-center justify-center h-full px-8 animate-fade-in">
|
|
<Card classes="max-w-md w-full">
|
|
<div class="p-8 text-center">
|
|
<div class="w-16 h-16 rounded-full bg-accent/10 flex items-center justify-center mx-auto mb-5">
|
|
<svg class="w-8 h-8 text-accent" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" />
|
|
</svg>
|
|
</div>
|
|
|
|
<h3 class="text-[18px] font-semibold text-text mb-2">Download Whisper Model</h3>
|
|
<p class="text-[13px] text-text-secondary mb-1">
|
|
Kon needs the <span class="font-medium text-text">{modelSize}</span> model to transcribe speech.
|
|
</p>
|
|
<p class="text-[11px] text-text-tertiary mb-6">
|
|
{modelInfo[modelSize]?.size ?? "?"} · {modelInfo[modelSize]?.accuracy ?? "?"} accuracy · 100% offline
|
|
</p>
|
|
|
|
{#if downloading}
|
|
<div class="mb-4">
|
|
<div class="h-[6px] bg-bg-elevated rounded-full overflow-hidden mb-2">
|
|
<div
|
|
class="h-full bg-accent rounded-full transition-all duration-300 shadow-[0_0_8px_rgba(232,168,124,0.4)]"
|
|
style="width: {progress}%"
|
|
></div>
|
|
</div>
|
|
<p class="text-[12px] text-text-secondary">
|
|
{progress}% · {formatBytes(downloaded)} / {formatBytes(total)}
|
|
</p>
|
|
</div>
|
|
{:else}
|
|
<button
|
|
class="px-6 py-2.5 rounded-xl text-[14px] font-medium text-white bg-accent hover:bg-accent-hover
|
|
shadow-[0_4px_16px_rgba(232,168,124,0.3)] active:scale-[0.97] transition-all duration-150"
|
|
onclick={startDownload}
|
|
>
|
|
Download Model
|
|
</button>
|
|
{/if}
|
|
|
|
{#if error}
|
|
<p class="text-[12px] text-danger mt-3">{error}</p>
|
|
{/if}
|
|
|
|
<p class="text-[10px] text-text-tertiary mt-5">
|
|
Models are cached locally. No data leaves your machine.
|
|
</p>
|
|
</div>
|
|
</Card>
|
|
</div>
|