Module 3 — Blocks : mise en page et événements
gr.Interface couvre les démonstrations à une seule fonction, un ensemble d'entrées, un ensemble de sorties. Dès qu'on veut deux étapes qui s'enchaînent, un second bouton qui vide les champs, un panneau latéral qui change avec un onglet, ou un état qui persiste entre deux appels, on atteint la limite du composant. gr.Blocks est le second point d'entrée de Gradio, plus verbeux mais infiniment plus expressif, et c'est lui que vous utiliserez pour toute démonstration réelle.
Le modèle mental de gr.Blocks
Le renversement de perspective est le suivant : avec gr.Interface, vous décrivez ce que fait votre fonction et Gradio choisit la mise en page. Avec gr.Blocks, vous décrivez la page, composant par composant, et vous branchez des fonctions sur les événements que ces composants émettent. C'est le passage d'un cadre déclaratif à un cadre impératif, comparable au passage d'un <form> HTML à un framework à composants comme React.
L'ossature d'un gr.Blocks tient toujours en trois zones. Un bloc with gr.Blocks() as demo: ouvre le conteneur. À l'intérieur, on crée les composants (gr.Textbox, gr.Image, gr.Button) et on les organise en lignes (gr.Row) et colonnes (gr.Column). Enfin, on branche les événements — typiquement bouton.click(fn=..., inputs=..., outputs=...) — qui décrivent ce qui doit se passer quand l'utilisateur interagit. La séparation entre la déclaration et le branchement rend le code plus lisible, à condition de suivre cet ordre.
Lignes, colonnes et espaces
Le composant gr.Row place ses enfants côte à côte. Le composant gr.Column les empile verticalement. Une gr.Column(scale=1) à côté d'une gr.Column(scale=3) occupe un quart de la largeur contre trois quarts, ce qui suffit pour un panneau latéral. gr.Tab regroupe plusieurs vues dans des onglets, ce qui est utile quand une démonstration propose deux modes (« transcription » et « transcription puis résumé » par exemple) qui partagent peu de composants.
Un piège tenace concerne les composants créés en dehors d'un bloc with. Un gr.Textbox() posé au niveau du module Python, en dehors du with gr.Blocks(), n'appartient à aucune page et sera silencieusement ignoré. Toute la déclaration doit vivre à l'intérieur du gestionnaire de contexte du bloc racine.
Les événements : .click, .change, .submit
Trois événements couvrent 90 % des besoins. bouton.click(fn, inputs, outputs) déclenche fn sur le clic du bouton, avec les valeurs courantes des composants inputs passées en argument et les valeurs retournées assignées aux composants outputs. composant.change(...) déclenche à chaque modification du composant : idéal pour un gr.Slider qui met à jour un aperçu, mais à réserver aux calculs légers. composant.submit(...) déclenche sur la validation d'un champ texte (touche Entrée), et se combine bien avec un gr.Textbox pour éviter d'obliger l'utilisateur à cliquer.
Les listes inputs et outputs peuvent contenir zéro, un ou plusieurs composants. Pour vider trois champs d'un coup, la même liste passe dans les deux paramètres et la fonction retourne trois "". Cette flexibilité rend gr.Blocks remarquablement compact malgré son côté impératif.
Enchaîner transcription puis résumé
Le fil rouge se prolonge : à partir du module 2, on transcrit l'audio, mais l'utilisateur préférerait souvent un résumé plutôt qu'une transcription brute. Un gr.Blocks avec deux boutons enchaînés — « Transcrire », puis « Résumer » — répond parfaitement à ce besoin.
import gradio as gr
from transformers import pipeline
transcripteur = pipeline("automatic-speech-recognition", model="openai/whisper-tiny")
resumeur = pipeline("summarization", model="facebook/bart-large-cnn")
def transcrire(audio):
if audio is None:
return ""
frequence, tableau = audio
if tableau.ndim > 1:
tableau = tableau.mean(axis=1)
tableau = tableau.astype("float32") / 32768.0
return transcripteur({"raw": tableau, "sampling_rate": frequence})["text"].strip()
def resumer(texte):
if not texte.strip():
return "Rien à résumer."
sortie = resumeur(texte, max_length=80, min_length=20, do_sample=False)
return sortie[0]["summary_text"]
with gr.Blocks(title="Transcription puis résumé") as demo:
gr.Markdown("## Enregistrez un extrait, transcrivez, puis résumez.")
with gr.Row():
with gr.Column(scale=1):
audio = gr.Audio(sources=["upload", "microphone"], type="numpy")
bouton_transcrire = gr.Button("Transcrire", variant="primary")
with gr.Column(scale=2):
texte = gr.Textbox(label="Transcription", lines=8)
bouton_resumer = gr.Button("Résumer")
resume = gr.Textbox(label="Résumé", lines=4)
bouton_transcrire.click(fn=transcrire, inputs=audio, outputs=texte)
bouton_resumer.click(fn=resumer, inputs=texte, outputs=resume)
demo.launch()
Deux idées à retenir. Aucun état n'est stocké entre les deux appels : la sortie de transcrire est écrite dans le composant texte, et resumer la relit depuis ce composant. Cet aller-retour via l'interface est explicite et permet à l'utilisateur d'éditer la transcription avant de la résumer, ce qui est presque toujours souhaitable. Le composant gr.Markdown en tête sert d'entête et de mode d'emploi, remplaçant les paramètres title et description de gr.Interface.
Passer une valeur en cascade avec .then
Quand on veut vraiment enchaîner deux fonctions sans intervention utilisateur — « transcrire puis résumer en un seul clic » — on utilise la méthode .then sur un événement.
bouton_transcrire.click(fn=transcrire, inputs=audio, outputs=texte).then(
fn=resumer, inputs=texte, outputs=resume
)
La deuxième fonction n'est appelée qu'après la première, et reçoit la sortie mise à jour du composant intermédiaire. Cette chaîne est linéaire — pas de branches conditionnelles — mais elle suffit pour la grande majorité des enchaînements.
L'état partagé avec gr.State
Certaines démonstrations ont besoin de mémoire entre deux appels : un compteur d'essais, l'historique d'une conversation, une liste d'images ajoutées. gr.State est le composant invisible qui stocke une valeur Python arbitraire côté serveur, associée à la session de l'utilisateur.
with gr.Blocks() as demo:
entree = gr.Textbox(label="Un mot")
memoire = gr.State(value=[])
sortie = gr.Textbox(label="Mots précédents")
def ajouter(mot, liste):
liste = liste + [mot]
return liste, ", ".join(liste)
entree.submit(fn=ajouter, inputs=[entree, memoire], outputs=[memoire, sortie])
Le point important est que gr.State est propre à chaque session. Un utilisateur qui rafraîchit la page perd son état ; deux utilisateurs simultanés ont chacun le leur. Pour un état vraiment partagé (une base de données de retours utilisateurs par exemple), il faut passer par un fichier ou par un service externe, jamais par une variable globale du script (qui serait à la fois écrite par plusieurs sessions et perdue au redémarrage).
Une organisation Blocks lisible respecte trois étapes strictes dans l'ordre : d'abord la déclaration de tous les composants dans une mise en page claire (lignes et colonnes), puis le branchement de tous les événements en bas du fichier. Mélanger les deux — un bouton, son événement, un composant, un autre événement — rend le fichier illisible dès qu'il dépasse cinquante lignes.
En résumé
gr.Blocksremplacegr.Interfacedès qu'on veut plusieurs zones, plusieurs boutons ou une mise en page personnalisée ; il expose la page composant par composant plutôt que par la fonction.gr.Row,gr.Column(avecscale) etgr.Tabcouvrent toute la mise en page usuelle ; les composants créés hors duwith gr.Blocks()sont silencieusement ignorés.- Les événements
.click,.change,.submitreçoiventinputsetoutputset prennent des listes ;.thenchaîne deux fonctions sans intervention utilisateur. gr.Stateconserve une valeur Python entre appels par session ; pour un état vraiment partagé, passer par un fichier ou un service externe, jamais par une globale.
Le module suivant introduit gr.ChatInterface, un composant de plus haut niveau qui encapsule à lui seul la mise en page, l'historique et les événements d'une conversation.