Skip to content

Commit 19a5450

Browse files
committed
Add the on-device plant scan (phase 2)
Gemma 4 E2B runs on the phone through LiteRT-LM (react-native-litert-lm). The model is a single 2.6 GB file downloaded once, on Wi-Fi, and never leaves the phone; photos are not sent anywhere. - Model: download with progress, cancel and resume, disk and RAM checks (6 GB phones and up), loaded on first use and freed when idle - Scan tab: photo, up to three species with their confidence, the user confirms or types the species - Species care sheets generated by the model, validated against a schema with retries, stored and shared by plants of the same species - New plant pre-filled from the scan, with proposed care tasks - Plant page shows its sheet, and can generate one from the species - Fake model for the browser and Expo Go (EXPO_PUBLIC_FAKE_AI=1) The engine choice is explained in docs/ai-engine.md. Claude-Session: https://claude.ai/code/session_01Jg25nsPfSVmrDW93LK1Wby
1 parent a30433c commit 19a5450

42 files changed

Lines changed: 3725 additions & 44 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Tout reste sur le téléphone : pas de compte, pas de serveur. Les données sont
1313
- **Récurrence glissante** : la prochaine échéance part du jour où le soin a été fait. En hiver (novembre à février), l'intervalle est multiplié par le coefficient du soin. Après deux « terreau encore humide » de suite, l'app propose d'allonger l'intervalle.
1414
- **Journal** : tout ce qui a été fait ou reporté, par plante.
1515
- **Résumé quotidien** : une notification locale par jour, à l'heure choisie.
16+
- **Scan** : on photographie une plante, Gemma 4 E2B propose l'espèce, puis rédige sa fiche d'entretien et les soins à programmer. Le modèle tourne sur le téléphone : il se télécharge une fois (2,6 Go, en Wi-Fi) et aucune photo ne sort du téléphone. Il faut un téléphone Android avec 6 Go de RAM. Voir [docs/ai-engine.md](docs/ai-engine.md).
1617

1718
## Développer
1819

@@ -24,13 +25,16 @@ npx tsc --noEmit # typecheck
2425
npx expo lint # lint
2526
```
2627

28+
Le scan a besoin du module natif de l'APK : il ne marche ni dans Expo Go ni dans le navigateur. Pour essayer ses écrans quand même, lance `EXPO_PUBLIC_FAKE_AI=1 npx expo start` : un faux modèle répond à la place de Gemma.
29+
2730
Structure :
2831

2932
```
3033
src/app/ écrans (Expo Router)
34+
src/ai/ modèle sur le téléphone (téléchargement, exécution) et questions du scan
3135
src/components/ composants, dont le design system dans components/ui
3236
src/db/ base SQLite : schéma et migrations, requêtes, hooks réactifs
33-
src/lib/ règles de récurrence, dates, libellés
37+
src/lib/ règles de récurrence, dates, libellés, fiche espèce et sa validation
3438
src/notifications/ résumé quotidien
3539
src/theme/ couleurs, typographie, espacements (clair et sombre)
3640
```

‎app.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
"photosPermission": "Plantule accède à tes photos pour illustrer tes plantes.",
5555
"microphonePermission": false
5656
}
57-
]
57+
],
58+
"react-native-litert-lm"
5859
],
5960
"experiments": {
6061
"typedRoutes": true,

‎docs/ROADMAP.md‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Application Android de gestion des plantes. Usage perso : moi et mes proches, ch
2727
│ Expo Router · écrans │
2828
│ SQLite (lieux, pièces, plantes, soins, journal, réglages) │
2929
│ Photos dans le dossier de l'app · notifications locales │
30-
│ Gemma 4 embarqué (phase 2) │
30+
│ Gemma 4 E2B embarqué (LiteRT-LM), téléchargé au premier usage │
3131
└────────────────────────────────────────────────────────────────────┘
3232
```
3333

@@ -39,8 +39,8 @@ Application Android de gestion des plantes. Usage perso : moi et mes proches, ch
3939
|---|---|
4040
| Lieu | nom |
4141
| Pièce / zone | lieu, nom, exposition, intérieur ou extérieur |
42-
| Plante | lieu, surnom, espèce, pièce, date d'arrivée, pot et substrat, notes, photo principale |
43-
| Fiche espèce (phase 2) | nom commun et latin, lumière, arrosage, humidité, température, toxicité pour les animaux |
42+
| Plante | lieu, surnom, espèce, fiche espèce, pièce, date d'arrivée, pot et substrat, notes, photo principale |
43+
| Fiche espèce | nom commun et latin, lumière, arrosage, humidité, température, toxicité pour les chats et les chiens, rythme d'engrais, de brumisation et de rempotage, conseils. Générée par Gemma et partagée par les plantes de la même espèce |
4444
| Tâche d'entretien | plante, type, intervalle en jours, ajustement hiver, dernière fois faite, prochaine échéance |
4545
| Journal | plante, type d'action, date, note |
4646
| Photo | plante, date, fichier |
@@ -91,12 +91,14 @@ Onglets : **Aujourd'hui · Plantes · Scan** (phase 2) **· Maison**. L'onglet M
9191

