Skip to content

fix(android): Pixel/Tensor image gen MNN CPU path and clear labeling#457

Open
Ayush7614 wants to merge 1 commit into
off-grid-ai:mainfrom
Ayush7614:fix/pixel-tensor-image-gen-mnn
Open

fix(android): Pixel/Tensor image gen MNN CPU path and clear labeling#457
Ayush7614 wants to merge 1 commit into
off-grid-ai:mainfrom
Ayush7614:fix/pixel-tensor-image-gen-mnn

Conversation

@Ayush7614

@Ayush7614 Ayush7614 commented Jul 4, 2026

Copy link
Copy Markdown

Summary

  • Force MNN CPU image load and generation on Pixel 10 (OpenCL/GPU path is broken on-device).
  • Rename MNN model labels from GPU to MNN across catalog, filters, and Download Manager.
  • Add Tensor-specific image recommendation banner for Pixel devices.
  • Hide the NPU backend filter when the device has no QNN catalog entries.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Related Issues

Fixes #432

Test plan

  • npx jest tests/integration/models/pixel10ImageGenCpuPath.test.ts
  • npx jest tests/unit/services/hardware.test.ts (Pixel/Tensor/MNN cases)
  • npx jest tests/unit/services/huggingFaceModelBrowser.test.ts (MNN labeling)
  • Manual on Pixel 10 XL: Image Models tab shows MNN models, NPU filter hidden, generation completes

Summary by CodeRabbit

  • New Features

    • Added device-aware image generation support for Pixel 10 devices, automatically routing them to the appropriate backend.
    • Improved model browsing and download labels to show clearer backend names, including MNN.
  • Bug Fixes

    • Fixed image generation backend selection when a device lacks NPU support.
    • Updated recommendations and warnings to guide users toward compatible models more accurately.
    • Refined filter options so unavailable backends are hidden when not supported.

…ling

Pixel 10 OpenCL image generation is broken on-device (same class of issue as
LiteRT GPU). Force cpuOnly load and disable OpenCL during generation on Pixel 10.

Rename MNN catalog/filter labels from "GPU" to "MNN" so Tensor/Pixel users are
not told to pick a "CPU model" while the UI only offered GPU and NPU filters.
Hide the NPU filter when the device has no QNN catalog entries.

Fixes off-grid-ai#432
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a requiresCpuImageBackend() check in HardwareService to detect Pixel 10 devices, forcing OpenCL-unsupported status and CPU-only image generation. Threads NPU availability through Models screen hooks/components to hide the NPU filter when unavailable. Renames GPU backend labels to MNN across UI, services, and tests.

Changes

Pixel 10 CPU-only image generation and MNN rebranding

Layer / File(s) Summary
HardwareService CPU-backend detection
src/services/hardware.ts
Adds requiresCpuImageBackend() detecting Pixel 10 devices, forces getOpenCLCapability() to return unsupported with reason pixel_10_cpu_only, and updates recommendation banner text for non-NPU Snapdragon, Tensor/Pixel 10, and fallback devices.
CPU enforcement in generation and model loading
src/services/imageGenerationService.ts, src/services/activeModelService/index.ts, src/screens/ModelsScreen/imageDownloadQnn.ts
generateImage disables OpenCL when the CPU backend is required; doLoadImageModelLocked derives cpuOnly from the same check; QNN no-NPU warning text updated to recommend MNN.
NPU availability wiring
src/screens/ModelsScreen/useImageModels.ts, src/screens/ModelsScreen/useModelsScreen.ts, src/screens/ModelsScreen/ImageModelsTab.tsx, src/screens/ModelsScreen/ImageFilterBar.tsx
Tracks imageNpuAvailable via SoC info, auto-switches backend filter away from qnn when no NPU, and forwards npuAvailable to hide the NPU filter option in ImageFilterBar.
GPU-to-MNN label updates
src/screens/ModelsScreen/constants.ts, src/screens/DownloadManagerScreen/useDownloadManager.ts, src/services/huggingFaceModelBrowser.ts
Renames GPU backend label/author/display name references to MNN.
Tests
__tests__/integration/models/pixel10ImageGenCpuPath.test.ts, __tests__/unit/services/hardware.test.ts, __tests__/unit/services/huggingFaceModelBrowser.test.ts
Adds a new integration suite for the Pixel 10 CPU path and updates unit tests for OpenCL caching and MNN wording.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ImageGenerationService
  participant HardwareService
  participant ActiveModelService

  User->>ImageGenerationService: generateImage()
  ImageGenerationService->>HardwareService: requiresCpuImageBackend()
  HardwareService-->>ImageGenerationService: true (Pixel 10 detected)
  ImageGenerationService->>ImageGenerationService: useOpenCL = false
  ImageGenerationService->>ActiveModelService: doLoadImageModelLocked()
  ActiveModelService->>HardwareService: requiresCpuImageBackend()
  HardwareService-->>ActiveModelService: true
  ActiveModelService->>ActiveModelService: forceCpuImage -> cpuOnly = true
  ActiveModelService-->>ImageGenerationService: model loaded (CPU/MNN)
  ImageGenerationService-->>User: image generated via CPU backend
