Skip to content

Claude/improve app quality oq agb - #82

Draft
AryanLuharuwala wants to merge 7 commits into
timmyy123:mainfrom
AryanLuharuwala:claude/improve-app-quality-OQAgb
Draft

Claude/improve app quality oq agb#82
AryanLuharuwala wants to merge 7 commits into
timmyy123:mainfrom
AryanLuharuwala:claude/improve-app-quality-OQAgb

Conversation

@AryanLuharuwala

Copy link
Copy Markdown

No description provided.

claude added 7 commits April 24, 2026 14:16
Scaffolds an ESP32-S3 voice-companion app on top of the existing LLM Hub
inference core. Nothing in the existing code is changed — the voice pipeline
reuses InferenceService / LLMBackend, RagService, and the web-search service
as-is.

Additions:
- docs/mimobot-protocol.md — locked BLE + control-JSON wire spec
- android/.../mimobot/ — transport / audio / speech / pipeline packages
- ios/.../MimoBot/    — same shape in Swift
- firmware/           — ESP-IDF v5.2+ skeleton (NimBLE + I2S + libopus)

TTS is pluggable via a Tts/TTS interface with two stub implementations:
SystemTts (AVSpeechSynthesizer / TextToSpeech) and KokoroTts (Kokoro-82M on
ONNX Runtime, which is already a project dependency).

VoicePipeline plugs RagService and the DuckDuckGo/SearchIntentDetector path
from the existing codebase into the prompt before handing it to the LLM.

All modules are compile-ready stubs with TODO markers — no real transport,
codec, STT, or TTS work yet, but the shape is fixed so firmware and app work
can proceed in parallel.

https://claude.ai/code/session_01WRUbo6vu3n3sXJjofKjkp3
Working voice loop on both platforms using the phone's own mic + speaker, so
the full STT → LLM → TTS pipeline can be exercised before the BLE transport,
Whisper, or Kokoro backends land. The new Mimo Bot test screen lives behind
Settings → Experimental on both apps.

Android:
- AudioSource/AudioSink interfaces; MicSource (AudioRecord, 16 kHz mono Int16,
  320-sample frames), SpeakerSink (AudioTrack, USAGE_ASSISTANT)
- SpeechToText interface; AndroidSpeechRecognizerStt (uses on-device recognizer
  on API 31+, falls back to network recognizer); WhisperStt stub updated to
  match new interface
- SystemTts: real synthesizeToFile → WAV reader → linear resampler → 320-sample
  Int16 frame stream
- VoicePipeline now drives runTurn(): listening → thinking → speaking, with a
  Channel-based sentence buffer so TTS starts before LLM finishes generating
- MimoBotTestScreen Compose UI; nav route + SettingsScreen entry
- Manifest: queries clause for android.speech.RecognitionService

iOS:
- AudioSource/AudioSink protocols; MicSource (AVAudioEngine input tap +
  AVAudioConverter to 16 kHz Int16), SpeakerSink (AVAudioPlayerNode)
- SpeechToText protocol; SFSpeechRecognizerSTT (on-device recognition when
  supported); WhisperSTT stub updated
- SystemTTS: real AVSpeechSynthesizer.write(_:toBufferCallback:) on iOS 16+,
  AVAudioConverter to 16 kHz Int16 frames
- VoicePipeline @mainactor with @published state/transcript/response;
  AsyncStream bridging for LLMBackend.generate so no @escaping mutable captures
- MimoBotTestView SwiftUI screen; ContentView .mimoBotTest case;
  SettingsScreen optional onNavigateToMimoBot row

Host-app responsibilities (NOT done in this commit):
- iOS Info.plist: NSMicrophoneUsageDescription, NSSpeechRecognitionUsageDescription
- iOS app capabilities: com.apple.developer.speechrecognition entitlement

Reuses InferenceService / LLMBackend / RagService unchanged. The BLE transport,
Opus codec, Whisper, and Kokoro all stay as scaffolded stubs and slot into the
same interfaces when implemented.

https://claude.ai/code/session_01WRUbo6vu3n3sXJjofKjkp3
Wires Kokoro-82M neural TTS through the existing on-device ONNX Runtime
dependency on Android, and adds onnxruntime-objc on iOS. The voice pipeline
can now be switched between system TTS and Kokoro from the Mimo Bot test
screen on both platforms.