9292
### Phase 2 — Scan IA sur le téléphone
9393

94-
- [ ] Prototype : faire tourner Gemma 4 E2B dans l'app et mesurer la vitesse, la RAM et la précision sur nos propres plantes. Bibliothèques à comparer : `react-native-executorch`, `llama.rn`, `react-native-litert-lm`. Vérifier que l'entrée image est bien supportée. Comparer avec l'API gratuite de Pl@ntNet.
95-
- [ ] Intégrer le moteur d'inférence (module natif, donc un development build)
96-
- [ ] Télécharger le modèle au premier usage, en Wi-Fi, avec la progression affichée. Il ne va pas dans l'APK.
97-
- [ ] Écran de scan : photo → espèce, niveau de confiance et alternatives → confirmation
98-
- [ ] Fiche d'entretien générée par Gemma, en JSON validé contre un schéma, enregistrée sur le téléphone
99-
- [ ] Créer une plante depuis le scan, avec un planning d'entretien pré-rempli
94+
- [x] Choisir la bibliothèque : `react-native-litert-lm` (LiteRT-LM de Google), Gemma 4 E2B avec image en un seul fichier de 2,6 Go. Détails et options écartées dans [ai-engine.md](ai-engine.md)
95+
- [ ] Mesurer sur un vrai téléphone : temps de chargement et d'analyse, RAM, GPU ou CPU, précision sur nos propres plantes. Comparer avec l'API gratuite de Pl@ntNet
96+
- [x] Intégrer le moteur d'inférence (module natif, inclus dans l'APK construit par GitHub Actions)
97+
- [x] Télécharger le modèle au premier usage, en Wi-Fi, avec la progression affichée, l'annulation et la reprise. Il ne va pas dans l'APK.
98+
- [x] Écran de scan : photo → espèce, niveau de confiance et alternatives → confirmation
99+
- [x] Fiche d'entretien générée par Gemma, en JSON validé contre un schéma, enregistrée sur le téléphone
100+
- [x] Créer une plante depuis le scan, avec un planning d'entretien pré-rempli
101+
- [x] Générer la fiche d'une plante déjà enregistrée, à partir de son espèce
100102

101103
### Phase 3 — Enrichissement
102104