Loading

Related issues: #432 (Pixel 10 XL image generation failure due to forced GPU/OpenCL path is addressed by CPU-only fallback and MNN backend routing)

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description has summary, type, issue link, and test plan, but it omits the required screenshots section for a UI change. Add the Screenshots / Screen Recordings section with Android before/after images and any relevant notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #432 by restoring Pixel 10 XL image generation, fixing backend labels, and aligning available filters.
Out of Scope Changes check ✅ Passed The added Tensor banner and NPU-filter handling are directly tied to the Pixel 10 image-gen fix and label cleanup.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title accurately summarizes the main Android Pixel/Tensor MNN CPU-path and labeling changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

fix(android): Force MNN CPU path on Pixel 10 and rename MNN labels from GPU

🐞 Bug fix 🕐 20-40 Minutes

Grey Divider

AI Description

• Force cpuOnly image model load and disable OpenCL during generation on Pixel 10 devices, where
 the OpenCL/GPU path is broken on-device.
• Add requiresCpuImageBackend() to HardwareService and a dedicated Tensor/Pixel 10 branch in
 getImageModelRecommendation() with a device-specific banner.
• Rename all MNN backend labels from "GPU" → "MNN" across the model catalog, filter bar, Download
 Manager, and error messages so Pixel/Tensor users are not confused by a "GPU" label.
• Hide the NPU filter chip in ImageFilterBar when the device has no QNN catalog entries
 (npuAvailable prop), and auto-reset a stale qnn filter to mnn on mount.
• Add integration test for Pixel 10 CPU path and extend unit tests for Tensor/MNN banner text and
 OpenCL capability.
Diagram

graph TD
    A["ImageGenerationService"] -->|"requiresCpuImageBackend()"| H["HardwareService"]
    B["ActiveModelService"] -->|"requiresCpuImageBackend()"| H
    H -->|"getOpenCLCapability()"| C["OpenCL Check"]
    H -->|"getImageModelRecommendation()"| D["Tensor/Pixel 10 Branch"]
    E["useImageModels"] -->|"getSoCInfo().hasNPU"| H
    E -->|"imageNpuAvailable"| F["ImageModelsTab"]
    F -->|"npuAvailable prop"| G["ImageFilterBar"]
    E -->|"displayName (MNN)"| I["HFModelBrowser"]

    subgraph Legend
      direction LR
      _svc(["Service"]) ~~~ _hook["Hook/Component"] ~~~ _ext{{"External"}}
    end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Capability probe at runtime
  • ➕ Automatically covers future devices with the same GPU bug without code changes
  • ➕ No hardcoded device name strings to maintain
  • ➖ Requires a native module or a test render to detect the broken OpenCL path
  • ➖ More complex to implement and test; may add startup latency
2. Remote feature flag / config
  • ➕ Can be toggled without a release if the GPU driver is fixed in a future Android update
  • ➕ Covers device variants (Pro, Pro XL, etc.) without string matching
  • ➖ Adds a network dependency to the image load path
  • ➖ Requires a flag service infrastructure that may not exist yet

Recommendation: The PR's approach of a model-name string check (includes('pixel 10')) in requiresCpuImageBackend() is pragmatic for a known broken device, but has a few trade-offs worth considering.

Files changed (14) +134 / -20

Enhancement (4) +26 / -6
ImageFilterBar.tsxAdd npuAvailable prop to hide NPU filter chip on non-Qualcomm devices +11/-3

Add npuAvailable prop to hide NPU filter chip on non-Qualcomm devices

• Accepts an optional 'npuAvailable' prop (default 'true'). When false, the NPU chip is filtered out of 'backendOptions' before rendering. The 'backendOptions' array is passed down to 'FilterExpandedSection' instead of the static 'BACKEND_OPTIONS'. 'getBackendLabel' is also updated to return 'MNN' for the 'mnn' filter.

