Compare commits

..

20 Commits

Author SHA1 Message Date
Gitea Actions
91b27ac22e chore: bump sidecar version to 1.0.8 [skip ci] 2026-04-08 19:27:02 +00:00
Gitea Actions
1210acd07f chore: bump version to 2.0.9 [skip ci] 2026-04-08 19:23:05 +00:00
Developer
352615c15c Fix Deepgram broken pipe: wait for WebSocket before starting audio
All checks were successful
Tests / Python Backend Tests (push) Successful in 5s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 2m0s
Audio capture started immediately after spawning the WebSocket thread,
but the WebSocket hadn't connected yet. Audio chunks sent to the
unconnected WebSocket caused a broken pipe error.

Fix: added a threading.Event that start_recording() waits on (up to
15s) before opening the audio stream. The event is set in _ws_lifecycle
after the WebSocket connects and handshake completes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:18:47 -07:00
Developer
a3bcc5bee5 Show transcription start errors in UI, improve error logging
All checks were successful
Tests / Python Backend Tests (push) Successful in 5s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 2m5s
Start Transcription button now shows the error message when it fails
instead of silently reverting. Common causes:
- Missing PortAudio library on Linux
- Audio device not accessible
- Deepgram connection failure

Also added error details to backend console output and captured
the last error from the Deepgram engine for better diagnostics.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:15:43 -07:00
Developer
b91fe876f9 Stop sidecar process when the app exits
All checks were successful
Tests / Python Backend Tests (push) Successful in 5s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 2m6s
The sidecar process was orphaned when the Tauri app closed, leaving
ports 8080/8081 in use. On next launch the new sidecar couldn't bind
those ports and failed to start.

Added RunEvent::Exit handler that stops the sidecar before the app
process terminates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:54:53 -07:00
Developer
7e04d6b4af Fix Linux CPU sidecar bundling CUDA, add cleanup workflow
All checks were successful
Tests / Python Backend Tests (push) Successful in 6s
Tests / Frontend Tests (push) Successful in 7s
Tests / Rust Sidecar Tests (push) Successful in 2m9s
Linux CPU sidecar: PyPI's default torch on Linux includes CUDA
(~800MB). UV_NO_SOURCES only bypasses our custom CUDA index but
still gets CUDA-enabled torch from PyPI. Now explicitly installs
CPU-only torch from pytorch.org/whl/cpu after sync. Same fix
applied to Windows.

New cleanup-releases.yml workflow (manual trigger):
- Configurable: keep N app releases, keep N sidecar releases
- Dry run mode (default) shows what would be deleted without deleting
- Protects v1.4.0 (last pre-Tauri release)
- Shows release sizes in MB

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:21:16 -07:00
Developer
15c4e262b9 Document macOS quarantine workaround in README
All checks were successful
Tests / Python Backend Tests (push) Successful in 6s
Tests / Frontend Tests (push) Successful in 7s
Tests / Rust Sidecar Tests (push) Successful in 2m1s
macOS Gatekeeper blocks unsigned apps with "damaged" error.
Added xattr -cr command to Troubleshooting section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 11:02:55 -07:00
Gitea Actions
2246723220 chore: bump sidecar version to 1.0.7 [skip ci] 2026-04-08 17:05:36 +00:00
Gitea Actions
1c586738f3 chore: bump version to 2.0.8 [skip ci] 2026-04-08 16:58:00 +00:00
Developer
fb02a24334 Remove CUDA sidecar builds, keep CPU + Cloud only
All checks were successful
Tests / Python Backend Tests (push) Successful in 6s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 2m3s
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>
2026-04-08 09:49:36 -07:00
Developer
ce64cacc5e Use max compression for sidecar zips to reduce upload size
All checks were successful
Tests / Python Backend Tests (push) Successful in 5s
Tests / Frontend Tests (push) Successful in 7s
Tests / Rust Sidecar Tests (push) Successful in 2m1s
zip -9 on Linux, 7z -mx=9 on Windows. Compression takes longer but
produces smaller files which upload faster over the network.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 09:42:26 -07:00
Gitea Actions
14a7ca3b30 chore: bump sidecar version to 1.0.6 [skip ci] 2026-04-08 16:26:36 +00:00
Gitea Actions
5b7387f9c6 chore: bump version to 2.0.7 [skip ci] 2026-04-08 16:21:51 +00:00
Developer
293362baa1 Cloud sidecar auto-detects variant and guides user to configure
All checks were successful
Tests / Python Backend Tests (push) Successful in 5s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 2m7s
On first launch, the cloud sidecar now:
1. Detects it's the cloud variant (DeviceManager import fails)
2. Auto-switches config from "local" to "byok" mode
3. Shows "Setup needed: Open Settings > Remote Transcription >
   enter your Deepgram API key" as a friendly status message
