When to move
gr.Interface is one function, one form. Move to gr.Blocks when you need layout, several buttons doing different things, outputs that feed back into inputs, or anything conversational.
import gradio as gr
with gr.Blocks(title="Transcribe and summarise") as demo:
gr.Markdown("## Upload audio, get a summary")
with gr.Row():
with gr.Column(scale=1):
audio = gr.Audio(type="filepath", label="Audio")
lang = gr.Dropdown(["auto", "en", "hi"], value="auto", label="Language")
go = gr.Button("Transcribe", variant="primary")
with gr.Column(scale=2):
text = gr.Textbox(label="Transcript", lines=10)
summary = gr.Textbox(label="Summary", lines=4)
sum_btn = gr.Button("Summarise this")
go.click(fn=transcribe, inputs=[audio, lang], outputs=text)
sum_btn.click(fn=summarise, inputs=text, outputs=summary)
demo.queue().launch()The pattern is always the same: declare components inside the with block, then wire events with component.click(fn=..., inputs=..., outputs=...). inputs and outputs are lists of components, and Gradio passes their current values in and writes returned values back.
Chat, which is a component
def respond(message, history):
messages = history + [{"role": "user", "content": message}]
out = pipe(messages, max_new_tokens=256)
return out[0]["generated_text"][-1]["content"]
gr.ChatInterface(respond, type="messages").launch()gr.ChatInterface gives you the message list, the history, retry, undo and clear. history arrives in exactly the {"role", "content"} shape that apply_chat_template wants, which is not a coincidence and saves a translation layer.
For streaming, make the function a generator and yield the accumulating string:
def respond(message, history):
partial = ""
for chunk in stream_from_model(message, history):
partial += chunk
yield partialYield the whole string so far, not the chunk. Yielding chunks replaces the display each time and the user sees one word flickering.
State, and the mistake that leaks data between users
This is the most important paragraph in the lesson.
A Space is one process serving everybody. A module-level variable is shared by every visitor:
history = [] # WRONG: one list for the whole internet
def chat(msg):
history.append(msg) # every user appends to the same listOne person’s conversation appears in another’s. On anything handling personal text this is a data breach with a two-line cause, and it is easy to write because it works perfectly when you are the only user.
The fix is gr.State, which is per-session:
with gr.Blocks() as demo:
session = gr.State([]) # separate list per browser session
box = gr.Textbox()
out = gr.Textbox()
def chat(msg, hist):
hist = hist + [msg]
return "\n".join(hist), hist
box.submit(chat, [box, session], [out, session])The state goes in as an input and comes back as an output. It lives as long as the browser session and dies with it. gr.BrowserState persists in local storage across reloads for preferences; anything that must outlive the session belongs in the storage lesson later in this module.
Shared module-level objects are correct for exactly one thing: the model, which is read-only and should be shared.
Making the interface respond to itself
mode = gr.Radio(["summarise", "translate"], value="summarise")
target = gr.Dropdown(["hi", "bn", "ta"], visible=False)
mode.change(
fn=lambda m: gr.update(visible=(m == "translate")),
inputs=mode, outputs=target,
)gr.update() changes a component’s properties rather than its value: visibility, label, choices, interactivity. This is how a form stops showing fields that do not apply, which matters more for comprehension than it looks.
Progress, so the wait is legible
A ten-second handler with no feedback reads as broken. Gradio will show a spinner on its own, and you can do better in four lines:
def transcribe(path, progress=gr.Progress()):
chunks = split_audio(path)
out = []
for c in progress.tqdm(chunks, desc="Transcribing"):
out.append(asr(c)["text"])
return " ".join(out)Add the progress parameter and Gradio wires a bar into the interface. On free hardware, where everything is slower than you would like, this is the difference between a visitor waiting and a visitor reloading, and reloading queues a second job while the first is still running, which makes the problem worse for everybody.
The queue, briefly
demo.queue() puts requests in a managed queue with position and estimated wait shown to the user. It is on by default in current versions and worth knowing about because concurrency_limit is how you stop eight simultaneous visitors from exhausting the memory of a free container. A queue that makes people wait visibly is better than a Space that crashes.
Do this now
Take a Space you have built with Interface and rewrite it in Blocks with two buttons, where the second acts on the output of the first. Then add a gr.State and check that opening the Space in two browsers keeps the two sessions apart.
Every lesson here is open. Your progress is recorded once you pass the Image Generation, in Practice exam.
Check your understanding
A chat Space keeps conversation history in a module-level list. It works perfectly in testing and, once public, users report seeing fragments of other people’s conversations. Why?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Arjun, your AI tutor
