Repository navigation
feat(firebase-ai-logic-basics): add image generation and on-device hybrid inference guides - #214
AustinBenoit wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request reorganizes the Firebase AI Logic documentation by extracting web-only patterns into dedicated capability guides for "Image Generation (Nano Banana)" and "On-Device Hybrid Inference (Web)", updating the capability table in SKILL.md and cleaning up web_setup.md. The review feedback identifies critical improvements for the code examples in the new guides, specifically recommending the use of safe calls in Kotlin to prevent runtime crashes when handling empty or blocked model responses, and adding an error handler to the JavaScript FileReader promise to avoid hanging indefinitely on file read failures.
| val generatedImageAsBitmap: Bitmap? = model.generateContent(prompt) | ||
| // Handle the generated image | ||
| .candidates.first().content.parts.filterIsInstance<ImagePart>().firstOrNull()?.image |
There was a problem hiding this comment.
In Kotlin, candidates can be empty, and content within a Candidate can be null (for example, if the response was blocked by safety filters). Calling .first() on an empty list throws a NoSuchElementException, and accessing .content directly can lead to a NullPointerException or compilation error.
Using safe calls (firstOrNull() and ?.) is highly recommended to prevent crashes.
| val generatedImageAsBitmap: Bitmap? = model.generateContent(prompt) | |
| // Handle the generated image | |
| .candidates.first().content.parts.filterIsInstance<ImagePart>().firstOrNull()?.image | |
| val generatedImageAsBitmap: Bitmap? = model.generateContent(prompt) | |
| // Handle the generated image | |
| .candidates.firstOrNull()?.content?.parts?.filterIsInstance<ImagePart>()?.firstOrNull()?.image |
| val generatedImageAsBitmap = model.generateContent(prompt) | ||
| // Handle the generated image | ||
| .candidates.first().content.parts.filterIsInstance<ImagePart>().firstOrNull()?.image |
There was a problem hiding this comment.
Similarly to the generation example above, candidates can be empty and content can be null if the model's response is blocked or fails. Use safe navigation to avoid potential runtime exceptions.
| val generatedImageAsBitmap = model.generateContent(prompt) | |
| // Handle the generated image | |
| .candidates.first().content.parts.filterIsInstance<ImagePart>().firstOrNull()?.image | |
| val generatedImageAsBitmap = model.generateContent(prompt) | |
| // Handle the generated image | |
| .candidates.firstOrNull()?.content?.parts?.filterIsInstance<ImagePart>()?.firstOrNull()?.image |
| async function fileToGenerativePart(file) { | ||
| const base64EncodedDataPromise = new Promise((resolve) => { | ||
| const reader = new FileReader(); | ||
| reader.onloadend = () => resolve(reader.result.split(',')[1]); | ||
| reader.readAsDataURL(file); | ||
| }); | ||
| return { | ||
| type: "inlineData", | ||
| inlineData: { data: await base64EncodedDataPromise, mimeType: file.type }, | ||
| }; | ||
| } |
There was a problem hiding this comment.
In the fileToGenerativePart helper function, the FileReader promise does not handle the onerror event. If reading the file fails (e.g., due to permission issues or file corruption), the promise will remain pending indefinitely, which can hang the application flow. Adding an onerror handler to reject the promise ensures robust error handling.
| async function fileToGenerativePart(file) { | |
| const base64EncodedDataPromise = new Promise((resolve) => { | |
| const reader = new FileReader(); | |
| reader.onloadend = () => resolve(reader.result.split(',')[1]); | |
| reader.readAsDataURL(file); | |
| }); | |
| return { | |
| type: "inlineData", | |
| inlineData: { data: await base64EncodedDataPromise, mimeType: file.type }, | |
| }; | |
| } | |
| async function fileToGenerativePart(file) { | |
| const base64EncodedDataPromise = new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onloadend = () => resolve(reader.result.split(',')[1]); | |
| reader.onerror = reject; | |
| reader.readAsDataURL(file); | |
| }); | |
| return { | |
| type: "inlineData", | |
| inlineData: { data: await base64EncodedDataPromise, mimeType: file.type }, | |
| }; | |
| } |
90d3c93 to
f77234d
Compare
1c00c95 to
45b041e
Compare
45b041e to
9ad4c50
Compare
…brid inference guides ### Summary (Stack 5/6) Extracts the remaining Web-only usage sections from `web_setup.md` into dedicated capability guides (`image_generation.md` and `on_device_hybrid.md`), completing the `SKILL.md` routing table and trimming `web_setup.md` to its final setup-only state. ### Changes - Adds `image_generation.md` (Nano Banana `responseModalities: [TEXT, IMAGE]` across all 4 platforms; Imagen deprecation notice). - Adds `on_device_hybrid.md` (Web Chrome `mode: InferenceMode.PREFER_ON_DEVICE` + `inCloudParams` / `onDeviceParams` + mobile platform stubs). - Finalizes `web_setup.md` and `SKILL.md`.
9ad4c50 to
8212b72
Compare
Summary (Stack 4/5)
Extracts the remaining Web-only usage sections from
web_setup.mdinto dedicated capability guides (image_generation.mdandon_device_hybrid.md), completing theSKILL.mdrouting table and trimmingweb_setup.mdto its final setup-only state.Changes
image_generation.md(Nano BananaresponseModalities: [TEXT, IMAGE]across all 4 platforms; Imagen deprecation notice).on_device_hybrid.md(Web Chromemode: InferenceMode.PREFER_ON_DEVICE+inCloudParams/onDeviceParams+ mobile platform stubs).web_setup.mdandSKILL.md.