Skip to content

fix: Windows port conflict + HF_HUB_OFFLINE for offline model loading#85

Open
caaaaaleb wants to merge 4 commits into
debpalash:mainfrom
caaaaaleb:main
Open

fix: Windows port conflict + HF_HUB_OFFLINE for offline model loading#85
caaaaaleb wants to merge 4 commits into
debpalash:mainfrom
caaaaaleb:main

Conversation

@caaaaaleb
Copy link
Copy Markdown

@caaaaaleb caaaaaleb commented May 18, 2026

Summary

Three fixes to improve reliability on Windows and restricted networks:

1. Windows kill_orphan_on_port implementation

The Windows version was an empty function, causing stale backend processes to occupy the port. When a new backend spawned, uvicorn couldn't bind → shutdown → model loading continued in half-dead process → confusing "Could not import module AutoModel" error.

Fix: Implement kill_orphan_on_port for Windows using netstat -ano to find the listening PID and taskkill /PID /F to terminate it.

2. HF_HUB_OFFLINE=1 at import time

On restricted networks, lazy imports deep in the HuggingFace stack could trigger httpx.ConnectTimeout before local_files_only=True takes effect.

Fix: Set HF_HUB_OFFLINE=1 in main.py before ANY library import, and in model_manager._load_model_sync() with proper save/restore.

3. local_files_only=True for from_pretrained

Added local_files_only=True to OmniVoice.from_pretrained() call as belt-and-suspenders alongside HF_HUB_OFFLINE.

Tested

  • Backend starts and stays running on Windows
  • Model loads successfully via Tauri spawn
  • /system/info returns correct model checkpoint info
  • TTS generation works end-to-end

Summary by CodeRabbit

  • Documentation

    • README replaced with a Simplified Chinese Windows-focused user guide: quick-start, installation modes (MSI/source/dev), build/FAQ, repo layout, ports, and links.
  • New Features

    • Full English+Simplified Chinese i18n with UI language toggle (persisted) and numerous localized UI strings.
    • Added Windows start scripts for production and dev with automated health checks and launch.
  • Bug Fixes

    • Enforced offline model loading and startup settings to avoid external HF network access.
    • Auto-kill of orphaned Windows processes occupying local ports.
  • Chores

    • Vite dev server bound to 127.0.0.1; .gitignore updated to exclude MSI artifacts.

Review Change Stack

- Implement kill_orphan_on_port for Windows using netstat + taskkill
- Set HF_HUB_OFFLINE=1 in main.py before any HF library import
- Set HF_HUB_OFFLINE=1 in model_manager._load_model_sync with restore
- Add local_files_only=True to OmniVoice.from_pretrained call

Fixes "Could not import module AutoModel" error caused by port
conflict when stale backend processes occupy the port on Windows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

📝 Walkthrough

Walkthrough

README replaced with a Simplified Chinese Windows usage guide; frontend adds full i18n (en+zh) and uses translations across UI; backend forces HF offline at startup and during model loads (restores env), uses local_files_only=True, and Windows tauri backend kills orphan TCP listeners; Vite binds 127.0.0.1; Windows start scripts added.

Changes

Documentation & Windows scripts

Layer / File(s) Summary
README Chinese full replacement
README.md
Replaces README with Simplified Chinese “使用指南” covering quick-start, MSI/build, dev workflows, fixes table, troubleshooting, and related links.
Windows start scripts
start.bat, start-dev.bat
New/updated batch scripts to validate venv, kill existing listeners, start backend/frontend, poll readiness, open browser, and stop services.
Ignore MSI build artifacts
.gitignore
Adds *.msi pattern under build artifacts.

Frontend i18n

Layer / File(s) Summary
i18n init and locales
frontend/src/i18n/index.ts, frontend/src/i18n/locales/en.json, frontend/src/i18n/locales/zh.json, frontend/src/main-app.jsx
Register zh locale alongside en, change fallback to zh, persist language in localStorage, import i18n before app render, and add large en/zh catalogs.
Use translations across UI
frontend/src/**/*.{jsx,js,ts,tsx}
Many components/pages/hooks updated to use useTranslation()/i18n.t(...) replacing hardcoded English strings with translation keys and templates.
Vite dev server binding
frontend/vite.config.js
Set server.host to '127.0.0.1' for IPv4-only dev binding.

Backend offline/model behavior

Layer / File(s) Summary
Startup environment flags
backend/main.py
Set HF_HUB_OFFLINE="1" at module import time; on Windows set TORCH_COMPILE_DISABLE/TORCHDYNAMO_DISABLE/TORCHINDUCTOR_DISABLE before importing torch.
Model loading with offline constraints
backend/services/model_manager.py
_load_model_sync() saves prior HF_HUB_OFFLINE, sets it to "1" during load, calls OmniVoice.from_pretrained(..., local_files_only=True), restores/removes env var; torch.compile attempted only for CUDA unless disabled; get_model() uses _get_gpu_pool() for executor.

Windows process cleanup (tauri backend)