4. Stays in READY state so the UI is fully accessible

The user can then open Settings, enter their Deepgram API key,
save, and start transcribing without needing to know about modes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 09:17:06 -07:00
Developer
41f50dedec Fix cloud sidecar crash on first launch
All checks were successful
Tests / Python Backend Tests (push) Successful in 5s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 3m11s
The cloud sidecar excludes the local Whisper engine module, but on
first launch the config defaults to remote.mode="local" which tries
to import it. Now catches the ImportError gracefully and shows an
error message telling the user to switch to Cloud (Deepgram) mode
in Settings. The API server still starts so Settings is accessible.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 09:12:17 -07:00
Developer
d8b7811153 Fix NaN% in sidecar download progress
All checks were successful
Tests / Python Backend Tests (push) Successful in 6s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 2m4s
The Rust backend emits {downloaded, total, phase, message} but the
Svelte component was reading event.payload.progress which doesn't
exist, resulting in NaN. Now calculates percentage from downloaded/total.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 09:06:39 -07:00
Developer
ec8922672c Fix Stop Transcription button not updating after click
All checks were successful
Tests / Python Backend Tests (push) Successful in 6s
Tests / Frontend Tests (push) Successful in 8s
Tests / Rust Sidecar Tests (push) Successful in 2m9s
After calling POST /api/stop, the button stayed on "Stop Transcription"
because the state update depended on the WebSocket broadcast which can
be delayed or missed (event loop threading issue).

Fix: poll GET /api/status immediately after start/stop API calls to
update the UI state directly, rather than waiting for the WebSocket.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 07:26:06 -07:00
Gitea Actions
375669f657 chore: bump sidecar version to 1.0.5 [skip ci] 2026-04-08 00:43:01 +00:00
Gitea Actions
c8b11fb0ad chore: bump version to 2.0.6 [skip ci] 2026-04-08 00:37:28 +00:00
Developer
273a926f03 Fix YAML parse error: use block scalar for echo with colons
All checks were successful
Tests / Python Backend Tests (push) Successful in 5s
Tests / Frontend Tests (push) Successful in 7s
Tests / Rust Sidecar Tests (push) Successful in 2m7s
Gitea's YAML parser treats `echo "text: value"` as a mapping when
on a single `run:` line. Using block scalar (`run: |`) avoids this.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:21:42 -07:00
19 changed files with 227 additions and 67 deletions

View File

@@ -16,7 +16,8 @@ jobs:
RELEASE_TAG: "${{ inputs.tag }}"
steps:
- name: Show tag
run: echo "Building for tag: ${RELEASE_TAG}"
run: |
echo "Building for tag: ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:

View File

@@ -16,7 +16,8 @@ jobs:
RELEASE_TAG: "${{ inputs.tag }}"
steps:
- name: Show tag
run: echo "Building for tag: ${RELEASE_TAG}"
run: |
echo "Building for tag: ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:

View File

@@ -16,7 +16,8 @@ jobs:
RELEASE_TAG: "${{ inputs.tag }}"
steps:
- name: Show tag
run: echo "Building cloud sidecar for tag ${RELEASE_TAG}"
run: |
echo "Building cloud sidecar for tag ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:
@@ -164,7 +165,8 @@ jobs:
RELEASE_TAG: "${{ inputs.tag }}"
steps:
- name: Show tag
run: echo "Building cloud sidecar for tag ${RELEASE_TAG}"
run: |
echo "Building cloud sidecar for tag ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:

