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