#
Introduction à la classe Blocks

Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une nouvelle API de bas niveau appelée `gradio.Blocks`.

Quelle est la différence entre `Interface` et `Blocks` ?

- ⚡ `Interface` : une API de haut niveau qui vous permet de créer une démo complète d’apprentissage automatique simplement en fournissant une liste d’entrées et de sorties.

- 🧱 `Blocks` : une API de bas niveau qui vous permet d’avoir un contrôle total sur les flux de données et la disposition de votre application. Vous pouvez construire des applications très complexes, en plusieurs étapes, en utilisant `Blocks`.

###
Pourquoi Blocks 🧱 ?

Comme nous l’avons vu dans les sections précédentes, la classe `Interface` vous permet de créer facilement des démos d’apprentissage automatique à part entière avec seulement quelques lignes de code. L’API `Interface` est extrêmement facile à utiliser, mais elle n’a pas la flexibilité qu’offre l’API `Blocks`. Par exemple, vous pourriez vouloir :

- regrouper des démos connexes sous forme d’onglets multiples dans une application web,
- modifier la mise en page de votre démo, par exemple pour spécifier l’emplacement des entrées et des sorties,
- disposer d’interfaces multi-étapes dans lesquelles la sortie d’un modèle devient l’entrée du modèle suivant ou avoir des flux de données plus flexibles en général,
- modifier les propriétés d’un composant (par exemple, les choix dans une liste déroulante) ou sa visibilité en fonction des entrées de l’utilisateur.

Nous allons explorer tous ces concepts ci-dessous.

###
Création d'une démo simple en utilisant Blocks

Après avoir installé Gradio, exécutez le code ci-dessous sous forme de script Python, de notebook Jupyter ou de notebook Colab.

    import gradio as gr

    def flip_text(x):
        return x[::-1]

    demo = gr.Blocks()

    with demo:
        gr.Markdown(
            """
        # Flip Text!
        Start typing below to see the output.
        """
        )
        input = gr.Textbox(placeholder="Flip this text")
        output = gr.Textbox()

        input.change(fn=flip_text, inputs=input, outputs=output)

    demo.launch()

Ce simple exemple ci-dessus introduit 4 concepts qui sous-tendent les Blocks :

1. Les Blocks vous permettent de construire des applications web qui combinent Markdown, HTML, boutons et composants interactifs simplement en instanciant des objets en Python dans un contexte `with gradio.Blocks`.

🙋Si vous n'êtes pas familier avec l'instruction `with` en Python, nous vous recommandons de consulter l'excellent tutoriel de Real Python. Revenez ici après l'avoir lu 🤗

L’ordre dans lequel vous instanciez les composants est important car chaque élément est restitué dans l’application Web dans l’ordre où il a été créé. (Les mises en page plus complexes sont abordées ci-dessous)

2. Vous pouvez définir des fonctions Python ordinaires n’importe où dans votre code et les exécuter avec des entrées utilisateur en utilisant les `Blocks`. Dans notre exemple, nous avons une fonction simple qui inverse le texte entré mais vous pouvez écrire n’importe quelle fonction Python, du simple calcul au traitement des prédictions d’un modèle d’apprentissage automatique.

3. Vous pouvez assigner des événements à n’importe quel composant `Blocks`. Ainsi, votre fonction sera exécutée lorsque le composant sera cliqué, modifié, etc. Lorsque vous assignez un événement, vous passez trois paramètres :

- `fn` : la fonction qui doit être appelée,
- `inputs` : la (liste) des composants d’entrée
- `outputs` : la (liste) des composants de sortie qui doivent être appelés.
  Dans l’exemple ci-dessus, nous exécutons la fonction `flip_text()` lorsque la valeur de la `Textbox` nommée input `input` change. L’événement lit la valeur dans `input`, la passe comme paramètre de nom à `flip_text()`, qui renvoie alors une valeur qui est assignée à notre seconde `Textbox` nommée `output`. Pour voir la liste des événements que chaque composant supporte, consultez la documentation de Gradio.

