OpenAI et React Native : projet JARVIS v.1
Tags: programming, mobile, reactnative, ai
Published: 2024-04-07 21:29:58

Yo les devs !

Depuis quelques temps, j'écris des articles dans mon coin et je me suis dit qu'il serait peut-être intéressant de les poster ici aussi. Pour ça, je commence par repost un article que j'ai écrit **il y a un an** (en 2023), donc n'hésitez pas à réagir dans les commentaires, comme ça je saurai si je devrais en faire d'autres !

---

À l’heure où j’écris cet article, l’IA met le monde en ébullition. ChatGPT, Microsoft Bing, ou encore Bard, toutes ces IA font parler d’elle, en bien ou en mal (mention spéciale aux [débuts rocambolesques de Microsoft Bing version IA](https://www.numerama.com/tech/1267426-premier-test-de-bing-chatgpt-je-me-suis-dispute-avec-une-ia.html)). Et tandis que les _devfluenceurs_ (si si, ça existe) parlent de l’arrivée du modèle GPT-4 d’OpenAI, j’ai moi-même eu envie de tester ce qu’il était possible de faire avec l’un des modèles proposés par OpenAI.

Entre les générateurs d’images (Midjourney, Stable Diffusion), les générateurs de musiques (Mubert), ou encore les IA moins… “sérieuses” (clin d’oeil à [ChatCGT](https://chatcgt.fr/)), il y a des projets pour tous les besoins. Mais pour accéder à ces IA, il faut ouvrir son navigateur internet et partager les ressources de l’IA avec les milliers d’autres requêtes envoyées en même temps que la vôtre. Alors, j’ai pensé à me faire mon propre chatbot ! Et qui sait, peut-être en ferai-je un jour une sorte de Jarvis, comme celui de Tony Stark ?

Pour mon chatbot personnel, le choix d’une application mobile m’a paru évident. Après tout, c’est le futur Jarvis, n’est-ce pas ?
J’ai donc choisi de faire du React Native, ayant déjà une petite envie de découvrir davantage ce framework. Et, pour rester dans le thème des IA, j’ai choisi de me faire aider de ChatGPT pour me lancer dans ce projet. S’il est certain que ChatGPT ne fera pas l’intégralité du code de l’application, il peut être intéressant de l’avoir comme assistant sur ce projet dont je ne maîtrise pas (encore) toutes les subtilités de la technologie employée.

## Mise en place du projet

N'étant pas familier avec le développement mobile, j'ai demandé à ChatGPT comment réaliser mon projet en plusieurs étapes. Il m'a donné une réponse découpée en huit étapes, avec ligne de commande et exemple de code, donc commençons à mettre tout ça en place.

### Environnement

Deux éléments sont indispensables pour ce projet :

- Node.js
- Expo Go (Une app mobile pour tester un projet React Native développé avec [Expo](https://expo.dev/))

Une fois les installations terminées, on peut créer son projet et apercevoir le “Hello World” de React Native en effectuant les commandes :

```
npx create-expo-app myChatBot && cd myChatBot
npx expo start
```

### Paramétrage

Maintenant que le projet est mis en place, il reste encore deux autres éléments à récupérer :

- Une clé de l’API d’OpenAI (pour pouvoir communiquer avec une instance GPT-3)
- Une icône pour l’application

La clé d’API est requise pour chacune des requêtes que l’on fera à l’IA. On peut obtenir cette clé en créant un compte chez [OpenAI](https://platform.openai.com/), puis en allant gérer les clés d’API de ce compte. L’icône, quant à elle, servira pour l’affichage de l’application dans le téléphone, ainsi que pour un éventuel _splash screen_. Pour les moins doués en graphisme, il est possible de passer par une bibliothèque d’images libres de droit et par [le template Figma fourni par la documentation d’Expo](https://www.figma.com/community/file/1155362909441341285). Pour ma part, j’ai utilisé le site [Reshot](https://www.reshot.com/) pour trouver mon icône de cerveau, que j’ai légèrement retravaillée via Inkscape.

![cerveau.png](https://nb-dev.fr/images/rn-chatbot/icon.webp)

Les images qui serviront pour l’icône et le _splash screen_ de l’application doivent être ajoutées dans le dossier `assets` de l’application. On peut ensuite modifier certains paramètres de l’application, tels que la couleur de fond du _splash screen_, ou celle de l’icône (dans le cas d’un appareil Android).

```json
"expo": {
  ...,
  "splash": {
    "image": "./assets/splash.png",
    "backgroundColor": "#333"
  },
  "android": {
    "adaptiveIcon": {
      "foregroundImage": "./assets/adaptive-icon.png",
      "backgroundColor": "#333"
    }
  }
}
```

### Paramétrage rapide

À ce stade, tout se déroule sans problème : l’application possède une icône, un _splash screen_ et un simple écran où s’affiche le “Hello World”. Si ChatGPT m’a déjà fourni un exemple de code pour l’interface de mon application, j’ai choisi d’ajouter certains éléments, ce qui donne la liste d’éléments suivante :


1. Un peu de style
2. Un header (avec l’icône de l’application à l’intérieur)
3. Une zone de liste pour les messages
4. Une zone d’input et son bouton d’envoi
5. Une fenêtre modale pour les informations de l’application (version, nom du développeur…)
6. Un _spinner_ (pendant que l’IA réfléchit à sa réponse)

## Le code

Avec ma liste et les indications de ChatGPT, je sais déjà par où commencer : mettre en place le premier élément de ma liste : **le style**.

Il est donc temps d’attaquer le fichier `App.js` !

### Un peu de style

En ce qui concerne le style, j’ai choisi un thème de couleurs “Matrix” (du vert et du noir, en somme). Le header et la partie contenant la zone d’input seront d’un noir foncé, tandis que la partie où s’afficheront les messages aura une couleur de fond plus proche du gris.

Pour bien distinguer les messages venant de l’IA de ceux de l’utilisateur, j’ai décidé de leur donner des couleurs différentes, ainsi qu’une police différente. En ce qui concerne le loading spinner de l’application, il adoptera également les couleurs du thème “Matrix”. Pour qu’il soit bien visible, j’ai choisi de le mettre dans un vert relativement clair, qui contraste bien avec le noir du header et de la zone d’input.

(Le code correspondant au style se trouve dans mon fichier `App.js`, il serait peut-être un peu long de le mettre entièrement ici)

### L'interface

Si j’avais planifié dès le départ le style que je voulais appliquer à mon interface, c’est en travaillant effectivement sur l’interface que j’ai pu “peaufiner” les détails de style (couleurs de bordure, espacements, …).

Ainsi, en suivant ma liste et en déviant du modèle proposé par ChatGPT, j’ai commencé par ajouter mon header, dans lequel figure également le logo de l’application.

```react
<View style={styles.container}>
  <View style={styles.topBar}>
    <View>
      <Text style={styles.itemTopBar}>My ChatBot v.1.0</Text>
    </View>

    <View style={styles.imageTopBarContainer}>
      <Image source={require('./assets/icon.png')} style={styles.imageTopBar}/>
    </View>
  </View>

  ...
</View>
```

J’ai ensuite ajouté la zone permettant de lister les messages envoyés et reçus (à l’aide de l’élément `FlatList`), ainsi que la dernière partie de mon interface : **la zone d’input**.

```react
<FlatList
  style={styles.list}
  data={messages}
  renderItem={renderItem}
  keyExtractor={(item) => item.id.toString()}
  ref={refFlatList}/>

<View style={styles.inputContainer}>
  <TextInput
    multiline
    cursorColor="#00FF41"
    style={styles.input}
    placeholder="Votre message..."
    placeholderTextColor="#777"
    value={inputValue}
    onChangeText={setInputValue} />
  
  <TouchableOpacity style={styles.button} onPress={() => { handleSend(); }}>
    <Image source={require('./assets/send.png')}/>
  </TouchableOpacity>
</View>
```

L’interface étant des plus simplistes, tous les éléments nécessaire pour faire fonctionner l’application sont déjà présents. Il ne manque plus que la gestion des messages avec le bot !

### Les messages

Pour “gérer” les messages, il faut d’abord commencer par ajouter ce que l’on envoie dans la liste des messages, qui est directement reliée à l’élément `FlatList` de l’interface. Pour cela, on va utiliser ce que React Native appelle un `state`, un genre particulier de variable qui subsiste entre chaque rechargement de l’affichage d’un élément de l’interface.

Ici, on utilise le hook `useState()` pour avoir la variable `state` et son setter :

```react
const [messages, setMessages] = useState([]);
```

On initialise la liste des messages comme étant un tableau vide, que l’on remplira par la suite grâce à la méthode setter `setMessages()`. Pour que l’affichage du message se fasse, deux méthodes entrent en jeu :

- `renderItem()` (insère le composant qui représente le message dans la `FlatList`)
- `handleSend()` (ajoute le message dans la state messages)

Ma demande à ChatGPT m’a déjà fourni la méthode `renderItem()`, que je modifie ensuite pour y ajouter mes classes CSS :

```react
const renderItem = ({ item }) => (
  <View style={item.sender === "bot" ? styles.botMessageContainer : styles.myMessageContainer}>
    <Text style={item.sender === "bot" ? styles.botMessage : styles.myMessage}>{ item.text }</Text>
  </View>
);
```

Je récupère ensuite la méthode `handleSend()` telle qu’elle m’est donnée par ChatGPT, et qui sera activée à chaque pression sur le bouton "Envoyer" :

```react
const handleSend = () => {
  if (inputValue) {
    prompt = inputValue.trim();
    setInputValue("");
    setMessages([...messages, { id: messages.length + 1, text: prompt, sender: (messages.length + 1) % 2 == 0 ? "user" : "bot" }]);
  }
};
```

À ce stade, je peux commencer à tester l’application via Expo et envoyer des messages dans l’application pour ajuster le CSS des bulles de message.

![bulles.png](https://nb-dev.fr/images/rn-chatbot/test-message-chatbot.webp)

Maintenant que la liste existe et que le CSS est correct, on peut essayer de converser véritablement avec notre instance GPT-3. C’est la partie la plus importante de notre application, et c’est à ce moment-là que j’ai rencontré **les premières vraies erreurs de ChatGPT** dans ma demande initiale, sans compter les quelques imprécisions que j’avais déjà rencontré dans sa réponse, mais que j’avais pu corriger immédiatement.

## Les imperfections de ChatGPT

C’est au moment d’implémenter le dialogue avec l’instance GPT-3 que j’ai rencontré mon premier problème avec ChatGPT. En effet, j’ai rencontré deux erreurs bloquantes en essayant de suivre les indications de ChatGPT :

- L’utilisation d’OpenAI avec React Native
- L’accès aux variables d’environnement dans une application en React Native

Dans le premier cas, après avoir interrogé plusieurs fois ChatGPT sans succès, je me suis résolu à aller voir directement dans la documentation d’OpenAI (la doc ne trahit jamais !), qui m’a donné les lignes de code dont j’avais besoin.

Dans le second cas, j’ai également fait plusieurs demandes à ChatGPT, toutes infructueuses. Cette fois-ci, il n’y avait pas vraiment de documentation à disposition, mais j’ai pu trouver la solution à mon problème grâce à [un article Medium](https://medium.com/swlh/how-to-properly-use-environment-variables-in-an-expo-react-native-app-7ab852590b30).

En ce qui concerne la raison de ces réponses erronées, une partie de la réponse à cette question réside, je pense, dans deux faits :

- Les données de ChatGPT ne vont que jusqu’en 2021
- ChatGPT n’est pas relié à Internet

De par ces deux faits, on constate qu’il y a beaucoup de chance qu’il base sa réflexion et ses réponses sur des documentations dépréciées, ce qui est problèmatique dans le cas d’un framework qui continue d’évoluer. De plus, il lui est impossible de se corriger en vérifiant d’autres sources sur Internet, puisqu’il ne fonctionne qu’avec les données qui ont servi à son entraînement.

Bref, comme beaucoup l’ont déjà remarqué, à l’heure actuelle, **ChatGPT n’est pas encore au niveau pour remplacer un développeur humain** (enfin ça, c’est vrai pour GPT-3).

## Finalisation de l'application

### Les messages, partie 2

Après avoir réussi à mettre en place la communication avec l’instance GPT-3, un problème s’est posé : les messages ne s’affichaient qu’une fois que le bot avait répondu. J’avais remarqué qu’il m’était impossible de faire fonctionner un double appel au setter d’une state, j’ai donc cherché s’il était possible de le faire d’une quelconque manière. Ici, pas de demande à ChatGPT, une recherche Google classique m’a mené sur [l’article de blog d’un développeur](https://www.andreadiotallevi.com/blog/how-to-handle-multiple-set-state-calls-on-the-same-object-in-react) qui contenait la solution à mon problème (et une explication détaillée de l’origine du problème).

Cela change la nature de ce que j’envoie au setter `setMessages()` dans ma méthode `handleSend()` :

```react
// Manipulation pour pouvoir envoyer les messages "un à un"
setMessages(previousMessages => ([...previousMessages, { id: previousMessages.length + 1, text: prompt, sender: "user" }]));
```

### Un peu de style, partie 2

Au niveau du style de l’application, j’ai voulu accentuer la différence entre les messages du bot et ceux de l’utilisateur. J’ai donc choisi de changer la police des messages du bot en monospace (un peu cliché, mais cela reste dans l’esprit de l’application, après tout).

J’ai aussi voulu ajuster l’espacement présent entre la `FlatList` et le reste des éléments de l’interface. Au début, j’avais opté pour du _padding_ mais, après de multiples tentatives, le rendu ne correspondait toujours pas à ce que je voulais. En faisant des recherches (encore une fois sans l’aide de ChatGPT), je suis tombé sur une [_issue_ Github](https://github.com/facebook/react-native/issues/15707) sur le dépôt du projet React Native.
Finalement, la solution était simple : changer le _padding_ pour du _margin_.

## Ajouts et fioritures

### Un modal, une application

La plupart des applications possèdent une section “À propos”, dédiée à l’affichage d’informations diverses sur l’application ou sur la personne qui l’a produite. J’ai donc voulu ajouter cela à mon application !

Pour ce faire, j’ai exploité l’élément `Modal` de React Native, que je fais apparaître via une pression sur l’icône de l’application présente dans le header. Dans ce modal, les informations apparaissent sous la forme d’une liste à puces, que je fais apparaître via leur code Unicode (j’ai trouvé l’astuce sur [cet article du site atomlab.dev](https://www.atomlab.dev/tutorials/react-native-bullet-list)). Pour certaines de ces informations, j’ai voulu intégrer un lien hypertexte, et j’ai donc utilisé la méthode `Linking.openURL()` pour y parvenir.

Comme je voulais que le modal s’affiche au-dessus de la `FlatList` des messages, il a également été nécessaire d’ajouter un _overlay_, et d’effectuer quelques changements au niveau du CSS (notamment au niveau du `z-index` des éléments).

![modal.png](https://nb-dev.fr/images/rn-chatbot/modal-app.webp)

### Un loading spinner, pour le feedback

Comme son nom l’indique, un _loading spinner_ est un élément fait pour indiquer **un état de chargement**. Il est donc nécessaire d’en avoir un, afin que l’utilisateur sache que le bot va répondre, et que ce n’est pas juste l’application qui plante !

Étant un élément courant de nos jours, en ce qui concerne l’ajout de cet élément dans mon application, j’ai préféré suivre un vieil adage des développeurs qui dit :

> Ne réinvente pas la roue.

J’ai donc choisi un _spinner_ simple et dont il est facilement possible d’adapter le style : `react-native-loading-spinner-overlay`.

### Scroll auto au dernier message

Pour cette première version de mon chatbot, une chose anodine mais pourtant très courante dans les applications de messagerie faisait défaut : **le scroll vers le dernier message**. Dans la majorité des applications, lorsqu’un nouveau message apparaît dans la liste, la vue se déplace afin d’afficher le message dans son entièreté (ou du moins, afficher la fin du message). En voulant ajouter ce comportement “anodin”, je me suis cependant heurté à un problème pour le moins épineux : un problème d’ordre `index out of range` (classique, mais pas toujours évident).

Après quelques rapides recherches sans résultats probants, je me suis résolu à me tourner à nouveau vers ChatGPT. Fort de ma nouvelle compréhension du fonctionnement d’une state et de son _setter_, j’ai expliqué mon hypothèse sur la raison pour laquelle mon scroll ne fonctionne pas, et je lui ai demandé, sur la base de cette hypothèse, comment il ferait pour résoudre le problème.

Et, il faut reconnaître deux choses :

- Mon hypothèse était la bonne
- ChatGPT a très vite compris le problème et m’a fourni une solution adéquate

Cette solution se résume à exécuter mon scroll au bon moment, c’est-à-dire **à la fin de l’animation faisant apparaître le message** dans la `FlatList`. Cela donne une fonction telle que :

```react
requestAnimationFrame( () => refFlatList.current.scrollToEnd({ animated: true }) );
```

Je ne pense pas que j’aurais trouvé cette méthode aussi vite, sans l’aide de ChatGPT, et c’est l’un des avantages qu’on ne peut lui nier : **il permet un gain de temps considérable**.

## Build v.1.0

Satisfait des éléments ajoutés jusqu’à présent, j’ai pensé qu’il était temps d’installer la version 1 de ce projet !

Pour _build_ mon projet, je me suis aidé de la documentation d’Expo afin de bien régler les différents paramètres nécessaires au bon déroulement du processus. Je me suis aussi aperçu de quelque chose : les variables d’environnement telles que je les utilisais n’étaient pas embarquées dans l’APK produit par le build. De ce fait, pas de clé d’API, donc pas de bot !

Pour remédier à cela, il a fallu créer un _secret_ avec une commande d’EAS (l’outil qui gère les builds Expo) et modifier légèrement le code. Et voilà, on tient une première version !

## Conclusion

Ce petit projet n’a pas d’autre but que de me permettre de travailler sur React Native, et accessoirement de me prendre pour Tony Stark. Il est encore très sommaire, mais il est fort probable que je revienne dessus plus tard pour y ajouter d’autres fonctionnalités qui rapprocheront mon chatbot du niveau du célèbre Jarvis.

Tout le code est disponible [sur mon dépôt GitHub](https://github.com/Mar-Nb/react-native-chatbot). Vous pouvez retrouver l'article [sur mon blog](https://nb-dev.fr/2023/react-native-chatbot-v1/) et vous pouvez également me suivre [sur twitter](https://twitter.com/da_whistler), où je parle essentiellement de tech et de mon activité de dév.

Merci de m’avoir lu jusqu’ici, et à la prochaine ! 👋