Layer / File(s) Summary
Windows netstat/taskkill port cleanup
frontend/src-tauri/src/backend.rs
Implements non-Unix kill_orphan_on_port(port) to run netstat -ano -p TCP, filter LISTENING lines for exact port, parse PID, and invoke taskkill /PID <pid> /F; handles invocation/parsing errors safely.

Sequence Diagram(s)

sequenceDiagram
  participant BackendMain as backend/main.py
  participant HFLib as HuggingFaceLibraries
  BackendMain->>BackendMain: set HF_HUB_OFFLINE=1
  BackendMain->>HFLib: import HF libraries (initialize offline)
Loading
sequenceDiagram
  participant LoadFunc as _load_model_sync()
  participant HFEnv as HF_HUB_OFFLINE Env
  participant OmniVoice as OmniVoice.from_pretrained
  LoadFunc->>HFEnv: save prior HF_HUB_OFFLINE
  LoadFunc->>HFEnv: set HF_HUB_OFFLINE=1
  LoadFunc->>OmniVoice: from_pretrained(local_files_only=True)
  OmniVoice->>OmniVoice: load weights from local files
  LoadFunc->>HFEnv: restore previous HF_HUB_OFFLINE or remove
Loading
sequenceDiagram
  participant KillFunc as kill_orphan_on_port()
  participant NetStat as netstat -ano -p TCP
  participant TaskKill as taskkill /F
  KillFunc->>NetStat: run netstat to get TCP listeners
  NetStat-->>KillFunc: connection table lines
  KillFunc->>KillFunc: filter LISTENING entries for port
  KillFunc->>KillFunc: parse PID
  KillFunc->>TaskKill: taskkill /PID <pid> /F
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 新版指南落窗前,中文一读心欢然,
模型离网本地载,环境还原细又全。
端口孤儿被寻见,netstat 找到 taskkill 闪,
小兔鼓掌合并入,代码安稳又一班。

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

- 全文翻译为中文
- 添加本 Fork 修复版说明(Windows 端口冲突、离线模式)
- 更新下载链接指向本 Fork Release
- 增加国内网络环境使用指南

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
README.md (2)

281-293: 💤 Low value

Add language identifier to fenced code block.

The architecture diagram code block should specify a language identifier to satisfy markdown linting rules. Consider using text or plaintext for ASCII art diagrams.

📝 Proposed fix
-```
+```text
 ┌─────────────────────────────────────────────────┐
 │                  前端 (React)                     │

As per coding guidelines, static analysis (markdownlint-cli2) reports: "Fenced code blocks should have a language specified (MD040)".

🤖 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 `@README.md` around lines 281 - 293, The fenced ASCII-art diagram block (the
triple-backtick block containing the box with "前端 (React)" and "后端 (FastAPI)"
lines) needs a language identifier to satisfy MD040; update the opening fence
from ``` to ```text (or ```plaintext) so the block becomes a labeled code fence
(e.g., ```text) and keep the enclosed ASCII art unchanged.

8-9: 💤 Low value

Consider badge consistency with fork messaging.

The stars badge references the original repository (debpalash/OmniVoice-Studio) while the release badge references the fork (caaaaaleb/OmniVoice-Studio). This mixing could confuse users about which repository they're viewing.

Since this is a documented fork providing fixes, consider either:

  1. Using fork references for both badges, or
  2. Adding a note clarifying that stars reflect the upstream project