4. Blocks détermine automatiquement si un composant doit être interactif (accepter les entrées de l’utilisateur) ou non, en fonction des déclencheurs d’événements que vous définissez. Dans notre exemple, la première zone de texte est interactive, puisque sa valeur est utilisée par la fonction `flip_text()`. La deuxième zone de texte n’est pas interactive, puisque sa valeur n’est jamais utilisée comme entrée. Dans certains cas, vous voudrez peut-être passer outre, ce que vous pouvez faire en passant un booléen au paramètre `interactive` du composant (par exemple, `gr.Textbox(placeholder="Flip this text", interactive=True)`).

###
Personnaliser la mise en page de votre démo

Comment pouvons-nous utiliser `Blocks` pour personnaliser la mise en page de notre démo ? Par défaut, `Blocks` affiche verticalement dans une colonne les composants que vous créez. Vous pouvez changer cela en créant des colonnes supplémentaires `avec gradio.Column():` ou des lignes `avec gradio.Row():` et en créant des composants dans ces contextes.

Voici ce que vous devez garder à l’esprit : tout composant créé sous une `Column` (c’est aussi le défaut) sera disposé verticalement. Tout composant créé sous une `Row` sera disposé horizontalement, comme le modèle flexbox dans le développement web.

Enfin, vous pouvez également créer des onglets pour votre démo en utilisant le gestionnaire de contexte `with gradio.Tabs()`. Dans ce contexte, vous pouvez créer plusieurs onglets en spécifiant des enfants `with gradio.TabItem(name_of_tab):`. Tout composant créé dans un contexte `with gradio.TabItem(name_of_tab):` apparaît dans cet onglet.

Maintenant, ajoutons une fonction `flip_image()` à notre démo et ajoutons un nouvel onglet qui retourne les images. Vous trouverez ci-dessous un exemple avec 2 onglets et utilisant également une `Row` :

    import numpy as np
    import gradio as gr

    demo = gr.Blocks()

    def flip_text(x):
        return x[::-1]

    def flip_image(x):
        return np.fliplr(x)

    with demo:
        gr.Markdown("Flip text or image files using this demo.")
        with gr.Tabs():
            with gr.TabItem("Flip Text"):
                with gr.Row():
                    text_input = gr.Textbox()
                    text_output = gr.Textbox()
                text_button = gr.Button("Flip")
            with gr.TabItem("Flip Image"):
                with gr.Row():
                    image_input = gr.Image()
                    image_output = gr.Image()
                image_button = gr.Button("Flip")

        text_button.click(flip_text, inputs=text_input, outputs=text_output)
        image_button.click(flip_image, inputs=image_input, outputs=image_output)

    demo.launch()

Vous remarquerez que dans cet exemple, nous avons également créé un composant `Button` dans chaque onglet et avons assigné un événement de clic à chaque bouton qui est l’élément qui exécute réellement la fonction.

###
Exploration des événements et de l'état

De la même manière que vous pouvez contrôler la mise en page, `Blocks` vous donne un contrôle fin sur les événements qui déclenchent les appels de fonction. Chaque composant et de nombreux layouts ont des événements spécifiques qu’ils supportent.

Par exemple, le composant `Textbox` a 2 événements : `change()` (lorsque la valeur contenue dans la zone de texte change), et `submit()` (lorsqu’un utilisateur appuie sur la touche Entrée alors qu’il est concentré sur la zone de texte). Les composants plus complexes peuvent avoir encore plus d’événements : par exemple, le composant `Audio` a aussi des événements séparés pour quand le fichier audio est joué, effacé, mis en pause, etc. Consultez la documentation pour connaître les événements pris en charge par chaque composant.