src/screens/ModelsScreen/ImageFilterBar.tsx

ImageModelsTab.tsxThread imageNpuAvailable prop through to ImageFilterBar +6/-3

Thread imageNpuAvailable prop through to ImageFilterBar

• Adds 'imageNpuAvailable' to the 'Props' type and 'ScrollContentProps', destructures it in both components, and passes it as 'npuAvailable' to 'ImageFilterBar'.

src/screens/ModelsScreen/ImageModelsTab.tsx

useImageModels.tsTrack imageNpuAvailable state and auto-reset stale qnn filter +8/-0

Track imageNpuAvailable state and auto-reset stale qnn filter

• Adds 'imageNpuAvailable' state (default 'false'). It is set from 'socInfo.hasNPU' in both 'loadHFModels' and the mount-time recommendation effect. The recommendation effect also resets the backend filter from 'qnn' to 'mnn' if the device has no NPU. The value is included in the hook's return object.

src/screens/ModelsScreen/useImageModels.ts

useModelsScreen.tsExpose imageNpuAvailable from useImageModels in the view model +1/-0

Expose imageNpuAvailable from useImageModels in the view model

• Adds 'imageNpuAvailable: image.imageNpuAvailable' to the 'useModelsScreen' return object so it is available to the screen and its child components.

src/screens/ModelsScreen/useModelsScreen.ts

Bug fix (7) +37 / -10
hardware.tsAdd requiresCpuImageBackend() and Tensor/Pixel 10 image recommendation branch +23/-2

Add requiresCpuImageBackend() and Tensor/Pixel 10 image recommendation branch

• Adds 'requiresCpuImageBackend()' which returns true for any 'Pixel 10' device on Android. 'getOpenCLCapability()' now short-circuits with 'pixel_10_cpu_only' when that flag is set. A new 'tensor' vendor branch in 'getImageModelRecommendation()' emits a Pixel 10-specific CPU notice or a generic Tensor banner. Existing Snapdragon-no-NPU and fallback banners are updated from 'GPU' to 'MNN' wording.

src/services/hardware.ts

imageGenerationService.tsDisable OpenCL during image generation on Pixel 10 +5/-1

Disable OpenCL during image generation on Pixel 10

• Before calling '_runGenerationAndSave', 'useOpenCL' is now forced to 'false' when 'hardwareService.requiresCpuImageBackend()' returns true, overriding the user setting. This prevents the broken OpenCL path from being invoked during generation on Pixel 10.

src/services/imageGenerationService.ts

index.tsForce cpuOnly image model load on Pixel 10 +3/-2

Force cpuOnly image model load on Pixel 10

• Reads 'hardwareService.requiresCpuImageBackend()' before calling 'doLoadImageModel' and passes the result as 'cpuOnly', replacing the hardcoded 'false'. Also updates the NPU-incompatible error message to say 'MNN model' instead of 'GPU model'.

src/services/activeModelService/index.ts

huggingFaceModelBrowser.tsRename MNN model displayName from '(GPU)' to '(MNN)' +3/-2

Rename MNN model displayName from '(GPU)' to '(MNN)'

• The 'parseFileName' function now labels MNN backend models as '(MNN)' instead of '(GPU)' in their 'displayName'. A comment explains the rationale: Pixel/Tensor users should not be told to pick a 'CPU model' when the catalog only shows 'GPU' and 'NPU'.

src/services/huggingFaceModelBrowser.ts

constants.tsRename BACKEND_OPTIONS mnn label from 'GPU' to 'MNN' +1/-1

Rename BACKEND_OPTIONS mnn label from 'GPU' to 'MNN'

• The 'BACKEND_OPTIONS' constant now uses ''MNN'' as the display label for the 'mnn' filter key.

src/screens/ModelsScreen/constants.ts

imageDownloadQnn.tsUpdate NPU-incompatible warning to suggest MNN instead of CPU model +1/-1

Update NPU-incompatible warning to suggest MNN instead of CPU model

• The warning message shown when a user tries to download an NPU model on a non-Qualcomm device now says 'Consider downloading an MNN model instead' rather than 'a CPU model'.

src/screens/ModelsScreen/imageDownloadQnn.ts

useDownloadManager.tsRename MNN backend author label from 'GPU' to 'MNN' in Download Manager +1/-1

Rename MNN backend author label from 'GPU' to 'MNN' in Download Manager