🤖 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 `@README.md` around lines 8 - 9, The README mixes upstream and fork badges
(stars link uses https://github.com/debpalash/OmniVoice-Studio while release
uses https://github.com/caaaaaleb/OmniVoice-Studio); make them consistent by
either updating the stars badge URL to the fork (change the href/src that
reference debpalash to caaaaaleb) or add a short clarifying sentence beneath the
badges stating that the star count reflects the upstream repository; ensure the
two anchor/img entries shown in the diff are updated together so both badges
point to the same repository or are accompanied by the clarification text.
frontend/src-tauri/src/backend.rs (1)

103-109: ⚡ Quick win

Surface cleanup failures in the logs.

This branch silently returns when netstat cannot be launched and ignores taskkill exit status. If cleanup fails because of permissions or a missing binary, we lose the only clue and fall back to the same bind error this PR is trying to fix.

Proposed change
     let out = match std::process::Command::new("netstat")
         .args(["-ano", "-p", "TCP"])
         .output()
     {
         Ok(o) => o,
-        Err(_) => return,
+        Err(e) => {
+            log::warn!("Failed to run netstat for port {}: {}", port, e);
+            return;
+        }
     };
+    if !out.status.success() {
+        log::warn!(
+            "netstat exited with status {:?} while probing port {}: {}",
+            out.status.code(),
+            port,
+            String::from_utf8_lossy(&out.stderr)
+        );
+        return;
+    }
...
-                let _ = std::process::Command::new("taskkill")
-                    .args(["/PID", &pid.to_string(), "/F"])
-                    .output();
+                match std::process::Command::new("taskkill")
+                    .args(["/PID", &pid.to_string(), "/F"])
+                    .output()
+                {
+                    Ok(kill) if !kill.status.success() => {
+                        log::warn!(
+                            "taskkill failed for pid {} on port {}: {}",
+                            pid,
+                            port,
+                            String::from_utf8_lossy(&kill.stderr)
+                        );
+                    }
+                    Err(e) => {
+                        log::warn!(
+                            "Failed to invoke taskkill for pid {} on port {}: {}",
+                            pid,
+                            port,
+                            e
+                        );
+                    }
+                    _ => {}
+                }

Also applies to: 135-137

🤖 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-tauri/src/backend.rs` around lines 103 - 109, The code silently
returns when std::process::Command::new("netstat").output() fails and also
ignores the exit status of the subsequent taskkill invocation; update the Err(_)
arm of the Command::new("netstat")...output() match to log the actual error
(include the error from output()) instead of returning, and after
spawning/running taskkill check its ExitStatus and log a clear error if it
failed (include stdout/stderr if available) so permission/missing-binary
failures are visible; update the same pattern referenced around the other
invocation (lines near 135-137) to perform the same logging and non-silent
failure handling.
🤖 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-tauri/src/backend.rs`:
- Around line 128-137: The current Windows branch blindly taskkills whatever PID
netstat reports; update the logic around the PID extraction (the block that
parses parts/ pid_str and produces pid and the taskkill call) to first query the
target process command line or executable and verify it belongs to our spawned
OmniVoice backend before killing: after parsing pid, run a safe metadata lookup
(e.g. via PowerShell/Get-Process or wmic for the given pid) to obtain
ProcessName/Path/CommandLine, compare that value against the expected backend
executable name or unique marker used when we spawn the backend, and only call
std::process::Command::new("taskkill") if the verification matches; otherwise
log a port-conflict error and surface the conflict instead of killing the
process.

---

Nitpick comments:
In `@frontend/src-tauri/src/backend.rs`:
- Around line 103-109: The code silently returns when
std::process::Command::new("netstat").output() fails and also ignores the exit
status of the subsequent taskkill invocation; update the Err(_) arm of the
Command::new("netstat")...output() match to log the actual error (include the
error from output()) instead of returning, and after spawning/running taskkill
check its ExitStatus and log a clear error if it failed (include stdout/stderr
if available) so permission/missing-binary failures are visible; update the same
pattern referenced around the other invocation (lines near 135-137) to perform
the same logging and non-silent failure handling.

In `@README.md`:
- Around line 281-293: The fenced ASCII-art diagram block (the triple-backtick
block containing the box with "前端 (React)" and "后端 (FastAPI)" lines) needs a
language identifier to satisfy MD040; update the opening fence from ``` to
```text (or ```plaintext) so the block becomes a labeled code fence (e.g.,
```text) and keep the enclosed ASCII art unchanged.
- Around line 8-9: The README mixes upstream and fork badges (stars link uses
https://github.com/debpalash/OmniVoice-Studio while release uses
https://github.com/caaaaaleb/OmniVoice-Studio); make them consistent by either
updating the stars badge URL to the fork (change the href/src that reference
debpalash to caaaaaleb) or add a short clarifying sentence beneath the badges
stating that the star count reflects the upstream repository; ensure the two
anchor/img entries shown in the diff are updated together so both badges point
to the same repository or are accompanied by the clarification text.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d3c53b90-30d8-4ff7-9193-1a53c28c786e

📥 Commits

Reviewing files that changed from the base of the PR and between e4dbf4c and 43b4d63.

📒 Files selected for processing (4)
  • README.md
  • backend/main.py
  • backend/services/model_manager.py
  • frontend/src-tauri/src/backend.rs

Comment on lines +128 to +137
let parts: Vec<&str> = line.split_whitespace().collect();
if let Some(pid_str) = parts.last() {
if let Ok(pid) = pid_str.parse::<u32>() {
log::warn!(
"Killing orphan process {} on port {} (Windows)",
pid, port
);
let _ = std::process::Command::new("taskkill")
.args(["/PID", &pid.to_string(), "/F"])
.output();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't taskkill arbitrary listeners on this port.

Line 135 force-kills whichever PID netstat reports, but this helper never verifies that the process is actually our backend. If some other local service is bound to the configured port, startup can terminate an unrelated app instead of surfacing a port conflict. Please check the process command line/executable matches the spawned OmniVoice backend before killing it.

🤖 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-tauri/src/backend.rs` around lines 128 - 137, The current
Windows branch blindly taskkills whatever PID netstat reports; update the logic
around the PID extraction (the block that parses parts/ pid_str and produces pid
and the taskkill call) to first query the target process command line or
executable and verify it belongs to our spawned OmniVoice backend before
killing: after parsing pid, run a safe metadata lookup (e.g. via
PowerShell/Get-Process or wmic for the given pid) to obtain
ProcessName/Path/CommandLine, compare that value against the expected backend
executable name or unique marker used when we spawn the backend, and only call
std::process::Command::new("taskkill") if the verification matches; otherwise
log a port-conflict error and surface the conflict instead of killing the
process.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 `@README.md`:
- Around line 11-13: The directory tree lists both README.md and 使用指南.md but the
tree labels 使用指南.md as “本文件” while this PR edits README.md, causing confusion;
update the directory tree entry so the “本文件” label correctly refers to README.md
(or rename the file consistently) by changing the label for 使用指南.md to its
proper description and/or marking README.md as “本文件”, and ensure the README.md
and 使用指南.md filenames and their descriptive comments (in the README listing) are
consistent.
- Around line 9-25: The README.md's directory tree fenced code block is missing
a language tag (causing MD040); open the fenced block in README.md and add a
language tag (e.g., replace "```" with "```text") so the tree block is
recognized as plain text; locate the block shown in the diff (the directory tree
starting with "E:\OmniVoice-Studio\") and update its opening fence accordingly.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 17571d8b-401d-4d42-b46a-320f6c8d737c

📥 Commits

Reviewing files that changed from the base of the PR and between 43b4d63 and 16da0b0.

📒 Files selected for processing (1)
  • README.md

Comment thread README.md
Comment on lines 9 to 25
```

Open normally after. One-time fix.
</details>

<details>
<summary><b>Windows — first launch takes 5–10 minutes</b></summary>
<br/>

The app bootstraps a Python virtual environment, installs dependencies, and downloads ffmpeg on first run. The splash screen shows each step. Subsequent launches start in seconds.
</details>

<details>
<summary><b>Linux — AppImage needs FUSE</b></summary>
<br/>

If FUSE isn't available, use the `.deb` package or extract-and-run:

```bash
chmod +x OmniVoice.Studio_*.AppImage
./OmniVoice.Studio_*.AppImage --appimage-extract-and-run
```
</details>

<details>
<summary><b>Linux — White screen on Fedora 44 / Ubuntu 24.04</b></summary>
<br/>

Some newer distros ship a WebKit/GTK version with compositing issues. Try:

```bash
WEBKIT_DISABLE_COMPOSITING_MODE=1 ./OmniVoice.Studio_*.AppImage
E:\OmniVoice-Studio\
├── README.md # 项目说明(中文)
├── 使用指南.md # 本文件
├── OmniVoice_Studio_0.2.7_x64.msi # Windows 安装包 (~7 MB)
├── backend/ # Python 后端
│ ├── main.py # FastAPI 入口
│ └── services/
│ └── model_manager.py # 模型加载
├── frontend/ # React 前端 (Tauri)
│ └── src-tauri/
│ └── src/
│ └── backend.rs # 后端进程管理
├── omnivoice/ # OmniVoice Python 包
├── pyproject.toml # Python 项目配置
└── uv.lock # 依赖锁定文件
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced code block to satisfy markdownlint.

Line 9 opens a fenced block without a language, triggering MD040 and causing lint noise.

Suggested patch
-```
+```text
 E:\OmniVoice-Studio\
 ├── README.md                              # 项目说明(中文)
 ...
 └── uv.lock                                 # 依赖锁定文件
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 9-9: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @README.md around lines 9 - 25, The README.md's directory tree fenced code
block is missing a language tag (causing MD040); open the fenced block in
README.md and add a language tag (e.g., replace "" with "text") so the
tree block is recognized as plain text; locate the block shown in the diff (the
directory tree starting with "E:\OmniVoice-Studio") and update its opening
fence accordingly.


</details>

<!-- fingerprinting:phantom:triton:hawk -->

<!-- This is an auto-generated comment by CodeRabbit -->

Comment thread README.md Outdated
- Add complete zh/en i18n across 50+ source files with i18next
- Add language switcher (中/EN toggle) in header with localStorage persistence
- Fix _gpu_pool NameError in model_manager.py (__getattr__ internal access)
- Fix TritonMissing on Windows: disable torch.compile/dynamo/inductor via env vars + explicit check
- Fix Vite IPv6-only binding: force host to 127.0.0.1
- Add start.bat (production, .venv only) and start-dev.bat (Vite HMR)
- Rebuild frontend dist with all fixes
- Update README with new features and fix table

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
start.bat (1)

1-66: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Convert file to Windows line endings (CRLF).

The file uses Unix line endings (LF-only), which will cause parsing failures on Windows due to CMD.exe parser bugs. Batch files require CRLF line endings to work reliably.

🔧 How to fix

Convert the file to Windows line endings using one of:

Git:

# Configure git to auto-convert on checkout (run once in repo root)
git config core.autocrlf true
git rm --cached start.bat
git reset --hard

Command line:

# Using unix2dos (if available)
unix2dos start.bat

# Or using PowerShell
(Get-Content start.bat) | Set-Content start.bat

Editor:

  • VS Code: Click "LF" in status bar → select "CRLF"
  • Notepad++: Edit → EOL Conversion → Windows (CR LF)

As per coding guidelines, every fix must work on Windows (x64) with no platform-only regressions.

🤖 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 `@start.bat` around lines 1 - 66, The start.bat file currently uses LF-only
line endings which will break CMD parsing; convert start.bat to Windows CRLF
endings (ensure every line ends with CRLF) and commit that change; you can do
this by enabling Git autocrlf and re-checking out the file (git config
core.autocrlf true; git rm --cached start.bat; git reset --hard), or run a
conversion tool (unix2dos start.bat) or change the EOL in your editor (e.g., VS
Code: click LF → select CRLF), then verify the batch commands like the for
loops, start "OmniVoice-Backend", curl health check and taskkill lines run
correctly on Windows before committing.
start-dev.bat (1)

1-89: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Convert file to Windows line endings (CRLF).

The file uses Unix line endings (LF-only), which will cause parsing failures on Windows due to CMD.exe parser bugs. Batch files require CRLF line endings to work reliably.

🔧 How to fix

Convert the file to Windows line endings using one of:

Git:

# Configure git to auto-convert on checkout (run once in repo root)
git config core.autocrlf true
git rm --cached start-dev.bat
git reset --hard

Command line:

# Using unix2dos (if available)
unix2dos start-dev.bat

# Or using PowerShell
(Get-Content start-dev.bat) | Set-Content start-dev.bat

Editor:

  • VS Code: Click "LF" in status bar → select "CRLF"
  • Notepad++: Edit → EOL Conversion → Windows (CR LF)

As per coding guidelines, every fix must work on Windows (x64) with no platform-only regressions.

🤖 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 `@start-dev.bat` around lines 1 - 89, The batch script uses LF-only line
endings which break CMD parsing; convert start-dev.bat to Windows CRLF line
endings and commit the change so Windows can execute it reliably. Fix by
converting the file (e.g., run unix2dos on start-dev.bat, use PowerShell
Set-Content to rewrite with CRLF, enable git core.autocrlf and refresh the file,
or change EOL to CRLF in your editor/VS Code) and verify the top of the script
(starts with "`@echo` off") runs successfully on a Windows x64 environment before
pushing.
frontend/src/pages/Projects.jsx (1)

163-176: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Variable shadowing causes runtime error.

The loop variable t at line 163 shadows the translation function t from useTranslation() declared at line 90. When line 167 attempts to call t('projects.transcription_item'), it will fail with TypeError: t is not a function because t now refers to the current transcription object, not the translation function.

🐛 Proposed fix: rename loop variable
-    for (const t of transcriptions) {
+    for (const trans of transcriptions) {
       list.push({
         type: 'transcripts',
-        id: t.id || String(Math.random()),
-        title: (t.text || t('projects.transcription_item')).slice(0, 120),
-        subtitle: [t.language, t.duration_s ? `${Math.round(t.duration_s)}s` : ''].filter(Boolean).join(' · '),
-        ts: t.timestamp ? Date.parse(t.timestamp) : 0,
+        id: trans.id || String(Math.random()),
+        title: (trans.text || t('projects.transcription_item')).slice(0, 120),
+        subtitle: [trans.language, trans.duration_s ? `${Math.round(trans.duration_s)}s` : ''].filter(Boolean).join(' · '),
+        ts: trans.timestamp ? Date.parse(trans.timestamp) : 0,
         accent: '`#83a598`',
         Icon: FileText,
         onClick: () => {
-          navigator.clipboard.writeText(t.text || '');
+          navigator.clipboard.writeText(trans.text || '');
         },
       });
     }
🤖 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/pages/Projects.jsx` around lines 163 - 176, The loop is
shadowing the translation function t from useTranslation() by using `for (const
t of transcriptions)`, causing `t('projects.transcription_item')` to fail;
rename the loop variable (e.g., `for (const transcript of transcriptions)` or
`tr`) and update all references inside the loop (`t.id`, `t.text`, `t.language`,
`t.duration_s`, `t.timestamp`) to the new name so the translation function `t`
remains callable.
🧹 Nitpick comments (2)
frontend/src/components/ExportModal.jsx (1)

76-80: 💤 Low value

Consider adding language to useMemo dependencies.

The useMemo uses t('dub.original') but only depends on dubTracks. While the component will re-render when language changes (via react-i18next context), explicitly tracking language in dependencies makes the intent clearer and satisfies ESLint exhaustive-deps.

📦 Suggested addition to dependencies
+import { useTranslation } from 'react-i18next';
+
 export default function ExportModal({
   ...
 }) {
   const { t, i18n } = useTranslation();
   ...
   const allTracks = useMemo(() => {
     const out = [{ code: 'original', label: t('dub.original'), kind: 'original' }];
     (dubTracks || []).forEach(t => out.push({ code: t, label: t.toUpperCase(), kind: 'dub' }));
     return out;
-  }, [dubTracks]);
+  }, [dubTracks, i18n.language, t]);
🤖 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/ExportModal.jsx` around lines 76 - 80, The memo for
allTracks uses the i18n translator t('dub.original') but only lists dubTracks in
its dependency array; update the useMemo dependencies for the allTracks constant
(the useMemo call that builds out from dubTracks) to include the
translator/language so the memo updates when language changes—e.g., add either
the t function or the current i18n.language value to the dependency array to
satisfy exhaustive-deps and ensure labels update on locale change.
frontend/src/components/GlossaryPanel.jsx (1)

175-182: 💤 Low value

Consider renaming map parameter to avoid shadowing.

The loop variable t shadows the translation function from useTranslation(). While this doesn't cause a runtime error (since the translation function isn't called inside the map), it reduces code clarity and could confuse maintainers.

