Compare commits
1 Commits
v1.4.4
...
feature/ta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a01dfb3fa |
@@ -365,14 +365,11 @@ jobs:
|
||||
run: brew install portaudio
|
||||
|
||||
- name: Build sidecar (CPU)
|
||||
env:
|
||||
UV_NO_SOURCES: "1"
|
||||
run: |
|
||||
# UV_NO_SOURCES bypasses pyproject.toml's [tool.uv.sources] which forces
|
||||
# torch from the CUDA index (no macOS ARM wheels there).
|
||||
# Applies to both uv sync AND uv run (which re-resolves).
|
||||
# Default PyPI torch includes MPS (Apple Silicon GPU) support.
|
||||
uv sync
|
||||
# --no-sources bypasses pyproject.toml's [tool.uv.sources] which forces
|
||||
# torch from the CUDA index (no macOS ARM wheels there)
|
||||
# Default PyPI torch includes MPS (Apple Silicon GPU) support
|
||||
uv sync --no-sources
|
||||
uv run pyinstaller local-transcription-headless.spec
|
||||
|
||||
- name: Package sidecar (CPU)
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -63,6 +63,3 @@ dist/
|
||||
|
||||
# Tauri
|
||||
src-tauri/target/
|
||||
|
||||
# Windows NTFS alternate data streams
|
||||
*:Zone.Identifier
|
||||
|
||||
BIN
2025-live-transcription-research.md:Zone.Identifier
Normal file
BIN
2025-live-transcription-research.md:Zone.Identifier
Normal file
Binary file not shown.
BIN
DEEPGRAM_PROXY_PLAN.md:Zone.Identifier
Normal file
BIN
DEEPGRAM_PROXY_PLAN.md:Zone.Identifier
Normal file
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "local-transcription",
|
||||
"private": true,
|
||||
"version": "1.4.4",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "local-transcription"
|
||||
version = "1.0.2"
|
||||
version = "1.0.0"
|
||||
description = "A standalone desktop application for real-time speech-to-text transcription using Whisper models"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
494
src-tauri/Cargo.lock
generated
494
src-tauri/Cargo.lock
generated
@@ -47,15 +47,6 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atk"
|
||||
version = "0.18.2"
|
||||
@@ -347,16 +338,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
@@ -380,9 +361,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation",
|
||||
"core-graphics-types",
|
||||
"foreign-types 0.5.0",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -393,7 +374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -534,17 +515,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
@@ -822,15 +792,6 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
@@ -838,7 +799,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||
dependencies = [
|
||||
"foreign-types-macros",
|
||||
"foreign-types-shared 0.3.1",
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -852,12 +813,6 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.3.1"
|
||||
@@ -1267,25 +1222,6 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.13.1",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1396,7 +1332,6 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@@ -1407,38 +1342,6 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.20"
|
||||
@@ -1457,11 +1360,9 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1865,12 +1766,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
@@ -1879,11 +1774,8 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
|
||||
[[package]]
|
||||
name = "local-transcription"
|
||||
version = "1.4.3"
|
||||
version = "1.4.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -1891,8 +1783,6 @@ dependencies = [
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-process",
|
||||
"tauri-plugin-shell",
|
||||
"tokio",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2021,23 +1911,6 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
@@ -2259,50 +2132,6 @@ dependencies = [
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
@@ -2898,49 +2727,6 @@ version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams 0.4.2",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.2"
|
||||
@@ -2971,7 +2757,7 @@ dependencies = [
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams 0.5.0",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
@@ -2999,20 +2785,6 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.17",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
@@ -3028,64 +2800,12 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -3095,15 +2815,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
@@ -3161,29 +2872,6 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.24.0"
|
||||
@@ -3326,18 +3014,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.18.0"
|
||||
@@ -3618,12 +3294,6 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
@@ -3677,27 +3347,6 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
@@ -3719,7 +3368,7 @@ checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"block2",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dispatch2",
|
||||
@@ -3796,7 +3445,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
"reqwest 0.13.2",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@@ -4070,19 +3719,6 @@ dependencies = [
|
||||
"toml 0.9.12+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.4.3"
|
||||
@@ -4194,45 +3830,11 @@ dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
@@ -4514,12 +4116,6 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.8"
|
||||
@@ -4569,12 +4165,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.1"
|
||||
@@ -4733,19 +4323,6 @@ dependencies = [
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-streams"
|
||||
version = "0.5.0"
|
||||
@@ -5022,17 +4599,6 @@ dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
@@ -5078,15 +4644,6 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -5575,12 +5132,6 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
@@ -5614,37 +5165,8 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "2.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"indexmap 2.13.1",
|
||||
"memchr",
|
||||
"thiserror 2.0.18",
|
||||
"zopfli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "local-transcription"
|
||||
version = "1.4.4"
|
||||
version = "1.4.0"
|
||||
description = "Real-time speech-to-text transcription for streamers"
|
||||
authors = ["Local Transcription Contributors"]
|
||||
edition = "2021"
|
||||
@@ -19,8 +19,3 @@ tauri-plugin-dialog = "2"
|
||||
tauri-plugin-process = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
reqwest = { version = "0.12", features = ["json", "stream"] }
|
||||
futures-util = "0.3"
|
||||
zip = { version = "2", default-features = false, features = ["deflate"] }
|
||||
bytes = "1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 41 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 739 B After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 52 KiB |
@@ -1,41 +1,9 @@
|
||||
mod sidecar;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use tauri::Manager;
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.manage(sidecar::ManagedSidecar(Mutex::new(
|
||||
sidecar::SidecarManager::new(),
|
||||
)))
|
||||
.setup(|app| {
|
||||
let resource_dir = app
|
||||
.path()
|
||||
.resource_dir()
|
||||
.expect("failed to resolve resource dir");
|
||||
let data_dir = app
|
||||
.path()
|
||||
.app_data_dir()
|
||||
.expect("failed to resolve app data dir");
|
||||
|
||||
// Ensure the data directory exists
|
||||
std::fs::create_dir_all(&data_dir).expect("failed to create app data dir");
|
||||
|
||||
sidecar::init_dirs(resource_dir, data_dir);
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
sidecar::check_sidecar,
|
||||
sidecar::download_sidecar,
|
||||
sidecar::check_sidecar_update,
|
||||
sidecar::get_sidecar_port,
|
||||
sidecar::start_sidecar,
|
||||
sidecar::stop_sidecar,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -1,580 +0,0 @@
|
||||
use std::io::BufRead;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
const REPO_API: &str =
|
||||
"https://repo.anhonesthost.net/api/v1/repos/streamer-tools/local-transcription";
|
||||
|
||||
const BINARY_NAME: &str = if cfg!(windows) {
|
||||
"local-transcription-backend.exe"
|
||||
} else {
|
||||
"local-transcription-backend"
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Directory state (initialised once during Tauri setup)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static DIRS: std::sync::OnceLock<SidecarDirs> = std::sync::OnceLock::new();
|
||||
|
||||
struct SidecarDirs {
|
||||
#[allow(dead_code)]
|
||||
resource_dir: PathBuf,
|
||||
data_dir: PathBuf,
|
||||
}
|
||||
|
||||
/// Called from Tauri `setup` to persist the resource / data directories.
|
||||
pub fn init_dirs(resource_dir: PathBuf, data_dir: PathBuf) {
|
||||
let _ = DIRS.set(SidecarDirs {
|
||||
resource_dir,
|
||||
data_dir,
|
||||
});
|
||||
}
|
||||
|
||||
fn data_dir() -> &'static PathBuf {
|
||||
&DIRS.get().expect("sidecar::init_dirs not called").data_dir
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Version helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn version_file() -> PathBuf {
|
||||
data_dir().join("sidecar-version.txt")
|
||||
}
|
||||
|
||||
fn read_installed_version() -> Option<String> {
|
||||
std::fs::read_to_string(version_file())
|
||||
.ok()
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
fn sidecar_dir_for_version(version: &str) -> PathBuf {
|
||||
data_dir().join(format!("sidecar-{version}"))
|
||||
}
|
||||
|
||||
fn binary_path_for_version(version: &str) -> PathBuf {
|
||||
sidecar_dir_for_version(version).join(BINARY_NAME)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Gitea API types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GiteaRelease {
|
||||
tag_name: String,
|
||||
assets: Vec<GiteaAsset>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct GiteaAsset {
|
||||
name: String,
|
||||
browser_download_url: String,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform / arch detection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn platform_token() -> &'static str {
|
||||
if cfg!(target_os = "windows") {
|
||||
"windows"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"macos"
|
||||
} else {
|
||||
"linux"
|
||||
}
|
||||
}
|
||||
|
||||
fn arch_token() -> &'static str {
|
||||
if cfg!(target_arch = "aarch64") {
|
||||
"aarch64"
|
||||
} else {
|
||||
"x86_64"
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the expected asset prefix, e.g. `sidecar-linux-x86_64-cuda`.
|
||||
fn asset_prefix(variant: &str) -> String {
|
||||
format!("sidecar-{}-{}-{}", platform_token(), arch_token(), variant)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tauri commands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Returns `true` when a sidecar binary is installed and the file exists.
|
||||
#[tauri::command]
|
||||
pub fn check_sidecar() -> bool {
|
||||
if let Some(version) = read_installed_version() {
|
||||
binary_path_for_version(&version).exists()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Download progress payload emitted via `sidecar-download-progress`.
|
||||
#[derive(Clone, Serialize)]
|
||||
struct DownloadProgress {
|
||||
downloaded: u64,
|
||||
total: u64,
|
||||
phase: String, // "downloading" | "extracting" | "done" | "error"
|
||||
message: String,
|
||||
}
|
||||
|
||||
/// Download & install the latest sidecar release.
|
||||
///
|
||||
/// `variant` is typically `"cuda"` or `"cpu"`.
|
||||
#[tauri::command]
|
||||
pub async fn download_sidecar(app: AppHandle, variant: String) -> Result<String, String> {
|
||||
use futures_util::StreamExt;
|
||||
|
||||
let emit = |progress: DownloadProgress| {
|
||||
let _ = app.emit("sidecar-download-progress", progress);
|
||||
};
|
||||
|
||||
// 1. Fetch releases from Gitea (filter to sidecar-v* tags) ---------------
|
||||
emit(DownloadProgress {
|
||||
downloaded: 0,
|
||||
total: 0,
|
||||
phase: "downloading".into(),
|
||||
message: "Fetching release info...".into(),
|
||||
});
|
||||
|
||||
let releases_url = format!("{REPO_API}/releases?limit=20");
|
||||
let client = reqwest::Client::new();
|
||||
let releases: Vec<GiteaRelease> = client
|
||||
.get(&releases_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch releases: {e}"))?
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse releases: {e}"))?;
|
||||
|
||||
// Find the latest release whose tag starts with `sidecar-v`
|
||||
let release = releases
|
||||
.into_iter()
|
||||
.find(|r| r.tag_name.starts_with("sidecar-v"))
|
||||
.ok_or_else(|| "No sidecar release found".to_string())?;
|
||||
|
||||
let version = release.tag_name.clone(); // e.g. "sidecar-v1.0.2"
|
||||
|
||||
// 2. Find matching asset ----------------------------------------------------
|
||||
let prefix = asset_prefix(&variant);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|a| a.name.starts_with(&prefix) && a.name.ends_with(".zip"))
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"No asset matching '{}' in release {}. Available: {}",
|
||||
prefix,
|
||||
version,
|
||||
release
|
||||
.assets
|
||||
.iter()
|
||||
.map(|a| a.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
})?;
|
||||
|
||||
let total_size = asset.size;
|
||||
let download_url = asset.browser_download_url.clone();
|
||||
|
||||
// 3. Stream download ---------------------------------------------------------
|
||||
emit(DownloadProgress {
|
||||
downloaded: 0,
|
||||
total: total_size,
|
||||
phase: "downloading".into(),
|
||||
message: format!("Downloading {}...", asset.name),
|
||||
});
|
||||
|
||||
let response = client
|
||||
.get(&download_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Download request failed: {e}"))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Download failed with status {}", response.status()));
|
||||
}
|
||||
|
||||
let tmp_zip = data_dir().join("_sidecar_download.zip");
|
||||
let mut file = tokio::fs::File::create(&tmp_zip)
|
||||
.await
|
||||
.map_err(|e| format!("Cannot create temp file: {e}"))?;
|
||||
|
||||
let mut stream = response.bytes_stream();
|
||||
let mut downloaded: u64 = 0;
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk.map_err(|e| format!("Download stream error: {e}"))?;
|
||||
file.write_all(&chunk)
|
||||
.await
|
||||
.map_err(|e| format!("Write error: {e}"))?;
|
||||
downloaded += chunk.len() as u64;
|
||||
|
||||
emit(DownloadProgress {
|
||||
downloaded,
|
||||
total: total_size,
|
||||
phase: "downloading".into(),
|
||||
message: format!(
|
||||
"Downloading... {:.1} / {:.1} MB",
|
||||
downloaded as f64 / 1_048_576.0,
|
||||
total_size as f64 / 1_048_576.0
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
file.flush()
|
||||
.await
|
||||
.map_err(|e| format!("Flush error: {e}"))?;
|
||||
drop(file);
|
||||
|
||||
// 4. Extract zip -------------------------------------------------------------
|
||||
emit(DownloadProgress {
|
||||
downloaded,
|
||||
total: total_size,
|
||||
phase: "extracting".into(),
|
||||
message: "Extracting sidecar...".into(),
|
||||
});
|
||||
|
||||
let dest_dir = sidecar_dir_for_version(&version);
|
||||
if dest_dir.exists() {
|
||||
std::fs::remove_dir_all(&dest_dir)
|
||||
.map_err(|e| format!("Cannot clean old dir: {e}"))?;
|
||||
}
|
||||
std::fs::create_dir_all(&dest_dir)
|
||||
.map_err(|e| format!("Cannot create sidecar dir: {e}"))?;
|
||||
|
||||
// Extraction is blocking I/O -- offload to a spawn_blocking thread.
|
||||
let zip_path = tmp_zip.clone();
|
||||
let dest = dest_dir.clone();
|
||||
tokio::task::spawn_blocking(move || extract_zip(&zip_path, &dest))
|
||||
.await
|
||||
.map_err(|e| format!("Join error: {e}"))?
|
||||
.map_err(|e| format!("Extraction error: {e}"))?;
|
||||
|
||||
// Remove the temp zip
|
||||
let _ = std::fs::remove_file(&tmp_zip);
|
||||
|
||||
// 5. Set executable permissions on Unix -------------------------------------
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let bin = dest_dir.join(BINARY_NAME);
|
||||
if bin.exists() {
|
||||
let mut perms = std::fs::metadata(&bin)
|
||||
.map_err(|e| format!("metadata error: {e}"))?
|
||||
.permissions();
|
||||
perms.set_mode(0o755);
|
||||
std::fs::set_permissions(&bin, perms)
|
||||
.map_err(|e| format!("chmod error: {e}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Write version file & clean up old versions ----------------------------
|
||||
std::fs::write(version_file(), &version)
|
||||
.map_err(|e| format!("Failed to write version file: {e}"))?;
|
||||
|
||||
cleanup_old_versions(&version);
|
||||
|
||||
emit(DownloadProgress {
|
||||
downloaded,
|
||||
total: total_size,
|
||||
phase: "done".into(),
|
||||
message: "Sidecar installed successfully".into(),
|
||||
});
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
/// Check if there is a newer sidecar release than the installed one.
|
||||
/// Returns `Some(tag_name)` when an update is available, or `None`.
|
||||
#[tauri::command]
|
||||
pub async fn check_sidecar_update() -> Result<Option<String>, String> {
|
||||
let installed = match read_installed_version() {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let releases_url = format!("{REPO_API}/releases?limit=20");
|
||||
let releases: Vec<GiteaRelease> = reqwest::Client::new()
|
||||
.get(&releases_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch releases: {e}"))?
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse releases: {e}"))?;
|
||||
|
||||
let latest = releases
|
||||
.iter()
|
||||
.find(|r| r.tag_name.starts_with("sidecar-v"));
|
||||
|
||||
match latest {
|
||||
Some(rel) if rel.tag_name != installed => Ok(Some(rel.tag_name.clone())),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Zip extraction helper
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn extract_zip(zip_path: &std::path::Path, dest: &std::path::Path) -> Result<(), String> {
|
||||
let file =
|
||||
std::fs::File::open(zip_path).map_err(|e| format!("Cannot open zip: {e}"))?;
|
||||
let mut archive =
|
||||
zip::ZipArchive::new(file).map_err(|e| format!("Invalid zip: {e}"))?;
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut entry = archive
|
||||
.by_index(i)
|
||||
.map_err(|e| format!("Zip entry error: {e}"))?;
|
||||
let entry_path = match entry.enclosed_name() {
|
||||
Some(p) => p.to_owned(),
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let out_path = dest.join(&entry_path);
|
||||
|
||||
if entry.is_dir() {
|
||||
std::fs::create_dir_all(&out_path)
|
||||
.map_err(|e| format!("mkdir error: {e}"))?;
|
||||
} else {
|
||||
if let Some(parent) = out_path.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.map_err(|e| format!("mkdir error: {e}"))?;
|
||||
}
|
||||
let mut outfile = std::fs::File::create(&out_path)
|
||||
.map_err(|e| format!("create file error: {e}"))?;
|
||||
std::io::copy(&mut entry, &mut outfile)
|
||||
.map_err(|e| format!("copy error: {e}"))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Cleanup old versions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn cleanup_old_versions(current_version: &str) {
|
||||
let data = data_dir();
|
||||
let current_dir_name = format!("sidecar-{current_version}");
|
||||
if let Ok(entries) = std::fs::read_dir(data) {
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
if name.starts_with("sidecar-v") // e.g. sidecar-v1.0.1
|
||||
&& name != current_dir_name
|
||||
&& entry.path().is_dir()
|
||||
{
|
||||
let _ = std::fs::remove_dir_all(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SidecarManager — launch / stop / query the backend process
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct ReadyEvent {
|
||||
event: String,
|
||||
port: u16,
|
||||
}
|
||||
|
||||
pub struct SidecarManager {
|
||||
child: Option<std::process::Child>,
|
||||
port: Option<u16>,
|
||||
}
|
||||
|
||||
impl SidecarManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
child: None,
|
||||
port: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` when the child process is still alive.
|
||||
pub fn is_running(&mut self) -> bool {
|
||||
match &mut self.child {
|
||||
Some(child) => match child.try_wait() {
|
||||
Ok(Some(_)) => {
|
||||
// Process has exited
|
||||
self.child = None;
|
||||
self.port = None;
|
||||
false
|
||||
}
|
||||
Ok(None) => true,
|
||||
Err(_) => false,
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the sidecar if it is not already running. Returns the port.
|
||||
pub fn ensure_running(&mut self) -> Result<u16, String> {
|
||||
if self.is_running() {
|
||||
return self
|
||||
.port
|
||||
.ok_or_else(|| "Sidecar running but port unknown".into());
|
||||
}
|
||||
|
||||
let is_dev = cfg!(debug_assertions)
|
||||
|| std::env::var("LOCAL_TRANSCRIPTION_DEV")
|
||||
.map(|v| v == "1")
|
||||
.unwrap_or(false);
|
||||
|
||||
let mut cmd = if is_dev {
|
||||
self.build_dev_command()?
|
||||
} else {
|
||||
self.build_prod_command()?
|
||||
};
|
||||
|
||||
// Hide the console window on Windows in release mode.
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
cmd.stdout(std::process::Stdio::piped());
|
||||
cmd.stderr(std::process::Stdio::piped());
|
||||
|
||||
let mut child = cmd.spawn().map_err(|e| format!("Failed to spawn sidecar: {e}"))?;
|
||||
|
||||
// Wait for the `{"event":"ready","port":...}` line on stdout.
|
||||
let stdout = child
|
||||
.stdout
|
||||
.take()
|
||||
.ok_or("Failed to capture sidecar stdout")?;
|
||||
|
||||
let port = Self::wait_for_ready(stdout)?;
|
||||
|
||||
self.child = Some(child);
|
||||
self.port = Some(port);
|
||||
Ok(port)
|
||||
}
|
||||
|
||||
/// Stop the sidecar process if running.
|
||||
pub fn stop(&mut self) {
|
||||
if let Some(mut child) = self.child.take() {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
}
|
||||
self.port = None;
|
||||
}
|
||||
|
||||
/// Return the port the sidecar is listening on, if known.
|
||||
pub fn port(&self) -> Option<u16> {
|
||||
self.port
|
||||
}
|
||||
|
||||
// -- private helpers -------------------------------------------------------
|
||||
|
||||
fn build_dev_command(&self) -> Result<std::process::Command, String> {
|
||||
let mut cmd = std::process::Command::new("python");
|
||||
cmd.args(["-m", "backend.main_headless"]);
|
||||
|
||||
// Try to find the project root (parent of src-tauri)
|
||||
if let Some(dirs) = DIRS.get() {
|
||||
let project_root = dirs
|
||||
.resource_dir
|
||||
.parent() // src-tauri
|
||||
.and_then(|p| p.parent()); // project root
|
||||
if let Some(root) = project_root {
|
||||
cmd.current_dir(root);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
fn build_prod_command(&self) -> Result<std::process::Command, String> {
|
||||
let version = read_installed_version()
|
||||
.ok_or("No sidecar version installed")?;
|
||||
let bin = binary_path_for_version(&version);
|
||||
if !bin.exists() {
|
||||
return Err(format!("Sidecar binary not found at {}", bin.display()));
|
||||
}
|
||||
let mut cmd = std::process::Command::new(&bin);
|
||||
cmd.current_dir(
|
||||
bin.parent()
|
||||
.ok_or("Cannot determine sidecar parent dir")?,
|
||||
);
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
fn wait_for_ready(stdout: std::process::ChildStdout) -> Result<u16, String> {
|
||||
let reader = std::io::BufReader::new(stdout);
|
||||
let timeout = std::time::Duration::from_secs(120);
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
for line in reader.lines() {
|
||||
if start.elapsed() > timeout {
|
||||
return Err("Timed out waiting for sidecar ready event".into());
|
||||
}
|
||||
let line = line.map_err(|e| format!("IO error reading stdout: {e}"))?;
|
||||
if let Ok(evt) = serde_json::from_str::<ReadyEvent>(&line) {
|
||||
if evt.event == "ready" {
|
||||
return Ok(evt.port);
|
||||
}
|
||||
}
|
||||
// Ignore other lines (e.g. log output)
|
||||
}
|
||||
Err("Sidecar process exited before sending ready event".into())
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tauri-managed SidecarManager state & commands
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Wrapper so we can store `SidecarManager` in Tauri's managed state.
|
||||
pub struct ManagedSidecar(pub Mutex<SidecarManager>);
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_sidecar_port(state: tauri::State<'_, ManagedSidecar>) -> Result<Option<u16>, String> {
|
||||
let mut mgr = state
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|e| format!("Lock error: {e}"))?;
|
||||
// Refresh running status before returning port
|
||||
if !mgr.is_running() {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(mgr.port())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn start_sidecar(state: tauri::State<'_, ManagedSidecar>) -> Result<u16, String> {
|
||||
let mut mgr = state
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|e| format!("Lock error: {e}"))?;
|
||||
mgr.ensure_running()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn stop_sidecar(state: tauri::State<'_, ManagedSidecar>) -> Result<(), String> {
|
||||
let mut mgr = state
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|e| format!("Lock error: {e}"))?;
|
||||
mgr.stop();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"productName": "Local Transcription",
|
||||
"version": "1.4.4",
|
||||
"identifier": "net.anhonesthost.local-transcription",
|
||||
"version": "1.4.0",
|
||||
"identifier": "com.localtranscription.app",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
"devUrl": "http://localhost:1420",
|
||||
@@ -30,7 +30,6 @@
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico",
|
||||
"icons/icon.png"
|
||||
]
|
||||
|
||||
198
src/App.svelte
198
src/App.svelte
@@ -5,19 +5,13 @@
|
||||
import Controls from "$lib/components/Controls.svelte";
|
||||
import TranscriptionDisplay from "$lib/components/TranscriptionDisplay.svelte";
|
||||
import Settings from "$lib/components/Settings.svelte";
|
||||
import SidecarSetup from "$lib/components/SidecarSetup.svelte";
|
||||
import { backendStore } from "$lib/stores/backend";
|
||||
import { configStore } from "$lib/stores/config";
|
||||
|
||||
type SidecarState = "checking" | "needs_setup" | "starting" | "connected";
|
||||
|
||||
let showSettings = $state(false);
|
||||
let sidecarState = $state<SidecarState>("checking");
|
||||
|
||||
let obsDisplayUrl = $derived(backendStore.obsUrl);
|
||||
let syncDisplayUrl = $derived(backendStore.syncUrl);
|
||||
let isConnected = $derived(backendStore.connectionState === "connected");
|
||||
let connectionState = $derived(backendStore.connectionState);
|
||||
|
||||
function openSettings() {
|
||||
showSettings = true;
|
||||
@@ -27,55 +21,9 @@
|
||||
showSettings = false;
|
||||
}
|
||||
|
||||
async function checkAndLaunchSidecar() {
|
||||
try {
|
||||
const { invoke } = await import("@tauri-apps/api/core");
|
||||
|
||||
// Check if sidecar is installed
|
||||
sidecarState = "checking";
|
||||
const installed = await invoke<boolean>("check_sidecar");
|
||||
|
||||
if (!installed) {
|
||||
sidecarState = "needs_setup";
|
||||
return;
|
||||
}
|
||||
|
||||
await launchSidecar();
|
||||
} catch {
|
||||
// Not running in Tauri (browser dev mode) - skip sidecar check
|
||||
// and connect directly to localhost:8081
|
||||
sidecarState = "starting";
|
||||
backendStore.setPort(8081);
|
||||
backendStore.connect();
|
||||
configStore.loadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
async function launchSidecar() {
|
||||
try {
|
||||
const { invoke } = await import("@tauri-apps/api/core");
|
||||
|
||||
sidecarState = "starting";
|
||||
await invoke("start_sidecar");
|
||||
|
||||
const port = await invoke<number>("get_sidecar_port");
|
||||
backendStore.setPort(port);
|
||||
backendStore.connect();
|
||||
configStore.loadConfig();
|
||||
} catch {
|
||||
// If sidecar launch fails, still try connecting to default port
|
||||
sidecarState = "starting";
|
||||
backendStore.connect();
|
||||
configStore.loadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
async function onSidecarReady() {
|
||||
await launchSidecar();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
checkAndLaunchSidecar();
|
||||
backendStore.connect();
|
||||
configStore.loadConfig();
|
||||
|
||||
return () => {
|
||||
backendStore.disconnect();
|
||||
@@ -83,135 +31,33 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if sidecarState === "checking"}
|
||||
<div class="connecting-overlay">
|
||||
<div class="connecting-content">
|
||||
<div class="connecting-icon">
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<h2>Local Transcription</h2>
|
||||
<p>Checking setup...</p>
|
||||
</div>
|
||||
<div class="app-shell">
|
||||
<Header onSettingsClick={openSettings} />
|
||||
<StatusBar />
|
||||
|
||||
<div class="display-links">
|
||||
<span class="link-label">OBS:</span>
|
||||
<a href={obsDisplayUrl} target="_blank" rel="noopener">{obsDisplayUrl}</a>
|
||||
{#if syncDisplayUrl}
|
||||
<span class="link-separator">|</span>
|
||||
<span class="link-label">Sync:</span>
|
||||
<a href={syncDisplayUrl} target="_blank" rel="noopener"
|
||||
>{syncDisplayUrl}</a
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{:else if sidecarState === "needs_setup"}
|
||||
<SidecarSetup onComplete={onSidecarReady} />
|
||||
<TranscriptionDisplay />
|
||||
<Controls />
|
||||
|
||||
{:else if !isConnected}
|
||||
<div class="connecting-overlay">
|
||||
<div class="connecting-content">
|
||||
<div class="connecting-icon">
|
||||
{#if connectionState === "error"}
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#e74c3c" 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>
|
||||
{:else}
|
||||
<div class="spinner"></div>
|
||||
{/if}
|
||||
</div>
|
||||
<h2>Local Transcription</h2>
|
||||
{#if connectionState === "error"}
|
||||
<p>Cannot connect to backend</p>
|
||||
<p class="hint">Make sure the Python backend is running:<br>
|
||||
<code>uv run python -m backend.main_headless</code></p>
|
||||
{:else}
|
||||
<p>Connecting to backend...</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="version-label">v{backendStore.version}</div>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
<div class="app-shell">
|
||||
<Header onSettingsClick={openSettings} />
|
||||
<StatusBar />
|
||||
|
||||
<div class="display-links">
|
||||
<span class="link-label">OBS:</span>
|
||||
<a href={obsDisplayUrl} target="_blank" rel="noopener">{obsDisplayUrl}</a>
|
||||
{#if syncDisplayUrl}
|
||||
<span class="link-separator">|</span>
|
||||
<span class="link-label">Sync:</span>
|
||||
<a href={syncDisplayUrl} target="_blank" rel="noopener"
|
||||
>{syncDisplayUrl}</a
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<TranscriptionDisplay />
|
||||
<Controls />
|
||||
|
||||
<div class="version-label">v{backendStore.version}</div>
|
||||
</div>
|
||||
|
||||
{#if showSettings}
|
||||
<Settings onClose={closeSettings} />
|
||||
{/if}
|
||||
{#if showSettings}
|
||||
<Settings onClose={closeSettings} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.connecting-overlay {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
.connecting-content {
|
||||
text-align: center;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.connecting-content h2 {
|
||||
margin: 16px 0 8px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.connecting-content p {
|
||||
margin: 4px 0;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.connecting-content .hint {
|
||||
margin-top: 16px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.connecting-content code {
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
padding: 4px 8px;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.connecting-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--border-color);
|
||||
border-top-color: var(--accent-color, #4CAF50);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,384 +0,0 @@
|
||||
<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<{ progress: number; message: string }>(
|
||||
"sidecar-download-progress",
|
||||
(event) => {
|
||||
progress = event.payload.progress;
|
||||
progressMessage = event.payload.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">
|
||||
The app needs to download its transcription engine before you can start.
|
||||
Choose the version that best fits your hardware.
|
||||
</p>
|
||||
|
||||
<div class="variant-options">
|
||||
<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">Standard (CPU)</span>
|
||||
<span class="variant-desc">Works on all computers (~500 MB download)</span>
|
||||
</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">GPU Accelerated (CUDA)</span>
|
||||
<span class="variant-desc">Faster transcription with NVIDIA GPU (~2 GB download)</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;
|
||||
}
|
||||
|
||||
.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>
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Version information for Local Transcription."""
|
||||
|
||||
__version__ = "1.4.4"
|
||||
__version_info__ = (1, 4, 4)
|
||||
__version__ = "1.4.0"
|
||||
__version_info__ = (1, 4, 0)
|
||||
|
||||
# Version history:
|
||||
# 1.4.0 - Auto-update feature:
|
||||
|
||||
Reference in New Issue
Block a user