• 'getImageAuthor' now returns ''MNN'' for the 'mnn' backend instead of ''GPU'', keeping the Download Manager consistent with the rest of the UI.

src/screens/DownloadManagerScreen/useDownloadManager.ts

Tests (3) +71 / -4
pixel10ImageGenCpuPath.test.tsNew integration test: Pixel 10 forces CPU image path and MNN-only recommendation +46/-0

New integration test: Pixel 10 forces CPU image path and MNN-only recommendation

• New integration test suite mocking a Pixel 10 Pro XL device. Verifies that 'requiresCpuImageBackend()' returns true, 'getOpenCLCapability()' returns 'pixel_10_cpu_only', and 'getImageModelRecommendation()' returns 'mnn' with a Pixel 10-specific banner.

tests/integration/models/pixel10ImageGenCpuPath.test.ts

hardware.test.tsAdd Pixel 10 CPU path and Tensor MNN banner unit test cases +23/-2

Add Pixel 10 CPU path and Tensor MNN banner unit test cases

• Adds 'cachedOpenCLCapability' reset in 'beforeEach'. Updates two existing assertions from 'bannerText.toContain('GPU')' to ''MNN''. Adds a new Tensor device test asserting the 'MNN' + 'Tensor' banner, and a Pixel 10 test asserting 'requiresCpuImageBackend()', 'getOpenCLCapability()' reason, and 'compatibleBackends'.

tests/unit/services/hardware.test.ts

huggingFaceModelBrowser.test.tsUpdate MNN model parse test to expect '(MNN)' display name +2/-2

Update MNN model parse test to expect '(MNN)' display name

• Renames the test description from 'parses MNN backend zip as a GPU model' to 'as an MNN model' and updates the 'displayName' assertion from ''Anything V5 (GPU)'' to ''Anything V5 (MNN)''.

