feat(setup): faster downloads by default + prominent, encouraged HF-token entry - #669
Conversation
…oken entry
Two changes that make first-run downloads faster and easier to speed up further.
1. Segmented (multi-connection) downloader is now ON by default. The app forces
the legacy-LFS path (HF_HUB_DISABLE_XET=1) for clear progress, but that path
is single-stream and slow — which is why downloads felt sluggish. The built-in
IDM/uGet-style segmented accelerator (parallel byte-ranges, live speed/ETA)
was already implemented but defaulted OFF. Flip it ON: it only engages when
Xet is inactive (the default), and ANY failure falls back to snapshot_download
("can never compromise a correct install"). Pure-httpx, cross-platform,
auth-safe (token never forwarded to a CDN). Override with
OMNIVOICE_SEGMENTED_DOWNLOAD=0.
2. The Hugging Face token field is now a prominent, always-visible card right
above Continue — was a collapsed "advanced" fold almost nobody opened. A free
token gives authenticated downloads (higher rate limits, fewer stalls), so it
pairs with change #1 to keep the parallel fetch from getting throttled. The
card leads with the speed benefit, shows a saved-state, and adds a one-click
"Get one free →" link to huggingface.co/settings/tokens.
Docs: downloading-models.md updated — the legacy-LFS section now documents the
default-on segmented accelerator + the HF-token speed tip, and the tuning table
reflects OMNIVOICE_SEGMENTED_DOWNLOAD=0 as the disable knob (docs-sync).
Test: test_segmented_download_default.py pins the new default ON and that the
env override still disables it; existing FDL-08 behavior tests stay green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughFlips the segmented (multi-connection) model downloader from opt-in to enabled by default, updates the backend preference resolver default, install-loop comment, and docs accordingly, and adds tests for the new default. Separately, redesigns the Hugging Face token UI in the setup wizard from a collapsed ChangesSegmented Downloader Default Flip
HF Token Card UI Redesign
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5 | ❌ 4❌ Failed checks (4 warnings)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Filename | Overview |
|---|---|
| backend/api/routers/setup/download.py | Default flipped from False→True for the segmented downloader; call site comment updated. The download loop is unchanged; fallback to snapshot_download is intact. The private _create_symlink import is now on the hot path for every install. |
| frontend/src/components/WizardLibrary.jsx | Replaces collapsed DetailsHF-token fold with a prominent always-visible card; adds openExternal import and browser navigation to HF token settings page. Logic for saved/error states is correct; unhandled async rejection is a minor concern. |
| frontend/src/pages/SetupWizard.css | Removes the old quiet-disclosure CSS; adds hfcard block with border, background, and saved-state green variant using color-mix. Straightforward styling, no issues. |
| frontend/src/i18n/locales/en.json | Adds hf_token_card_title, hf_token_get, and hf_token_saved_fast keys. The original hf_token_title key is retained but is no longer referenced by the JSX — likely orphaned unless used elsewhere in the codebase. |
| docs/downloading-models.md | Updates the tuning table env var from =1 (opt-in) to =0 (opt-out) and adds a paragraph explaining the default-on segmented accelerator. Accurately reflects the code change. |
| tests/test_segmented_download_default.py | New test file pins the default-on behavior and verifies env override in both directions. Tests are well-structured and cover the critical regression case. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[install_model POST] --> B[run _do in thread via asyncio.to_thread]
B --> C{attempt == 1 AND
_segmented_enabled AND
NOT _xet_active?}
C -- YES now default ON --> D[_segmented_snapshot]
D --> E{success?}
E -- YES --> G[_validate_snapshot_has_weights]
E -- NO --> F[log + _snapshot_path=None]
F --> H[snapshot_download fallback]
C -- NO --> H
H --> G
G --> I[install_done SSE event]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[install_model POST] --> B[run _do in thread via asyncio.to_thread]
B --> C{attempt == 1 AND
_segmented_enabled AND
NOT _xet_active?}
C -- YES now default ON --> D[_segmented_snapshot]
D --> E{success?}
E -- YES --> G[_validate_snapshot_has_weights]
E -- NO --> F[log + _snapshot_path=None]
F --> H[snapshot_download fallback]
C -- NO --> H
H --> G
G --> I[install_done SSE event]
Comments Outside Diff (1)
-
backend/api/routers/setup/download.py, line 169 (link)Private
huggingface_hubAPI now on the default-on critical path_create_symlinkis a_-prefixed internal symbol fromhuggingface_hub.file_download. Previously this only mattered for opt-in users (OMNIVOICE_SEGMENTED_DOWNLOAD=1); now it gates every first-run download. If a futurehuggingface_hubrelease renames or removes it, the import itself raisesImportError— that propagates out of_segmented_snapshot, falls through tosnapshot_download, so correctness is preserved, but users on a fresh install see a log warning and slower single-stream speeds with no indication of why.Consider guarding the import inside a
try/except ImportErrorand logging clearly, or pinning the minimumhuggingface_hubversion inrequirements.txt/pyproject.tomlto one where this symbol is known-stable.
Reviews (1): Last reviewed commit: "feat(setup): faster downloads by default..." | Re-trigger Greptile
| disabled={!hfToken.trim() || hfState === 'saving'} | ||
| onClick={saveHfToken} | ||
| className="swiz-lib__hfcard-link" | ||
| onClick={() => openExternal('https://huggingface.co/settings/tokens')} |
There was a problem hiding this comment.
New outbound browser navigation — per repo policy, flag all new external URLs
The app now navigates users to https://huggingface.co/settings/tokens via the system browser. The repo's custom rule asks that every new outbound call not covered by "GitHub Issues or HuggingFace model download" be flagged. This is a user-triggered browser open (not a programmatic HTTP request from the app), so it poses no privacy or telemetry risk, but it's a new external destination the app pushes — worth confirming it's intentional and within the privacy contract documented to users.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| <button | ||
| type="button" | ||
| className="frs-btn frs-btn--quiet" | ||
| disabled={!hfToken.trim() || hfState === 'saving'} | ||
| onClick={saveHfToken} | ||
| className="swiz-lib__hfcard-link" | ||
| onClick={() => openExternal('https://huggingface.co/settings/tokens')} | ||
| > | ||
| {hfState === 'saving' | ||
| ? t('firstrun.hf_token_saving', 'saving…') | ||
| : t('firstrun.hf_token_save', 'Save')} | ||
| {t('firstrun.hf_token_get', "Don't have a token? Get one free →")} | ||
| </button> |
There was a problem hiding this comment.
Unawaited async call in
onClick swallows Tauri-opener errors silently
openExternal is async but the onClick lambda does not await it and returns void. In the Tauri path the function catches its own errors and falls back to window.open, so the real failure scenario is rare — but if both paths throw (e.g., plugin not loaded and window.open blocked by the webview), the rejection is swallowed with no user feedback. Wrapping the call or catching in the handler avoids the silent failure.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/WizardLibrary.jsx`:
- Around line 319-321: The HF token card strings are missing from the
non-English locale files, so add firstrun.hf_token_saved_fast,
firstrun.hf_token_card_title, and firstrun.hf_token_get to each of the remaining
locale JSON files referenced by WizardLibrary.jsx, matching the existing en.json
entries and keeping the translations consistent across ar.json, de.json,
es.json, fr.json, hi.json, id.json, it.json, ja.json, ko.json, nl.json, pl.json,
pt.json, ru.json, sv.json, th.json, tr.json, uk.json, vi.json, zh-CN.json, and
zh-TW.json.
In `@frontend/src/i18n/locales/en.json`:
- Around line 1958-1963: The new HF token strings are only present in the
English locale and are missing from the other locale bundles, so add the same
keys to every locale file listed here. Update each translation file to include
hf_token_card_title, hf_token_get, and hf_token_saved_fast alongside the
existing hf_token_* entries, keeping the keys consistent across all locale
bundles.
In `@tests/test_segmented_download_default.py`:
- Around line 12-14: The test module is importing app code at module load time
and mutating sys.path globally, which can cause stale state and order-dependent
pytest behavior. Move the backend path setup and the import of
_segmented_enabled out of the module top level and into a helper or directly
into each test so the module is resolved at runtime for every test case. Use the
existing _segmented_enabled symbol as the target import location, and keep any
path adjustment scoped to the runtime import path rather than executed during
test collection.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: cda17019-529d-4fa7-8551-3214362350c9
📒 Files selected for processing (6)
backend/api/routers/setup/download.pydocs/downloading-models.mdfrontend/src/components/WizardLibrary.jsxfrontend/src/i18n/locales/en.jsonfrontend/src/pages/SetupWizard.csstests/test_segmented_download_default.py
| {hfState === 'saved' | ||
| ? `✓ ${t('firstrun.hf_token_saved_fast', 'Hugging Face token saved — downloads are now faster')}` | ||
| : t('firstrun.hf_token_card_title', 'Add a free Hugging Face token for faster downloads')} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python - <<'PY'
import json
from pathlib import Path
import sys
keys = [
"firstrun.hf_token_saved_fast",
"firstrun.hf_token_card_title",
"firstrun.hf_token_get",
]
locale_dir = Path("frontend/src/i18n/locales")
files = sorted(locale_dir.glob("*.json"))
print(f"Locale files found: {len(files)}")
if len(files) != 21:
print("WARN: expected 21 locale files.")
def has_key(obj, dotted):
cur = obj
for part in dotted.split("."):
if not isinstance(cur, dict) or part not in cur:
return False
cur = cur[part]
return True
missing = {k: [] for k in keys}
for f in files:
data = json.loads(f.read_text(encoding="utf-8"))
for k in keys:
if not has_key(data, k):
missing[k].append(str(f))
failed = False
for k, misses in missing.items():
if misses:
failed = True
print(f"\nMissing key: {k}")
for m in misses:
print(f" - {m}")
if failed:
sys.exit(1)
print("\nAll keys exist in all locale files.")
PYRepository: debpalash/OmniVoice-Studio
Length of output: 2607
Add the new HF-token strings to the remaining locale files
firstrun.hf_token_saved_fast, firstrun.hf_token_card_title, and firstrun.hf_token_get are only present in frontend/src/i18n/locales/en.json. Add them to ar.json, de.json, es.json, fr.json, hi.json, id.json, it.json, ja.json, ko.json, nl.json, pl.json, pt.json, ru.json, sv.json, th.json, tr.json, uk.json, vi.json, zh-CN.json, and zh-TW.json so the HF token card stays localized.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/components/WizardLibrary.jsx` around lines 319 - 321, The HF
token card strings are missing from the non-English locale files, so add
firstrun.hf_token_saved_fast, firstrun.hf_token_card_title, and
firstrun.hf_token_get to each of the remaining locale JSON files referenced by
WizardLibrary.jsx, matching the existing en.json entries and keeping the
translations consistent across ar.json, de.json, es.json, fr.json, hi.json,
id.json, it.json, ja.json, ko.json, nl.json, pl.json, pt.json, ru.json, sv.json,
th.json, tr.json, uk.json, vi.json, zh-CN.json, and zh-TW.json.
Sources: Coding guidelines, Path instructions
| "hf_token_card_title": "Add a free Hugging Face token for faster downloads", | ||
| "hf_token_get": "Don't have a token? Get one free →", | ||
| "hf_token_save": "Save", | ||
| "hf_token_saving": "saving…", | ||
| "hf_token_saved": "Hugging Face token saved", | ||
| "hf_token_saved_fast": "Hugging Face token saved — downloads are now faster", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python - <<'PY'
import json
from pathlib import Path
base = Path("frontend/src/i18n/locales")
target_keys = ["hf_token_card_title", "hf_token_get", "hf_token_saved_fast"]
files = sorted(base.glob("*.json"))
if not files:
raise SystemExit("No locale JSON files found under frontend/src/i18n/locales")
missing = {k: [] for k in target_keys}
for f in files:
data = json.loads(f.read_text(encoding="utf-8"))
firstrun = data.get("firstrun", {})
for k in target_keys:
if k not in firstrun:
missing[k].append(f.name)
print(f"Locale files scanned: {len(files)}")
for k in target_keys:
if missing[k]:
print(f"\n{k} missing in:")
for name in missing[k]:
print(f" - {name}")
else:
print(f"\n{k}: present in all locale files")
PYRepository: debpalash/OmniVoice-Studio
Length of output: 1019
Add the new HF token keys to every locale bundle
Lines 1958-1963 add hf_token_card_title, hf_token_get, and hf_token_saved_fast in frontend/src/i18n/locales/en.json, but all three are missing from the same 20 locale files: ar.json, de.json, es.json, fr.json, hi.json, id.json, it.json, ja.json, ko.json, nl.json, pl.json, pt.json, ru.json, sv.json, th.json, tr.json, uk.json, vi.json, zh-CN.json, zh-TW.json. This will fall back to English in those locales.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/i18n/locales/en.json` around lines 1958 - 1963, The new HF token
strings are only present in the English locale and are missing from the other
locale bundles, so add the same keys to every locale file listed here. Update
each translation file to include hf_token_card_title, hf_token_get, and
hf_token_saved_fast alongside the existing hf_token_* entries, keeping the keys
consistent across all locale bundles.
Sources: Coding guidelines, Path instructions
| sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "backend")) | ||
|
|
||
| from api.routers.setup.download import _segmented_enabled # noqa: E402 |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Move app-module import to runtime; avoid module-level path mutation in tests
Line 12 and Line 14 load app code at module import time. In pytest sessions this can create stale module state and order-dependent behavior. Import _segmented_enabled inside a helper (or inside each test) so each test resolves the module at runtime.
Proposed fix
import os
import sys
+import importlib
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "backend"))
-
-from api.routers.setup.download import _segmented_enabled # noqa: E402
+
+def _get_segmented_enabled():
+ mod = importlib.import_module("api.routers.setup.download")
+ return mod._segmented_enabled
def test_segmented_is_on_by_default(monkeypatch):
monkeypatch.delenv("OMNIVOICE_SEGMENTED_DOWNLOAD", raising=False)
- assert _segmented_enabled() is True
+ assert _get_segmented_enabled() is True
def test_env_override_can_disable(monkeypatch):
monkeypatch.setenv("OMNIVOICE_SEGMENTED_DOWNLOAD", "0")
- assert _segmented_enabled() is False
+ assert _get_segmented_enabled() is False
def test_env_override_truthy_keeps_it_on(monkeypatch):
for val in ("1", "true", "on", "yes"):
monkeypatch.setenv("OMNIVOICE_SEGMENTED_DOWNLOAD", val)
- assert _segmented_enabled() is True
+ assert _get_segmented_enabled() is TrueAs per path instructions, tests should avoid module-level imports of app modules and resolve them at run time.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "backend")) | |
| from api.routers.setup.download import _segmented_enabled # noqa: E402 | |
| import os | |
| import sys | |
| import importlib | |
| sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "backend")) | |
| def _get_segmented_enabled(): | |
| mod = importlib.import_module("api.routers.setup.download") | |
| return mod._segmented_enabled | |
| def test_segmented_is_on_by_default(monkeypatch): | |
| monkeypatch.delenv("OMNIVOICE_SEGMENTED_DOWNLOAD", raising=False) | |
| assert _get_segmented_enabled() is True | |
| def test_env_override_can_disable(monkeypatch): | |
| monkeypatch.setenv("OMNIVOICE_SEGMENTED_DOWNLOAD", "0") | |
| assert _get_segmented_enabled() is False | |
| def test_env_override_truthy_keeps_it_on(monkeypatch): | |
| for val in ("1", "true", "on", "yes"): | |
| monkeypatch.setenv("OMNIVOICE_SEGMENTED_DOWNLOAD", val) | |
| assert _get_segmented_enabled() is True |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_segmented_download_default.py` around lines 12 - 14, The test
module is importing app code at module load time and mutating sys.path globally,
which can cause stale state and order-dependent pytest behavior. Move the
backend path setup and the import of _segmented_enabled out of the module top
level and into a helper or directly into each test so the module is resolved at
runtime for every test case. Use the existing _segmented_enabled symbol as the
target import location, and keep any path adjustment scoped to the runtime
import path rather than executed during test collection.
Source: Path instructions
Renames [Unreleased] → [0.3.8] — 2026-06-24 with a one-paragraph headline in the house style, and adds the entries merged since v0.3.7 that weren't yet logged: faster default downloads + the surfaced HF-token card (#669/#657), the auto-play toggle (#666), the status-bar version badge (#671), and the Windows/stability fixes — WhisperX-on-Windows (#630), transcribe timeout (#656), preview playback (#653/#659), stale dub session (#660), bad-instruct 400 (#664/#612), Insert popover clipping (#672), and the M1 startup-hang bound (#632). A fresh empty [Unreleased] is left above it for the next cycle. This makes cutting v0.3.8 a single `git tag` away: release.yml extracts this section verbatim as the GitHub Release body, so the tag ships real notes instead of the auto-generated fallback. (Owner adjusts the date if tagged on another day; no version files touched — this is docs only.) Co-authored-by: mergetest <test@local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses owner feedback: surface + encourage the HF token near Continue, and make the slow downloader faster.
1. Faster downloads — segmented accelerator ON by default
The app forces the legacy-LFS path (
HF_HUB_DISABLE_XET=1) for clear progress, but that path is single-stream and slow — the root of "downloads are very slow." The built-in multi-connection segmented downloader (parallel byte-ranges, IDM/uGet style, live speed/ETA) was already implemented and unit-tested, just defaulted OFF. Flipping it ON:snapshot_downloadon any error — "can never compromise a correct install."OMNIVOICE_SEGMENTED_DOWNLOAD=0.2. HF token — prominent, encouraged, near Continue
Was a collapsed "advanced" fold almost nobody opened. Now a prominent always-visible card right above the Continue button:
A token raises rate limits, so it pairs with #1 to keep the parallel fetch from getting throttled. One-click link to
huggingface.co/settings/tokens.Docs & tests
docs/downloading-models.md: legacy-LFS section documents the default-on accelerator + HF-token speed tip; tuning table now showsOMNIVOICE_SEGMENTED_DOWNLOAD=0as the disable knob (docs-sync).test_segmented_download_default.py: pins default ON + env override disables; existing FDL-08 behavior tests stay green.🤖 Generated with Claude Code
Summary
OMNIVOICE_SEGMENTED_DOWNLOAD=0to opt out and a fallback tosnapshot_downloadon errors.UI sketch
Before:
After:
Behavior flow
flowchart TD A[Setup starts] --> B{OMNIVOICE_SEGMENTED_DOWNLOAD=0?} B -- yes --> C[Use snapshot_download] B -- no --> D{Segmented downloader enabled?} D --> E[Try segmented multi-connection download] E -- success --> F[Done] E -- error --> C C --> F </mermaid> <!-- end of auto-generated comment: release notes by coderabbit.ai -->