feat(ai): upgrade Chirpy on-device AI with proper APIs, download UX, and streaming#5579
Conversation
Replace the binary isSupported polling loop with reactive modelStatus StateFlow collection. ChirpyAssistantSheet now shows: - Loading spinner during Checking state - Download progress bar during Downloading state - Normal chat UI when Available - Hidden when Unavailable Thread modelReadiness through ChirpyUiState, DocsBrowserScreen, and DocsPageRouteScreen to the sheet composable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add ModelReadiness sealed interface to DocModels.kt with states: Checking, Downloading (with progress), Available, Unavailable - Add modelStatus: StateFlow<ModelReadiness> to AIDocAssistant interface - Implement modelStatus in KeywordFallbackAssistant (always Unavailable) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ackoff - Migrate from deprecated FirebaseAIOnDevice static API to instance-based model.onDeviceExtension for checkStatus/download/warmUp - Add ModelReadiness sealed interface (Checking/Downloading/Available/Unavailable) with determinate download progress tracking - Surface download state in UI with progress bar and percentage - Add warmUp() call after model becomes available - Add exponential backoff retry (up to 3 attempts) for BUSY/BATTERY errors - Add defensive response cleanup (strip backtick fences from on-device output) - Configure OnDeviceConfig with STABLE model option, temperature 0.7, topK 40 - Fix system instruction to honestly describe knowledge sources - Log usageMetadata (prompt/response token counts) for debugging - Add App Check TODO breadcrumb for future hybrid/cloud adoption - Extract Chirpy sheet states into separate composables for detekt compliance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add ChirpyFab composable with M3 Expressive motion patterns: - Available: gentle breathing pulse (alive/ready feel) - Checking: spinning indeterminate ring with reduced opacity icon - Downloading: determinate circular progress arc with spring animation - Unavailable: hidden (no FAB rendered) - Show FAB during Checking/Downloading states (not just Available) - Fix first-request failure: AutoIntroduceChirpy now waits for ModelReadiness.Available before firing intro inference - Fix choppy streaming: throttle partial emissions to ≤1 per 80ms to reduce Markdown re-render pressure on the UI thread - Replace hardcoded strings with proper string resources - Remove unused imports from screen composables Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add lightweight fast path in answerStream: prompts <100 chars with no page context and empty history skip the expensive bundle load + page ranking pipeline entirely, sending just the question with system instruction - Tighten intro prompt to 'Say hi in 1-2 sentences' for concise output - Intro now generates in <1s vs ~3-5s previously Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wire 6 generates @JvmField on companion ADAPTER properties. In KMP, androidHostTest compiles commonMain against common metadata (emitting a getter call) but at runtime the JVM class has a field (no getter), causing NoSuchMethodError. Fix: - Move StoreForwardPacketHandlerImplTest to jvmTest source set (pure Kotlin tests with no Android deps, avoids the metadata/JVM mismatch) - Migrate test helpers to use instance encode() (Wire Message method) instead of Companion.ADAPTER.encode() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🖼️ Preview staleness check — advisoryThis PR modifies UI composables but does not update any
Changed UI files: What to check:
Adding previews checklist:
If this PR does not require preview updates (e.g., logic-only change, non-visual refactor), add the |
📄 Docs staleness check — advisoryThis PR modifies user-facing UI source files but does not update any page under
Changed source files: What to check:
New page checklist (if adding a new doc page):
If this PR does not require a doc update (e.g., internal refactor, bug fix, test change), add the
|
Motivation
Chirpy's on-device AI (Gemini Nano) was using deprecated Firebase AI APIs that stopped working in recent releases. The implementation lacked download progress feedback, had choppy streaming, and the model would sometimes identify as "Gemma" instead of Chirpy.
Approach
Full audit against the Firebase AI Logic SDK at our release tag (
firebase-ai:17.12.0+firebase-ai-ondevice:16.0.0-beta02), then a ground-up rework of the assistant and its UX:API migration and reliability:
FirebaseAIOnDevice.checkStatus()/.download()to instance-basedmodel.onDeviceExtension?.checkStatus()/.download()/.warmUp()OnDeviceConfigwithInferenceMode.ONLY_ON_DEVICE,OnDeviceModelOption.STABLE, tuned temperature/topK/maxTokensDownload state UX:
ModelReadinesssealed interface (Checking,Downloading(progress),Available,Unavailable)DownloadStatus.DownloadInProgressExpressive M3 FAB (
ChirpyFab.kt):Streaming and intro fixes:
Test fix:
StoreForwardPacketHandlerImplTestfromcommonTesttojvmTestto avoid a Wire 6@JvmField+ KMPandroidHostTestbinary incompatibility (tests pass onjvmTest; the metadata-vs-field mismatch only affectsandroidHostTest)Type.ADAPTER.encode()to instanceencode()Key files
androidApp/src/google/.../GeminiNanoDocAssistant.kt-- core AI implementationfeature/docs/.../ui/ChirpyFab.kt-- new animated FABfeature/docs/.../navigation/DocsNavigation.kt-- state wiring, intro logicfeature/docs/.../model/DocModels.kt--ModelReadinesssealed interfacecore/data/src/jvmTest/.../StoreForwardPacketHandlerImplTest.kt-- moved from commonTestNotes for reviewers
GoogleAiModule.kt-- needs research on impact to fdroid/sideload channels before enabling@JvmFieldissue is a known KMP interop problem; moving tojvmTestis the cleanest fix since these tests have no Android dependencies