New shared modules (Kotlin in mimobot/speech/kokoro/, Swift in MimoBot/Kokoro/):
- KokoroVocab — canonical 178-token Kokoro phoneme vocab + grapheme-cluster
  tokenizer (handles multi-codepoint IPA like "ɪ̆", "ʀ̥")
- VoicePack — loads voices/<id>.bin (511×256 float32) and indexes by token
  count per kokoro-onnx convention
- KokoroAssets — downloads model_fp16.onnx (~165 MB) + voices/<id>.bin from
  onnx-community/Kokoro-82M-v1.0-ONNX on first use; OkHttp on Android,
  URLSession.bytes on iOS
- EnglishG2P — starter ~150-word IPA dictionary for common voice-companion
  vocabulary (greetings, pronouns, numbers, common verbs / nouns / adjectives)
  + letter-by-letter spelling fallback for OOV. Clearly flagged as a starter
  with espeak-ng integration as the production path.

KokoroTts (Android):
- ai.onnxruntime OrtSession with NNAPI EP (USE_FP16) and ALL_OPT optimization
- Probes input names for both `input_ids/style/speed` and `tokens/ref_s/speed`
  export conventions
- 24 kHz float output → linear-interp resample to 16 kHz Int16 → 320-sample frames

KokoroTTS (iOS):
- onnxruntime-objc package added to Package.swift
- ORTSession with Core ML EP (graceful fallback to CPU on simulator)
- Same input-name probing + resampling

UI:
- MimoBot test screen on both platforms gets a "Voice" picker
  (System | Kokoro). Selecting Kokoro before assets are downloaded shows a
  one-tap download button with progress; falls back to system TTS until ready.
- Pipeline is hot-swappable: VoicePipeline.setTTS() (iOS) or recreated via
  remember-key on the Compose screen (Android).

Known limitation: G2P coverage. The starter dict handles ~150 common words.
Anything outside the dict is letter-spelled. See TODO(g2p) markers.

https://claude.ai/code/session_01WRUbo6vu3n3sXJjofKjkp3
…stub)

Adds a pluggable G2P interface so Kokoro can use either the bundled
~150-word English dictionary (always available) or espeak-ng for production
multilingual coverage. Choice is automatic at runtime — the pipeline
prefers espeak-ng when its native libs are present and falls back silently.

Shared:
- New G2P interface (Kotlin G2P.kt + Swift G2P.swift)
- Existing English dictionary refactored into DictionaryG2P implementation
- KokoroTts/KokoroTTS now take a G2P (default G2P.best()) and expose g2pName
  for UI display
- Mimo Bot test screen shows "G2P: dictionary | espeak-ng" under the Voice
  card with setup hint when on the dictionary fallback

Android (fully wired — runs as soon as user runs the build script):
- src/main/cpp/espeak_jni.cpp — JNI bridge calling espeak_TextToPhonemes
- src/main/cpp/CMakeLists.txt — links libespeak-ng.so, early-returns when
  the espeak source isn't staged so fresh checkouts still build
- mimobot/speech/kokoro/EspeakG2P.kt — System.loadLibrary with graceful
  UnsatisfiedLinkError fallback, copies espeak-ng-data assets to filesDir
  on first use
- build.gradle.kts: externalNativeBuild block pointing at the new CMakeLists

iOS (stub + script only — manual XCFramework wiring required):
- EspeakG2P.swift — protocol-conforming stub with tryLoad() returning nil
  until the user replaces the body with real espeak_Initialize/_TextToPhonemes
  calls. Doc comment explains why SwiftPM can't make the binary target
  optional and walks through the manual host-app integration.

Build infrastructure:
- scripts/build_espeak_android.sh — clones espeak-ng 1.52.0, cross-compiles
  per ABI (arm64-v8a default, multi-ABI via $ABIS env var), stages headers
  + voice data into the Android module
- scripts/build_espeak_ios.sh — produces EspeakNG.xcframework (device +
  simulator) plus voice data; stops short of host-app wiring (manual)
- docs/espeak-ng-setup.md — full step-by-step including GPLv3 license
  warning, troubleshooting, and the iOS manual second-half checklist