‎docs/ai-engine.md‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Moteur IA sur le téléphone
2+
3+
Décision du 23/09/2026 pour la phase 2 (scan). Code : `src/ai/engine.ts` et `src/ai/model-*.ts`, derrière le contrat `src/ai/types.ts`.
4+
5+
## Choix
6+
7+
**Gemma 4 E2B** au format LiteRT-LM (`.litertlm`), exécuté par **[react-native-litert-lm](https://github.com/hung-yueh/react-native-litert-lm) 0.7.0**, qui embarque le moteur officiel de Google **LiteRT-LM 0.15.0** (module Nitro, config plugin Expo).
8+
9+
Pourquoi :
10+
11+
- c'est le moteur et le fichier que Google utilise dans son app de démo Google AI Edge Gallery pour Gemma 4 avec **image** (tâche « Ask Image ») : l'entrée image est un chemin officiel, pas un bricolage ;
12+
- un seul fichier de 2,6 Go contient le texte, la vision et l'audio (chargés à la demande) ;
13+
- accélération GPU (OpenCL, compilée dans la lib) avec repli automatique sur le CPU ;
14+
- sortie contrainte par **JSON Schema** (LLGuidance) : le JSON ne peut pas être malformé ;
15+
- compile avec RN 0.86, New Architecture et `expo prebuild` (vérifié en local avec le JDK 17 de la CI).
16+
17+
## Options comparées
18+
19+
| | react-native-litert-lm | llama.rn | react-native-executorch |
20+
|---|---|---|---|
21+
| Moteur | LiteRT-LM (Google) | llama.cpp | ExecuTorch (Meta) |
22+
| Gemma 4 E2B + image | Oui, un seul fichier | Oui, GGUF + projecteur `mmproj` | **Non** : les `.pte` Gemma 4 publiés sont texte seul |
23+
| À télécharger | 2,59 Go | Q4_0 2,84 Go + mmproj Q8_0 0,56 Go = 3,4 Go | 2,4 à 2,6 Go |
24+
| GPU / NPU Android | GPU OpenCL (+ fichiers NPU pour quelques puces) | OpenCL Adreno seulement, NPU Hexagon expérimental | Vulkan |
25+
| JSON contraint | JSON Schema | JSON Schema → grammaire | non |
26+
| Arrêter une génération | non (voir limites) | oui | oui |
27+
| Maintenance | 1 mainteneur, actif (0.7.0 du 31/08) | très actif | très actif (Software Mansion) |
28+
29+
Écartés aussi : MediaPipe LLM Inference (en maintenance selon Google), et Gemini Nano via AICore / ML Kit (seulement sur quelques téléphones haut de gamme, modèle imposé).
30+
31+
## Modèle
32+
33+
- URL (miroir public `litert-community`, sans compte ni jeton, vérifiée : 200, et 206 pour la reprise) :
34+
`https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/6e5c4f1e395deb959c494953478fa5cec4b8008f/gemma-4-E2B-it.litertlm`
35+
- Épinglée sur un commit : le fichier ne peut pas changer sous nos pieds.
36+
- Taille : 2 588 147 712 octets (2,6 Go). SHA-256 : `181938105e0eefd105961417e8da75903eacda102c4fce9ce90f50b97139a63c`.
37+
- Stocké dans `Documents/models/` de l'app, pas dans le cache (Android ne l'efface pas tout seul).
38+
39+
## Mémoire
40+
41+
Mesures de Google (Galaxy S26 Ultra, contexte 2048) : ~1,7 Go de RAM sur CPU, ~0,7 Go sur GPU (plus la mémoire GPU). Les poids sont projetés en mémoire depuis le fichier.
42+
L'app exige **6 Go de RAM** (seuil technique : `Device.totalMemory` ≥ 5 Go, un téléphone de 6 Go en déclare ~5,5). En dessous, le scan est « indisponible » avec la raison. Google AI Edge Gallery demande 8 Go, mais pour un contexte de 32 000 jetons ; on en utilise 4096. Le seuil est dans `MIN_TOTAL_MEMORY_BYTES` (`src/ai/model-files.ts`) : à baisser pour essayer un téléphone de 4 Go.
43+
44+
## Réglages
45+
46+
- Chargement au premier `generate()`, sur GPU. Si l'app meurt pendant un chargement GPU, les suivants se font sur CPU (fichier témoin `gpu-load.pending`).
47+
- Libéré après 3 minutes sans demande, et dans `deleteModel()`. Une seule génération à la fois (file d'attente).
48+
- Contexte 4096 jetons (une image en coûte ~280), réponse 1024 jetons par défaut, 2048 au plus.
49+
- Température 0,3 par défaut, topK 64, topP 0,95, « thinking » désactivé.
50+
- Photo redimensionnée à 1024 px de côté au plus, en JPEG.
51+
- `jsonSchema` passé à LLGuidance. Si le moteur refuse le schéma, la demande est relancée sans contrainte (la réponse est de toute façon validée par l'appelant).
52+
- Téléchargement : `DownloadTask` d'expo-file-system (SDK 57) avec progression, annulation et reprise (en-tête `Range`), même après un redémarrage de l'app. Écrit en `.part`, vérifié (taille et en-tête `LITERTLM`) puis renommé. Espace disque vérifié avant (+300 Mo de marge). Refus hors Wi-Fi sauf accord explicite (expo-network).
53+
54+
## Limites connues
55+
56+
- **Pas d'arrêt en cours de génération** : la 0.7.0 n'expose pas `cancelProcess()` (corrigé sur la branche principale de la lib, pas encore publié). `signal` rejette tout de suite, mais le calcul continue en arrière-plan jusqu'à la fin de la réponse et la demande suivante attend.
57+
- **Température fixée au chargement** : une demande avec une autre température recharge le modèle (quelques secondes). Garder une température constante.
58+
- GPU : il faut OpenCL, absent de certains téléphones. Le repli CPU marche mais le traitement de l'image est ~7 fois plus lent.
59+
- Premier chargement GPU plus long (compilation mise en cache).
60+
- iOS non géré ici (la lib le supporte, mais un modèle de plus de 2 Go demande un entitlement Apple payant).
61+
- APK arm64 : 70 Mo, dont ~25 Mo pour le moteur (`liblitertlm_jni.so` fait 21 Mo). Le modèle n'est pas dans l'APK.
62+
- Le config plugin de la lib force Kotlin 2.3.0 et `minSdkVersion` 26.
63+
- Build local : le JDK 25 livré avec Android Studio fait échouer la configuration CMake de plusieurs libs natives. Utiliser un JDK 17 (comme la CI).
64+
65+
## À mesurer sur un vrai téléphone
66+
67+
Temps de chargement, temps d'une identification avec photo, RAM, repli CPU/GPU, justesse sur nos plantes (et comparaison avec Pl@ntNet).

‎package-lock.json‎

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"expo-image-manipulator": "~57.0.19",
1818
"expo-image-picker": "~57.0.19",
1919
"expo-linking": "~57.0.10",
20+
"expo-network": "~57.0.2",
2021
"expo-notifications": "~57.0.20",
2122
"expo-router": "~57.0.22",
2223
"expo-splash-screen": "~57.0.9",
@@ -29,6 +30,8 @@
2930
"react-dom": "19.2.3",
3031
"react-native": "0.86.3",
3132
"react-native-gesture-handler": "~2.32.0",
33+
"react-native-litert-lm": "^0.7.0",
34+
"react-native-nitro-modules": "^0.37.1",
3235
"react-native-reanimated": "4.5.1",
3336
"react-native-safe-area-context": "~5.7.0",
3437
"react-native-screens": "~4.26.0",

0 commit comments

Comments
 (0)