♻️ Suggested refactor
-              {terms.map(t => (
+              {terms.map(term => (
                 <GlossaryRow
-                  key={t.id}
-                  term={t}
-                  onUpdate={(patch) => onUpdate(t.id, patch)}
-                  onDelete={() => onDelete(t.id)}
+                  key={term.id}
+                  term={term}
+                  onUpdate={(patch) => onUpdate(term.id, patch)}
+                  onDelete={() => onDelete(term.id)}
                 />
               ))}
🤖 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/GlossaryPanel.jsx` around lines 175 - 182, Rename the
map loop parameter that currently uses "t" to avoid shadowing the translation
function from useTranslation(); for example, change the iterator in the
terms.map callback to "term" or "termItem" and update all references inside the
callback (key, term prop, onUpdate/onDelete closures) so GlossaryRow receives
the same data but no longer conflicts with the translation function name.
🤖 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/App.jsx`:
- Around line 216-218: The default compareText initializer references an
undefined i18n instead of using the translation hook; inside the App component
call useTranslation() (e.g., const { t } = useTranslation()) and update the
useState initializer for compareText to use t('voice.default_test_phrase') (with
a try/catch or fallback string) so the localized default is used; update any
usages that referenced i18n.t to use the destructured t function and ensure
useTranslation is imported and invoked within the App component scope.

In `@frontend/src/pages/CloneDesignTab.jsx`:
- Line 211: The concatenation at the JSX expression using
t('voice.using_profile') + profiles.find(p => p.id === selectedProfile)?.name
breaks localization; instead pass the profile name as an interpolation variable
to the translator. Replace the concatenated expression with a single t call that
supplies the name (e.g., t('voice.using_profile', { name: profiles.find(p =>
p.id === selectedProfile)?.name })) so translators control spacing/word order;
update the JSX where selectedProfile and profiles are referenced accordingly.

In `@frontend/src/pages/Transcriptions.jsx`:
- Around line 76-82: The useCallback hooks for copyText and exportAll capture
the translation function t but do not include it in their dependency arrays,
causing stale translations after language changes; update the useCallback
declarations for copyText and exportAll to include t (e.g., useCallback(..., [t,
...otherDeps])) so they re-create with the latest translator when language
switches.

---

Outside diff comments:
In `@frontend/src/pages/Projects.jsx`:
- Around line 163-176: The loop is shadowing the translation function t from
useTranslation() by using `for (const t of transcriptions)`, causing
`t('projects.transcription_item')` to fail; rename the loop variable (e.g., `for
(const transcript of transcriptions)` or `tr`) and update all references inside
the loop (`t.id`, `t.text`, `t.language`, `t.duration_s`, `t.timestamp`) to the
new name so the translation function `t` remains callable.

In `@start-dev.bat`:
- Around line 1-89: The batch script uses LF-only line endings which break CMD
parsing; convert start-dev.bat to Windows CRLF line endings and commit the
change so Windows can execute it reliably. Fix by converting the file (e.g., run
unix2dos on start-dev.bat, use PowerShell Set-Content to rewrite with CRLF,
enable git core.autocrlf and refresh the file, or change EOL to CRLF in your
editor/VS Code) and verify the top of the script (starts with "`@echo` off") runs
successfully on a Windows x64 environment before pushing.

In `@start.bat`:
- Around line 1-66: The start.bat file currently uses LF-only line endings which
will break CMD parsing; convert start.bat to Windows CRLF endings (ensure every
line ends with CRLF) and commit that change; you can do this by enabling Git
autocrlf and re-checking out the file (git config core.autocrlf true; git rm
--cached start.bat; git reset --hard), or run a conversion tool (unix2dos
start.bat) or change the EOL in your editor (e.g., VS Code: click LF → select
CRLF), then verify the batch commands like the for loops, start
"OmniVoice-Backend", curl health check and taskkill lines run correctly on
Windows before committing.

---

Nitpick comments:
In `@frontend/src/components/ExportModal.jsx`:
- Around line 76-80: The memo for allTracks uses the i18n translator
t('dub.original') but only lists dubTracks in its dependency array; update the
useMemo dependencies for the allTracks constant (the useMemo call that builds
out from dubTracks) to include the translator/language so the memo updates when
language changes—e.g., add either the t function or the current i18n.language
value to the dependency array to satisfy exhaustive-deps and ensure labels
update on locale change.

In `@frontend/src/components/GlossaryPanel.jsx`:
- Around line 175-182: Rename the map loop parameter that currently uses "t" to
avoid shadowing the translation function from useTranslation(); for example,
change the iterator in the terms.map callback to "term" or "termItem" and update
all references inside the callback (key, term prop, onUpdate/onDelete closures)
so GlossaryRow receives the same data but no longer conflicts with the
translation function name.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a747c065-c8fb-4fe7-8464-b682031eb714

📥 Commits

Reviewing files that changed from the base of the PR and between 16da0b0 and 81789e3.

📒 Files selected for processing (56)
  • .gitignore
  • README.md
  • backend/main.py
  • backend/services/model_manager.py
  • frontend/src/App.jsx
  • frontend/src/components/AudioTrimmer.jsx
  • frontend/src/components/BatchAddDialog.jsx
  • frontend/src/components/BootstrapSplash.jsx
  • frontend/src/components/CaptureWidget.jsx
  • frontend/src/components/CastingView.jsx
  • frontend/src/components/CheckpointBanner.jsx
  • frontend/src/components/CompareModal.jsx
  • frontend/src/components/DirectionDialog.jsx
  • frontend/src/components/DubSegmentRow.jsx
  • frontend/src/components/DubSegmentTable.jsx
  • frontend/src/components/ErrorBoundary.jsx
  • frontend/src/components/ExportModal.jsx
  • frontend/src/components/FloatingPill.jsx
  • frontend/src/components/GlossaryPanel.jsx
  • frontend/src/components/Header.jsx
  • frontend/src/components/KeyboardCheatsheet.jsx
  • frontend/src/components/LogsFooter.jsx
  • frontend/src/components/MultiLangPicker.jsx
  • frontend/src/components/NavRail.jsx
  • frontend/src/components/NotificationPanel.jsx
  • frontend/src/components/ReadinessChecklist.jsx
  • frontend/src/components/SearchableSelect.jsx
  • frontend/src/components/Sidebar.jsx
  • frontend/src/components/StoriesEditor.jsx
  • frontend/src/components/VoicePreview.jsx
  • frontend/src/components/WaveformTimeline.jsx
  • frontend/src/hooks/useAppData.js
  • frontend/src/hooks/useDubWorkflow.js
  • frontend/src/hooks/useProfiles.js
  • frontend/src/hooks/useTTS.js
  • frontend/src/i18n/index.ts
  • frontend/src/i18n/locales/en.json
  • frontend/src/i18n/locales/zh.json
  • frontend/src/main-app.jsx
  • frontend/src/pages/BatchQueue.jsx
  • frontend/src/pages/CloneDesignTab.jsx
  • frontend/src/pages/DonatePage.jsx
  • frontend/src/pages/DubTab.jsx
  • frontend/src/pages/EnterprisePage.jsx
  • frontend/src/pages/Launchpad.jsx
  • frontend/src/pages/Projects.jsx
  • frontend/src/pages/Settings.jsx
  • frontend/src/pages/SetupWizard.jsx
  • frontend/src/pages/ToolsPage.jsx
  • frontend/src/pages/Transcriptions.jsx
  • frontend/src/pages/VoiceGallery.jsx
  • frontend/src/pages/VoiceProfile.jsx
  • frontend/src/ui/Dialog.jsx
  • frontend/vite.config.js
  • start-dev.bat
  • start.bat
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/main.py

Comment thread frontend/src/App.jsx
Comment on lines +216 to +218
const [compareText, setCompareText] = useState(() => {
try { return i18n?.t?.('voice.default_test_phrase'); } catch { return "The quick brown fox jumps over the lazy dog."; }
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP '\bi18n\?\.t\?\(' frontend/src/App.jsx
rg -nP '^import .*\\bi18n\\b' frontend/src/App.jsx

Repository: debpalash/OmniVoice-Studio

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Check if file exists and get line count
ls -la frontend/src/App.jsx 2>/dev/null || echo "File not found at frontend/src/App.jsx"
wc -l frontend/src/App.jsx 2>/dev/null || echo "Cannot count lines"

Repository: debpalash/OmniVoice-Studio

Length of output: 167


🏁 Script executed:

#!/bin/bash
# Read lines 216-218 and surrounding context
if [ -f frontend/src/App.jsx ]; then
  echo "=== Lines 210-225 ===" 
  sed -n '210,225p' frontend/src/App.jsx | cat -n
fi

Repository: debpalash/OmniVoice-Studio

Length of output: 985


🏁 Script executed:

#!/bin/bash
# Search for import statements related to i18n, translation, or useTranslation
if [ -f frontend/src/App.jsx ]; then
  echo "=== Import statements for i18n/translation ===" 
  head -50 frontend/src/App.jsx | grep -nE 'import.*i18n|import.*useTranslation|import.*translation'
fi

Repository: debpalash/OmniVoice-Studio

Length of output: 167


🏁 Script executed:

#!/bin/bash
# Check for all imports at the top of the file
if [ -f frontend/src/App.jsx ]; then
  echo "=== First 60 lines (imports section) ===" 
  head -60 frontend/src/App.jsx | cat -n
fi

Repository: debpalash/OmniVoice-Studio

Length of output: 3581


🏁 Script executed:

#!/bin/bash
# Search for useTranslation usage in App.jsx
rg -n 'useTranslation\|const \{ t \}' frontend/src/App.jsx | head -20

Repository: debpalash/OmniVoice-Studio

Length of output: 52


Undefined i18n reference prevents localized default compare text.

Line 217 references i18n without a binding. The useTranslation hook is imported but never instantiated in the App component, so the i18n object doesn't exist and the try/catch always falls back to English text.

Call useTranslation() to destructure the t function and use it directly:

Suggested fix
+  const { t } = useTranslation();
+
   const [compareText, setCompareText] = useState(() => {
-    try { return i18n?.t?.('voice.default_test_phrase'); } catch { return "The quick brown fox jumps over the lazy dog."; }
+    return t('voice.default_test_phrase', { defaultValue: 'The quick brown fox jumps over the lazy dog.' });
   });
📝 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.

Suggested change
const [compareText, setCompareText] = useState(() => {
try { return i18n?.t?.('voice.default_test_phrase'); } catch { return "The quick brown fox jumps over the lazy dog."; }
});
const { t } = useTranslation();
const [compareText, setCompareText] = useState(() => {
return t('voice.default_test_phrase', { defaultValue: 'The quick brown fox jumps over the lazy dog.' });
});
🤖 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/App.jsx` around lines 216 - 218, The default compareText
initializer references an undefined i18n instead of using the translation hook;
inside the App component call useTranslation() (e.g., const { t } =
useTranslation()) and update the useState initializer for compareText to use
t('voice.default_test_phrase') (with a try/catch or fallback string) so the
localized default is used; update any usages that referenced i18n.t to use the
destructured t function and ensure useTranslation is imported and invoked within
the App component scope.

<div className="clone-profile-banner">
<span className="clone-profile-banner__label">
Using profile: {profiles.find(p => p.id === selectedProfile)?.name}
{t('voice.using_profile')}{profiles.find(p => p.id === selectedProfile)?.name}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use interpolation for the selected-profile label.

Line 211 concatenates translated text with the profile name, which breaks spacing/word order in some locales. Pass name through t(...) interpolation instead.

Proposed fix
-                  {t('voice.using_profile')}{profiles.find(p => p.id === selectedProfile)?.name}
+                  {t('voice.using_profile', {
+                    name: profiles.find(p => p.id === selectedProfile)?.name ?? '',
+                  })}
📝 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.

Suggested change
{t('voice.using_profile')}{profiles.find(p => p.id === selectedProfile)?.name}
{t('voice.using_profile', {
name: profiles.find(p => p.id === selectedProfile)?.name ?? '',
})}
🤖 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/pages/CloneDesignTab.jsx` at line 211, The concatenation at the
JSX expression using t('voice.using_profile') + profiles.find(p => p.id ===
selectedProfile)?.name breaks localization; instead pass the profile name as an
interpolation variable to the translator. Replace the concatenated expression
with a single t call that supplies the name (e.g., t('voice.using_profile', {
name: profiles.find(p => p.id === selectedProfile)?.name })) so translators
control spacing/word order; update the JSX where selectedProfile and profiles
are referenced accordingly.

Comment on lines 76 to 82
const copyText = useCallback((text) => {
navigator.clipboard.writeText(text).then(
() => toast.success('Copied to clipboard'),
() => toast.error('Copy failed')
() => toast.success(t('transcriptions.copied_to_clipboard')),
() => toast.error(t('transcriptions.copy_failed'))
);
}, []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify callback dependency arrays around translated toasts.
rg -n -C2 "const copyText = useCallback|const exportAll = useCallback|toast\\.success\\(|toast\\.error\\(" frontend/src/pages/Transcriptions.jsx

Repository: debpalash/OmniVoice-Studio

Length of output: 681


Add t to callback dependency arrays to prevent stale translations after language switch.

The copyText (line 76) and exportAll (line 96) callbacks use the t function but omit it from their dependency arrays. This causes the callbacks to retain old translations when the language changes at runtime.

🔧 Proposed fix
   const copyText = useCallback((text) => {
     navigator.clipboard.writeText(text).then(
       () => toast.success(t('transcriptions.copied_to_clipboard')),
       () => toast.error(t('transcriptions.copy_failed'))
     );
-  }, []);
+  }, [t]);
@@
   const exportAll = useCallback(() => {
@@
     URL.revokeObjectURL(url);
     toast.success(t('transcriptions.exported_transcriptions'));
-  }, [transcriptions]);
+  }, [transcriptions, t]);
🤖 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/pages/Transcriptions.jsx` around lines 76 - 82, The useCallback
hooks for copyText and exportAll capture the translation function t but do not
include it in their dependency arrays, causing stale translations after language
changes; update the useCallback declarations for copyText and exportAll to
include t (e.g., useCallback(..., [t, ...otherDeps])) so they re-create with the
latest translator when language switches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant