CUDA sidecars are ~2GB and too slow to upload from the Windows runner. Cloud (Deepgram) provides faster transcription anyway. Removed: - CUDA build steps from Windows and Linux sidecar workflows - CUDA option from the SidecarSetup download screen Remaining sidecar variants: - Cloud (Deepgram): ~50 MB - recommended for most users - Local CPU: ~500 MB - for offline/privacy use CUDA can be revisited once the managed Deepgram service is ready. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
421 lines
9.3 KiB
Svelte
421 lines
9.3 KiB
Svelte
<script lang="ts">
|
|
import { invoke } from "@tauri-apps/api/core";
|
|
import { listen } from "@tauri-apps/api/event";
|
|
import { onMount } from "svelte";
|
|
|
|
interface Props {
|
|
onComplete: () => void;
|
|
}
|
|
|
|
let { onComplete }: Props = $props();
|
|
|
|
type SetupState = "choose" | "downloading" | "error" | "success";
|
|
|
|
let setupState = $state<SetupState>("choose");
|
|
let variant = $state<"cpu" | "cuda">("cpu");
|
|
let progress = $state(0);
|
|
let progressMessage = $state("");
|
|
let errorMessage = $state("");
|
|
|
|
let unlisten: (() => void) | null = null;
|
|
|
|
onMount(() => {
|
|
return () => {
|
|
if (unlisten) {
|
|
unlisten();
|
|
unlisten = null;
|
|
}
|
|
};
|
|
});
|
|
|
|
async function startDownload() {
|
|
setupState = "downloading";
|
|
progress = 0;
|
|
progressMessage = "Starting download...";
|
|
errorMessage = "";
|
|
|
|
try {
|
|
// Listen for progress events from the Tauri backend
|
|
unlisten = await listen<{ downloaded: number; total: number; phase: string; message: string }>(
|
|
"sidecar-download-progress",
|
|
(event) => {
|
|
const { downloaded, total, message } = event.payload;
|
|
progress = total > 0 ? (downloaded / total) * 100 : 0;
|
|
progressMessage = message;
|
|
}
|
|
);
|
|
|
|
await invoke("download_sidecar", { variant });
|
|
|
|
// Download complete
|
|
setupState = "success";
|
|
if (unlisten) {
|
|
unlisten();
|
|
unlisten = null;
|
|
}
|
|
|
|
// Brief pause to show success, then proceed
|
|
setTimeout(() => {
|
|
onComplete();
|
|
}, 1500);
|
|
} catch (err) {
|
|
setupState = "error";
|
|
errorMessage = err instanceof Error ? err.message : String(err);
|
|
if (unlisten) {
|
|
unlisten();
|
|
unlisten = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function retry() {
|
|
setupState = "choose";
|
|
progress = 0;
|
|
progressMessage = "";
|
|
errorMessage = "";
|
|
}
|
|
</script>
|
|
|
|
<div class="setup-overlay">
|
|
<div class="setup-card">
|
|
<div class="setup-header">
|
|
<h1 class="app-title">Local Transcription</h1>
|
|
<h2 class="setup-heading">First-Time Setup</h2>
|
|
</div>
|
|
|
|
{#if setupState === "choose"}
|
|
<p class="setup-description">
|
|
Choose a transcription engine. You can change this later in Settings.
|
|
</p>
|
|
|
|
<div class="variant-options">
|
|
<label class="variant-option" class:selected={variant === "cloud"}>
|
|
<input
|
|
type="radio"
|
|
name="variant"
|
|
value="cloud"
|
|
bind:group={variant}
|
|
/>
|
|
<div class="variant-info">
|
|
<span class="variant-name">Cloud (Deepgram)</span>
|
|
<span class="variant-desc">~50 MB download</span>
|
|
<span class="variant-detail">
|
|
Fast, accurate streaming transcription via Deepgram's servers.
|
|
Requires internet and a Deepgram API key.
|
|
Best for most users — low resource usage, works on any hardware.
|
|
</span>
|
|
<span class="variant-tag recommended">Recommended</span>
|
|
</div>
|
|
</label>
|
|
|
|
<label class="variant-option" class:selected={variant === "cpu"}>
|
|
<input
|
|
type="radio"
|
|
name="variant"
|
|
value="cpu"
|
|
bind:group={variant}
|
|
/>
|
|
<div class="variant-info">
|
|
<span class="variant-name">Local - CPU</span>
|
|
<span class="variant-desc">~500 MB download</span>
|
|
<span class="variant-detail">
|
|
Runs Whisper AI models locally on your CPU. No internet needed
|
|
after download. Good for privacy or offline use, but slower and
|
|
uses more system resources than cloud.
|
|
</span>
|
|
</div>
|
|
</label>
|
|
|
|
</div>
|
|
|
|
<button class="download-btn" onclick={startDownload}>
|
|
Download & Install
|
|
</button>
|
|
|
|
{:else if setupState === "downloading"}
|
|
<div class="progress-section">
|
|
<p class="progress-message">{progressMessage}</p>
|
|
<div class="progress-bar-track">
|
|
<div
|
|
class="progress-bar-fill"
|
|
style="width: {progress}%"
|
|
></div>
|
|
</div>
|
|
<p class="progress-percent">{Math.round(progress)}%</p>
|
|
</div>
|
|
|
|
{:else if setupState === "error"}
|
|
<div class="error-section">
|
|
<div class="error-icon">
|
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#f44336" stroke-width="2">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<line x1="15" y1="9" x2="9" y2="15"/>
|
|
<line x1="9" y1="9" x2="15" y2="15"/>
|
|
</svg>
|
|
</div>
|
|
<p class="error-title">Download Failed</p>
|
|
<p class="error-message">{errorMessage}</p>
|
|
<button class="retry-btn" onclick={retry}>
|
|
Try Again
|
|
</button>
|
|
</div>
|
|
|
|
{:else if setupState === "success"}
|
|
<div class="success-section">
|
|
<div class="success-icon">
|
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#4CAF50" stroke-width="2">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<polyline points="16 9 10.5 15 8 12.5"/>
|
|
</svg>
|
|
</div>
|
|
<p class="success-title">Setup Complete</p>
|
|
<p class="success-message">The transcription engine is ready to go.</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.setup-overlay {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
width: 100%;
|
|
background-color: #1e1e1e;
|
|
}
|
|
|
|
.setup-card {
|
|
background-color: #2a2a2a;
|
|
border-radius: 12px;
|
|
padding: 40px;
|
|
max-width: 480px;
|
|
width: 100%;
|
|
margin: 20px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
.setup-header {
|
|
text-align: center;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.app-title {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: #e0e0e0;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.setup-heading {
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
color: #a0a0a0;
|
|
}
|
|
|
|
.setup-description {
|
|
font-size: 14px;
|
|
color: #a0a0a0;
|
|
line-height: 1.6;
|
|
text-align: center;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.variant-options {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.variant-option {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 14px 16px;
|
|
border: 2px solid #444;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: border-color 0.15s ease, background-color 0.15s ease;
|
|
}
|
|
|
|
.variant-option:hover {
|
|
background-color: #333;
|
|
border-color: #555;
|
|
}
|
|
|
|
.variant-option.selected {
|
|
border-color: #4CAF50;
|
|
background-color: rgba(76, 175, 80, 0.08);
|
|
}
|
|
|
|
.variant-option input[type="radio"] {
|
|
width: 18px;
|
|
height: 18px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.variant-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
|
|
.variant-name {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.variant-desc {
|
|
font-size: 12px;
|
|
color: #888;
|
|
}
|
|
|
|
.variant-detail {
|
|
font-size: 11px;
|
|
color: #666;
|
|
line-height: 1.4;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.variant-tag {
|
|
display: inline-block;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
padding: 2px 6px;
|
|
border-radius: 3px;
|
|
margin-top: 4px;
|
|
width: fit-content;
|
|
}
|
|
|
|
.variant-tag.recommended {
|
|
background: rgba(76, 175, 80, 0.15);
|
|
color: #4CAF50;
|
|
}
|
|
|
|
.download-btn {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 12px 24px;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: white;
|
|
background-color: #4CAF50;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: background-color 0.15s ease;
|
|
}
|
|
|
|
.download-btn:hover {
|
|
background-color: #45a049;
|
|
}
|
|
|
|
.download-btn:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
/* Progress state */
|
|
.progress-section {
|
|
text-align: center;
|
|
padding: 20px 0;
|
|
}
|
|
|
|
.progress-message {
|
|
font-size: 14px;
|
|
color: #a0a0a0;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.progress-bar-track {
|
|
width: 100%;
|
|
height: 8px;
|
|
background-color: #3a3a3a;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.progress-bar-fill {
|
|
height: 100%;
|
|
background-color: #4CAF50;
|
|
border-radius: 4px;
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.progress-percent {
|
|
font-size: 13px;
|
|
color: #707070;
|
|
}
|
|
|
|
/* Error state */
|
|
.error-section {
|
|
text-align: center;
|
|
padding: 10px 0;
|
|
}
|
|
|
|
.error-icon {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.error-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #f44336;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.error-message {
|
|
font-size: 13px;
|
|
color: #a0a0a0;
|
|
margin-bottom: 20px;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.retry-btn {
|
|
display: inline-block;
|
|
padding: 10px 28px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: white;
|
|
background-color: #4CAF50;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: background-color 0.15s ease;
|
|
}
|
|
|
|
.retry-btn:hover {
|
|
background-color: #45a049;
|
|
}
|
|
|
|
/* Success state */
|
|
.success-section {
|
|
text-align: center;
|
|
padding: 20px 0;
|
|
}
|
|
|
|
.success-icon {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.success-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #4CAF50;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.success-message {
|
|
font-size: 14px;
|
|
color: #a0a0a0;
|
|
}
|
|
</style>
|