From 9044b986f3ace84ad827a784f2d26031cf6763bb Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Fri, 13 Mar 2026 11:33:32 -0700 Subject: [PATCH] Move tauri imports behind platform abstraction, merge CUDA build into release workflow - Add openPath and pickDirectory to PlatformFilesystem interface - Remove direct @tauri-apps/plugin-shell and plugin-dialog imports from app - Merge build-cuda.yml into release.yml as a parallel job --- .github/workflows/build-cuda.yml | 73 ------------------- .github/workflows/release.yml | 60 +++++++++++++++ .../ServerSettings/ModelManagement.tsx | 14 +--- app/src/platform/types.ts | 2 + tauri/src/platform/filesystem.ts | 20 ++++- web/src/platform/filesystem.ts | 10 ++- 6 files changed, 91 insertions(+), 88 deletions(-) delete mode 100644 .github/workflows/build-cuda.yml diff --git a/.github/workflows/build-cuda.yml b/.github/workflows/build-cuda.yml deleted file mode 100644 index 7076b3f4..00000000 --- a/.github/workflows/build-cuda.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build CUDA Backend - -on: - workflow_dispatch: - push: - tags: - - "v*" - -jobs: - build-cuda-windows: - runs-on: windows-latest - permissions: - contents: write - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: "pip" - - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install pyinstaller - pip install -r backend/requirements.txt - - - name: Install PyTorch with CUDA 12.1 - run: | - pip install torch --index-url https://download.pytorch.org/whl/cu121 --force-reinstall --no-deps - pip install torchaudio --index-url https://download.pytorch.org/whl/cu121 - - - name: Verify CUDA support in torch - run: | - python -c "import torch; print(f'CUDA available in build: {torch.cuda.is_available()}'); print(f'CUDA version: {torch.version.cuda}')" - - - name: Build CUDA server binary - shell: bash - working-directory: backend - run: python build_binary.py --cuda - - - name: Split binary for GitHub Releases - shell: bash - run: | - python scripts/split_binary.py \ - backend/dist/voicebox-server-cuda.exe \ - --output release-assets/ - - - name: Upload split parts to GitHub Release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v1 - with: - files: | - release-assets/voicebox-server-cuda.part*.exe - release-assets/voicebox-server-cuda.sha256 - release-assets/voicebox-server-cuda.manifest - draft: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload binary as workflow artifact (for testing) - uses: actions/upload-artifact@v4 - with: - name: voicebox-server-cuda-windows - path: backend/dist/voicebox-server-cuda.exe - retention-days: 7 - - # Linux CUDA build can be added later with: - # build-cuda-linux: - # runs-on: ubuntu-22.04 - # ... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dcb78521..01aacb0f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -158,3 +158,63 @@ jobs: prerelease: false args: ${{ matrix.args }} includeUpdaterJson: true + + build-cuda-windows: + runs-on: windows-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install pyinstaller + pip install -r backend/requirements.txt + + - name: Install PyTorch with CUDA 12.1 + run: | + pip install torch --index-url https://download.pytorch.org/whl/cu121 --force-reinstall --no-deps + pip install torchaudio --index-url https://download.pytorch.org/whl/cu121 + + - name: Verify CUDA support in torch + run: | + python -c "import torch; print(f'CUDA available in build: {torch.cuda.is_available()}'); print(f'CUDA version: {torch.version.cuda}')" + + - name: Build CUDA server binary + shell: bash + working-directory: backend + run: python build_binary.py --cuda + + - name: Split binary for GitHub Releases + shell: bash + run: | + python scripts/split_binary.py \ + backend/dist/voicebox-server-cuda.exe \ + --output release-assets/ + + - name: Upload split parts to GitHub Release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + files: | + release-assets/voicebox-server-cuda.part*.exe + release-assets/voicebox-server-cuda.sha256 + release-assets/voicebox-server-cuda.manifest + draft: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload binary as workflow artifact + uses: actions/upload-artifact@v4 + with: + name: voicebox-server-cuda-windows + path: backend/dist/voicebox-server-cuda.exe + retention-days: 7 diff --git a/app/src/components/ServerSettings/ModelManagement.tsx b/app/src/components/ServerSettings/ModelManagement.tsx index b9c6c09c..5d113d89 100644 --- a/app/src/components/ServerSettings/ModelManagement.tsx +++ b/app/src/components/ServerSettings/ModelManagement.tsx @@ -446,8 +446,7 @@ export function ModelManagement() { className="text-xs text-muted-foreground h-7 px-2" onClick={async () => { try { - const { open } = await import('@tauri-apps/plugin-shell'); - await open(cacheDir.path); + await platform.filesystem.openPath(cacheDir.path); } catch { toast({ title: 'Failed to open model folder', variant: 'destructive' }); } @@ -462,14 +461,9 @@ export function ModelManagement() { className="text-xs text-muted-foreground h-7 px-2" onClick={async () => { try { - const { open: openDialog } = await import('@tauri-apps/plugin-dialog'); - const selected = await openDialog({ - directory: true, - title: 'Choose model storage folder', - }); - if (!selected) return; - const newDir = - typeof selected === 'string' ? selected : (selected as { path: string }).path; + const newDir = await platform.filesystem.pickDirectory( + 'Choose model storage folder', + ); if (!newDir) return; setPendingMigrateDir(newDir); } catch { diff --git a/app/src/platform/types.ts b/app/src/platform/types.ts index eeba4c99..ef936575 100644 --- a/app/src/platform/types.ts +++ b/app/src/platform/types.ts @@ -10,6 +10,8 @@ export interface FileFilter { export interface PlatformFilesystem { saveFile(filename: string, blob: Blob, filters?: FileFilter[]): Promise; + openPath(path: string): Promise; + pickDirectory(title: string): Promise; } export interface UpdateStatus { diff --git a/tauri/src/platform/filesystem.ts b/tauri/src/platform/filesystem.ts index ff0eaf4d..1d8ded39 100644 --- a/tauri/src/platform/filesystem.ts +++ b/tauri/src/platform/filesystem.ts @@ -1,4 +1,4 @@ -import type { PlatformFilesystem, FileFilter } from '@/platform/types'; +import type { FileFilter, PlatformFilesystem } from '@/platform/types'; export const tauriFilesystem: PlatformFilesystem = { async saveFile(filename: string, blob: Blob, filters?: FileFilter[]) { @@ -12,9 +12,8 @@ export const tauriFilesystem: PlatformFilesystem = { if (!filePath) return; // User cancelled the dialog - const resolvedPath = typeof filePath === 'string' - ? filePath - : (filePath as { path: string }).path; + const resolvedPath = + typeof filePath === 'string' ? filePath : (filePath as { path: string }).path; if (!resolvedPath) { throw new Error('Failed to resolve save path from dialog'); @@ -23,4 +22,17 @@ export const tauriFilesystem: PlatformFilesystem = { const arrayBuffer = await blob.arrayBuffer(); await writeFile(resolvedPath, new Uint8Array(arrayBuffer)); }, + + async openPath(path: string) { + const { open } = await import('@tauri-apps/plugin-shell'); + await open(path); + }, + + async pickDirectory(title: string) { + const { open } = await import('@tauri-apps/plugin-dialog'); + const selected = await open({ directory: true, title }); + if (!selected) return null; + const dir = typeof selected === 'string' ? selected : (selected as { path: string }).path; + return dir || null; + }, }; diff --git a/web/src/platform/filesystem.ts b/web/src/platform/filesystem.ts index 1f45a49c..3a871ad2 100644 --- a/web/src/platform/filesystem.ts +++ b/web/src/platform/filesystem.ts @@ -1,4 +1,4 @@ -import type { PlatformFilesystem, FileFilter } from '@/platform/types'; +import type { FileFilter, PlatformFilesystem } from '@/platform/types'; export const webFilesystem: PlatformFilesystem = { async saveFile(filename: string, blob: Blob, _filters?: FileFilter[]) { @@ -12,4 +12,12 @@ export const webFilesystem: PlatformFilesystem = { window.URL.revokeObjectURL(url); document.body.removeChild(a); }, + + async openPath(_path: string) { + // No filesystem access in browser + }, + + async pickDirectory(_title: string) { + return null; + }, };