-
Notifications
You must be signed in to change notification settings - Fork 1.6k
fix: GUI startup with external server + data refresh on server switch #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -53,6 +53,35 @@ fn find_voicebox_pid_on_port(port: u16) -> Option<u32> { | |||||||||||||||||||||||||
| None | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| /// Check if a Voicebox server is responding on the given port. | ||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||
| /// Sends an HTTP GET to `/health` and returns `true` if the response | ||||||||||||||||||||||||||
| /// contains the expected JSON field (`"status"`), confirming it's | ||||||||||||||||||||||||||
| /// a Voicebox backend rather than an unrelated service. | ||||||||||||||||||||||||||
| #[allow(dead_code)] // Used in platform-specific cfg blocks | ||||||||||||||||||||||||||
| fn check_health(port: u16) -> bool { | ||||||||||||||||||||||||||
| let url = format!("http://127.0.0.1:{}/health", port); | ||||||||||||||||||||||||||
| match reqwest::blocking::Client::builder() | ||||||||||||||||||||||||||
| .timeout(std::time::Duration::from_secs(3)) | ||||||||||||||||||||||||||
| .build() | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| Ok(client) => match client.get(&url).send() { | ||||||||||||||||||||||||||
| Ok(resp) => { | ||||||||||||||||||||||||||
| if !resp.status().is_success() { | ||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // Verify the body looks like a Voicebox health response | ||||||||||||||||||||||||||
| match resp.text() { | ||||||||||||||||||||||||||
| Ok(body) => body.contains("status"), | ||||||||||||||||||||||||||
|
Comment on lines
+73
to
+75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parse the health body instead of searching for Any HTTP service that returns Possible fix- match resp.text() {
- Ok(body) => body.contains("status"),
- Err(_) => false,
- }
+ match resp.json::<serde_json::Value>() {
+ Ok(body) => {
+ body.get("status").and_then(|v| v.as_str()) == Some("healthy")
+ && body.get("model_loaded").map(|v| v.is_boolean()).unwrap_or(false)
+ && body.get("gpu_available").map(|v| v.is_boolean()).unwrap_or(false)
+ }
+ Err(_) => false,
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| Err(_) => false, | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| Err(_) => false, | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| Err(_) => false, | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| struct ServerState { | ||||||||||||||||||||||||||
| child: Mutex<Option<tauri_plugin_shell::process::CommandChild>>, | ||||||||||||||||||||||||||
| server_pid: Mutex<Option<u32>>, | ||||||||||||||||||||||||||
|
|
@@ -80,7 +109,8 @@ async fn start_server( | |||||||||||||||||||||||||
| return Ok(format!("http://127.0.0.1:{}", SERVER_PORT)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Check if a voicebox server is already running on our port (from previous session with keep_running=true) | ||||||||||||||||||||||||||
| // Check if a voicebox server is already running on our port (from previous session with keep_running=true, | ||||||||||||||||||||||||||
| // or an externally started server e.g. via `python`, `uvicorn`, Docker, etc.) | ||||||||||||||||||||||||||
| #[cfg(unix)] | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| use std::process::Command; | ||||||||||||||||||||||||||
|
|
@@ -101,6 +131,20 @@ async fn start_server( | |||||||||||||||||||||||||
| *state.server_pid.lock().unwrap() = Some(pid); | ||||||||||||||||||||||||||
| return Ok(format!("http://127.0.0.1:{}", SERVER_PORT)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| // Process name doesn't contain "voicebox" — could be an external | ||||||||||||||||||||||||||
| // Python/uvicorn/Docker server. Verify via HTTP health check. | ||||||||||||||||||||||||||
| println!("Port {} in use by '{}' (PID: {}), checking if it's a Voicebox server...", SERVER_PORT, command, pid_str); | ||||||||||||||||||||||||||
| if check_health(SERVER_PORT) { | ||||||||||||||||||||||||||
| println!("Health check passed — reusing external server on port {}", SERVER_PORT); | ||||||||||||||||||||||||||
| return Ok(format!("http://127.0.0.1:{}", SERVER_PORT)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| println!("Health check failed — port is occupied by a non-Voicebox process"); | ||||||||||||||||||||||||||
| return Err(format!( | ||||||||||||||||||||||||||
| "Port {} is already in use by another application ({}). \ | ||||||||||||||||||||||||||
| Close it or change the Voicebox server port.", | ||||||||||||||||||||||||||
| SERVER_PORT, command | ||||||||||||||||||||||||||
| )); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
@@ -114,18 +158,24 @@ async fn start_server( | |||||||||||||||||||||||||
| &format!("127.0.0.1:{}", SERVER_PORT).parse().unwrap(), | ||||||||||||||||||||||||||
| std::time::Duration::from_secs(1), | ||||||||||||||||||||||||||
| ).is_ok() { | ||||||||||||||||||||||||||
| // Port is in use — check if it's a voicebox process | ||||||||||||||||||||||||||
| // Port is in use — check if it's a voicebox process by name first | ||||||||||||||||||||||||||
| if let Some(pid) = find_voicebox_pid_on_port(SERVER_PORT) { | ||||||||||||||||||||||||||
| println!("Found existing voicebox-server on port {} (PID: {}), reusing it", SERVER_PORT, pid); | ||||||||||||||||||||||||||
| *state.server_pid.lock().unwrap() = Some(pid); | ||||||||||||||||||||||||||
| return Ok(format!("http://127.0.0.1:{}", SERVER_PORT)); | ||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||
| return Err(format!( | ||||||||||||||||||||||||||
| "Port {} is already in use by another application. \ | ||||||||||||||||||||||||||
| Close the other application or change the Voicebox port.", | ||||||||||||||||||||||||||
| SERVER_PORT | ||||||||||||||||||||||||||
| )); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| // Process name doesn't match — could be an external Python/Docker server. | ||||||||||||||||||||||||||
| // Verify via HTTP health check before giving up. | ||||||||||||||||||||||||||
| println!("Port {} in use by unknown process, checking if it's a Voicebox server...", SERVER_PORT); | ||||||||||||||||||||||||||
| if check_health(SERVER_PORT) { | ||||||||||||||||||||||||||
| println!("Health check passed — reusing external server on port {}", SERVER_PORT); | ||||||||||||||||||||||||||
| return Ok(format!("http://127.0.0.1:{}", SERVER_PORT)); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return Err(format!( | ||||||||||||||||||||||||||
| "Port {} is already in use by another application. \ | ||||||||||||||||||||||||||
| Close the other application or change the Voicebox port.", | ||||||||||||||||||||||||||
| SERVER_PORT | ||||||||||||||||||||||||||
| )); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate the health payload before marking the app ready.
apiClient.getHealth()only guaranteesresponse.okplus JSON parsing inapp/src/lib/api/client.ts:58-82. A different service with a generic/healthendpoint on that URL will still flipserverReadytotrue, even though the Tauri startup path may have rejected it. Check for Voicebox-specific fields before accepting the fallback.Possible fix
const pollInterval = setInterval(async () => { try { - await apiClient.getHealth(); + const health = await apiClient.getHealth(); + if ( + health?.status !== 'healthy' || + typeof health.model_loaded !== 'boolean' || + typeof health.gpu_available !== 'boolean' + ) { + return; + } console.log('External server detected via health check'); clearInterval(pollInterval); setServerReady(true);📝 Committable suggestion
🤖 Prompt for AI Agents