View File

@@ -16,7 +16,8 @@ jobs:
RELEASE_TAG: "${{ inputs.tag }}"
steps:
- name: Show tag
run: echo "Building for tag: ${RELEASE_TAG}"
run: |
echo "Building for tag: ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:
@@ -39,26 +40,17 @@ jobs:
sudo apt-get update
sudo apt-get install -y portaudio19-dev
- name: Build sidecar (CUDA)
run: |
uv sync --frozen || uv sync
uv run pyinstaller local-transcription-headless.spec
- name: Package sidecar (CUDA)
run: |
cd dist/local-transcription-backend && zip -r ../../sidecar-linux-x86_64-cuda.zip .
- name: Build sidecar (CPU)
run: |
rm -rf dist/local-transcription-backend build/
uv sync --no-sources
# PyPI's default torch on Linux includes CUDA (~800MB).
# Replace with CPU-only torch from the dedicated index.
uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --force-reinstall
# Run pyinstaller directly from venv to prevent uv run from
# re-resolving torch back to the CUDA version via pyproject.toml sources
.venv/bin/pyinstaller local-transcription-headless.spec
- name: Package sidecar (CPU)
run: |
cd dist/local-transcription-backend && zip -r ../../sidecar-linux-x86_64-cpu.zip .
cd dist/local-transcription-backend && zip -9 -r ../../sidecar-linux-x86_64-cpu.zip .
- name: Upload to sidecar release
env:

View File

@@ -16,7 +16,8 @@ jobs:
RELEASE_TAG: "${{ inputs.tag }}"
steps:
- name: Show tag
run: echo "Building for tag: ${RELEASE_TAG}"
run: |
echo "Building for tag: ${RELEASE_TAG}"
- uses: actions/checkout@v4
with:

View File

@@ -54,29 +54,19 @@ jobs:
choco install 7zip -y
}
- name: Build sidecar (CUDA)
shell: powershell
run: |
uv sync --frozen
if ($LASTEXITCODE -ne 0) { uv sync }
uv run pyinstaller local-transcription-headless.spec
- name: Package sidecar (CUDA)
shell: powershell
run: |
7z a -tzip -mx=5 sidecar-windows-x86_64-cuda.zip .\dist\local-transcription-backend\*
- name: Build sidecar (CPU)
shell: powershell
run: |
Remove-Item -Recurse -Force dist\local-transcription-backend, build -ErrorAction SilentlyContinue
$env:UV_NO_SOURCES = "1"
uv sync
# PyPI's default torch includes CUDA. Replace with CPU-only.
uv pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu --force-reinstall
.venv\Scripts\pyinstaller.exe local-transcription-headless.spec
- name: Package sidecar (CPU)
shell: powershell
run: |
7z a -tzip -mx=5 sidecar-windows-x86_64-cpu.zip .\dist\local-transcription-backend\*
7z a -tzip -mx=9 sidecar-windows-x86_64-cpu.zip .\dist\local-transcription-backend\*
- name: Upload to sidecar release
shell: powershell

View File

@@ -0,0 +1,102 @@
name: Cleanup Old Releases
on:
workflow_dispatch:
inputs:
keep_app_releases:
description: 'Number of app releases to keep'
required: false
default: '3'
keep_sidecar_releases:
description: 'Number of sidecar releases to keep'
required: false
default: '2'
dry_run:
description: 'Dry run (show what would be deleted without deleting)'
required: false
default: 'true'
jobs:
cleanup:
name: Cleanup Old Releases
runs-on: ubuntu-latest
steps:
- name: Cleanup releases
env:
BUILD_TOKEN: ${{ secrets.BUILD_TOKEN }}
run: |
REPO_API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
KEEP_APP="${{ inputs.keep_app_releases }}"
KEEP_SIDECAR="${{ inputs.keep_sidecar_releases }}"
DRY_RUN="${{ inputs.dry_run }}"
echo "=== Cleanup Configuration ==="
echo "Keep app releases: ${KEEP_APP}"
echo "Keep sidecar releases: ${KEEP_SIDECAR}"
echo "Dry run: ${DRY_RUN}"
echo ""
# Fetch all releases
ALL_RELEASES=$(curl -s -H "Authorization: token ${BUILD_TOKEN}" \
"${REPO_API}/releases?limit=50")
# ── App releases (v* tags, not sidecar-v*) ──
echo "=== App Releases ==="
APP_RELEASES=$(echo "$ALL_RELEASES" | jq -c '[.[] | select(.tag_name | startswith("v")) | select(.tag_name | startswith("sidecar") | not)]')
APP_TOTAL=$(echo "$APP_RELEASES" | jq 'length')
echo "Found ${APP_TOTAL} app releases, keeping ${KEEP_APP}"
if [ "$APP_TOTAL" -gt "$KEEP_APP" ]; then
echo "$APP_RELEASES" | jq -c ".[$KEEP_APP:][]" | while read -r release; do
ID=$(echo "$release" | jq -r '.id')
TAG=$(echo "$release" | jq -r '.tag_name')
SIZE=$(echo "$release" | jq '[.assets[]?.size // 0] | add // 0')
SIZE_MB=$(echo "scale=1; $SIZE / 1048576" | bc 2>/dev/null || echo "?")
# Protect v1.4.0 (last pre-Tauri release)
if [ "$TAG" = "v1.4.0" ]; then
echo " PROTECT ${TAG} (${SIZE_MB} MB)"
continue
fi
if [ "$DRY_RUN" = "true" ]; then
echo " WOULD DELETE ${TAG} (ID: ${ID}, ${SIZE_MB} MB)"
else
echo " DELETING ${TAG} (ID: ${ID}, ${SIZE_MB} MB)..."
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
"${REPO_API}/releases/${ID}"
fi
done
else
echo " Nothing to clean up"
fi
echo ""
# ── Sidecar releases (sidecar-v* tags) ──
echo "=== Sidecar Releases ==="
SIDECAR_RELEASES=$(echo "$ALL_RELEASES" | jq -c '[.[] | select(.tag_name | startswith("sidecar-v"))]')
SIDECAR_TOTAL=$(echo "$SIDECAR_RELEASES" | jq 'length')
echo "Found ${SIDECAR_TOTAL} sidecar releases, keeping ${KEEP_SIDECAR}"
if [ "$SIDECAR_TOTAL" -gt "$KEEP_SIDECAR" ]; then
echo "$SIDECAR_RELEASES" | jq -c ".[$KEEP_SIDECAR:][]" | while read -r release; do
ID=$(echo "$release" | jq -r '.id')
TAG=$(echo "$release" | jq -r '.tag_name')
SIZE=$(echo "$release" | jq '[.assets[]?.size // 0] | add // 0')
SIZE_MB=$(echo "scale=1; $SIZE / 1048576" | bc 2>/dev/null || echo "?")
if [ "$DRY_RUN" = "true" ]; then
echo " WOULD DELETE ${TAG} (ID: ${ID}, ${SIZE_MB} MB)"
else
echo " DELETING ${TAG} (ID: ${ID}, ${SIZE_MB} MB)..."
curl -s -X DELETE -H "Authorization: token ${BUILD_TOKEN}" \
"${REPO_API}/releases/${ID}"
fi
done
else
echo " Nothing to clean up"
fi
echo ""
echo "=== Done ==="

View File

