Update development scripts and add macOS icon assets
- Renamed backend development script from `dev:backend` to `dev:server` for clarity. - Added new macOS icon assets and configuration files for the application. - Enhanced App component to improve server management during production and development modes. - Updated ConnectionForm to reset state after successful submission and conditionally render the update button. - Implemented database initialization on application startup in the backend.
|
After Width: | Height: | Size: 4.6 MiB |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"fill" : "automatic",
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"blend-mode" : "normal",
|
||||
"glass" : true,
|
||||
"image-name" : "Voicebox.png",
|
||||
"name" : "Voicebox",
|
||||
"position" : {
|
||||
"scale" : 0.37,
|
||||
"translation-in-points" : [
|
||||
-0.12338405356129378,
|
||||
297.27705195772353
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
@@ -437,7 +437,7 @@ projects
|
||||
|
||||
## 💡 Development Workflow
|
||||
|
||||
1. **Start backend**: `bun run dev:backend` (or via Tauri)
|
||||
1. **Start backend**: `bun run dev:server` (or via Tauri)
|
||||
2. **Start frontend**: `bun run dev` (Tauri) or `bun run dev:web` (web)
|
||||
3. **Generate API client**: `bun run generate:api` (after backend changes)
|
||||
4. **Build server binary**: `bun run build:server` (for Tauri bundling)
|
||||
|
||||
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
@@ -16,45 +16,57 @@ function App() {
|
||||
const [activeTab, setActiveTab] = useState('profiles');
|
||||
const [serverReady, setServerReady] = useState(false);
|
||||
|
||||
// Setup window close handler and auto-start server when running in Tauri
|
||||
// Setup window close handler and auto-start server when running in Tauri (production only)
|
||||
useEffect(() => {
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup window close handler to check setting and stop server if needed
|
||||
// This works in both dev and prod, but will only stop server if it was started by the app
|
||||
setupWindowCloseHandler().catch((error) => {
|
||||
console.error('Failed to setup window close handler:', error);
|
||||
});
|
||||
|
||||
// Auto-start server
|
||||
// Only auto-start server in production mode
|
||||
// In dev mode, user runs server separately
|
||||
if (!import.meta.env?.PROD) {
|
||||
console.log('Dev mode: Skipping auto-start of server (run it separately)');
|
||||
setServerReady(true); // Mark as ready so UI doesn't show loading screen
|
||||
// Mark that server was not started by app (so we don't try to stop it on close)
|
||||
// @ts-expect-error - adding property to window
|
||||
window.__voiceboxServerStartedByApp = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto-start server in production
|
||||
if (serverStarting) {
|
||||
return;
|
||||
}
|
||||
|
||||
serverStarting = true;
|
||||
console.log('Running in Tauri, starting bundled server...');
|
||||
console.log('Production mode: Starting bundled server...');
|
||||
|
||||
startServer(false)
|
||||
.then(() => {
|
||||
console.log('Server is ready');
|
||||
setServerReady(true);
|
||||
// Mark that we started the server (so we know to stop it on close)
|
||||
// @ts-expect-error - adding property to window
|
||||
window.__voiceboxServerStartedByApp = true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to auto-start server:', error);
|
||||
serverStarting = false;
|
||||
// @ts-expect-error - adding property to window
|
||||
window.__voiceboxServerStartedByApp = false;
|
||||
});
|
||||
|
||||
// Cleanup: stop server on actual unmount (not StrictMode remount)
|
||||
// Note: Window close is handled separately in Tauri Rust code
|
||||
return () => {
|
||||
// In dev mode, React StrictMode causes remounts, so we skip cleanup
|
||||
// In production, window close event handles server shutdown based on setting
|
||||
if (import.meta.env?.PROD) {
|
||||
// Only stop if setting says to stop (handled by window close event)
|
||||
// This cleanup is mainly for React remounts in dev mode
|
||||
serverStarting = false;
|
||||
}
|
||||
// Window close event handles server shutdown based on setting
|
||||
serverStarting = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useEffect } from 'react';
|
||||
import * as z from 'zod';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -37,8 +38,16 @@ export function ConnectionForm() {
|
||||
},
|
||||
});
|
||||
|
||||
// Sync form with store when serverUrl changes externally
|
||||
useEffect(() => {
|
||||
form.reset({ serverUrl });
|
||||
}, [serverUrl, form]);
|
||||
|
||||
const { isDirty } = form.formState;
|
||||
|
||||
function onSubmit(data: ConnectionFormValues) {
|
||||
setServerUrl(data.serverUrl);
|
||||
form.reset(data); // Reset form state after successful submission
|
||||
toast({
|
||||
title: 'Server URL updated',
|
||||
description: `Connected to ${data.serverUrl}`,
|
||||
@@ -68,7 +77,9 @@ export function ConnectionForm() {
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit">Update Connection</Button>
|
||||
{isDirty && (
|
||||
<Button type="submit">Update Connection</Button>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
|
||||
@@ -62,8 +62,14 @@ export async function setupWindowCloseHandler(): Promise<void> {
|
||||
const { useServerStore } = await import('@/stores/serverStore');
|
||||
const keepRunning = useServerStore.getState().keepServerRunningOnClose;
|
||||
|
||||
if (!keepRunning) {
|
||||
// Stop server before closing
|
||||
// Check if server was started by this app instance
|
||||
// In dev mode, serverStartedByApp will be false, so we won't try to stop a separately-run server
|
||||
// We need to access the module-level variable - this is a bit hacky but works
|
||||
// @ts-expect-error - accessing module-level variable from another module
|
||||
const serverStartedByApp = window.__voiceboxServerStartedByApp ?? false;
|
||||
|
||||
if (!keepRunning && serverStartedByApp) {
|
||||
// Stop server before closing (only if we started it)
|
||||
try {
|
||||
await stopServer();
|
||||
} catch (error) {
|
||||
|
||||
@@ -706,6 +706,7 @@ async def trigger_model_download(request: models.ModelDownloadRequest):
|
||||
async def startup_event():
|
||||
"""Run on application startup."""
|
||||
print("voicebox API starting up...")
|
||||
database.init_db()
|
||||
print(f"Database initialized at {database._db_path}")
|
||||
print(f"GPU available: {torch.cuda.is_available()}")
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"scripts": {
|
||||
"dev": "cd tauri && bun run tauri dev",
|
||||
"dev:web": "cd web && bun run dev",
|
||||
"dev:backend": "source backend/venv/bin/activate && uvicorn backend.main:app --reload --port 8000",
|
||||
"dev:server": "source backend/venv/bin/activate && uvicorn backend.main:app --reload --port 8000",
|
||||
"build": "cd tauri && bun run tauri build",
|
||||
"build:web": "cd web && bun run build",
|
||||
"generate:api": "./scripts/generate-api.sh",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>voicebox</string>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>voicebox</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,3 +1,56 @@
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
// Compile macOS Liquid Glass icon
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let project_root = env!("CARGO_MANIFEST_DIR");
|
||||
// AppIcon.icon is in the root, two levels up from src-tauri
|
||||
let icon_source = format!("{}/../../AppIcon.icon", project_root);
|
||||
let gen_dir = format!("{}/gen", project_root);
|
||||
|
||||
std::fs::create_dir_all(&gen_dir).expect("Failed to create gen directory");
|
||||
|
||||
if std::path::Path::new(&icon_source).exists() {
|
||||
println!("cargo:rerun-if-changed={}", icon_source);
|
||||
println!("cargo:rerun-if-changed={}/icon.json", icon_source);
|
||||
println!("cargo:rerun-if-changed={}/Assets", icon_source);
|
||||
|
||||
let partial_plist = format!("{}/partial.plist", gen_dir);
|
||||
let output = Command::new("xcrun")
|
||||
.args([
|
||||
"actool",
|
||||
"--compile", &gen_dir,
|
||||
"--output-format", "human-readable-text",
|
||||
"--output-partial-info-plist", &partial_plist,
|
||||
"--app-icon", "voicebox",
|
||||
"--include-all-app-icons",
|
||||
"--target-device", "mac",
|
||||
"--minimum-deployment-target", "11.0",
|
||||
"--platform", "macosx",
|
||||
&icon_source,
|
||||
])
|
||||
.output();
|
||||
|
||||
match output {
|
||||
Ok(output) => {
|
||||
if !output.status.success() {
|
||||
eprintln!("actool stderr: {}", String::from_utf8_lossy(&output.stderr));
|
||||
eprintln!("actool stdout: {}", String::from_utf8_lossy(&output.stdout));
|
||||
panic!("actool failed to compile icon");
|
||||
}
|
||||
println!("Successfully compiled icon to {}", gen_dir);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to execute xcrun actool: {}", e);
|
||||
eprintln!("Make sure you have Xcode Command Line Tools installed");
|
||||
panic!("Icon compilation failed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("cargo:warning=Icon source not found at {}, skipping icon compilation", icon_source);
|
||||
}
|
||||
}
|
||||
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -24,8 +24,12 @@
|
||||
],
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "10.15"
|
||||
}
|
||||
"minimumSystemVersion": "11.0",
|
||||
"infoPlist": "Info.plist"
|
||||
},
|
||||
"resources": [
|
||||
"gen/**/*"
|
||||
]
|
||||
},
|
||||
"app": {
|
||||
"security": {
|
||||
|
||||