mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-26 13:45:16 -07:00
Initialize voicebox project with backend, frontend, and Tauri setup. Added configuration files, dependencies, and basic structure for components, hooks, and utilities. Included README and setup documentation for guidance.
This commit is contained in:
@@ -0,0 +1,754 @@
|
||||
# Tauri App Plan
|
||||
|
||||
Plan for building voicebox as a Tauri 2.0 desktop app with shared frontend code for web deployment.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
voicebox/
|
||||
├── app/ # Shared React frontend (used by both web & desktop)
|
||||
│ ├── src/
|
||||
│ │ ├── components/
|
||||
│ │ │ ├── VoiceProfiles/
|
||||
│ │ │ ├── Generation/
|
||||
│ │ │ ├── AudioStudio/
|
||||
│ │ │ ├── History/
|
||||
│ │ │ └── ServerSettings/
|
||||
│ │ ├── lib/
|
||||
│ │ │ ├── api/ # Generated OpenAPI client
|
||||
│ │ │ ├── hooks/ # React Query hooks
|
||||
│ │ │ └── utils/
|
||||
│ │ ├── types/
|
||||
│ │ ├── App.tsx
|
||||
│ │ └── main.tsx
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.json
|
||||
│ ├── vite.config.ts
|
||||
│ └── tailwind.config.ts
|
||||
│
|
||||
├── tauri/ # Tauri desktop app
|
||||
│ ├── src/ # Thin wrapper, imports from ../app
|
||||
│ │ └── main.tsx # Entry point that renders App from ../app
|
||||
│ ├── src-tauri/ # Rust backend
|
||||
│ │ ├── src/
|
||||
│ │ │ └── main.rs
|
||||
│ │ ├── icons/
|
||||
│ │ ├── binaries/ # Bundled Python server
|
||||
│ │ │ ├── voicebox-server-x86_64-apple-darwin
|
||||
│ │ │ ├── voicebox-server-aarch64-apple-darwin
|
||||
│ │ │ ├── voicebox-server-x86_64-unknown-linux-gnu
|
||||
│ │ │ └── voicebox-server-x86_64-pc-windows-msvc.exe
|
||||
│ │ ├── capabilities/
|
||||
│ │ │ └── default.json
|
||||
│ │ ├── Cargo.toml
|
||||
│ │ ├── Cargo.lock
|
||||
│ │ ├── tauri.conf.json
|
||||
│ │ └── build.rs
|
||||
│ ├── package.json
|
||||
│ └── vite.config.ts # Points to ../app
|
||||
│
|
||||
├── web/ # Web deployment
|
||||
│ ├── src/
|
||||
│ │ └── main.tsx # Entry point that renders App from ../app
|
||||
│ ├── package.json
|
||||
│ └── vite.config.ts # Points to ../app
|
||||
│
|
||||
├── backend/ # Python FastAPI server
|
||||
│ ├── main.py
|
||||
│ ├── models.py
|
||||
│ ├── tts.py
|
||||
│ ├── transcribe.py
|
||||
│ ├── profiles.py
|
||||
│ ├── history.py
|
||||
│ ├── studio.py
|
||||
│ ├── database.py
|
||||
│ ├── utils/
|
||||
│ ├── requirements.txt
|
||||
│ └── build_binary.py # PyInstaller build script
|
||||
│
|
||||
├── scripts/
|
||||
│ ├── build-server.sh # Build Python server for all platforms
|
||||
│ └── generate-api.sh # Generate OpenAPI client
|
||||
│
|
||||
├── package.json # Root workspace config (Bun workspaces)
|
||||
└── bun.lockb # Bun lockfile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Desktop App (Tauri 2.0)
|
||||
- **Tauri 2.9.5+** - Latest stable version
|
||||
- **Rust** - Tauri backend
|
||||
- **React + TypeScript** - Frontend (shared with web)
|
||||
- **Vite** - Build tool
|
||||
- **Bun** - Fast package manager and JavaScript runtime
|
||||
|
||||
### Shared Frontend
|
||||
- **React 18+** - UI framework
|
||||
- **TypeScript** - Type safety
|
||||
- **Vite** - Build tool and dev server
|
||||
- **Bun** - Package manager (faster than npm/yarn/pnpm)
|
||||
- **React Query** - Server state management
|
||||
- **Zustand** - Client state management
|
||||
- **Tailwind CSS** - Styling
|
||||
- **WaveSurfer.js** - Audio visualization
|
||||
|
||||
### Backend Bundling
|
||||
- **PyInstaller** - Bundle Python server as standalone binary
|
||||
- **FastAPI** - Python web framework
|
||||
- **Tauri Sidecar** - Execute bundled Python server
|
||||
|
||||
---
|
||||
|
||||
## Bundling Python Server with Tauri
|
||||
|
||||
### 1. Build Python Server as Standalone Binary
|
||||
|
||||
**Using PyInstaller:**
|
||||
```python
|
||||
# backend/build_binary.py
|
||||
import PyInstaller.__main__
|
||||
import sys
|
||||
import os
|
||||
|
||||
def build_server():
|
||||
PyInstaller.__main__.run([
|
||||
'main.py',
|
||||
'--onefile',
|
||||
'--name', 'voicebox-server',
|
||||
'--add-data', 'data:data', # Include data files
|
||||
'--hidden-import', 'torch',
|
||||
'--hidden-import', 'transformers',
|
||||
'--hidden-import', 'fastapi',
|
||||
'--collect-all', 'qwen-tts',
|
||||
'--noconfirm',
|
||||
])
|
||||
|
||||
if __name__ == '__main__':
|
||||
build_server()
|
||||
```
|
||||
|
||||
**Build script for all platforms:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# scripts/build-server.sh
|
||||
|
||||
# Determine platform
|
||||
PLATFORM=$(rustc --print host-tuple)
|
||||
|
||||
# Build Python binary
|
||||
cd backend
|
||||
python build_binary.py
|
||||
|
||||
# Rename with platform triple
|
||||
cd dist
|
||||
mv voicebox-server ../src-tauri/binaries/voicebox-server-${PLATFORM}
|
||||
|
||||
echo "Built voicebox-server-${PLATFORM}"
|
||||
```
|
||||
|
||||
**Platform-specific binaries needed:**
|
||||
- macOS Intel: `voicebox-server-x86_64-apple-darwin`
|
||||
- macOS ARM: `voicebox-server-aarch64-apple-darwin`
|
||||
- Linux: `voicebox-server-x86_64-unknown-linux-gnu`
|
||||
- Windows: `voicebox-server-x86_64-pc-windows-msvc.exe`
|
||||
|
||||
### 2. Configure Tauri to Bundle Binary
|
||||
|
||||
**tauri/src-tauri/tauri.conf.json:**
|
||||
```json
|
||||
{
|
||||
"bundle": {
|
||||
"identifier": "sh.voicebox.app",
|
||||
"externalBin": [
|
||||
"binaries/voicebox-server"
|
||||
],
|
||||
"resources": [
|
||||
"binaries/*"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"beforeBuildCommand": "bun run build",
|
||||
"devPath": "http://localhost:5173",
|
||||
"distDir": "../dist"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Capabilities (src-tauri/capabilities/default.json):**
|
||||
```json
|
||||
{
|
||||
"identifier": "default",
|
||||
"description": "Default permissions",
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"shell:allow-execute",
|
||||
"shell:allow-spawn",
|
||||
"fs:default"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Launch Python Server from Tauri
|
||||
|
||||
**tauri/src-tauri/src/main.rs:**
|
||||
```rust
|
||||
use tauri::{command, Manager};
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
use std::sync::Mutex;
|
||||
|
||||
struct ServerState {
|
||||
child: Mutex<Option<tauri_plugin_shell::process::CommandChild>>,
|
||||
}
|
||||
|
||||
#[command]
|
||||
async fn start_server(app: tauri::AppHandle, state: tauri::State<'_, ServerState>) -> Result<String, String> {
|
||||
let sidecar = app.shell()
|
||||
.sidecar("voicebox-server")
|
||||
.map_err(|e| format!("Failed to get sidecar: {}", e))?;
|
||||
|
||||
let (mut rx, child) = sidecar
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to spawn: {}", e))?;
|
||||
|
||||
// Store child process
|
||||
*state.child.lock().unwrap() = Some(child);
|
||||
|
||||
// Wait for server to be ready (listen for startup log)
|
||||
tokio::spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
if let tauri_plugin_shell::process::CommandEvent::Stdout(line) = event {
|
||||
if String::from_utf8_lossy(&line).contains("Uvicorn running") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok("Server started on http://localhost:8000".to_string())
|
||||
}
|
||||
|
||||
#[command]
|
||||
async fn stop_server(state: tauri::State<'_, ServerState>) -> Result<(), String> {
|
||||
if let Some(child) = state.child.lock().unwrap().take() {
|
||||
child.kill().map_err(|e| format!("Failed to kill: {}", e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.manage(ServerState {
|
||||
child: Mutex::new(None),
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![start_server, stop_server])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Call from Frontend
|
||||
|
||||
**app/src/lib/hooks/useServer.ts:**
|
||||
```typescript
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { useState } from 'react';
|
||||
|
||||
export function useServer() {
|
||||
const [serverUrl, setServerUrl] = useState<string>('http://localhost:8000');
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
const startServer = async () => {
|
||||
try {
|
||||
const url = await invoke<string>('start_server');
|
||||
setServerUrl(url);
|
||||
setIsRunning(true);
|
||||
return url;
|
||||
} catch (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const stopServer = async () => {
|
||||
try {
|
||||
await invoke('stop_server');
|
||||
setIsRunning(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to stop server:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return { serverUrl, isRunning, startServer, stopServer };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shared Frontend Approach
|
||||
|
||||
### App Package Structure
|
||||
|
||||
The `app/` directory contains all React code that's shared between desktop and web.
|
||||
|
||||
**app/vite.config.ts:**
|
||||
```typescript
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, 'src/main.tsx'),
|
||||
formats: ['es'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['react', 'react-dom'],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Tauri Wrapper
|
||||
|
||||
**tauri/src/main.tsx:**
|
||||
```typescript
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from '../app/src/App';
|
||||
import '../app/src/index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
```
|
||||
|
||||
**tauri/vite.config.ts:**
|
||||
```typescript
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '../app/src'),
|
||||
},
|
||||
},
|
||||
clearScreen: false,
|
||||
server: {
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
},
|
||||
envPrefix: ['VITE_', 'TAURI_'],
|
||||
build: {
|
||||
target: 'es2021',
|
||||
minify: !process.env.TAURI_DEBUG,
|
||||
sourcemap: !!process.env.TAURI_DEBUG,
|
||||
outDir: 'dist',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**tauri/package.json:**
|
||||
```json
|
||||
{
|
||||
"name": "@voicebox/tauri",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Web Wrapper
|
||||
|
||||
**web/src/main.tsx:**
|
||||
```typescript
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from '../../app/src/App';
|
||||
import '../../app/src/index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
```
|
||||
|
||||
**web/vite.config.ts:**
|
||||
```typescript
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, '../app/src'),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Initial Setup
|
||||
|
||||
```bash
|
||||
# Install Bun (if not installed)
|
||||
curl -fsSL https://bun.sh/install | bash
|
||||
|
||||
# Create Tauri app with official CLI
|
||||
cd voicebox
|
||||
bunx create-tauri-app tauri
|
||||
|
||||
# Move app code to shared directory
|
||||
mkdir app
|
||||
# Move tauri/src/* to app/src/
|
||||
|
||||
# Create web directory
|
||||
mkdir web
|
||||
cd web
|
||||
bunx create-vite . --template react-ts
|
||||
|
||||
# Setup Bun workspace in root package.json
|
||||
cat > package.json << 'EOF'
|
||||
{
|
||||
"name": "voicebox",
|
||||
"private": true,
|
||||
"workspaces": ["app", "tauri", "web"]
|
||||
}
|
||||
EOF
|
||||
|
||||
# Install all dependencies
|
||||
bun install
|
||||
```
|
||||
|
||||
### 2. Development
|
||||
|
||||
**Terminal 1 - Backend (Python FastAPI):**
|
||||
```bash
|
||||
cd backend
|
||||
python -m venv venv
|
||||
source venv/bin/activate # or venv\Scripts\activate on Windows
|
||||
pip install -r requirements.txt
|
||||
uvicorn main:app --reload --port 8000
|
||||
```
|
||||
|
||||
**Terminal 2 - Frontend (Tauri dev mode):**
|
||||
```bash
|
||||
cd tauri
|
||||
bun run tauri dev
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Start Vite dev server on port 5173
|
||||
2. Launch Tauri window pointing to localhost:5173
|
||||
3. Hot reload on code changes
|
||||
|
||||
**For web development:**
|
||||
```bash
|
||||
cd web
|
||||
bun run dev
|
||||
```
|
||||
|
||||
### 3. Building for Production
|
||||
|
||||
**Build Python server:**
|
||||
```bash
|
||||
./scripts/build-server.sh
|
||||
```
|
||||
|
||||
**Build Tauri app:**
|
||||
```bash
|
||||
cd tauri
|
||||
bun run tauri build
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Build React frontend with Vite
|
||||
2. Bundle Python server binary
|
||||
3. Create platform-specific installers:
|
||||
- macOS: `.app`, `.dmg`
|
||||
- Windows: `.exe`, `.msi`
|
||||
- Linux: `.deb`, `.AppImage`
|
||||
|
||||
**Build web app:**
|
||||
```bash
|
||||
cd web
|
||||
bun run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform-Specific Considerations
|
||||
|
||||
### macOS
|
||||
- Need both Intel and ARM builds
|
||||
- Sign and notarize for distribution outside App Store
|
||||
- Request permissions for microphone access (audio recording)
|
||||
|
||||
**tauri.conf.json additions:**
|
||||
```json
|
||||
{
|
||||
"bundle": {
|
||||
"macOS": {
|
||||
"minimumSystemVersion": "10.15",
|
||||
"entitlements": "src-tauri/Entitlements.plist"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Entitlements.plist:**
|
||||
```xml
|
||||
<?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>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
### Windows
|
||||
- Build with NSIS or WiX installer
|
||||
- Consider code signing for SmartScreen
|
||||
|
||||
### Linux
|
||||
- Provide multiple formats: .deb, .AppImage, .rpm
|
||||
- Consider Flatpak for broader distribution
|
||||
|
||||
---
|
||||
|
||||
## OpenAPI Client Generation
|
||||
|
||||
**scripts/generate-api.sh:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Start backend if not running
|
||||
if ! curl -s http://localhost:8000/openapi.json > /dev/null; then
|
||||
echo "Starting backend..."
|
||||
cd backend
|
||||
uvicorn main:app --port 8000 &
|
||||
BACKEND_PID=$!
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
# Download OpenAPI schema
|
||||
curl http://localhost:8000/openapi.json > app/openapi.json
|
||||
|
||||
# Generate TypeScript client
|
||||
cd app
|
||||
bunx openapi-typescript-codegen \
|
||||
--input openapi.json \
|
||||
--output src/lib/api \
|
||||
--client fetch
|
||||
|
||||
echo "API client generated in app/src/lib/api"
|
||||
|
||||
# Kill backend if we started it
|
||||
if [ ! -z "$BACKEND_PID" ]; then
|
||||
kill $BACKEND_PID
|
||||
fi
|
||||
```
|
||||
|
||||
**Add to package.json:**
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"generate:api": "./scripts/generate-api.sh"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server Mode Architecture
|
||||
|
||||
### Local Mode (Default)
|
||||
1. Tauri app starts
|
||||
2. App invokes `start_server` command
|
||||
3. Rust spawns bundled Python binary as sidecar
|
||||
4. Frontend connects to `http://localhost:8000`
|
||||
5. All features work locally
|
||||
|
||||
### Remote Mode (One-Click)
|
||||
1. User clicks "Start Server" on GPU machine
|
||||
2. Tauri invokes `start_server` with `--host 0.0.0.0` flag
|
||||
3. Server displays connection URL (e.g., `http://192.168.1.100:8000`)
|
||||
4. User enters URL in client app
|
||||
5. Client connects to remote server
|
||||
6. All API calls go to remote machine
|
||||
|
||||
**Rust command with args:**
|
||||
```rust
|
||||
#[command]
|
||||
async fn start_server(
|
||||
app: tauri::AppHandle,
|
||||
state: tauri::State<'_, ServerState>,
|
||||
remote: bool,
|
||||
) -> Result<String, String> {
|
||||
let mut sidecar = app.shell().sidecar("voicebox-server")
|
||||
.map_err(|e| format!("Failed to get sidecar: {}", e))?;
|
||||
|
||||
if remote {
|
||||
sidecar = sidecar.args(["--host", "0.0.0.0"]);
|
||||
}
|
||||
|
||||
// ... rest of spawn logic
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI/CD for Multi-Platform Builds
|
||||
|
||||
**GitHub Actions workflow:**
|
||||
```yaml
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [macos-latest, ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd tauri
|
||||
bun install
|
||||
|
||||
- name: Build Python server
|
||||
run: |
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
pip install pyinstaller
|
||||
python build_binary.py
|
||||
|
||||
- name: Build Tauri app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
projectPath: tauri
|
||||
tagName: v__VERSION__
|
||||
releaseName: 'voicebox v__VERSION__'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Decisions
|
||||
|
||||
### Why This Structure?
|
||||
|
||||
1. **Shared `app/` directory** - Single source of truth for UI code
|
||||
2. **Thin wrappers** - `tauri/` and `web/` just configure build tools
|
||||
3. **Sidecar pattern** - Bundle Python server without modifying Tauri core
|
||||
4. **PyInstaller** - Creates standalone Python binary with all dependencies
|
||||
5. **Platform-specific binaries** - Tauri automatically selects correct binary per platform
|
||||
|
||||
### Benefits
|
||||
|
||||
- ✅ No code duplication between web and desktop
|
||||
- ✅ Python server bundled - users don't install Python
|
||||
- ✅ Single command to build everything
|
||||
- ✅ Type-safe API calls via OpenAPI generation
|
||||
- ✅ Native performance with Tauri
|
||||
- ✅ Web fallback for unsupported platforms
|
||||
- ✅ Fast development with Bun (20-30x faster installs than npm)
|
||||
|
||||
### Tradeoffs
|
||||
|
||||
- ⚠️ Large bundle size (Python runtime + ML models + Tauri)
|
||||
- ⚠️ Need to build Python binary for each platform
|
||||
- ⚠️ First launch slow (model loading)
|
||||
- ⚠️ Separate web build doesn't include server (requires separate backend deployment)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Set up monorepo structure
|
||||
2. Initialize Tauri app with `bunx create-tauri-app`
|
||||
3. Create shared `app/` directory
|
||||
4. Configure Vite to share code
|
||||
5. Build Python server with PyInstaller
|
||||
6. Configure Tauri sidecar
|
||||
7. Test on macOS, Windows, Linux
|
||||
8. Set up CI/CD for multi-platform builds
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Tauri 2.0 Documentation](https://v2.tauri.app/)
|
||||
- [Tauri Sidecar Guide](https://v2.tauri.app/develop/sidecar/)
|
||||
- [Bun Documentation](https://bun.sh/docs)
|
||||
- [Bun Workspaces](https://bun.sh/docs/install/workspaces)
|
||||
- [PyInstaller Documentation](https://pyinstaller.org/)
|
||||
- [React Query Documentation](https://tanstack.com/query/latest)
|
||||
- [OpenAPI TypeScript Codegen](https://github.com/ferdikoomen/openapi-typescript-codegen)
|
||||
Reference in New Issue
Block a user