tests/unit/services/huggingFaceModelBrowser.test.ts

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📎 Requirement gaps (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 25 rules

Grey Divider


Action required

1. Em dashes in bannerText 📘 Rule violation ✧ Quality
Description
User-facing bannerText strings include Unicode em dashes (), which are disallowed in
user-visible text content. This can lead to noncompliant UI copy and inconsistent typography across
platforms.
Code

src/services/hardware.ts[R397-416]

      rec = {
        recommendedBackend: 'mnn',
        bannerText:
-          'GPU models recommended \u2014 your Snapdragon doesn\u2019t support NPU acceleration',
+          'MNN models recommended \u2014 your Snapdragon does not support NPU acceleration',
+        compatibleBackends: ['mnn'],
+      };
+    } else if (socInfo.vendor === 'tensor') {
+      rec = {
+        recommendedBackend: 'mnn',
+        bannerText:
+          this.requiresCpuImageBackend()
+            ? 'MNN models recommended for Pixel \u2014 image generation runs on CPU on Pixel 10 until GPU support lands'
+            : 'MNN models recommended for Pixel and Tensor devices',
        compatibleBackends: ['mnn'],
      };
    } else {
      rec = {
        recommendedBackend: 'mnn',
        bannerText:
-          'GPU models recommended \u2014 NPU requires Snapdragon 888+',
+          'MNN models recommended \u2014 NPU acceleration requires Snapdragon 888+',
Relevance

⭐⭐ Medium

No clear history enforcing “no em dashes” in app UI; copy-style dash suggestions often rejected (PR
#289).

PR-#289

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1570610 forbids em dashes () in any user-facing text. In
src/services/hardware.ts, the modified bannerText strings include \u2014 (em dash) in multiple
user-visible recommendation messages.

Rule 1570610: Disallow em dashes in user-facing text content; use hyphens instead
src/services/hardware.ts[397-416]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
User-facing `bannerText` strings in `HardwareService.getImageModelRecommendation()` contain Unicode em dashes (`—`, U+2014). The compliance rule requires using ASCII hyphens (`-`) instead in user-visible text.

## Issue Context
These `bannerText` fields are displayed to users as recommendation banners, so they are in-scope for the text-content restriction.

## Fix Focus Areas
- src/services/hardware.ts[397-416]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Unhandled SoCInfo rejection 🐞 Bug ☼ Reliability
Description
useImageModels() starts a nested hardwareService.getSoCInfo() promise chain without error handling,
so a failure in SoC detection can produce an unhandled promise rejection and skip updating
imageNpuAvailable/backendFilter. This can leave the image models UI in an inconsistent state after a
DeviceInfo/native failure.
Code

src/screens/ModelsScreen/useImageModels.ts[R135-139]

+      hardwareService.getSoCInfo().then(soc => {
+        if (cancelled) return;
+        setImageNpuAvailable(soc.hasNPU);
+        setBackendFilter(prev => (!soc.hasNPU && prev === 'qnn') ? 'mnn' : prev);
+      });
Relevance

⭐⭐⭐ High

Team repeatedly accepts adding try/catch/.catch to prevent unhandled promise rejections and state
desync (e.g., PR #116).

PR-#116

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new nested getSoCInfo().then(...) has no error handler, while getSoCInfo() awaits a native
call (DeviceInfo.getHardware()) without try/catch, so the rejection can propagate unhandled and
prevent the state updates in the .then from executing.

src/screens/ModelsScreen/useImageModels.ts[130-139]
src/services/hardware.ts[308-334]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`useImageModels()` triggers `hardwareService.getSoCInfo()` in a nested `.then(...)` chain without a `.catch(...)` or surrounding `try/catch`. If `getSoCInfo()` rejects (e.g., `DeviceInfo.getHardware()` fails), this becomes an unhandled promise rejection and the state updates inside the `.then` never run.

### Issue Context
`hardwareService.getSoCInfo()` contains awaited native calls that can reject and it does not internally catch/convert those failures.

### Fix Focus Areas
- src/screens/ModelsScreen/useImageModels.ts[130-139]
- src/services/hardware.ts[308-334]

### Suggested fix
- In the mount effect, convert the chain to `async/await` with `try/catch`, or attach `.catch(...)` to the `getSoCInfo()` call.
- On error, set a safe fallback (e.g., `setImageNpuAvailable(false)` and ensure `backendFilter` is not left as `'qnn'`).
- Optionally, consider making `getSoCInfo()` resilient by catching native errors and returning `{ vendor: 'unknown', hasNPU: false }` rather than rejecting.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. NPU option hidden initially 🐞 Bug ≡ Correctness
Description
imageNpuAvailable is initialized to false and is passed down to ImageFilterBar, which removes the
qnn backend option when false. On NPU-capable devices, this temporarily hides the NPU filter until
SoC detection finishes and updates imageNpuAvailable.
Code

src/screens/ModelsScreen/useImageModels.ts[31]

+  const [imageNpuAvailable, setImageNpuAvailable] = useState(false);
Relevance

⭐ Low

Similar “avoid UI flicker from async detection” caching suggestion was rejected
(useTextGenerationAdvanced) in PR #313.

PR-#313

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hook now starts with imageNpuAvailable = false, and ImageFilterBar derives backendOptions by
filtering out 'qnn' when npuAvailable is false, so NPU will not appear until the async update
runs.

src/screens/ModelsScreen/useImageModels.ts[20-33]
src/screens/ModelsScreen/ImageFilterBar.tsx[117-130]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`imageNpuAvailable` starts as `false`, which causes the UI to initially hide the QNN/NPU backend option even on devices that support it, until async SoC detection completes.

### Issue Context
The filter options are derived from `npuAvailable`; passing `false` during initial render filters out `'qnn'`.

### Fix Focus Areas
- src/screens/ModelsScreen/useImageModels.ts[28-32]
- src/screens/ModelsScreen/ImageFilterBar.tsx[124-130]

### Suggested fix
- Use a tri-state like `imageNpuAvailable: boolean | null` where `null` means "unknown".
- In `ImageFilterBar`, when `npuAvailable == null`, show `'qnn'` but disable it (or show a loading state) instead of hiding it.
- Alternatively initialize from a cached SoC value if available, or default to `true` and only hide once you positively know there is no NPU.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/services/hardware.ts
Comment on lines 397 to +416
rec = {
recommendedBackend: 'mnn',
bannerText:
'GPU models recommended \u2014 your Snapdragon doesn\u2019t support NPU acceleration',
'MNN models recommended \u2014 your Snapdragon does not support NPU acceleration',
compatibleBackends: ['mnn'],
};
} else if (socInfo.vendor === 'tensor') {
rec = {
recommendedBackend: 'mnn',
bannerText:
this.requiresCpuImageBackend()
? 'MNN models recommended for Pixel \u2014 image generation runs on CPU on Pixel 10 until GPU support lands'
: 'MNN models recommended for Pixel and Tensor devices',
compatibleBackends: ['mnn'],
};
} else {
rec = {
recommendedBackend: 'mnn',
bannerText:
'GPU models recommended \u2014 NPU requires Snapdragon 888+',
'MNN models recommended \u2014 NPU acceleration requires Snapdragon 888+',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Em dashes in bannertext 📘 Rule violation ✧ Quality

User-facing bannerText strings include Unicode em dashes (), which are disallowed in
user-visible text content. This can lead to noncompliant UI copy and inconsistent typography across
platforms.
Agent Prompt
## Issue description
User-facing `bannerText` strings in `HardwareService.getImageModelRecommendation()` contain Unicode em dashes (`—`, U+2014). The compliance rule requires using ASCII hyphens (`-`) instead in user-visible text.

## Issue Context
These `bannerText` fields are displayed to users as recommendation banners, so they are in-scope for the text-content restriction.

## Fix Focus Areas
- src/services/hardware.ts[397-416]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +135 to +139
hardwareService.getSoCInfo().then(soc => {
if (cancelled) return;
setImageNpuAvailable(soc.hasNPU);
setBackendFilter(prev => (!soc.hasNPU && prev === 'qnn') ? 'mnn' : prev);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Unhandled socinfo rejection 🐞 Bug ☼ Reliability

useImageModels() starts a nested hardwareService.getSoCInfo() promise chain without error handling,
so a failure in SoC detection can produce an unhandled promise rejection and skip updating
imageNpuAvailable/backendFilter. This can leave the image models UI in an inconsistent state after a
DeviceInfo/native failure.
Agent Prompt
### Issue description
`useImageModels()` triggers `hardwareService.getSoCInfo()` in a nested `.then(...)` chain without a `.catch(...)` or surrounding `try/catch`. If `getSoCInfo()` rejects (e.g., `DeviceInfo.getHardware()` fails), this becomes an unhandled promise rejection and the state updates inside the `.then` never run.

### Issue Context
`hardwareService.getSoCInfo()` contains awaited native calls that can reject and it does not internally catch/convert those failures.

### Fix Focus Areas
- src/screens/ModelsScreen/useImageModels.ts[130-139]
- src/services/hardware.ts[308-334]

### Suggested fix
- In the mount effect, convert the chain to `async/await` with `try/catch`, or attach `.catch(...)` to the `getSoCInfo()` call.
- On error, set a safe fallback (e.g., `setImageNpuAvailable(false)` and ensure `backendFilter` is not left as `'qnn'`).
- Optionally, consider making `getSoCInfo()` resilient by catching native errors and returning `{ vendor: 'unknown', hasNPU: false }` rather than rejecting.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/screens/ModelsScreen/ImageFilterBar.tsx (1)

9-30: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Backend label mapping duplicated across getBackendLabel and BACKEND_OPTIONS.

getBackendLabel re-hardcodes the mnn/qnn label strings that already live in BACKEND_OPTIONS (used a few lines below at line 128). This PR had to touch both places to rename "GPU" → "MNN" — a sign the mapping isn't defined once. As per coding guidelines, "whether the mapping/rule is defined once and reused; avoid speculative abstraction and duplication."

♻️ Proposed consolidation
 function getBackendLabel(filter: BackendFilter): string {
-  if (filter === 'mnn') return 'MNN';
-  if (filter === 'qnn') return 'NPU';
-  if (filter === 'coreml') return 'Core ML';
-  return 'Backend';
+  if (filter === 'coreml') return 'Core ML';
+  return BACKEND_OPTIONS.find(o => o.key === filter)?.label ?? 'Backend';
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/screens/ModelsScreen/ImageFilterBar.tsx` around lines 9 - 30, The backend
label text is duplicated between getBackendLabel and BACKEND_OPTIONS in
ImageFilterBar, so update the component to derive labels from a single source of
truth. Move the backend-to-label mapping into one shared constant or helper used
by both getBackendLabel and the backend chip options, and keep the existing
symbols getBackendLabel and BACKEND_OPTIONS wired to that shared mapping so
future label changes only happen once.

Source: Coding guidelines

🧹 Nitpick comments (1)
src/services/huggingFaceModelBrowser.ts (1)

118-118: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Stale "GPU" reference in sort comment.

The comment // Sort: GPU first, then NPU still uses the old "GPU" terminology being replaced by "MNN" throughout this file/PR. Consider updating for consistency.

✏️ Suggested fix
-  // Sort: GPU first, then NPU; alphabetically within each group
+  // Sort: MNN first, then NPU; alphabetically within each group
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/huggingFaceModelBrowser.ts` at line 118, Update the stale sort
comment in the huggingFaceModelBrowser sorting logic so it matches the new
terminology used in this file. In the section around the model sorting
comparator, replace the old “GPU first, then NPU” wording with the current “MNN
first, then NPU” phrasing, keeping the rest of the comment aligned with the sort
behavior in the surrounding code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@__tests__/integration/models/pixel10ImageGenCpuPath.test.ts`:
- Around line 18-46: The current integration test only re-checks hardwareService
behavior and duplicates unit coverage instead of validating the Pixel 10
CPU-only flow across modules. Update pixel10ImageGenCpuPath.test.ts to exercise
the real cross-layer path by involving ImageGenerationService and/or
activeModelService (with the Pixel 10 mock) and assert that OpenCL is disabled
and cpuOnly/forceCpuImage is applied during model selection/load; if that
end-to-end path cannot be covered here, remove the test file since it does not
add integration coverage.

In `@src/services/imageGenerationService.ts`:
- Around line 458-461: The OpenCL setting is still presented as user-controlled
even when the backend forces CPU-only mode, so update the settings UI/state
handling to reflect the effective value. In the image generation flow around
requiresCpuImageBackend() and getOpenCLCapability(), detect the
pixel_10_cpu_only reason and disable or clearly annotate the OpenCL toggle so it
appears off/locked when hardwareService forces CPU image backend, keeping the
displayed setting aligned with imageUseOpenCL and useOpenCL.

---

Outside diff comments:
In `@src/screens/ModelsScreen/ImageFilterBar.tsx`:
- Around line 9-30: The backend label text is duplicated between getBackendLabel
and BACKEND_OPTIONS in ImageFilterBar, so update the component to derive labels
from a single source of truth. Move the backend-to-label mapping into one shared
constant or helper used by both getBackendLabel and the backend chip options,
and keep the existing symbols getBackendLabel and BACKEND_OPTIONS wired to that
shared mapping so future label changes only happen once.

---

Nitpick comments:
In `@src/services/huggingFaceModelBrowser.ts`:
- Line 118: Update the stale sort comment in the huggingFaceModelBrowser sorting
logic so it matches the new terminology used in this file. In the section around
the model sorting comparator, replace the old “GPU first, then NPU” wording with
the current “MNN first, then NPU” phrasing, keeping the rest of the comment
aligned with the sort behavior in the surrounding code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ed698648-acb0-4a96-a582-49eefcfc7043

📥 Commits

Reviewing files that changed from the base of the PR and between a0535cd and 9cf6a20.

📒 Files selected for processing (14)
  • __tests__/integration/models/pixel10ImageGenCpuPath.test.ts
  • __tests__/unit/services/hardware.test.ts
  • __tests__/unit/services/huggingFaceModelBrowser.test.ts
  • src/screens/DownloadManagerScreen/useDownloadManager.ts
  • src/screens/ModelsScreen/ImageFilterBar.tsx
  • src/screens/ModelsScreen/ImageModelsTab.tsx
  • src/screens/ModelsScreen/constants.ts
  • src/screens/ModelsScreen/imageDownloadQnn.ts
  • src/screens/ModelsScreen/useImageModels.ts
  • src/screens/ModelsScreen/useModelsScreen.ts
  • src/services/activeModelService/index.ts
  • src/services/hardware.ts
  • src/services/huggingFaceModelBrowser.ts
  • src/services/imageGenerationService.ts

Comment on lines +18 to +46
describe('Pixel 10 image generation CPU path integration', () => {
beforeEach(() => {
Platform.OS = 'android';
(hardwareService as any).cachedDeviceInfo = null;
(hardwareService as any).cachedSoCInfo = null;
(hardwareService as any).cachedImageRecommendation = null;
(hardwareService as any).cachedOpenCLCapability = null;
jest.clearAllMocks();
});

afterEach(() => {
Platform.OS = originalOS;
});

it('detects Pixel 10 and disables OpenCL image acceleration', async () => {
expect(hardwareService.requiresCpuImageBackend()).toBe(true);
const openCl = await hardwareService.getOpenCLCapability();
expect(openCl.supported).toBe(false);
expect(openCl.reason).toBe('pixel_10_cpu_only');
});

it('recommends only MNN models with a Pixel 10-specific banner', async () => {
const rec = await hardwareService.getImageModelRecommendation();
expect(rec.recommendedBackend).toBe('mnn');
expect(rec.compatibleBackends).toEqual(['mnn']);
expect(rec.bannerText).toContain('Pixel 10');
expect(DeviceInfo.getModel).toHaveBeenCalled();
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Integration test only exercises a single module — duplicates unit coverage instead of validating cross-layer behavior.

Both tests here call only hardwareService methods (requiresCpuImageBackend, getOpenCLCapability, getImageModelRecommendation) with a mocked react-native-device-info. The second test (Lines 39-45) is essentially identical to the "recommends MNN with Pixel 10 CPU notice" case already added in __tests__/unit/services/hardware.test.ts (lines 1035-1050) — same setup, same assertions.

Per the PR stack, the actual CPU-only enforcement happens in ImageGenerationService (disabling OpenCL) and activeModelService (forceCpuImage/cpuOnly) — neither is exercised here. A file named pixel10ImageGenCpuPath.test.ts under __tests__/integration/ implies it should verify the full path (HardwareService detection → ImageGenerationService/activeModelService consuming it), not just re-test HardwareService in isolation.

Consider either:

  • Extending this test to actually invoke ImageGenerationService/activeModelService with the Pixel 10 mock and assert OpenCL is disabled / cpuOnly: true is set during model load, or
  • Removing this file if it doesn't add coverage beyond the unit test.

As per coding guidelines, "Integration tests in __tests__/integration/ should verify how multiple modules work together end-to-end, using mocked native modules but real logic across layers."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/integration/models/pixel10ImageGenCpuPath.test.ts` around lines 18
- 46, The current integration test only re-checks hardwareService behavior and
duplicates unit coverage instead of validating the Pixel 10 CPU-only flow across
modules. Update pixel10ImageGenCpuPath.test.ts to exercise the real cross-layer
path by involving ImageGenerationService and/or activeModelService (with the
Pixel 10 mock) and assert that OpenCL is disabled and cpuOnly/forceCpuImage is
applied during model selection/load; if that end-to-end path cannot be covered
here, remove the test file since it does not add integration coverage.

Source: Coding guidelines

Comment on lines +458 to +461
const useOpenCL = hardwareService.requiresCpuImageBackend()
? false
: (settings.imageUseOpenCL ?? true);
return this._runGenerationAndSave({ params, enhancedPrompt, activeImageModel, steps, guidanceScale, imageWidth, imageHeight, useOpenCL });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "imageUseOpenCL" -C3 --type=ts --type=tsx 2>/dev/null

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 168


🏁 Script executed:

#!/bin/bash
rg -n "imageUseOpenCL|pixel_10_cpu_only|getOpenCLCapability|requiresCpuImageBackend|OpenCL" --type=ts --type=tsx --type=js --type=jsx

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 196


🏁 Script executed:

#!/bin/bash
set -e
printf 'FILES\n'
git ls-files | rg 'src/.*(imageGenerationService|settings|opencl|hardwareService)|docs/|Settings' || true
printf '\nMATCHES\n'
rg -n "imageUseOpenCL|pixel_10_cpu_only|getOpenCLCapability|requiresCpuImageBackend|OpenCL" src docs --hidden || true

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 18221


🏁 Script executed:

#!/bin/bash
set -e
git ls-files | sed -n '1,240p'

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 11644


🏁 Script executed:

#!/bin/bash
set -e
printf 'MODEL SETTINGS\n'
sed -n '45,80p' src/screens/ModelSettingsScreen/ImageGenerationSection.tsx
printf '\nGENERATION SETTINGS\n'
sed -n '78,110p' src/components/GenerationSettingsModal/ImageQualitySliders.tsx
printf '\nHARDWARE CAPABILITY\n'
sed -n '440,475p' src/services/hardware.ts

Repository: off-grid-ai/off-grid-ai-mobile

Length of output: 3969


Surface the Pixel 10 CPU-only override in Settings — the OpenCL toggle still looks user-controlled even though requiresCpuImageBackend() forces it off. Disable or annotate it when getOpenCLCapability().reason === 'pixel_10_cpu_only' so the UI matches the effective setting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/imageGenerationService.ts` around lines 458 - 461, The OpenCL
setting is still presented as user-controlled even when the backend forces
CPU-only mode, so update the settings UI/state handling to reflect the effective
value. In the image generation flow around requiresCpuImageBackend() and
getOpenCLCapability(), detect the pixel_10_cpu_only reason and disable or
clearly annotate the OpenCL toggle so it appears off/locked when hardwareService
forces CPU image backend, keeping the displayed setting aligned with
imageUseOpenCL and useOpenCL.

@sonarqubecloud

Copy link
Copy Markdown

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.

[Bug] Pixel 10 XL no Image Gen

1 participant