Vous pouvez attacher un déclencheur d’événement à aucun, un ou plusieurs de ces événements. Vous créez un déclencheur d’événement en appelant le nom de l’événement sur l’instance du composant comme une fonction. Par exemple, `textbox.change(...)` ou `btn.click(...)`. La fonction prend trois paramètres, comme indiqué ci-dessus :

- `fn` : la fonction à exécuter
- `inputs` : une (liste de) composante(s) dont les valeurs doivent être fournies comme paramètres d’entrée à la fonction. La valeur de chaque composant est mise en correspondance avec le paramètre de fonction correspondant, dans l’ordre. Ce paramètre peut être `None` si la fonction ne prend aucun paramètre.
- `outputs` : un (liste de) composant(s) dont les valeurs doivent être mises à jour en fonction des valeurs retournées par la fonction. Chaque valeur de retour met à jour la valeur du composant correspondant, dans l’ordre. Ce paramètre peut être None si la fonction ne retourne rien.

Vous pouvez même faire en sorte que le composant d’entrée et de sortie soit le même composant, comme nous le faisons dans cet exemple qui utilise un modèle GPT pour compléter du texte :

    import gradio as gr

    api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B")

    def complete_with_gpt(text):
        # Utilise les 50 derniers caractères du texte comme contexte.
        return text[:-50] + api(text[-50:])

    with gr.Blocks() as demo:
        textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4)
        btn = gr.Button("Generate")

        btn.click(complete_with_gpt, textbox, textbox)

    demo.launch()

###
Création de démos multi-étapes

Dans certains cas, vous pouvez vouloir une démo multi-étapes, dans laquelle vous réutilisez la sortie d’une fonction comme entrée de la suivante. C’est très facile à faire avec les `Blocks`, car vous pouvez utiliser un composant pour l’entrée d’un déclencheur d’événement mais la sortie d’un autre. Regardez le composant texte dans l’exemple ci-dessous, sa valeur est le résultat d’un modèle de conversion de la parole en texte, mais il est également transmis à un modèle d’analyse des sentiments :

    from transformers import pipeline

    import gradio as gr

    asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
    classifier = pipeline("text-classification")

    def speech_to_text(speech):
        text = asr(speech)["text"]
        return text

    def text_to_sentiment(text):
        return classifier(text)[0]["label"]

    demo = gr.Blocks()

    with demo:
        audio_file = gr.Audio(type="filepath")
        text = gr.Textbox()
        label = gr.Label()

        b1 = gr.Button("Recognize Speech")
        b2 = gr.Button("Classify Sentiment")

        b1.click(speech_to_text, inputs=audio_file, outputs=text)
        b2.click(text_to_sentiment, inputs=text, outputs=label)

    demo.launch()

###
Mise à jour des propriétés des composants

Jusqu’à présent, nous avons vu comment créer des événements pour mettre à jour la valeur d’un autre composant. Mais que se passe-t-il si vous voulez modifier d’autres propriétés d’un composant, comme la visibilité d’une zone de texte ou les choix dans un groupe de boutons radio ? Vous pouvez le faire en renvoyant la méthode `update()` d’une classe de composant au lieu d’une valeur de retour normale de votre fonction.

L’exemple le plus facile à illustrer est le suivant :

    import gradio as gr

    def change_textbox(choice):
        if choice == "short":
            return gr.Textbox.update(lines=2, visible=True)
        elif choice == "long":
            return gr.Textbox.update(lines=8, visible=True)
        else:
            return gr.Textbox.update(visible=False)

    with gr.Blocks() as block:
        radio = gr.Radio(
            ["short", "long", "none"], label="What kind of essay would you like to write?"
        )
        text = gr.Textbox(lines=2, interactive=True)

        radio.change(fn=change_textbox, inputs=radio, outputs=text)
        block.launch()

Nous venons d’explorer tous les concepts de base des `Blocks` ! Tout comme avec `Interface`, vous pouvez créer des démos sympas qui peuvent être partagées en utilisant `share=True` dans la méthode `launch()` ou déployées sur Spaces.