@@ -267,6 +267,15 @@ Both workflows require a `BUILD_TOKEN` secret in the repo settings (Gitea API to
## Troubleshooting
### macOS: "App is damaged and can't be opened"
macOS Gatekeeper blocks unsigned applications. Since the app is not yet signed with an Apple Developer certificate, you need to remove the quarantine flag before opening:
```bash
xattr -cr "/Applications/Local Transcription.app"
```
Then open the app normally. You only need to do this once after downloading.
### Model Loading Issues
- Models download automatically on first use to `~/.cache/huggingface/`
- First run requires internet connection

View File

@@ -106,6 +106,12 @@ class AppController:
DeviceManager = None
self.device_manager = DeviceManager() if DeviceManager else None
self.is_cloud_only = DeviceManager is None
# If this is the cloud-only sidecar and mode is still "local",
# auto-switch to "byok" so the engine doesn't try to load Whisper.
if self.is_cloud_only and self.config.get('remote.mode', 'local') == 'local':
self.config.set('remote.mode', 'byok')
# State
self._state = AppState.INITIALIZING
@@ -300,8 +306,17 @@ class AppController:
# Lazy-import heavy local transcription dependencies
global RealtimeTranscriptionEngine
if RealtimeTranscriptionEngine is None:
from client.transcription_engine_realtime import RealtimeTranscriptionEngine as _RTE
RealtimeTranscriptionEngine = _RTE
try:
from client.transcription_engine_realtime import RealtimeTranscriptionEngine as _RTE
RealtimeTranscriptionEngine = _RTE
except ImportError:
# Cloud-only sidecar -- local engine not available
self._set_state(
AppState.ERROR,
"Local transcription not available in this build. "
"Please switch to Cloud (Deepgram) mode in Settings."
)
return
if self.device_manager:
self.device_manager.set_device(device_config)
@@ -358,7 +373,15 @@ class AppController:
self._set_state(AppState.READY, f"Ready | Device: {device_display}")
else:
self._set_state(AppState.ERROR, message)
# Cloud sidecar with no API key -- show helpful setup message
# instead of a scary error. The user needs to enter their key.
if self.is_cloud_only:
self._set_state(
AppState.READY,
"Setup needed: Open Settings > Remote Transcription > enter your Deepgram API key"
)
else:
self._set_state(AppState.ERROR, message)
# ── Transcription Control ──────────────────────────────────────
@@ -373,7 +396,14 @@ class AppController:
try:
success = self.transcription_engine.start_recording()
if not success:
return False, "Failed to start recording"
import logging
# Check if there's a recent error in the logger
err_detail = getattr(self.transcription_engine, '_last_error', '')
msg = f"Failed to start recording"
if err_detail:
msg += f": {err_detail}"
print(f"ERROR: {msg}")
return False, msg
# Start server sync if enabled
if self.config.get('server_sync.enabled', False):

View File

@@ -156,17 +156,30 @@ class DeepgramTranscriptionEngine:
return True
self._stop_event.clear()
self._ws_connected = threading.Event()
self._is_recording = True
# Start the asyncio event-loop thread (handles WS send/receive)
self._thread = threading.Thread(target=self._run_event_loop, daemon=True)
self._thread.start()
# Wait for the WebSocket to connect before starting audio capture.
# Without this, audio chunks arrive before the WS is open -> broken pipe.
if not self._ws_connected.wait(timeout=15):
logger.error("Timed out waiting for Deepgram WebSocket connection")
print("ERROR: Timed out waiting for Deepgram WebSocket connection")
self._last_error = "Timed out connecting to Deepgram"
self._is_recording = False
self._stop_event.set()
return False
# Start the audio capture stream
try:
self._start_audio_stream()
except Exception as exc:
logger.error("Failed to open audio stream: %s", exc)
print(f"ERROR: Failed to open audio stream: {exc}")
self._last_error = f"Audio stream error: {exc}"
self._is_recording = False
self._stop_event.set()
return False
@@ -283,6 +296,11 @@ class DeepgramTranscriptionEngine:
if not await self._managed_handshake():
return
# Signal that the WebSocket is connected and ready
logger.info("WebSocket connected to Deepgram")
if hasattr(self, '_ws_connected'):
self._ws_connected.set()
# Run send and receive concurrently
await asyncio.gather(
self._send_loop(),

View File

@@ -1,7 +1,7 @@
{
"name": "local-transcription",
"private": true,
"version": "2.0.5",
"version": "2.0.9",
"type": "module",
"scripts": {
"dev": "vite dev",

View File

@@ -1,6 +1,6 @@
[project]
name = "local-transcription"
version = "1.0.4"
version = "1.0.8"
description = "A standalone desktop application for real-time speech-to-text transcription using Whisper models"
readme = "README.md"
requires-python = ">=3.9"

View File

@@ -1,6 +1,6 @@
[package]
name = "local-transcription"
version = "2.0.5"
version = "2.0.9"
description = "Real-time speech-to-text transcription for streamers"
authors = ["Local Transcription Contributors"]
edition = "2021"

View File

@@ -71,6 +71,17 @@ pub fn run() {
sidecar::reset_sidecar,
write_log,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app, event| {
if let tauri::RunEvent::Exit = event {
// Stop the sidecar when the app exits
if let Some(state) = app.try_state::<sidecar::ManagedSidecar>() {
if let Ok(mut mgr) = state.0.lock() {
eprintln!("[app] Stopping sidecar on exit...");
mgr.stop();
}
}
}
});
}

View File

@@ -1,6 +1,6 @@
{
"productName": "Local Transcription",
"version": "2.0.5",
"version": "2.0.9",
"identifier": "net.anhonesthost.local-transcription",
"build": {
"frontendDist": "../dist",

View File

@@ -8,17 +8,25 @@
);
let isLoading = $state(false);
let errorMessage = $state("");
async function toggleTranscription() {
if (isLoading) return;
isLoading = true;
errorMessage = "";
try {
if (isTranscribing) {
await backendStore.apiPost("/api/stop");
} else {
await backendStore.apiPost("/api/start");
}
} catch (err) {
console.error("Failed to toggle transcription:", err);
// Poll status to update UI immediately instead of waiting
// for WebSocket broadcast (which can be delayed or missed)
await backendStore.pollStatus();
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
console.error("Failed to toggle transcription:", msg);
errorMessage = msg;
} finally {
isLoading = false;
}
@@ -101,9 +109,19 @@
<button onclick={saveTranscriptions} disabled={!backendStore.connected}>
Save
</button>
{#if errorMessage}
<span class="error-msg">{errorMessage}</span>
{/if}
</div>
<style>
.error-msg {
color: #f44336;
font-size: 12px;
margin-left: 8px;
}
.controls {
display: flex;
align-items: center;

View File

@@ -36,11 +36,12 @@
try {
// Listen for progress events from the Tauri backend
unlisten = await listen<{ progress: number; message: string }>(
unlisten = await listen<{ downloaded: number; total: number; phase: string; message: string }>(
"sidecar-download-progress",
(event) => {
progress = event.payload.progress;
progressMessage = event.payload.message;
const { downloaded, total, message } = event.payload;
progress = total > 0 ? (downloaded / total) * 100 : 0;
progressMessage = message;
}
);
@@ -125,23 +126,6 @@
</div>
</label>
<label class="variant-option" class:selected={variant === "cuda"}>
<input
type="radio"
name="variant"
value="cuda"
bind:group={variant}
/>
<div class="variant-info">
<span class="variant-name">Local - GPU (NVIDIA CUDA)</span>
<span class="variant-desc">~2 GB download</span>
<span class="variant-detail">
Runs Whisper AI models locally using your NVIDIA GPU for fast
transcription. No internet needed after download. Requires an
NVIDIA GPU with CUDA support.
</span>
</div>
</label>
</div>
<button class="download-btn" onclick={startDownload}>

View File

@@ -302,6 +302,7 @@ export const backendStore = {
setPort,
connect: connectWebSocket,
disconnect,
pollStatus,
apiUrl,
apiFetch,
apiGet,

View File

@@ -1,7 +1,7 @@
"""Version information for Local Transcription."""
__version__ = "2.0.5"
__version_info__ = (2, 0, 5)
__version__ = "2.0.9"
__version_info__ = (2, 0, 9)
# Version history:
# 1.4.0 - Auto-update feature: