From c0dd653fc9e31da3dc4d282f55873791fa9071cc Mon Sep 17 00:00:00 2001
From: Tony Giorgio <101225832+TonyGiorgio@users.noreply.github.com>
Date: Sat, 10 Jan 2026 11:17:42 -0600
Subject: [PATCH 01/11] feat(ios): add TTS support using ONNX Runtime
- Add iOS-specific ort dependencies and build configuration
- Enable TTS module for iOS (was desktop-only)
- Add iOS-specific path handling for model storage
- Build ONNX Runtime 1.22.2 from source with all dependencies statically linked
- Add retry logic and combined static library creation
- Update GitHub Actions to build and cache ONNX Runtime for iOS
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
.github/workflows/mobile-build.yml | 65 ++++-
frontend/bun.lock | 1 +
frontend/src-tauri/.gitignore | 7 +
frontend/src-tauri/Cargo.toml | 17 ++
frontend/src-tauri/build.rs | 30 ++
.../scripts/build-ios-onnxruntime.sh | 259 ++++++++++++++++++
.../scripts/setup-ios-onnxruntime.sh | 78 ++++++
frontend/src-tauri/src/lib.rs | 41 ++-
frontend/src-tauri/src/tts.rs | 23 +-
9 files changed, 513 insertions(+), 8 deletions(-)
create mode 100755 frontend/src-tauri/scripts/build-ios-onnxruntime.sh
create mode 100755 frontend/src-tauri/scripts/setup-ios-onnxruntime.sh
diff --git a/.github/workflows/mobile-build.yml b/.github/workflows/mobile-build.yml
index 40e461ae..27681a3e 100644
--- a/.github/workflows/mobile-build.yml
+++ b/.github/workflows/mobile-build.yml
@@ -53,6 +53,63 @@ 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"]
+
+ [target.x86_64-apple-ios.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 +146,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 +158,11 @@ jobs:
retention-days: 5
- name: Submit to TestFlight
- if: github.event_name == 'push' && github.ref == 'refs/heads/master'
+ # TODO: Remove ios-tts condition after PR #378 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')
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/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/build.rs b/frontend/src-tauri/build.rs
index d860e1e6..ecad17e9 100644
--- a/frontend/src-tauri/build.rs
+++ b/frontend/src-tauri/build.rs
@@ -1,3 +1,33 @@
fn main() {
+ // iOS-specific build configuration for ONNX Runtime
+ #[cfg(target_os = "ios")]
+ {
+ // Get the path to the ONNX Runtime xcframework
+ let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
+ let ort_dir = format!("{}/onnxruntime-ios", manifest_dir);
+
+ // Check if building for simulator or device
+ let target = std::env::var("TARGET").unwrap_or_default();
+ let lib_path = if target.contains("sim") || target.contains("x86_64") {
+ format!("{}/onnxruntime.xcframework/ios-arm64_x86_64-simulator", ort_dir)
+ } else {
+ format!("{}/onnxruntime.xcframework/ios-arm64", ort_dir)
+ };
+
+ // Tell cargo where to find the ONNX Runtime static library
+ println!("cargo:rustc-link-search=native={}", lib_path);
+ println!("cargo:rustc-link-lib=static=onnxruntime");
+
+ // Link required iOS frameworks
+ println!("cargo:rustc-link-lib=framework=Foundation");
+ println!("cargo:rustc-link-lib=framework=Accelerate");
+
+ // Set ORT_LIB_LOCATION for the ort crate
+ println!("cargo:rustc-env=ORT_LIB_LOCATION={}", ort_dir);
+
+ // Rerun if the onnxruntime directory changes
+ println!("cargo:rerun-if-changed={}", ort_dir);
+ }
+
tauri_build::build()
}
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..fb1d2408
--- /dev/null
+++ b/frontend/src-tauri/scripts/build-ios-onnxruntime.sh
@@ -0,0 +1,259 @@
+#!/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=""
+
+# Create output directories
+echo ""
+echo "========================================"
+echo "Creating xcframework..."
+echo "========================================"
+
+mkdir -p "${OUTPUT_DIR}"
+mkdir -p "${XCFRAMEWORK_DIR}/ios-arm64"
+mkdir -p "${XCFRAMEWORK_DIR}/ios-arm64-simulator"
+mkdir -p "${XCFRAMEWORK_DIR}/Headers"
+
+# Copy the device library
+cp "$IOS_ARM64_LIB" "${XCFRAMEWORK_DIR}/ios-arm64/libonnxruntime.a"
+
+# Copy the simulator library (arm64 only for now)
+if [ -n "$IOS_SIM_ARM64_LIB" ] && [ -f "$IOS_SIM_ARM64_LIB" ]; 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
+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 "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-onnxruntime.sh b/frontend/src-tauri/scripts/setup-ios-onnxruntime.sh
new file mode 100755
index 00000000..88e0e491
--- /dev/null
+++ b/frontend/src-tauri/scripts/setup-ios-onnxruntime.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+# Setup ONNX Runtime for iOS builds
+# This script downloads pre-built ONNX Runtime xcframework from HuggingFace
+# or builds from source if needed.
+
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+TAURI_DIR="$(dirname "$SCRIPT_DIR")"
+ORT_VERSION="${ORT_VERSION:-1.20.1}"
+ORT_DIR="$TAURI_DIR/onnxruntime-ios"
+XCFRAMEWORK_DIR="$ORT_DIR/onnxruntime.xcframework"
+
+echo "Setting up ONNX Runtime $ORT_VERSION for iOS..."
+echo "Target directory: $ORT_DIR"
+
+# Check if already downloaded
+if [ -d "$XCFRAMEWORK_DIR" ]; then
+ echo "ONNX Runtime xcframework already exists at $XCFRAMEWORK_DIR"
+ echo "To re-download, remove the directory first: rm -rf $ORT_DIR"
+ exit 0
+fi
+
+# Create directory
+mkdir -p "$ORT_DIR"
+
+# Download pre-built xcframework from HuggingFace
+# Repository: https://huggingface.co/csukuangfj/ios-onnxruntime
+HF_BASE_URL="https://huggingface.co/csukuangfj/ios-onnxruntime/resolve/main"
+
+echo "Downloading ONNX Runtime $ORT_VERSION xcframework from HuggingFace..."
+
+# Download the xcframework directory structure
+# The structure is:
+# onnxruntime.xcframework/
+# Info.plist
+# Headers/
+# cpu_provider_factory.h
+# onnxruntime_c_api.h
+# onnxruntime_cxx_api.h
+# onnxruntime_cxx_inline.h
+# ios-arm64/
+# onnxruntime.a
+# ios-arm64_x86_64-simulator/
+# onnxruntime.a
+
+mkdir -p "$XCFRAMEWORK_DIR/Headers"
+mkdir -p "$XCFRAMEWORK_DIR/ios-arm64"
+mkdir -p "$XCFRAMEWORK_DIR/ios-arm64_x86_64-simulator"
+
+echo "Downloading Info.plist..."
+curl -L -o "$XCFRAMEWORK_DIR/Info.plist" \
+ "$HF_BASE_URL/$ORT_VERSION/onnxruntime.xcframework/Info.plist"
+
+echo "Downloading headers..."
+for header in cpu_provider_factory.h onnxruntime_c_api.h onnxruntime_cxx_api.h onnxruntime_cxx_inline.h; do
+ curl -L -o "$XCFRAMEWORK_DIR/Headers/$header" \
+ "$HF_BASE_URL/$ORT_VERSION/onnxruntime.xcframework/Headers/$header"
+done
+
+echo "Downloading iOS arm64 static library (this may take a while)..."
+curl -L -o "$XCFRAMEWORK_DIR/ios-arm64/libonnxruntime.a" \
+ "$HF_BASE_URL/$ORT_VERSION/onnxruntime.xcframework/ios-arm64/onnxruntime.a"
+
+echo "Downloading iOS simulator static library (this may take a while)..."
+curl -L -o "$XCFRAMEWORK_DIR/ios-arm64_x86_64-simulator/libonnxruntime.a" \
+ "$HF_BASE_URL/$ORT_VERSION/onnxruntime.xcframework/ios-arm64_x86_64-simulator/onnxruntime.a"
+
+echo ""
+echo "ONNX Runtime xcframework downloaded successfully!"
+echo "Location: $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_x86_64-simulator/libonnxruntime.a"
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..bfd5ab4a 100644
--- a/frontend/src-tauri/src/tts.rs
+++ b/frontend/src-tauri/src/tts.rs
@@ -612,11 +612,24 @@ 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, use the app's Documents directory which is accessible and persists
+ // NSHomeDirectory() + /Documents/tts_models
+ let home = std::env::var("HOME").context("Failed to get HOME directory on iOS")?;
+ let data_dir = PathBuf::from(home).join("Documents").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