Skip to content

smart search for your laptop files made with Rust & Swift

Notifications You must be signed in to change notification settings

margaretjgu/mac-search-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mac Search Demo

A macOS desktop app (Swift) with a local Rust-powered search service that supports keyword and semantic search, all offline. The Rust crate builds a cdylib exposed over a C FFI for Swift.

Repo layout:

repo-root
├─ app/                 (Swift - to be added)
│  ├─ SearchApp.xcodeproj
│  └─ Sources/
├─ searchsvc/           (Rust → C FFI)
│  ├─ Cargo.toml
│  └─ src/
│      └─ lib.rs
├─ models/              (Core ML assets)
└─ scripts/             (helper Bash)

1) Prerequisites

  • Xcode 15+
  • macOS 14+
  • Rust (via rustup): curl https://sh.rustup.rs -sSf | sh
  • cbindgen (for header generation): cargo install cbindgen

Optional for model conversion:

  • Python 3.10+
  • coremltools, torch, onnxruntime if you plan to convert or validate models locally

2) Build and test the Rust search service

  • Run tests
./scripts/test.sh
  • Build the dynamic library and generate a C header
./scripts/build.sh

Outputs:

  • dylib: searchsvc/target/release/libsearchsvc.dylib
  • header: searchsvc/target/ffi/searchsvc.h

3) Architecture (Hexagonal)

  • Domain (Entities): Document, SearchHit in searchsvc/src/lib.rs
  • Application (Use cases): SearchService with operations: index text/file/dir and query
  • Ports: C FFI functions (searchsvc_init, searchsvc_index_*, searchsvc_query_json)
  • Adapters:
    • Swift adapter calls C FFI
    • Core ML adapter computes embeddings in Swift and passes float vectors to Rust
  • Infrastructure:
    • File IO (indexing file/dir)
    • Storage (in-memory for demo; move to files in ~/Library/Application Support/SearchApp later)

CRUD mapping:

  • Create/Update: index_text, index_file, index_directory (re-index to update)
  • Read: query
  • Delete: not implemented yet; can be added by removing doc IDs from the maps

4) Local models: storage and runtime

Yes. Place Core ML models in models/ and include in the .app bundle. All inference runs on-device (CPU/GPU/ANE) via Core ML. Recommended starter:

  • Sentence embedding: all-MiniLM-L6-v2 converted to Core ML (≈100 MB). Produces 384–768-dim embeddings.
  • Alternatively use ONNX + onnxruntime-metal. Bundle the .onnx in models/ and link onnxruntime if you prefer.

Typical sizes (fit comfortably on Mac laptops):

  • Embedding model: ~100 MB (Core ML)
  • Inverted index (keyword for ~1M docs): ~1–2 GB
  • Flat vector index (768-d fp32): ~3 GB per million vectors

Keep everything offline in the user’s Application Support directory, e.g. ~/Library/Application Support/SearchApp/.

5) Swift app integration (high level)

  1. Create the Xcode project under app/ (macOS App, Swift). Add a target called SearchApp.
  2. Add a build phase to copy the Rust dylib and header into the app:
    • Run Script Phase after build:
      • Input: $(SRCROOT)/../searchsvc/target/release/libsearchsvc.dylib, $(SRCROOT)/../searchsvc/target/ffi/searchsvc.h
      • Copy the dylib to $(TARGET_BUILD_DIR)/$(EXECUTABLE_FOLDER_PATH)/ and the header to $(DERIVED_FILE_DIR)/
  3. Bridging header / module map:
    • Add a Swift bridging header and import searchsvc.h:
      #include "searchsvc.h"
    • In Build Settings, set SWIFT_OBJC_BRIDGING_HEADER to the path of your bridging header.
  4. Linker search path:
    • Add $(PROJECT_DIR)/../searchsvc/target/release to Library Search Paths and set Runpath Search Paths to include @executable_path/.
  5. Swift wrapper (pseudo-interface):
    • Create a Swift class that wraps the FFI calls: initialize handle, index files (e.g., ~/.aws/credentials), and call searchsvc_query_json.
  6. Embeddings in Swift:
    • Load the Core ML embedding model from the app bundle; for each document and for queries, compute a float vector and pass to Rust.

6) Indexing common locations (examples)

  • ~/.aws/credentials and ~/.aws/config
  • ~/.ssh/config
  • ~/Documents, ~/Desktop, ~/Library/Mobile Documents/com~apple~CloudDocs
  • Workspace folders you choose

You can index a directory via the FFI using searchsvc_index_file in a loop, or add a Swift-side crawler that reads files and calls searchsvc_index_text.

7) End-to-end test plan

  • Rust unit tests: ./scripts/test.sh
  • Manual functional test:
    1. Build Rust: ./scripts/build.sh
    2. Start the app in Xcode.
    3. On first launch, index ~/.aws/credentials.
    4. Search for aws_access_key_id and default and verify the file and snippets show up.
    5. Test semantic search by querying synonyms (e.g., "AWS key" → should retrieve the credentials file if embeddings are indexed).

8) Roadmap (optional next steps)

  • Persist indices to disk (JSON or SQLite for keyword; FAISS/sqlite-vec for vectors)
  • Incremental file watching (FSEvents) and re-index on change
  • Add delete/update APIs on the Rust side
  • Add a small HTTP port for alternative integration (keep FFI for in-process fast calls)

9) Troubleshooting

  • If Xcode cannot find the dylib at runtime, verify the Runpath Search Paths include @executable_path/ and the dylib is copied into the app bundle.
  • If cbindgen is missing: cargo install cbindgen and rerun ./scripts/build.sh.
  • If model fails to load, ensure the .mlmodelc is inside the app bundle’s resources.

About

smart search for your laptop files made with Rust & Swift

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published