diff --git a/content/articles/2023/11/chatgpt-to-the-rescue.md b/content/articles/2023/11/chatgpt-to-the-rescue.md new file mode 100644 index 000000000..0557286b7 --- /dev/null +++ b/content/articles/2023/11/chatgpt-to-the-rescue.md @@ -0,0 +1,263 @@ +--- +id: "30" +title: "ChatGPT à la rescousse" +description: "Vous n'utilisez pas encore copilot ou chatGPT en tant que développeur(euse) ? Eh bien, laissez-moi vous montrer un exemple de pourquoi vous devriez." +tags: + - chatgpt +date: "2023-11-21" +cover: "chatgpt-to-the-rescue/ai.jpg" + +language: "fr" +alternates: + - en: "/en/2023/11/chatgpt-to-the-rescue" +--- +Vous n'utilisez pas encore copilot ou chatGPT en tant que développeur(euse) ? + +Eh bien, laissez-moi vous montrer un exemple de pourquoi vous devriez. + +Je vous invite à me suivre d'un exemple récent où chatGPT m'a fait gagner des heures. Je vous montrerai également ces limites pendant l'exercice. + +## Les étapes d'écriture d'un billet de blog + +Je tiens ce blog depuis 20 ans. Ces dernières années, j'écris en moyenne entre 1 et 2 billets par mois. + +La pratique m'a permis d'être plus rapide. J'ai donc ajouté un peu de complexité et depuis quelques années, je passe certains de ces billets en anglais, au début pour les mettre sur Medium et désormais dans une section dédiée sur mon propre blog. + +Mais j'avoue que cela a complexifié ma routine d'écriture. Voici mes étapes : + +- Écriture du billet sur Google Docs. + +- Mise en forme, recherche d'image ou création d'image avec Procreate + +- Conversion en Markdown via une extension Google Docs + +- Sauvegarde de chaque image à la main, car Google Docs ne permet pas facilement de les récupérer + +- Test sous Nuxt du billet avec quelques informations supplémentaires (titre, description etc...) + +- Vérification de la mise en page, adaptations pour les images récupérées à part depuis Google Docs + +- Création de la version anglaise en copiant/collant le premier fichier + +- Utilisation de Deepl pour traduire par petits blocs afin de ne pas tomber sur la limite d'usage du site + +- Vérification de l'ensemble et publication + +Les deux premières étapes sont évidemment les plus importantes, tout le reste est peu intéressant et rébarbatif. Rébarbatif au point de parfois me pousser à laisser un billet de blog au rebut pendant plusieurs jours, voire semaines. + +J'avais déjà pensé à automatiser une partie du processus mais ça impliquait d'utiliser plusieurs APIs que je ne connaissais pas : Google Docs, Deepl, une librairie de conversion vers Markdown et, je le savais, plein de manipulations de fichiers.\ +C'était faisable, mais le coût me semblait excessif pour un blog personnel. + +Et puis un soir je me suis dit que ce serait un bon exercice à faire avec ChatGPT. + +## ChatGPT à la rescousse + +J'ai demandé à ChatGPT d'écrire un programme Python afin de réaliser toutes les opérations suivantes : + +- récupération du fichier depuis l'API Google Docs à partir de son ID + +- conversation en local au format Markdown + +- Copie aux bons endroits de mon blog avec mes normes de publication (année et mois), ajout de l'entête yaml front matter + +- Export de toutes les images du document original et modification du markdown pour utiliser mon markup et les bons chemins pour accéder aux images récupérées + +- Traduction automatique du fichier Français pour la version Anglaise + +Pour obtenir le résultat final, cela a pris entre **1 et 2h**, entrecoupé de nombreuses parties de jeu vidéo. Je ne sais pas combien de temps, j'aurais mis pour obtenir le même résultat, mais sachant que je ne connaissais aucune de ces APIs et que je ne suis pas un expert Python, je considère que j'aurais pu mettre **10 fois plus**. + +(Mon égo me dit 3 fois plus, mais je suis convaincu qu'il a tort) + +Mais, pourquoi 1h et pas juste 30 secondes le temps qu'il écrive sa réponse ? + +En fait, ce n'est pas ChatGPT qui est long, mais c'est la personne qui le manipule qui doit structurer sa demande et l'emmener au bon endroit. + +## ChatGPT est un assistant, pas un magicien + +Quand on utilise ChatGPT, il faut lui donner des instructions très précises pour obtenir un résultat. Mais ces instructions, vous allez les faire évoluer au fil du temps. + +Pour ma part, je l'utilise comme un assistant qui écrit le code pour moi, mais je me retrouve, comme dans un processus de développement classique, à explorer une piste, me rendre compte que ce n'est pas la plus efficace, rebrousser chemin, ajouter des règles métiers que j'avais oubliés etc... + +Plusieurs choses à savoir sur ChatGPT + +- Il donne rarement des recommandations sur l'usage de telle ou telle librairie ou de telle ou telle approche. C'est à vous d'être explicite + +- Il peut inventer des réponses. Il faut donc tester systématiquement + +- Il se fiche de savoir si c'est la méthode la plus optimale. Il fait ce qu'on lui demande de faire + +- selon la façon dont vous posez la question, il vous emmènera dans le mur. Il n'apportera aucun regard critique à votre démarche + +Prenons quelques exemples. J'ai lui ai demandé : + +> j'écris des documents sur Google docs. Est-ce que j'ai un moyen pour lire ce doc à partir d'un programme en python ? + +La question est clairement peu précise. Mais, j'espérais bien qu'il utilise l'API google Docs, ce qu'il a fait. + +```python +from googleapiclient.discovery import build +from google.oauth2 import service_account + +SERVICE_ACCOUNT_FILE = 'path/to/service_account.json' + +DOCUMENT_ID = 'your-document-id' + +SCOPES = ['https://www.googleapis.com/auth/documents.readonly'] + +creds = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) + +service = build('docs', 'v1', credentials=creds) + +document = service.documents().get(documentId=DOCUMENT_ID).execute() + +print(document['body']['content']) + +``` + +Il m'a aussi précisé qu'il fallait que je crée un service account sur Google et que j'active l'API. Comme je ne connaissais pas bien, on a fait quelques échanges supplémentaires pour qu'il m'explique tout cela. + +Je vous passe les quelques soucis d'environnement Python, qu'il m'a également aidé à résoudre, j'ai finalement récupéré le fichier et je lui ai demandé : + +> J'ai bien accès à mon document, comment convertir le contenu du doc en markdown ? + +Voici une partie du code proposé + +```python +from googleapiclient.discovery import build +from google.oauth2 import service_account + +SERVICE_ACCOUNT_FILE = 'path/to/your/service_account.json' +SCOPES = ['https://www.googleapis.com/auth/documents.readonly'] +creds = service_account.Credentials.from_service_account_file( + SERVICE_ACCOUNT_FILE, scopes=SCOPES) +service = build('docs', 'v1', credentials=creds) + +DOCUMENT_ID = 'your-document-id' + +document = service.documents().get(documentId=DOCUMENT_ID).execute() + +def convert_to_markdown(element): + # Your conversion logic here to convert title, lists etc… + return "" + +markdown_content = "" +for element in document['body']['content']: + markdown_content += convert_to_markdown(element) + +print(markdown_content) +``` + +Vous noterez le "**Your conversion logic here to convert title, lists etc...**" + +J'ai testé et effectivement, le fichier google docs comprend une liste d\'objets correspondant à des blocs de textes. + +À ce moment-là, j'aurais pu continuer à itérer avec chatGPT pour qu'il me propose toutes les fonctions de conversions pour chaque type d'éléments, mais j'ai senti que c'était loin d'être efficace. + +À cette étape, en tant que dev, [j'ai senti qu'il fallait réorienter mon assistant]{.underline}. J'ai fait une recherche et sur StackOverflow, je suis tombé sur un post qui préconisait d'utiliser plutôt l'export en format docx et ensuite la librairie **pypandoc** pour la conversion elle-même. Voilà qui pouvait me faire gagner un temps fou. + +**ChatGPT fait ce que vous lui demandez de faire. Ce n'est pas lui qui vous indiquera que votre approche est peu optimale !** + +Bref, cette-fois ci, je lui ai demandé de télécharger le fichier au format docx et de le convertir avec pypandoc. + +Il a donc modifié le code pour cela, et ce code incluait la ligne suivante : + +```python +output = pypandoc.convert_file(input_file, 'markdown', outputfile=output_file) +``` + + +Il m'a également dit + +> Les images seront extraites dans un dossier nommé selon le nom du fichier Markdown sans son extension, suivi de \_media. Par exemple, si votre fichier Markdown s'appelle output.md, le dossier contenant les images sera output_media. + +Mais ça, c'est faux. Et il ne semblait pas connaître la bonne réponse ou bien, je ne lui ai pas posé la question de la bonne façon. J'ai dû aller cette fois sur la doc de pypandoc pour corriger et ajouter un paramètre pour avoir mes images dans un répertoire de sortie : + +```python +output = pypandoc.convert_file(input_file, 'markdown', outputfile=output_file, extra_args=['--extract-media', '.']) +``` + + +**ChatGPT parfois peut ne pas savoir mais vous répondra avec assurance. Et vous lui ferez confiance...** + +Je vous passe une partie des étapes suivantes qui se sont très bien passées, copie du fichier markdown au bon endroit en fonction de la date du jour, ajout des entête yaml front matter. + +Cela s'est bien passé mais je lui ai posé les questions une par une, d\'où le temps final. C'est, il me semble, la façon la plus efficace d'avancer avec lui, être très précis dans ces demandes donc découper ses instructions en petits blocs. + +Je lui ai ensuite demandé d'utiliser Deepl pour traduire le document en Anglais. + +Je note qu'il m'a proposé une solution très simple et efficace + +```python +DEEPL_API_KEY = 'your-deepl-api-key' + +data = { + 'auth_key': DEEPL_API_KEY, + 'text': french_content, + 'target_lang': 'EN' +} + +response = requests.post('https://api.deepl.com/v2/translate', data=data) +``` + + +Cette solution n'utilise cependant pas le sdk Python proposé par Deepl. Est-ce grave ? Pas forcément. Mais en tout cas, il ne m'en a pas parlé. + +**Par défaut ChatGTP essaie souvent de s'en sortir avec les outils de base. Il m'a rarement proposé d'utiliser des libs additionnelles. Si je veux que ce soit le cas, je dois lui préciser explicitement.** + +Enfin, comme tout bon client qui se respecte, j'ai changé les spécifications à la fin du développement :) + +Eh oui, je me suis rendu compte que je souhaitais des adaptations dans le markdown généré. + +- Je souhaitais utiliser une balise custom pour l'affichage des images. + +- Pypandoc m'avait introduit de nombreux passages à la ligne inutile, ce qui perturbait Deepl pendant la traduction. + +Toutes ces modifications sont passées sans aucune embuche. ChatGPT a su proposer les bonnes modifications qui ont marché du premier coup. + +Un énorme point fort de ChatGPT, c'est sa maîtrise des regexp... + +Et c'est lui qui a su me proposer l'option suivante que j'aurais sans doute galéré à trouver tout seul dans la documentation. + +```python +extra_args=['--wrap=none'] +``` + + +**Dernier tips, le temps gagné par ChatGPT augmente avec la précision de vos instructions.** + +Vous noterez que mes premières instructions étaient très laconiques. Avec le temps, j'ai amélioré la façon de lui demander de travailler et j'ai gagné du temps. + +Par exemple : + +> Écris-moi un script python qui parcourt l\'ensemble de mes articles en markdown contenu dans un répertoire content et ces sous répertoires +> Fais-moi la liste des images utilisée dans ces articles +> Compare cette liste avec les images contenue dans un autre répertoire \"images\" + +> En sortie : +>- donne-moi la liste des images utilisées par articles +>- Donne-moi la liste des images non utilisées + +Plus les instructions d'entrée étaient précises, moins j'ai eu à retoucher. Dans l'exemple ci-dessus, j'ai pu ensuite détecter les images non utilisées de mon blog pour les supprimer. + +## Bilan + +D'un point de vue pratique, désormais ma routine d'écriture d'un article de blog est grandement simplifiée. J'ai gagné un temps fou (des heures ?) et je me suis débarrassé des tâches qui me plaisaient le moins. + +ChatGPT a été particulièrement efficace à un ou deux ratés prêts. Mais si je n'ai pas mis 30 secondes, c'est plus de ma faute que de la sienne. + +Vous pouvez retrouver l'ensemble du code produit [sur Github](https://github.com/hlassiege/eventuallycoding-tooling). C'est environ 200 lignes de code, écrites à 99% par ChatGPT. + +Je n'étais pas sûr de la solution, je l'ai même orienté sur une mauvaise approche au début. J'ai changé plusieurs fois de spécifications car je les faisais évoluer en même temps que je testais le résultat. + +En tant que développeur, je sais qu'il m'a fait gagner un temps fou. Il me remplace quand je code, avec une connaissance parfaite des API. Cela ne m'empêche pas de devoir réfléchir à mon processus de développement, tester, raffiner mes instructions, lui préciser les cas aux limites. Ce qui est normal, il ne lit pas dans les pensées et il a toujours besoin d'un opérateur pour le guider. Et l'expérience de l'opérateur va jouer sur le résultat obtenu.\ +Mais cela ouvre la voie à des personnes moins expertes en développement, mais toujours avec la même méthode de travail. + +Et non, ce n'est pas "moins bien". + +Pendant mes études, j'ai eu des cours d'assembleur sur processeur 68000. Je détestais ça. Mon professeur m'a dit à l'époque "*si vous ne savez pas faire de l'assembleur, vous n'arriverez à rien dans l'informatique*". + +Cette prédiction s'est révélée plutôt fausse. De nouveaux outils sont apparus qui m'ont permis de ne pas me préoccuper de l'assembleur. + +Aujourd'hui des outils comme ChatGPT vont me permettre de moins me préoccuper du langage de programmation. Cela n'empêchera pas, pour l'instant en tout cas, qu'un opérateur sera toujours nécessaire pour le guider. Et l'expérience de cet opérateur, son approche méthodologique, sa capacité à traduire des problèmes métiers complexes en une architecture logicielle et des blocs d'instructions lui feront mieux utiliser cet outil qu'un autre. + +Cela reste encore, pour l'instant, mon super-pouvoir de développeur. diff --git a/content/articles/en/2023/11/chatgpt-to-the-rescue.md b/content/articles/en/2023/11/chatgpt-to-the-rescue.md new file mode 100644 index 000000000..eaab4821f --- /dev/null +++ b/content/articles/en/2023/11/chatgpt-to-the-rescue.md @@ -0,0 +1,263 @@ +--- +id: "30" +title: "ChatGPT to the rescue" +description: "Not yet using copilot or chatGPT as a developer? Well, let me show you an example of why you should." +tags: + - chatgpt +date: "2023-11-21" +cover: "chatgpt-to-the-rescue/ai.jpg" + +language: "en" +alternates: + - fr: "/2023/11/chatgpt-to-the-rescue" +--- +Not yet using copilot or chatGPT as a developer? + +Well, let me show you an example of why you should. + +I invite you to follow me through a recent example where chatGPT saved me hours. I'll also show you these limitations during the exercise. + +## Steps to writing a blog post + +I've been writing this blog for 20 years. In recent years, I've been writing an average of between 1 and 2 posts a month. + +Practice has made me faster. So I've added a bit of complexity and for the past few years I've been putting some of these posts in English, initially to put on Medium and now in a dedicated section on my own blog. + +But I confess that this has made my writing routine more complex. Here are my steps: + +- Write the post in Google Docs. + +- Formatting, image search or image creation with Procreate + +- Convert to Markdown using a Google Docs extension + +- Save each image by hand, as Google Docs doesn't allow you to retrieve them easily + +- test with Nuxt of the post with some additional information (title, description etc...) + +- Check layout, adaptations for images retrieved separately from Google Docs + +- Creation of the English version by copying/pasting the first file + +- Use Deepl to translate in small chunks to avoid hitting the site's usage limit + +- Verification and publication + +The first two steps are obviously the most important; everything else is boring and uninteresting. Uninteresting to the point of sometimes making me leave a blog post in the scrap heap for several days, or even weeks. + +I'd already thought of automating part of the process, but that meant using several APIs I didn't know: Google Docs, Deepl, a Markdown conversion library and, I knew, lots of file manipulation. +It was doable, but the cost seemed excessive for a personal blog. + +Then one evening I thought it would be a good exercise to do with ChatGPT. + +## ChatGPT to the rescue + +I asked ChatGPT to write a Python program to perform all the following operations: + +- retrieve the file from the Google Docs API using its ID + +- local conversation in Markdown format + +- Copy in the right places on my blog with my publishing standards (year and month), add front matter yaml header + +- Exported all the images from the original document and modified the Markdown to use my markup and the correct paths to access the recovered images. + +- Automatic translation of the French file for the English version + +To get the final result, it took between **1 and 2 hours**, interspersed with numerous video games. I don't know how long it would have taken me to get the same result, but given that I didn't know any of these APIs and I'm not a Python expert, I reckon it could have taken me **10 times as long**. + +(My ego says 3 times more, but I'm convinced it's wrong). + +But why 1 hour and not just 30 seconds for him to write his answer? + +In fact, it's not ChatGPT that's long, it's the person handling it who has to structure his request and take it to the right place. + +## ChatGPT is an assistant, not a magician. + +When you use ChatGPT, you have to give it very precise instructions to obtain a result. But these instructions will evolve over time. + +For my part, I use it as an assistant who writes code for me, but I find myself, as in a classic development process, exploring a path, realizing that it's not the most efficient, turning back, adding business rules that I'd forgotten etc... + +A few things to know about ChatGPT + +- It rarely gives recommendations on the use of this or that library or this or that approach. It's up to you to be explicit. + +- It can invent answers. You must therefore systematically test + +- He doesn't care if it's the most optimal method. He does what he's told. + +- depending on how you ask the question, he'll drive you up the wall. He won't take a critical look at your approach. + +Let's take a few examples. I asked him: + +> I write documents on Google docs. Do I have a way to read this doc from a python program? + +Clearly not a very precise question. But I was hoping it would use the google Docs API, which it did. + +```python +from googleapiclient.discovery import build +from google.oauth2 import service_account + +SERVICE_ACCOUNT_FILE = 'path/to/service_account.json' + +DOCUMENT_ID = 'your-document-id' + +SCOPES = ['https://www.googleapis.com/auth/documents.readonly'] + +creds = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) + +service = build('docs', 'v1', credentials=creds) + +document = service.documents().get(documentId=DOCUMENT_ID).execute() + +print(document['body']['content']) + +``` + +He also told me that I had to create a service account on Google and activate the API. As I didn't know much about it, we had a few more exchanges so that he could explain it all to me. + +I'll pass on to you the few Python environment problems, which he also helped me to resolve, I finally got the file and asked him : + +> I've got access to my document, how do I convert the contents of the doc into Markdown? + +Here's some of the suggested code + +```python +from googleapiclient.discovery import build +from google.oauth2 import service_account + +SERVICE_ACCOUNT_FILE = 'path/to/your/service_account.json' +SCOPES = ['https://www.googleapis.com/auth/documents.readonly'] +creds = service_account.Credentials.from_service_account_file( + SERVICE_ACCOUNT_FILE, scopes=SCOPES) +service = build('docs', 'v1', credentials=creds) + +DOCUMENT_ID = 'your-document-id' + +document = service.documents().get(documentId=DOCUMENT_ID).execute() + +def convert_to_markdown(element): + # Your conversion logic here to convert title, lists etc... + return "" + +markdown_content = "" +for element in document['body']['content']: + markdown_content += convert_to_markdown(element) + +print(markdown_content) +``` + +Note the "**Your conversion logic here to convert title, lists etc...**". + +I've tested this and the google docs file does indeed include a list of objects corresponding to blocks of text. + +At this point, I could have continued iterating with chatGPT to get it to offer me all the conversion functions for each type of element, but I felt it was far from efficient. + +At this point, as a dev, [I felt I needed to redirect my assistant]{.underline}. I did a search on StackOverflow, I came across a post that advocated using the docx export instead and then the **pypandoc** library for the conversion itself. That could save me a lot of time. + +**ChatGPT does what you tell it to do. It won't tell you that your approach is less than optimal!** + +Anyway, this time I asked it to download the file in docx format and convert it with **pypandoc**. + +He modified the code to do this, and it included the following line: + +```python +output = pypandoc.convert_file(input_file, 'markdown', outputfile=output_file) +``` + + +He also told me + +> The images will be extracted into a folder named after the Markdown file name without its extension, followed by \_media. For example, if your Markdown file is called output.md, the folder containing the images will be output_media. + +But that's wrong. And he didn't seem to know the right answer, or I didn't ask him the right way. This time I had to go to the pypandoc doc to correct it and add a parameter to have my images in an output directory: + +```python +output = pypandoc.convert_file(input_file, 'markdown', outputfile=output_file, extra_args=['--extract-media', '.']) +``` + + +**ChatGPT sometimes may not know, but will answer you with confidence. And you'll trust it...** + +I'll skip some of the following steps, which went very smoothly, copying the markdown file to the right place according to today's date, adding the yaml front matter headers. + +It went well, but I asked him the questions one by one, hence the final time. It seems to me that this is the most effective way of making progress with him: be very precise in your requests, so break down your instructions into small blocks. + +I then asked him to use Deepl to translate the document into English. + +I note that he proposed a very simple and effective solution + +```python +DEEPL_API_KEY = 'your-deepl-api-key' + +data = { + auth_key': DEEPL_API_KEY, + 'text': french_content, + target_lang': 'EN +} + +response = requests.post('https://api.deepl.com/v2/translate', data=data) +``` + + +However, this solution doesn't use Deepl's Python sdk. Is this a problem? Not necessarily. But in any case, he didn't tell me about it. + +**By default, ChatGTP often tries to get by with the basic tools. It has rarely suggested that I use additional libs. If I want it to, I have to tell it explicitly**. + +Finally, like any self-respecting customer, I changed the specifications at the end of development :) + +Yes, I realized that I wanted some adaptations in the generated Markdown. + +- I wanted to use a custom tag for displaying images. + +- Pypandoc had introduced a lot of unnecessary line breaks, which confused Deepl during translation. + +All these changes went through without a hitch. ChatGPT was able to propose the right modifications that worked the first time. + +A huge strength of ChatGPT is its mastery of regexp... + +And it was ChatGPT who came up with the next option that I'd probably have struggled to find on my own in the documentation. + +```python +extra_args=['--wrap=none'] +``` + + +**Last but not least, the more precise your instructions, the more time ChatGPT saves.** + +You'll notice that my first instructions were very laconic. Over time, I've improved the way I ask it to work and I've saved time. + +For example: + +> Write me a python script that browses all my markdown articles contained in a content directory and its sub-directories. +> Make me a list of the images used in these articles +> Compare this list with the images contained in another directory called "images". + +> Output: +>- give me the list of images used in each article +>- Give me the list of unused images + +The more precise the input instructions, the less retouching I had to do. In the example above, I was then able to detect unused images on my blog and delete them. + +## Summary + +From a practical point of view, my blog post writing routine is now much simpler. I've saved an incredible amount of time (hours?) and I've got rid of the tasks I didn't like the most. + +ChatGPT has been particularly efficient. And if it didn't take me 30 seconds, that's more my fault than his. + +You can find all the code produced [on Github](https://github.com/hlassiege/eventuallycoding-tooling). It's about 200 lines of code, 99% written by ChatGPT. + +I wasn't sure about the solution, I even steered it towards the wrong approach at first. I changed the specifications several times as I was evolving them while testing the result. + +As a developer, I know that he has saved me an incredible amount of time. He replaces me when I'm coding, with perfect knowledge of the APIs. This doesn't mean I don't have to think about my development process, test and refine my instructions, and tell him where the limits are. Which is normal, since he's not a mind reader and always needs an operator to guide him. And the operator's experience will influence the result obtained. +But this opens the way to people with less development expertise, but still with the same working method. + +And no, it's not "less good". + +During my studies, I took assembler courses on the 68000 processor. I hated it. My teacher told me at the time, "_If you can't do assembler, you won't get anywhere in computing_". + +This prediction turned out to be rather wrong. New tools appeared that allowed me not to worry about assembler. + +Today, tools like ChatGPT will enable me to worry less about the programming language. This will not prevent me, for the time being at least, from still needing an operator to guide me. And this operator's experience, methodological approach and ability to translate complex business problems into a software architecture and blocks of instructions will make him or her use this tool better than any other. + +For the time being, this remains my superpower as a developer. diff --git a/public/images/chatgpt-to-the-rescue/ai.jpg b/public/images/chatgpt-to-the-rescue/ai.jpg new file mode 100644 index 000000000..6465b7749 Binary files /dev/null and b/public/images/chatgpt-to-the-rescue/ai.jpg differ