diff --git a/.github/workflows/mobile-build.yml b/.github/workflows/mobile-build.yml
index 40e461ae..564c3113 100644
--- a/.github/workflows/mobile-build.yml
+++ b/.github/workflows/mobile-build.yml
@@ -53,6 +53,59 @@ jobs:
working-directory: ./frontend
run: bun install
+ - name: Cache ONNX Runtime iOS build
+ uses: actions/cache@v4
+ id: cache-onnxruntime
+ with:
+ path: |
+ frontend/src-tauri/onnxruntime-ios
+ frontend/src-tauri/onnxruntime-build
+ key: onnxruntime-ios-built-1.22.2-v1
+ restore-keys: |
+ onnxruntime-ios-built-1.22.2-
+
+ - name: Build ONNX Runtime for iOS from source
+ if: steps.cache-onnxruntime.outputs.cache-hit != 'true'
+ working-directory: ./frontend/src-tauri
+ run: |
+ chmod +x scripts/build-ios-onnxruntime.sh
+ ./scripts/build-ios-onnxruntime.sh 1.22.2
+ timeout-minutes: 90
+
+ - name: Verify ONNX Runtime files
+ run: |
+ echo "Checking ONNX Runtime xcframework..."
+ ls -la ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/
+ ls -la ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64/
+ echo ""
+ echo "Library info:"
+ file ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64/libonnxruntime.a
+ echo ""
+ echo "Checking for Abseil symbols (should be included):"
+ nm ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64/libonnxruntime.a 2>/dev/null | grep -i "absl" | head -20 || echo "No abseil symbols found (they may be internal)"
+
+ - name: Configure Cargo for iOS ONNX Runtime
+ run: |
+ # Create cargo config with absolute paths for iOS builds
+ # This overrides ort-sys's build script to use our built-from-source library
+ WORKSPACE="${{ github.workspace }}"
+ mkdir -p "${WORKSPACE}/frontend/src-tauri/.cargo"
+ cat > "${WORKSPACE}/frontend/src-tauri/.cargo/config.toml" << EOF
+ # Auto-generated cargo config for iOS ONNX Runtime linking
+ # Uses absolute paths because xcodebuild may run cargo from different directories
+
+ [target.aarch64-apple-ios.onnxruntime]
+ rustc-link-search = ["${WORKSPACE}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64"]
+ rustc-link-lib = ["static=onnxruntime"]
+
+ [target.aarch64-apple-ios-sim.onnxruntime]
+ rustc-link-search = ["${WORKSPACE}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64-simulator"]
+ rustc-link-lib = ["static=onnxruntime"]
+ EOF
+
+ echo "Generated cargo config:"
+ cat "${WORKSPACE}/frontend/src-tauri/.cargo/config.toml"
+
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
@@ -89,6 +142,8 @@ jobs:
VITE_OPEN_SECRET_API_URL: https://enclave.trymaple.ai
VITE_MAPLE_BILLING_API_URL: https://billing.opensecret.cloud
VITE_CLIENT_ID: ba5a14b5-d915-47b1-b7b1-afda52bc5fc6
+ # ONNX Runtime location for ort-sys crate
+ ORT_LIB_LOCATION: ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64
- name: Upload iOS App
uses: actions/upload-artifact@v4
@@ -99,7 +154,11 @@ jobs:
retention-days: 5
- name: Submit to TestFlight
- if: github.event_name == 'push' && github.ref == 'refs/heads/master'
+ # TODO: Remove ios-tts-working condition after PR #385 is merged
+ # For this PR, we want to test TestFlight submissions on every build
+ if: |
+ (github.event_name == 'push' && github.ref == 'refs/heads/master') ||
+ (github.event_name == 'pull_request' && github.head_ref == 'ios-tts-working')
run: |
# Find the actual path of the IPA file
IPA_PATH=$(find frontend/src-tauri/gen/apple/build -name "*.ipa" | head -n 1)
diff --git a/.github/workflows/testflight-on-comment.yml b/.github/workflows/testflight-on-comment.yml
index f72da15a..784cbdbc 100644
--- a/.github/workflows/testflight-on-comment.yml
+++ b/.github/workflows/testflight-on-comment.yml
@@ -3,6 +3,12 @@ name: TestFlight on Comment
on:
issue_comment:
types: [created]
+ # TODO: Remove this push trigger after PR #385 is merged
+ # This is a temporary workaround because issue_comment workflows run from master,
+ # so they don't pick up workflow changes from the PR branch
+ push:
+ branches:
+ - ios-tts-working
permissions:
contents: read
@@ -11,7 +17,8 @@ permissions:
jobs:
check-comment:
- if: github.event.issue.pull_request && contains(github.event.comment.body, 'testflight build')
+ # Only run on issue_comment events, not on push
+ if: github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, 'testflight build')
runs-on: ubuntu-latest
outputs:
should-deploy: ${{ steps.check.outputs.should-deploy }}
@@ -63,12 +70,14 @@ jobs:
deploy-testflight:
needs: check-comment
- if: needs.check-comment.outputs.should-deploy == 'true'
+ # Run on push to ios-tts branch OR when triggered by comment with approval
+ if: github.event_name == 'push' || needs.check-comment.outputs.should-deploy == 'true'
runs-on: macos-latest-xlarge
steps:
- uses: actions/checkout@v4
with:
- ref: ${{ format('refs/pull/{0}/head', needs.check-comment.outputs.pr-number) }}
+ # For push events, use the current ref; for comment events, use the PR ref
+ ref: ${{ github.event_name == 'push' && github.ref || format('refs/pull/{0}/head', needs.check-comment.outputs.pr-number) }}
- name: Setup Bun
uses: oven-sh/setup-bun@v1
@@ -111,6 +120,59 @@ jobs:
working-directory: ./frontend
run: bun install
+ - name: Cache ONNX Runtime iOS build
+ uses: actions/cache@v4
+ id: cache-onnxruntime
+ with:
+ path: |
+ frontend/src-tauri/onnxruntime-ios
+ frontend/src-tauri/onnxruntime-build
+ key: onnxruntime-ios-built-1.22.2-v1
+ restore-keys: |
+ onnxruntime-ios-built-1.22.2-
+
+ - name: Build ONNX Runtime for iOS from source
+ if: steps.cache-onnxruntime.outputs.cache-hit != 'true'
+ working-directory: ./frontend/src-tauri
+ run: |
+ chmod +x scripts/build-ios-onnxruntime.sh
+ ./scripts/build-ios-onnxruntime.sh 1.22.2
+ timeout-minutes: 90
+
+ - name: Verify ONNX Runtime files
+ run: |
+ echo "Checking ONNX Runtime xcframework..."
+ ls -la ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/
+ ls -la ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64/
+ echo ""
+ echo "Library info:"
+ file ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64/libonnxruntime.a
+ echo ""
+ echo "Checking for Abseil symbols (should be included):"
+ nm ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64/libonnxruntime.a 2>/dev/null | grep -i "absl" | head -20 || echo "No abseil symbols found (they may be internal)"
+
+ - name: Configure Cargo for iOS ONNX Runtime
+ run: |
+ # Create cargo config with absolute paths for iOS builds
+ # This overrides ort-sys's build script to use our built-from-source library
+ WORKSPACE="${{ github.workspace }}"
+ mkdir -p "${WORKSPACE}/frontend/src-tauri/.cargo"
+ cat > "${WORKSPACE}/frontend/src-tauri/.cargo/config.toml" << EOF
+ # Auto-generated cargo config for iOS ONNX Runtime linking
+ # Uses absolute paths because xcodebuild may run cargo from different directories
+
+ [target.aarch64-apple-ios.onnxruntime]
+ rustc-link-search = ["${WORKSPACE}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64"]
+ rustc-link-lib = ["static=onnxruntime"]
+
+ [target.aarch64-apple-ios-sim.onnxruntime]
+ rustc-link-search = ["${WORKSPACE}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64-simulator"]
+ rustc-link-lib = ["static=onnxruntime"]
+ EOF
+
+ echo "Generated cargo config:"
+ cat "${WORKSPACE}/frontend/src-tauri/.cargo/config.toml"
+
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
@@ -147,6 +209,8 @@ jobs:
VITE_OPEN_SECRET_API_URL: https://enclave.trymaple.ai
VITE_MAPLE_BILLING_API_URL: https://billing.opensecret.cloud
VITE_CLIENT_ID: ba5a14b5-d915-47b1-b7b1-afda52bc5fc6
+ # ONNX Runtime location for ort-sys crate
+ ORT_LIB_LOCATION: ${{ github.workspace }}/frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/ios-arm64
- name: Submit to TestFlight
run: |
diff --git a/docs/ios-tts-local-development.md b/docs/ios-tts-local-development.md
new file mode 100644
index 00000000..274167a6
--- /dev/null
+++ b/docs/ios-tts-local-development.md
@@ -0,0 +1,166 @@
+# iOS TTS Local Development Guide
+
+This guide explains how to build and test the iOS TTS (Text-to-Speech) feature locally using the iOS Simulator or a physical device.
+
+## Overview
+
+The iOS TTS feature uses ONNX Runtime to run Kokoro TTS models. ONNX Runtime must be built from source for iOS because:
+1. Pre-built binaries from HuggingFace are missing Abseil symbols
+2. We need both device (arm64) and simulator (arm64) builds
+3. The simulator build requires a workaround for a libiconv linking bug
+
+## Prerequisites
+
+- macOS with Xcode installed (16.x+)
+- CMake 3.26+
+- Python 3.8+
+- Git
+- Nix (recommended) or manually install Rust toolchain
+
+## Quick Start
+
+### 1. Build ONNX Runtime
+
+```bash
+just ios-build-onnxruntime
+```
+
+This builds ONNX Runtime for both device and simulator (~5-10 minutes) and automatically generates the cargo config.
+
+The output will be in `frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/`.
+
+### 2. Regenerate Cargo Config (if needed)
+
+If you move the project or need to regenerate the cargo config:
+
+```bash
+just ios-setup-cargo-config
+```
+
+This creates `frontend/src-tauri/.cargo/config.toml` with the correct absolute paths for your machine.
+
+### 3. Fix arm64-sim Xcode Issue (if needed)
+
+If you see this error:
+```
+clang: error: version '-sim' in target triple 'arm64-apple-ios13.0-simulator-sim' is invalid
+```
+
+See [troubleshooting-ios-build.md](./troubleshooting-ios-build.md) for details.
+
+Quick fix:
+```bash
+just ios-fix-arch
+```
+
+### 4. Run on Simulator
+
+```bash
+# Boot simulator first
+xcrun simctl boot "iPhone 16 Pro"
+
+# Run the app
+just ios-dev-sim "iPhone 16 Pro"
+```
+
+### 5. Run on Physical Device
+
+```bash
+just ios-dev
+```
+
+Note: If you have a device connected (even wirelessly), `just ios-dev` may deploy to it instead of the simulator. Use `just ios-dev-sim` to explicitly target the simulator.
+
+## Troubleshooting
+
+### Vite Server Not Reachable
+
+The iOS simulator needs to connect to your development server. Ensure `frontend/vite.config.ts` has:
+
+```typescript
+server: {
+ host: "0.0.0.0",
+ port: 5173,
+ strictPort: true
+}
+```
+
+### Missing Abseil Symbols
+
+If you see linker errors like:
+```
+Undefined symbols for architecture arm64:
+ "_AbslInternalSpinLockDelay_lts_20240722"
+```
+
+This means the ONNX Runtime library wasn't built from source. Pre-built binaries are missing these symbols. Run `./scripts/build-ios-onnxruntime-all.sh` to build from source.
+
+### Simulator Build Fails with libiconv Error
+
+If the simulator build fails with:
+```
+ld: building for 'iOS-simulator', but linking in dylib built for 'iOS'
+```
+
+This is fixed by adding `CMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER` to the cmake flags. The `build-ios-onnxruntime-all.sh` script already includes this fix.
+
+### Cargo Not Finding Library
+
+1. Ensure `.cargo/config.toml` uses absolute paths
+2. Clean and rebuild: `cd frontend/src-tauri && cargo clean`
+3. Verify the library exists: `ls -la onnxruntime-ios/onnxruntime.xcframework/ios-arm64-simulator/`
+
+## Architecture Notes
+
+### Why Build from Source?
+
+1. **Abseil symbols**: ONNX Runtime depends on Abseil (Google's C++ library). Pre-built binaries don't include these statically linked.
+
+2. **Simulator support**: Pre-built libraries often only include device builds.
+
+3. **Version compatibility**: Building from source ensures compatibility with our ort-sys Rust crate version.
+
+### Build Artifacts
+
+After building, you'll have:
+```
+frontend/src-tauri/
+├── onnxruntime-build/ # Build directory (can be deleted after build)
+│ └── onnxruntime/ # ONNX Runtime source
+└── onnxruntime-ios/ # Output directory
+ └── onnxruntime.xcframework/
+ ├── Headers/
+ ├── Info.plist
+ ├── ios-arm64/
+ │ └── libonnxruntime.a # Device library (~69MB)
+ └── ios-arm64-simulator/
+ └── libonnxruntime.a # Simulator library (~69MB)
+```
+
+### .cargo/config.toml
+
+The cargo config tells the Rust `ort-sys` crate where to find the ONNX Runtime library. The keys are:
+- `[target.aarch64-apple-ios.onnxruntime]` - Device builds
+- `[target.aarch64-apple-ios-sim.onnxruntime]` - Simulator builds (ARM64 Mac)
+- `[target.x86_64-apple-ios.onnxruntime]` - Simulator builds (Intel Mac)
+
+### CI/CD
+
+The CI workflow (`mobile-build.yml`) builds ONNX Runtime from source and caches it. The cache key includes the version number, so updating `ORT_VERSION` will trigger a rebuild.
+
+## Cleaning Up
+
+To free disk space after testing:
+
+```bash
+# Remove build directory (keeps the built xcframework)
+rm -rf frontend/src-tauri/onnxruntime-build
+
+# Remove everything (requires rebuilding)
+rm -rf frontend/src-tauri/onnxruntime-build frontend/src-tauri/onnxruntime-ios
+```
+
+## Related Documentation
+
+- [troubleshooting-ios-build.md](./troubleshooting-ios-build.md) - arm64-sim architecture fix
+- [tts-research.md](./tts-research.md) - TTS implementation details
diff --git a/frontend/bun.lock b/frontend/bun.lock
index acf319d9..847fabcd 100644
--- a/frontend/bun.lock
+++ b/frontend/bun.lock
@@ -1,5 +1,6 @@
{
"lockfileVersion": 1,
+ "configVersion": 0,
"workspaces": {
"": {
"name": "maple",
diff --git a/frontend/src-tauri/.gitignore b/frontend/src-tauri/.gitignore
index 502406b4..332ca1f9 100644
--- a/frontend/src-tauri/.gitignore
+++ b/frontend/src-tauri/.gitignore
@@ -2,3 +2,10 @@
# will have compiled files and executables
/target/
/gen/schemas
+
+# ONNX Runtime iOS (built from source)
+/onnxruntime-ios/
+/onnxruntime-build/
+
+# Generated cargo config for iOS builds
+/.cargo/
diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml
index 4bfb6eb8..7a980a37 100644
--- a/frontend/src-tauri/Cargo.toml
+++ b/frontend/src-tauri/Cargo.toml
@@ -53,5 +53,22 @@ futures-util = "0.3"
dirs = "5.0"
sha2 = "0.10"
+[target.'cfg(target_os = "ios")'.dependencies]
+# TTS dependencies (Supertonic) - iOS
+# We build ONNX Runtime 1.22.2 from source for iOS (see scripts/build-ios-onnxruntime.sh)
+# We disable download-binaries and copy-dylibs since we link our own xcframework
+# Need "std" for Error trait impl and file operations, "ndarray" for tensor creation
+ort = { version = "2.0.0-rc.10", default-features = false, features = ["std", "ndarray"] }
+ndarray = { version = "0.16" }
+rand = "0.8"
+rand_distr = "0.4"
+hound = "3.5"
+unicode-normalization = "0.1"
+regex = "1.10"
+reqwest = { version = "0.12", features = ["stream"] }
+futures-util = "0.3"
+dirs = "5.0"
+sha2 = "0.10"
+
[target.'cfg(target_os = "android")'.dependencies]
openssl = { version = "0.10", default-features = false, features = ["vendored"] }
diff --git a/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj b/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj
index 435919c9..b26d4c6e 100644
--- a/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj
+++ b/frontend/src-tauri/gen/apple/maple.xcodeproj/project.pbxproj
@@ -251,18 +251,15 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ARCHS = (
- arm64,
- "arm64-sim",
- );
+ ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = maple_iOS/maple_iOS.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = X773Y823TN;
+ DEVELOPMENT_TEAM = "X773Y823TN";
ENABLE_BITCODE = NO;
- "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
- "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
+ "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\".\"",
@@ -294,11 +291,11 @@
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
);
PRODUCT_BUNDLE_IDENTIFIER = cloud.opensecret.maple;
- PRODUCT_NAME = Maple;
+ PRODUCT_NAME = "Maple";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
- VALID_ARCHS = "arm64 arm64-sim";
+ VALID_ARCHS = arm64;
};
name = debug;
};
@@ -425,18 +422,15 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ARCHS = (
- arm64,
- "arm64-sim",
- );
+ ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = maple_iOS/maple_iOS.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = X773Y823TN;
+ DEVELOPMENT_TEAM = "X773Y823TN";
ENABLE_BITCODE = NO;
- "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
- "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
+ "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\".\"",
@@ -468,11 +462,11 @@
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
);
PRODUCT_BUNDLE_IDENTIFIER = cloud.opensecret.maple;
- PRODUCT_NAME = Maple;
+ PRODUCT_NAME = "Maple";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
- VALID_ARCHS = "arm64 arm64-sim";
+ VALID_ARCHS = arm64;
};
name = release;
};
@@ -543,20 +537,17 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ARCHS = (
- arm64,
- "arm64-sim",
- );
+ ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = maple_iOS/maple_iOS.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
- DEVELOPMENT_TEAM = X773Y823TN;
+ DEVELOPMENT_TEAM = "X773Y823TN";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = X773Y823TN;
ENABLE_BITCODE = NO;
- "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
- "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
+ "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\".\"",
@@ -588,12 +579,12 @@
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
);
PRODUCT_BUNDLE_IDENTIFIER = cloud.opensecret.maple;
- PRODUCT_NAME = Maple;
+ PRODUCT_NAME = "Maple";
PROVISIONING_PROFILE_SPECIFIER = "86059ea7-ae8e-44af-8a58-b2ab7c78d299";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "86059ea7-ae8e-44af-8a58-b2ab7c78d299";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
- VALID_ARCHS = "arm64 arm64-sim";
+ VALID_ARCHS = arm64;
};
name = local;
};
diff --git a/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh b/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh
new file mode 100755
index 00000000..390c2aa8
--- /dev/null
+++ b/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh
@@ -0,0 +1,212 @@
+#!/bin/bash
+# Build ONNX Runtime from source for iOS (device + simulator)
+# This creates a static library with all dependencies (including Abseil) statically linked
+#
+# Prerequisites:
+# - macOS with Xcode installed
+# - CMake 3.26+
+# - Python 3.8+
+# - Git
+#
+# Usage: ./build-ios-onnxruntime-all.sh [version]
+# Example: ./build-ios-onnxruntime-all.sh 1.22.2
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+TAURI_DIR="$(dirname "$SCRIPT_DIR")"
+ORT_VERSION="${1:-1.22.2}"
+BUILD_DIR="${TAURI_DIR}/onnxruntime-build"
+OUTPUT_DIR="${TAURI_DIR}/onnxruntime-ios"
+XCFRAMEWORK_DIR="${OUTPUT_DIR}/onnxruntime.xcframework"
+
+IOS_DEPLOYMENT_TARGET="13.0"
+
+echo "========================================"
+echo "Building ONNX Runtime ${ORT_VERSION} for iOS"
+echo "(Device + Simulator)"
+echo "========================================"
+echo "Build directory: ${BUILD_DIR}"
+echo "Output directory: ${OUTPUT_DIR}"
+echo ""
+
+# Check prerequisites
+command -v cmake >/dev/null 2>&1 || { echo "Error: cmake is required"; exit 1; }
+command -v python3 >/dev/null 2>&1 || { echo "Error: python3 is required"; exit 1; }
+command -v git >/dev/null 2>&1 || { echo "Error: git is required"; exit 1; }
+command -v xcodebuild >/dev/null 2>&1 || { echo "Error: Xcode is required"; exit 1; }
+
+# Check if output already exists
+if [ -d "$XCFRAMEWORK_DIR" ] && [ -f "${XCFRAMEWORK_DIR}/ios-arm64/libonnxruntime.a" ] && [ -f "${XCFRAMEWORK_DIR}/ios-arm64-simulator/libonnxruntime.a" ]; then
+ echo "ONNX Runtime xcframework already exists with both device and simulator libraries"
+ echo "To rebuild, remove: rm -rf $OUTPUT_DIR"
+ exit 0
+fi
+
+mkdir -p "$BUILD_DIR"
+cd "$BUILD_DIR"
+
+# Clone ONNX Runtime
+clone_with_retry() {
+ local max_attempts=3
+ local attempt=1
+ while [ $attempt -le $max_attempts ]; do
+ echo "Attempt $attempt of $max_attempts..."
+ if git clone --depth 1 --branch "v${ORT_VERSION}" --recursive https://github.com/microsoft/onnxruntime.git; then
+ return 0
+ fi
+ sleep 10
+ attempt=$((attempt + 1))
+ done
+ return 1
+}
+
+if [ ! -d "onnxruntime" ]; then
+ echo "Cloning ONNX Runtime repository..."
+ clone_with_retry
+fi
+
+cd onnxruntime
+
+# Common cmake defines
+CMAKE_EXTRA_DEFINES="CMAKE_POLICY_VERSION_MINIMUM=3.5"
+
+# Function to build and combine libraries
+build_and_combine() {
+ local SYSROOT=$1
+ local OUTPUT_SUFFIX=$2
+ local EXTRA_CMAKE_DEFINES=$3
+
+ echo ""
+ echo "========================================"
+ echo "Building for ${SYSROOT} (arm64)..."
+ echo "========================================"
+
+ # Clean previous build for this target
+ rm -rf "build/iOS/Release"
+
+ ./build.sh \
+ --build_dir build/iOS \
+ --config Release \
+ --use_xcode \
+ --ios \
+ --apple_sysroot "${SYSROOT}" \
+ --osx_arch arm64 \
+ --apple_deploy_target "${IOS_DEPLOYMENT_TARGET}" \
+ --parallel \
+ --skip_tests \
+ --compile_no_warning_as_error \
+ --cmake_extra_defines "${CMAKE_EXTRA_DEFINES} ${EXTRA_CMAKE_DEFINES}"
+
+ # Find and combine libraries
+ local BUILD_OUTPUT_DIR="build/iOS/Release/Release-${SYSROOT}"
+ local COMBINED_LIB="${BUILD_OUTPUT_DIR}/libonnxruntime_combined.a"
+
+ echo "Combining static libraries..."
+ local LIBS=$(find build/iOS/Release -name "*.a" -path "*Release-${SYSROOT}*" -type f | grep -v "gtest\|gmock" | sort -u)
+
+ if [ -z "$LIBS" ]; then
+ echo "Error: No libraries found for ${SYSROOT}"
+ return 1
+ fi
+
+ libtool -static -o "$COMBINED_LIB" $LIBS 2>&1 | grep -v "warning duplicate member" || true
+
+ if [ ! -f "$COMBINED_LIB" ]; then
+ echo "Error: Failed to create combined library"
+ return 1
+ fi
+
+ echo "Created: $COMBINED_LIB ($(du -h "$COMBINED_LIB" | cut -f1))"
+
+ # Copy to output
+ mkdir -p "${XCFRAMEWORK_DIR}/${OUTPUT_SUFFIX}"
+ cp "$COMBINED_LIB" "${XCFRAMEWORK_DIR}/${OUTPUT_SUFFIX}/libonnxruntime.a"
+}
+
+# Create output directories
+mkdir -p "${OUTPUT_DIR}"
+mkdir -p "${XCFRAMEWORK_DIR}/Headers"
+
+# Build for device
+build_and_combine "iphoneos" "ios-arm64" ""
+
+# Build for simulator
+# CMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER fixes the libiconv linking bug
+build_and_combine "iphonesimulator" "ios-arm64-simulator" "CMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER"
+
+# Copy headers
+HEADER_DIR=$(find build -name "onnxruntime_c_api.h" -type f | head -n 1 | xargs dirname 2>/dev/null)
+if [ -n "$HEADER_DIR" ]; then
+ cp "${HEADER_DIR}"/*.h "${XCFRAMEWORK_DIR}/Headers/" 2>/dev/null || true
+fi
+if [ -d "include/onnxruntime/core/session" ]; then
+ cp include/onnxruntime/core/session/*.h "${XCFRAMEWORK_DIR}/Headers/" 2>/dev/null || true
+fi
+
+# Create Info.plist
+cat > "${XCFRAMEWORK_DIR}/Info.plist" << 'PLIST'
+
+
+
+
+ AvailableLibraries
+
+
+ HeadersPath
+ Headers
+ LibraryIdentifier
+ ios-arm64
+ LibraryPath
+ libonnxruntime.a
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ ios
+
+
+ HeadersPath
+ Headers
+ LibraryIdentifier
+ ios-arm64-simulator
+ LibraryPath
+ libonnxruntime.a
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ ios
+ SupportedPlatformVariant
+ simulator
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
+PLIST
+
+echo ""
+echo "========================================"
+echo "Build complete!"
+echo "========================================"
+echo ""
+echo "xcframework: ${XCFRAMEWORK_DIR}"
+echo ""
+echo "Libraries:"
+ls -lh "${XCFRAMEWORK_DIR}/ios-arm64/libonnxruntime.a"
+ls -lh "${XCFRAMEWORK_DIR}/ios-arm64-simulator/libonnxruntime.a"
+# Generate cargo config
+echo ""
+echo "Generating .cargo/config.toml..."
+"${SCRIPT_DIR}/setup-ios-cargo-config.sh"
+
+echo ""
+echo "Next steps:"
+echo "1. Fix arm64-sim issue if needed (see docs/troubleshooting-ios-build.md)"
+echo "2. Run: just ios-dev-sim 'iPhone 16 Pro'"
diff --git a/frontend/src-tauri/scripts/build-ios-onnxruntime.sh b/frontend/src-tauri/scripts/build-ios-onnxruntime.sh
new file mode 100755
index 00000000..8ae0971b
--- /dev/null
+++ b/frontend/src-tauri/scripts/build-ios-onnxruntime.sh
@@ -0,0 +1,299 @@
+#!/bin/bash
+# Build ONNX Runtime from source for iOS
+# This creates a static library with all dependencies (including Abseil) statically linked
+#
+# Prerequisites:
+# - macOS with Xcode installed
+# - CMake 3.26+
+# - Python 3.8+
+# - Git
+#
+# Usage: ./build-ios-onnxruntime.sh [version]
+# Example: ./build-ios-onnxruntime.sh 1.20.1
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+TAURI_DIR="$(dirname "$SCRIPT_DIR")"
+# Use latest 1.22.2 - older versions have Eigen hash mismatch issues with GitLab
+ORT_VERSION="${1:-1.22.2}"
+BUILD_DIR="${TAURI_DIR}/onnxruntime-build"
+OUTPUT_DIR="${TAURI_DIR}/onnxruntime-ios"
+XCFRAMEWORK_DIR="${OUTPUT_DIR}/onnxruntime.xcframework"
+
+# Minimum iOS version to support
+IOS_DEPLOYMENT_TARGET="13.0"
+
+echo "========================================"
+echo "Building ONNX Runtime ${ORT_VERSION} for iOS"
+echo "========================================"
+echo "Build directory: ${BUILD_DIR}"
+echo "Output directory: ${OUTPUT_DIR}"
+echo "iOS deployment target: ${IOS_DEPLOYMENT_TARGET}"
+echo ""
+
+# Check prerequisites
+command -v cmake >/dev/null 2>&1 || { echo "Error: cmake is required but not installed."; exit 1; }
+command -v python3 >/dev/null 2>&1 || { echo "Error: python3 is required but not installed."; exit 1; }
+command -v git >/dev/null 2>&1 || { echo "Error: git is required but not installed."; exit 1; }
+command -v xcodebuild >/dev/null 2>&1 || { echo "Error: Xcode is required but not installed."; exit 1; }
+
+# Check if output already exists
+if [ -d "$XCFRAMEWORK_DIR" ]; then
+ echo "ONNX Runtime xcframework already exists at $XCFRAMEWORK_DIR"
+ echo "To rebuild, remove the directory first: rm -rf $OUTPUT_DIR"
+ exit 0
+fi
+
+# Create build directory
+mkdir -p "$BUILD_DIR"
+cd "$BUILD_DIR"
+
+# Clone ONNX Runtime if not already cloned (with retry for transient network errors)
+clone_with_retry() {
+ local max_attempts=3
+ local attempt=1
+ while [ $attempt -le $max_attempts ]; do
+ echo "Attempt $attempt of $max_attempts..."
+ if git clone --depth 1 --branch "v${ORT_VERSION}" --recursive https://github.com/microsoft/onnxruntime.git; then
+ return 0
+ fi
+ echo "Clone failed, waiting 10 seconds before retry..."
+ sleep 10
+ attempt=$((attempt + 1))
+ done
+ echo "Failed to clone after $max_attempts attempts"
+ return 1
+}
+
+submodule_update_with_retry() {
+ local max_attempts=3
+ local attempt=1
+ while [ $attempt -le $max_attempts ]; do
+ echo "Attempt $attempt of $max_attempts..."
+ if git submodule update --init --recursive; then
+ return 0
+ fi
+ echo "Submodule update failed, waiting 10 seconds before retry..."
+ sleep 10
+ attempt=$((attempt + 1))
+ done
+ echo "Failed to update submodules after $max_attempts attempts"
+ return 1
+}
+
+if [ ! -d "onnxruntime" ]; then
+ echo "Cloning ONNX Runtime repository..."
+ clone_with_retry
+else
+ echo "ONNX Runtime repository already cloned"
+ cd onnxruntime
+ git fetch --tags
+ git checkout "v${ORT_VERSION}"
+ submodule_update_with_retry
+ cd ..
+fi
+
+cd onnxruntime
+
+# Common cmake extra defines to work around compatibility issues
+# CMAKE_POLICY_VERSION_MINIMUM=3.5 fixes nsync compatibility with newer CMake
+CMAKE_EXTRA_DEFINES="CMAKE_POLICY_VERSION_MINIMUM=3.5"
+
+# Build for iOS device (arm64)
+echo ""
+echo "========================================"
+echo "Building for iOS device (arm64)..."
+echo "========================================"
+
+./build.sh \
+ --config Release \
+ --use_xcode \
+ --ios \
+ --apple_sysroot iphoneos \
+ --osx_arch arm64 \
+ --apple_deploy_target "${IOS_DEPLOYMENT_TARGET}" \
+ --parallel \
+ --skip_tests \
+ --compile_no_warning_as_error \
+ --cmake_extra_defines "${CMAKE_EXTRA_DEFINES}"
+
+# ONNX Runtime builds multiple static libraries, we need to combine them
+# The libraries are in build/iOS/Release/Release-iphoneos/
+IOS_ARM64_BUILD_DIR="build/iOS/Release/Release-iphoneos"
+IOS_ARM64_COMBINED_LIB="${IOS_ARM64_BUILD_DIR}/libonnxruntime_combined.a"
+
+echo ""
+echo "Combining iOS arm64 static libraries..."
+
+# Find all ONNX Runtime static libraries and combine them
+# We need: onnxruntime_*, onnx*, protobuf-lite, re2, cpuinfo, abseil libs, etc.
+IOS_ARM64_LIBS=$(find build/iOS/Release -name "*.a" -path "*Release-iphoneos*" -type f | grep -v "gtest\|gmock" | sort -u)
+
+if [ -z "$IOS_ARM64_LIBS" ]; then
+ echo "Error: Could not find iOS arm64 static libraries"
+ exit 1
+fi
+
+echo "Found libraries to combine:"
+echo "$IOS_ARM64_LIBS" | head -20
+echo "..."
+
+# Use libtool to combine all static libraries into one
+libtool -static -o "$IOS_ARM64_COMBINED_LIB" $IOS_ARM64_LIBS
+
+if [ ! -f "$IOS_ARM64_COMBINED_LIB" ]; then
+ echo "Error: Failed to create combined library"
+ exit 1
+fi
+
+IOS_ARM64_LIB="$IOS_ARM64_COMBINED_LIB"
+echo "Created combined library: $IOS_ARM64_LIB"
+ls -lh "$IOS_ARM64_LIB"
+
+# SKIP SIMULATOR BUILD for now
+# The simulator build has a bug where it tries to link against the wrong iconv library:
+# "ld: building for 'iOS-simulator', but linking in dylib built for 'iOS'"
+# For TestFlight/App Store deployment, we only need the device build anyway.
+# Local development can use the desktop version or a physical device.
+echo ""
+echo "Skipping iOS simulator build (known ONNX Runtime CMake bug with libiconv)"
+echo "Device build is sufficient for TestFlight deployment"
+IOS_SIM_ARM64_LIB=""
+
+HAS_SIM_LIB=false
+if [ -n "$IOS_SIM_ARM64_LIB" ] && [ -f "$IOS_SIM_ARM64_LIB" ]; then
+ HAS_SIM_LIB=true
+fi
+
+# Create output directories
+echo ""
+echo "========================================"
+echo "Creating xcframework..."
+echo "========================================"
+
+mkdir -p "${OUTPUT_DIR}"
+mkdir -p "${XCFRAMEWORK_DIR}/ios-arm64"
+mkdir -p "${XCFRAMEWORK_DIR}/Headers"
+
+if [ "$HAS_SIM_LIB" = true ]; then
+ mkdir -p "${XCFRAMEWORK_DIR}/ios-arm64-simulator"
+fi
+
+# Copy the device library
+cp "$IOS_ARM64_LIB" "${XCFRAMEWORK_DIR}/ios-arm64/libonnxruntime.a"
+
+# Copy the simulator library (arm64 only for now)
+if [ "$HAS_SIM_LIB" = true ]; then
+ cp "$IOS_SIM_ARM64_LIB" "${XCFRAMEWORK_DIR}/ios-arm64-simulator/libonnxruntime.a"
+else
+ echo "Warning: No simulator library available"
+fi
+
+# Copy headers
+HEADER_DIR=$(find build -name "onnxruntime_c_api.h" -type f | head -n 1 | xargs dirname)
+if [ -n "$HEADER_DIR" ]; then
+ cp "${HEADER_DIR}"/*.h "${XCFRAMEWORK_DIR}/Headers/" 2>/dev/null || true
+fi
+
+# Also copy headers from include directory
+if [ -d "include/onnxruntime/core/session" ]; then
+ cp include/onnxruntime/core/session/*.h "${XCFRAMEWORK_DIR}/Headers/" 2>/dev/null || true
+fi
+
+# Create Info.plist for xcframework
+if [ "$HAS_SIM_LIB" = true ]; then
+cat > "${XCFRAMEWORK_DIR}/Info.plist" << 'PLIST'
+
+
+
+
+ AvailableLibraries
+
+
+ HeadersPath
+ Headers
+ LibraryIdentifier
+ ios-arm64
+ LibraryPath
+ libonnxruntime.a
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ ios
+
+
+ HeadersPath
+ Headers
+ LibraryIdentifier
+ ios-arm64-simulator
+ LibraryPath
+ libonnxruntime.a
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ ios
+ SupportedPlatformVariant
+ simulator
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
+PLIST
+else
+cat > "${XCFRAMEWORK_DIR}/Info.plist" << 'PLIST'
+
+
+
+
+ AvailableLibraries
+
+
+ HeadersPath
+ Headers
+ LibraryIdentifier
+ ios-arm64
+ LibraryPath
+ libonnxruntime.a
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ ios
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
+PLIST
+fi
+
+echo ""
+echo "========================================"
+echo "Build complete!"
+echo "========================================"
+echo ""
+echo "ONNX Runtime xcframework created at:"
+echo " ${XCFRAMEWORK_DIR}"
+echo ""
+echo "Contents:"
+ls -la "${XCFRAMEWORK_DIR}"
+echo ""
+echo "Static library sizes:"
+ls -lh "${XCFRAMEWORK_DIR}/ios-arm64/libonnxruntime.a"
+ls -lh "${XCFRAMEWORK_DIR}/ios-arm64-simulator/libonnxruntime.a" 2>/dev/null || echo "No simulator library"
+echo ""
+echo "Verifying library contains key symbols:"
+nm "${XCFRAMEWORK_DIR}/ios-arm64/libonnxruntime.a" 2>/dev/null | grep -i "OrtCreateSession" | head -3 || echo "Symbols check skipped"
diff --git a/frontend/src-tauri/scripts/setup-ios-cargo-config.sh b/frontend/src-tauri/scripts/setup-ios-cargo-config.sh
new file mode 100755
index 00000000..8ec821ff
--- /dev/null
+++ b/frontend/src-tauri/scripts/setup-ios-cargo-config.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+# Generate .cargo/config.toml for iOS ONNX Runtime linking
+# This script creates the config with absolute paths based on the current directory
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+TAURI_DIR="$(dirname "$SCRIPT_DIR")"
+CARGO_DIR="${TAURI_DIR}/.cargo"
+CONFIG_FILE="${CARGO_DIR}/config.toml"
+XCFRAMEWORK_DIR="${TAURI_DIR}/onnxruntime-ios/onnxruntime.xcframework"
+
+# Check if xcframework exists
+if [ ! -d "$XCFRAMEWORK_DIR" ]; then
+ echo "Error: ONNX Runtime xcframework not found at: $XCFRAMEWORK_DIR"
+ echo "Run ./scripts/build-ios-onnxruntime-all.sh first"
+ exit 1
+fi
+
+# Check for required libraries
+if [ ! -f "${XCFRAMEWORK_DIR}/ios-arm64/libonnxruntime.a" ]; then
+ echo "Error: Device library not found. Build ONNX Runtime first."
+ exit 1
+fi
+
+mkdir -p "$CARGO_DIR"
+
+echo "Generating ${CONFIG_FILE}..."
+
+cat > "$CONFIG_FILE" << EOF
+# Auto-generated cargo config for iOS ONNX Runtime linking
+# Generated by: scripts/setup-ios-cargo-config.sh
+# Regenerate with: ./scripts/setup-ios-cargo-config.sh
+
+[target.aarch64-apple-ios.onnxruntime]
+rustc-link-search = ["${XCFRAMEWORK_DIR}/ios-arm64"]
+rustc-link-lib = ["static=onnxruntime"]
+
+[target.aarch64-apple-ios-sim.onnxruntime]
+rustc-link-search = ["${XCFRAMEWORK_DIR}/ios-arm64-simulator"]
+rustc-link-lib = ["static=onnxruntime"]
+
+[env]
+ORT_LIB_LOCATION = "${XCFRAMEWORK_DIR}/ios-arm64"
+EOF
+
+echo "Created: $CONFIG_FILE"
+echo ""
+echo "Paths configured:"
+echo " Device: ${XCFRAMEWORK_DIR}/ios-arm64"
+echo " Simulator: ${XCFRAMEWORK_DIR}/ios-arm64-simulator"
+
+# Verify simulator library exists (warn if not)
+if [ ! -f "${XCFRAMEWORK_DIR}/ios-arm64-simulator/libonnxruntime.a" ]; then
+ echo ""
+ echo "Warning: Simulator library not found!"
+ echo "Device builds will work, but simulator builds will fail."
+ echo "Run ./scripts/build-ios-onnxruntime-all.sh to build both."
+fi
diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs
index 2deadf0e..a9cee65f 100644
--- a/frontend/src-tauri/src/lib.rs
+++ b/frontend/src-tauri/src/lib.rs
@@ -3,7 +3,8 @@ use tauri_plugin_deep_link::DeepLinkExt;
mod pdf_extractor;
mod proxy;
-#[cfg(desktop)]
+// TTS is available on desktop and iOS (not Android)
+#[cfg(any(desktop, target_os = "ios"))]
mod tts;
#[cfg(desktop)]
@@ -240,6 +241,7 @@ pub fn run() {
})
.plugin(tauri_plugin_updater::Builder::new().build());
+ // Mobile (iOS and Android) configuration
#[cfg(not(desktop))]
let mut builder = tauri::Builder::default()
.plugin(
@@ -258,7 +260,14 @@ pub fn run() {
builder = builder.plugin(tauri_plugin_sign_in_with_apple::init());
}
- #[cfg(not(desktop))]
+ // Add TTS state management for iOS
+ #[cfg(all(not(desktop), target_os = "ios"))]
+ {
+ builder = builder.manage(tts::TTSState::new());
+ }
+
+ // Android-specific configuration (no TTS)
+ #[cfg(all(not(desktop), target_os = "android"))]
let app = builder
.invoke_handler(tauri::generate_handler![
pdf_extractor::extract_document_content,
@@ -279,6 +288,34 @@ pub fn run() {
})
.plugin(tauri_plugin_updater::Builder::new().build());
+ // iOS-specific configuration (with TTS)
+ #[cfg(all(not(desktop), target_os = "ios"))]
+ let app = builder
+ .invoke_handler(tauri::generate_handler![
+ pdf_extractor::extract_document_content,
+ tts::tts_get_status,
+ tts::tts_download_models,
+ tts::tts_load_models,
+ tts::tts_synthesize,
+ tts::tts_unload_models,
+ tts::tts_delete_models,
+ ])
+ .setup(|app| {
+ // Set up the deep link handler for mobile
+ let app_handle = app.handle().clone();
+
+ // Register deep link handler - note that iOS does not support runtime registration
+ // but the handler for incoming URLs still works
+ app.deep_link().on_open_url(move |event| {
+ if let Some(url) = event.urls().first() {
+ handle_deep_link_event(url.as_ref(), &app_handle);
+ }
+ });
+
+ Ok(())
+ })
+ .plugin(tauri_plugin_updater::Builder::new().build());
+
app.run(tauri::generate_context!())
.expect("error while running tauri application");
}
diff --git a/frontend/src-tauri/src/tts.rs b/frontend/src-tauri/src/tts.rs
index 5c5f2566..e8b78e9b 100644
--- a/frontend/src-tauri/src/tts.rs
+++ b/frontend/src-tauri/src/tts.rs
@@ -612,11 +612,28 @@ impl TextToSpeech {
}
fn get_tts_models_dir() -> Result {
- let data_dir = dirs::data_local_dir()
- .context("Failed to get local data directory")?
- .join("cloud.opensecret.maple")
- .join("tts_models");
- Ok(data_dir)
+ // On iOS, we need to use a different approach since dirs::data_local_dir() may not work
+ #[cfg(target_os = "ios")]
+ {
+ // On iOS, store models under Library/Caches so they're not user-visible (Files app)
+ // and won't be iCloud-backed.
+ let home = std::env::var("HOME").context("Failed to get HOME directory on iOS")?;
+ let data_dir = PathBuf::from(home)
+ .join("Library")
+ .join("Caches")
+ .join("cloud.opensecret.maple")
+ .join("tts_models");
+ return Ok(data_dir);
+ }
+
+ #[cfg(not(target_os = "ios"))]
+ {
+ let data_dir = dirs::data_local_dir()
+ .context("Failed to get local data directory")?
+ .join("cloud.opensecret.maple")
+ .join("tts_models");
+ Ok(data_dir)
+ }
}
fn load_voice_style(models_dir: &Path) -> Result