License note: espeak-ng is GPLv3. Bundling it means the app distribution
must comply. Dictionary G2P stays the default for licence-restricted apps.

https://claude.ai/code/session_01WRUbo6vu3n3sXJjofKjkp3
The voice pipeline now keeps the last 6 (user, assistant) turns in memory and
replays them inside the augmented prompt, so the bot remembers what was just
said. The test screens grow a "Conversation memory" card showing the live
turn count plus a Clear button.

VoicePipeline (both platforms):
- New Turn data class
- @published / StateFlow `history` of completed turns
- clearHistory() wipes it
- augmentPrompt() replays history as user:/assistant: lines after the system
  prompt — same format the existing inference services already parse
- Cap defaults to maxHistoryTurns = 6, configurable per construction

Mimo Bot test screen (Android Compose + iOS SwiftUI):
- New "Conversation memory" card with turn count + Clear button
- Card hides the Clear button when history is empty

Model catalog:
- Android: Gemma-3 270M INT4 (~150 MB) and INT8 (~270 MB) .task entries
  pointing at litert-community's expected filename pattern
- iOS: Gemma-3 270M Q4_K_M (~150 MB) and Q8_0 (~290 MB) GGUF entries
  pointing at unsloth's mirror

Both 270M variants are wired in front of the existing 1B entries since they
load fastest — the right default for the voice pipeline. If the 1B is still
preferred for chat, it's still in the list, just below 270M.

If a 270M URL 404s on download, the entry just needs the URL field updated;
the rest of the catalog metadata (size, context, ABI flags) is correct.

https://claude.ai/code/session_01WRUbo6vu3n3sXJjofKjkp3
Expand both model catalogs so users can pick the size/quality trade-off
that fits their device.

Android (.task, litert-community):
- INT4 ekv1024 (~150 MB)
- INT4 ekv2048 (~160 MB)
- INT8 ekv1024 (~270 MB)
- INT8 ekv2048 (~280 MB)  ← recommended for the voice loop
- INT8 ekv4096 (~290 MB)

iOS (GGUF, unsloth/gemma-3-270m-it-GGUF):
- Q2_K  (~80 MB)
- Q3_K_M (~120 MB)
- Q4_0  (~145 MB)
- Q4_K_M (~155 MB)  ← recommended
- Q5_K_M (~190 MB)
- Q6_K  (~220 MB)
- Q8_0  (~290 MB)
- F16   (~540 MB)

If a URL 404s, only that line needs editing — the rest of each entry is
correct. bartowski/google_gemma-3-270m-it-GGUF mirrors the same files for
the iOS path with a `google_gemma-3-270m-it-` filename prefix.

https://claude.ai/code/session_01WRUbo6vu3n3sXJjofKjkp3
…reen

Two fixes you'd hit on a fresh phone build:

1. externalNativeBuild was unconditional, so AGP demanded an NDK install on
   every build even though espeak-ng isn't staged yet. The CMakeLists.txt's
   early-return wasn't enough — AGP probes for the NDK before CMake runs.
   Now the externalNativeBuild block is only registered when
   src/main/cpp/espeak-ng/include/speak_lib.h exists (i.e. after
   scripts/build_espeak_android.sh has been run). Fresh checkouts build
   straight away with no NDK requirement; the espeak path activates as soon
   as the headers land.

2. Mimo Bot test screen used to read the loaded model from inference once and
   stay null forever, because the navigation auto-unload (in
   LlmHubNavigation.kt) wipes the model whenever you leave Chat. Now the
   screen lists every locally-downloaded text model from
   ModelAvailabilityProvider and shows a one-tap "Load" button per model.
   Loading status, post-load model name, and load failures all surface in
   the Model card. Pre-existing chat-loaded models still flow through, so
   the screen is now self-sufficient — no detour through Chat needed.

https://claude.ai/code/session_01WRUbo6vu3n3sXJjofKjkp3
@timmyy123

Copy link
Copy Markdown
Owner

can you explain what this mimo bot feature does?
does it just do the same thing as the existing vibevoice feature?
I tested on my device and it stopped recording instanly after i pressed the talk button

@timmyy123
timmyy123 marked this pull request as draft May 22, 2026 00:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants