Skip to main content
The leap-ui module (introduced in v0.10.0) ships a ready-to-use voice assistant widget โ€” an animated orb, mic button, and status label โ€” backed by a state machine that handles recording, generation, and audio playback. Wire it to a model and it handles the rest. leap-ui is a Compose Multiplatform module, so the same widget runs on:
  • iOS โ€” bridged to UIKit via VoiceAssistantViewController and exposed to SwiftUI through UIViewControllerRepresentable.
  • macOS โ€” bridged to AppKit via VoiceAssistantNSViewController. SwiftUI hosts via NSViewControllerRepresentable + NSHostingController.
  • Android โ€” direct Compose for Android.
  • JVM Desktop โ€” Compose for Desktop. Same Maven artifact; you provide audio I/O implementations (the demo apps in leap-ui-demo/ ship patterns you can adapt).
  • Web (Wasm, experimental) โ€” present in the source tree (leap-ui-demo/web) but not yet covered by the stable release notes through v0.10.7 โ€” treat as preview.

Add the dependency

Add the LeapUI product to your target alongside LeapModelDownloader (the SPM product whose Swift ModelDownloader class is what the snippets below use to load the audio model). See the Quick Start for the full SPM setup.
In Swift sources, import LeapUi (lowercase i โ€” thatโ€™s the binary-target module name).
Dual-import opt-out required for this combination. LeapUI transitively bundles LeapSDK, and LeapModelDownloader re-exports the same Kotlin types under its own framework module, so the dual-import build-time guard fires #error at preprocessing time unless you opt out. Add LEAP_DUAL_IMPORT_ALLOW=1 to OTHER_CFLAGS for the affected target, and qualify ambiguous Swift type references with the source module (LeapSDK.Conversation vs. LeapModelDownloader.Conversation) or stick to a single import per file.If youโ€™d rather avoid the opt-out, swap LeapModelDownloader for LeapSDK in the target dependencies and rewrite the snippets below to use LeapDownloader(config:).loadModel(modelName:, quantizationType:) โ€” the cross-platform loader has the same shape minus the URLSession background-session integration.

Architecture

  • VoiceAssistantStore owns the session lifecycle. Instantiate once when the screen appears; close() it when it goes away.
  • VoiceConversation is a thin interface you implement to bridge the store to your model. Wrap the SDKโ€™s Conversation.generateResponse and forward AudioSample chunks to onAudioChunk.
  • Audio I/O uses VoiceAudioRecorder / VoiceAudioPlayer interfaces. iOS / macOS ship AppleAudioRecorder and AppleAudioPlayer defaults; Android / JVM reference implementations live in leap-ui-demo/.

Wire the model

The VoiceConversation adapter looks similar on every platform โ€” both implementations stream audio samples back through onAudioChunk.
The factory VoiceAssistantStore.makeForApple() hides Kotlin coroutine plumbing from Swift callers. It creates the store with a MainScope(), the default Apple audio recorder and player, and an EMA-smoothed amplitude.
Override defaults via the same makeForApple factory parameters:

Host the widget

Implement VoiceConversation

The store calls into a VoiceConversation you provide. A minimal adapter that wraps a normal Conversation:
The VoiceConversation protocol comes from LeapUI, so its audioSamples and onAudioChunk parameters use LeapUi.KotlinFloatArray / LeapUi.KotlinInt โ€” not native Swift [Float] / Int32. The on-device runner lives in LeapSDK, which has its own LeapSDK.KotlinFloatArray. Bridge between the two via the floatArrayToNSData / nsDataToFloatArray helpers exposed in both frameworks (see leap-ui-demo/shared/AppleVoiceConversation.swift for the canonical pattern).

Audio I/O implementations

The VoiceAudioRecorder and VoiceAudioPlayer contracts are short. Substitute your own implementations when the defaults donโ€™t fit.
AppleAudioRecorder and AppleAudioPlayer are the shipped defaults โ€” makeForApple() wires them up automatically. Implement the protocols directly if you need to integrate with custom AVAudioEngine pipelines.iOS apps must configure AVAudioSession for record + playback before the model starts streaming audio:
Add NSMicrophoneUsageDescription to your Info.plist.

interruptToSpeak

VoiceAssistantStore (v0.10.0+) exposes an interruptToSpeak: Boolean = true parameter controlling what happens when the user presses the orb during a response:
  • true (default) โ€” cancels the in-flight generation and immediately starts a new recording.
  • false โ€” only cancels. The user must press again to start a new recording.

Whatโ€™s in the module

Compatible models

Voice mode requires a model that emits audio output. The shipped demo uses LFM2.5-Audio-1.5B at Q4_0 quantization, with a system prompt of โ€œRespond with interleaved text and audio.โ€ See the LEAP Model Library for other audio-capable models.