storybook-build / changes (push) Has been cancelled
functional / changes (push) Has been cancelled
publish / version_or_publish (push) Has been cancelled
docs-build / changes (push) Has been cancelled
js / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
js / js-test (push) Has been cancelled
* Fix unreadable inherited text in dark mode
`.gradio-container` handed down
`color: var(--button-secondary-text-color)`, so every element without
its own color declaration inherited the secondary button text color.
Citrus is the only built-in theme whose dark-mode value for that
variable is dark, because its dark buttons are amber with dark text, so
Citrus dark mode was the only place the mismatch showed. The status
tracker timer text reported in #10940 came out at a contrast ratio of
1.15:1, as did the Audio record button, the HighlightedText score
legend and the JSON collapsed-node preview text.
Hand down `--body-text-color` instead. Also have the status tracker
declare its own color, so the reported case no longer depends on what
the container hands down; `.loading` and `.progress-level-inner` in the
same file already did.
* add changeset
* Remove timer text color unit tests
Requested in review. The assertions leaned on internal class names
and pinned a specific CSS variable, which is more implementation
detail than this fix needs.
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Show a one-time toast on HF Spaces where `hf_oauth: true` is missing from
the README metadata: without it users can't sign in to use their own
inference quota, and the Space owner can't authenticate to edit the
workflow. Also disables the "Sign in with 🤗" button in the toolbar and
shows a tooltip pointing at the same fix.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix SSR shutdown hanging on Ctrl+C
With ssr_mode=True, stopping the app printed "Stopping Node.js
server..." and then blocked for 30 seconds, and a second Ctrl+C
during that wait hung the process for good. It needs an app whose
config asks the client for a session heartbeat, so a gr.State, an
unload or stream event, or a per-session cache.
Three problems compounded. The signal handler called
node_process.wait() with no timeout. It also stayed installed while
it ran, so a second signal re-entered it and deadlocked on the
non-reentrant lock Popen._wait() holds across its waitpid() call.
And it left through sys.exit(0) without setting app.stop_event, so
Python kept serving the heartbeat streams that the Node proxy was
waiting on before it could exit.
install_shutdown_handlers() now ignores repeat signals, runs an
on_shutdown callback before touching Node, and stops Node through
stop_node_process(), which kills it if terminate() does not land
within five seconds. Blocks.launch passes _end_streaming_responses
as that callback, so the heartbeats end and Node drains on its own
instead of waiting for its 30 second force-close.
* add changeset
* Harden the SSR shutdown path after self-review
Guard the on_shutdown callback: it sets an asyncio.Event from the main
thread while the loop runs in uvicorn's, so a waiter cancelled mid-set
can raise, and an escaping exception would skip stopping Node and leave
it holding the user-facing port. Ending the streams is only a latency
optimisation; stopping Node is the part that must not be skipped.
Read the served app from server_app rather than app, because queue()
rebinds app but not server_app, so calling queue() after launch() would
point the handler at an App that is not being served.
Assert in the existing startup-ordering test that launch() passes
on_shutdown. The regression this fixes was the missing wiring, and the
other tests all drive node_server in isolation, so dropping that kwarg
would have left every test green.
Also close the child's pipe and reap it in the timeout test, which
otherwise leaks a ResourceWarning.
* Fix SSR Node proxy to scan for a free port by default
Co-authored-by: Cursor <cursoragent@cursor.com>
* changes
* changes
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <islamrealm@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
* Recalculate sidebar overlap on window resize
The --overlap-amount CSS variable was written once from inside
onMount, so the padding that keeps content clear of the sidebar
stayed frozen at the layout the app first rendered with. The resize
listener kept recomputing overlap_amount, but the new value never
reached the DOM.
Write the variable from an $effect instead. Placed after onMount so
the first write still follows the initial check_overlap() call,
leaving mount behaviour unchanged.
* add changeset
* Pin the test container flush left
Keeps the starting overlap independent of any ambient CSS that
matches .wrap, so the first assertion cannot go slack if such a
rule is introduced later.
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Let embedded apps shrink back after content shrinks
On Spaces, opening the additional-inputs accordion of a
gr.ChatInterface and closing it again left the chatbot permanently
taller. handle_resize() sizes the parent frame from the bottom of
root_container.children[0], but with fill_height=True that element
stretches to whatever height the frame has, so once the frame had
grown its measured bottom no longer said how much room the content
needed. The measurement was identical whether the accordion was open
or closed, which made the shrink branch unreachable.
Take a second measurement with the stretch removed, only while the
frame is taller than the height the parent gave us. Use it to shrink
only, never to grow, never give back more than we took, and do not
read a size we have asked for but the parent has not applied yet as a
parent-driven resize.
The guards added in #13563 are unchanged. The decision moves as-is
into resize.ts so it can be tested; four of the nine new tests fail
without this change.
* add changeset
* Guard the measured child instead of asserting it
getBoundingClientRect() never returns null, so the `if (!box)` check
could not catch a missing child; the `as HTMLElement` assertion said
the child always exists while the optional chaining said it might not.
Narrow the type and return early when there is no child.
* Trim comments to the density of the surrounding code
The new module and its tests carried far more commentary than the
files around them (40% and 15% of lines, against 4% in Blocks.svelte
and 0% in the neighbouring tests). Drop the ones that restate what
the code or the test name already says, and shorten the rest. The
comments moved over from #13563 are left as they were, and the two
that explain why the unstretched measurement is gated on the current
frame are kept, since that is the part that is easy to get wrong.
* Keep the parent's height when it resizes mid-request
If the parent resized the frame while a size we asked for was still in
flight, the viewport moved somewhere we never requested but
base_height was left at its old value: clearing awaiting_height took
the first branch, so the update in the else-if never ran. When the app
then asked for the new height itself, last_reported_height matched the
viewport from then on and the else-if stayed unreachable, pinning the
floor to the pre-resize height. Content shrinking after that gave back
more room than the parent had asked for.
Treat a viewport that matches neither what we asked for nor what we
had as the parent's own choice, and adopt it as the new floor.
* Cover the circuit breaker
Nothing exercised it: the viewport-fill guard catches the 100vh case
before the counter ever increments, and content that overflows by a
fixed amount produces an echo tick that resets it. Drive it with
content that needs more room on every tick.
* Fold the rigid-content tests into one round trip
Sizing to the content and growing past it were checked separately, so
nothing covered a non-stretching app shrinking back after it had grown
- the case where the new floor must not apply. One test covers all
three.
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Mark MCP tool args without defaults as required
get_input_schema built the JSON Schema for every published MCP tool
but never emitted a `required` array. JSON Schema treats an absent
`required` as "nothing is required", so every argument was advertised
as optional, including ones with no default that the function cannot
run without.
Each entry of endpoint_info["parameters"] already carries
parameter_has_default, and every other consumer of the API info
derives required-ness from it. Collect the parameters where it is
false and list them under `required`, omitting the key entirely when
nothing is required.
This only changes what is advertised, not what is accepted: the call
path already goes through construct_args, which raises "No value
provided for required argument" for these parameters.
* add changeset
* add changeset
* Add a multiple-required-parameters case to the MCP schema test
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Workflow: auto-create nodes for models, add an Output button, copyable errors
Addresses three of the remaining items in #13665:
- Adding a model node from the picker now spawns its input and output
components and wires them up, the same ready-to-run subgraph that Space
nodes have always produced. Previously a fresh model node appeared with
nothing attached, so it looked like it ran but produced nothing.
- The bottom bar gains an "Output" button alongside "Input" (output nodes
were only reachable by dragging from a port), and "Data" is renamed
"Dataset" to match its icon, tooltip and DATASET_MODALITY constant.
- Node error banners get a "copy" button, so a failure can be pasted
somewhere useful — canvas nodes swallow text selection for dragging.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Workflow: document the API oauth_token param in the View API panel
#13667 landed, so a caller can now hand a workflow a token via the request
body. Subgraph endpoints already declare one — `_build_endpoint_fn` synthesizes
`token: Optional[OAuthToken]` into the signature, so `/info` reports
`oauth_token: "optional"` for them — but the workflow's own View API panel said
nothing about it, which was the last item blocking API access to Space-hosted
workflows from being usable.
`describe_workflow_api` now reports the requirement, asked of the same builder
that registers the endpoints so the panel can't drift from `/info`, and the
panel adds a note plus `oauth_token` in all three snippets.
Also fixes the panel's file-parameter examples, which no client accepted: the
curl body needs the FileData payload (`{"path": ..., "meta": {...}}`) and the
JS snippet needs `handle_file`, where both previously emitted a bare URL
string. Verified by running the generated request: upload → call → the
workflow's function receives OAuthToken(token='hf_DOCS_CHECK').
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Workflow: one "Component" button, with roles derived from wiring
Replaces the bottom bar's Input/Output pair with a single "Component"
button. A component's direction was never really the user's to declare:
`WorkflowNodeSF` already picks between an editable widget and a read-only
output tile purely from whether the node's input port is connected, and
the port-drag path already infers the role from drag direction. The bar
was the one place that made the user pre-commit.
`reconcileComponentRoles` makes the role a function of the edge set —
driven components are subjects, undriven ones are references — and runs
on every edge mutation plus on load. Without it the collections could
disagree with the rendering, and that mattered: `workflow_api.py` builds
endpoint parameters from `references` (skipping any with an incoming
edge) and endpoints themselves from `subjects`, so a node that rendered
as an output while still filed under `references` contributed no
endpoint at all. That was reachable before this change — wire a model
into an Input node and its subgraph silently vanished from the API —
and the new Output button would only have widened the target.
Flipped nodes append rather than merge in place, since `subject_groups`
fixes the API's output-tuple order from `subjects` order.
API panel, from review feedback:
- the `oauth_token` note moves out of the per-endpoint cards to a single
note beneath all of them, so it doesn't read as one of the endpoint's
own parameters, and is cut down to one sentence with a link
- Copy moves onto the code block it copies
- long snippets scroll instead of being clipped: `overflow: hidden` on
the endpoint card resolved its flex minimum size to 0, so cards shrank
to fit the panel and cut their code mid-line
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Remove verbose workflow API panel CSS comments
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Publish the Prism global before its grammars load
Every https://gradio.app/docs/* page rendered for a moment and then replaced
itself with "500 Internal Error", because hydration threw
`ReferenceError: Prism is not defined` (#13677).
`prismjs/components/*` and `prism-svelte` are side-effect scripts: each does
`Prism.languages.x = ...` against a bare global and declares no imports of its
own, so nothing in the module graph orders them after prismjs. As *static*
imports the bundler is free to hoist them into a chunk that evaluates before the
`globalThis.Prism = Prism` statement that was supposed to precede them — and
rolldown compounds it by emitting prismjs as a lazily-invoked CommonJS factory,
so importing it does not initialise it either. The live chunk shows exactly
that: prismjs's factory closes and `Prism.languages.python={...}` runs on the
very next statement, with the global never assigned anywhere in the file.
The grammars in that chunk are python and typescript only, which identifies the
source as ParamViewer — rendered on every docs page — rather than the website's
own highlighter. `js/_website/src/lib/prism.ts` had the same defect though, and
it was live too: in the built html-gallery chunk its grammars sat ~10kB ahead of
its global assignment.
Dynamic imports are not hoisted, so both now assign the global first and await
the grammars after. ParamViewer gates on a promise (component init is sync, and
`highlight` already fell back to plain text) while prism.ts awaits at module
scope, keeping `highlight()` synchronous for the server `load` functions that
render the guides and changelog.
Verified against a real `VERCEL=1 vite build` of the website. Before: the build
reproduces the failing chunk byte-for-byte (D3k0kEOB.js, 22248 bytes, grammars
at offset 18830, no global). After: that chunk is gone, and every
grammar-bearing chunk is lazy-only with no static importer, so none can execute
ahead of the global.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Register the Prism grammars before ParamViewer renders
Publishing the global first stopped the `ReferenceError`, but the docs pages
still came out unhighlighted on a hard load, as @hysts found on the preview:
every parameter type rendered as plain text and only recovered after navigating
away and back.
prismjs schedules its own `highlightAll()` on a `requestAnimationFrame` as soon
as it loads. The grammar chunks are dynamic imports, so that pass runs before
they arrive, and `highlightElement` with no grammar does
`element.innerHTML = encode(element.textContent)` — wiping the server's
highlighting *and* the hydration comments the `{@html}` blocks claimed during
hydration. The derived does recompute once the grammars land, but it repaints
into nodes that are no longer in the document, so the page stays plain.
Reproduced against the real component (highlighted SSR markup as served by the
preview, hydrate, prism's auto pass, then the grammars): before, the type ends
as `value: str | Callable | None`; after, the token spans survive.
So don't need a second paint: await the grammars in `<script module>` instead of
gating a `$derived` on them. The component cannot render until they are
registered, so the client's first render agrees with the server's. The `try`
also stops a failed chunk from becoming an unhandled rejection — `highlight`
already falls back to plain text without a grammar.
This costs one round trip in front of hydration, since dynamic imports are not
preloaded; client-side navigation is unaffected as the module evaluates once.
Also drops `js/_website/vite-plugin-patch-paramviewer.ts`, dead since the
default import: its first replacement only matched `import * as Prism`, and its
second duplicates the guard already in `highlight`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Workflow: resizable nodes and a full-screen image view
Two of the remaining items in #13665.
Nodes get a drag handle in their bottom-right corner. Only width is settable —
height is content-driven and synced back by a ResizeObserver — and image
previews now cap at a multiple of the node width instead of a flat 320px, so
widening a node genuinely enlarges its preview, which is what makes comparing
two output images at a larger size possible. HTML previews already scaled
themselves to the node width, so they follow for free. Drag deltas are divided
by the canvas zoom, so the handle tracks the cursor at any zoom level.
Image inputs and outputs get an expand button that opens the image full screen,
letterboxed to fill the viewport (rather than drawn at natural size, which left
small images unreadable), dismissed by clicking or Esc.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Workflow: capture image inputs from the webcam and audio from the mic
The last of the input items in #13665 — finding an audio file to drag in is
often harder than just talking, and likewise for a photo.
Image and audio input nodes now offer capture next to upload. Everything
downstream only needs a `File` (`adopt_file` already turns one into the node's
value), so this is a small purpose-built widget rather than Gradio's `Webcam` /
audio recorder: a node body is ~200px wide, with nowhere to put device pickers,
waveforms or streaming modes. It also avoids adding a dependency on
`@gradio/audio` for one control.
The device is opened once behind a guard and always released on teardown, so a
cancelled or unmounted capture doesn't leave the camera light on. Capture is
only offered where `getUserMedia` exists, since it is undefined on plain http
away from localhost.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Workflow: settable node height, and visible upload/record buttons
Height was content-driven, so widening a node left an output image
letterboxed in dead space instead of getting bigger. Nodes now pin a
height when dragged vertically (`manual_height`, separate from the
measured `height` layout math reads), and the widget zone stretches to
fill it — images, text and HTML previews grow with the card. Height is
only offered where something can absorb the slack: a transform node is
all header and ports, so it stays width-only, with a matching cursor.
The handle was also unreachable on text nodes, where a textarea's own
grip covers the same corner — it now wins the hit test, and the native
grip is dropped for editable cards since the node handle does both axes
and persists.
Webcam and mic capture existed but were offered as a faint "or record
from mic" text link that read as decoration. The empty state is now a
drop zone with explicit Upload and Webcam/Record buttons, and a mic
click records immediately with a live timer instead of only arming the
device.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Workflow: start media capture from the click
* full screen improvement + a11y tweak
* Delete .changeset/nodes-capture-media.md
* Delete .changeset/nodes-grow-and-zoom.md
* add changeset
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: hannahblair <hannahblair@hotmail.co.uk>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Let API callers supply a token for gr.OAuthToken endpoints
`gr.OAuthToken` was only ever populated from the OAuth session cookie, so it
was always `None` for an API caller — no browser, no session. Any app whose
function takes one could not be driven programmatically at all, which is how
this surfaced: a deployed `gr.Workflow` reached its model nodes with no token
and failed with "Sign in with your HF account to use this model".
Callers can now pass `Client(..., oauth_token=...)`, which travels in the
request body as a reserved `oauth_token` field rather than a header — a Space
sits behind a proxy that strips `x-hf-*`, so a header never arrives. Because it
rides beside `data` instead of in it, it never becomes a positional argument or
appears in an endpoint's parameter schema, and it can't be captured by flagging
or cached examples. It also works from `curl` and any other client, with no
client-side support needed.
A token is sent only to endpoints that declare they take one: `get_api_info`
reports `oauth_token: "required" | "optional"` per endpoint, derived from the
function signature, and the client consults that before including the field. So
an app cannot collect tokens from calls that had no reason to carry one, and
`view_api()` states plainly which endpoints act on the caller's behalf.
`oauth_token=` is deliberately separate from `token=`, which only authenticates
the caller to the app, and is never inferred from a locally saved token.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Support oauth_token in the JS client, and move the e2e test
The JS client gains the same `oauth_token` option and the same gating: it is
included in the payload only for endpoints whose api_info declares they take a
gr.OAuthToken, so parity with the Python client holds and an app still cannot
collect tokens from calls that had no reason to carry one.
The end-to-end check moves into test_external.py, which already hits real
Spaces and is marked flaky and serial, rather than living in a file of its own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Address review: narrow the token flag, and close two ways to bypass the gate
`oauth_token_requirement` scanned every annotation, including the return type,
so a function that *returned* an OAuthToken was reported as one that receives
one. It now looks only at parameters.
`/call/v2` popped `oauth_token` from every request body, which reserved the name
globally and would swallow a real parameter that happened to be called that. It
is only treated as reserved for endpoints that declare they take a token.
In the client, `**kwargs` expanded after the gated payload, so passing
`oauth_token=` as an ordinary keyword argument would have sent a token to an
endpoint that never asked for one. The computed payload now goes last and wins.
Also reworded a docstring that opened with a quoted word, which `ruff format`
had to space away from the opening triple quote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Satisfy the backend type checker
`oauth_token_requirement` returned plain `str`, which cannot be assigned to the
`Literal["required", "optional"]` key on the APIEndpointInfo TypedDict, and the
e2e test subscripted `view_api()` (overloaded on return_format) and iterated
client.endpoints without narrowing the value type.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Surface oauth_token in the API docs, and fix the JS client gate
The per-endpoint `oauth_token` requirement reached `/info` but stopped there,
so nothing downstream showed it or acted on it:
- `transform_api_info()` rebuilt each endpoint as {parameters, returns, type},
dropping `oauth_token`. `submit()`'s gate therefore never matched and the JS
client never sent a token at all.
- The view-API page said nothing about which endpoints act on the caller's
behalf, and its snippets omitted `oauth_token` so copy-paste didn't work.
- Sending `oauth_token` to an endpoint that takes no token 500'd on
`construct_args`; it is now stripped from the args either way and only
honored where the fn declares one.
Also trims the explanatory inline comments added across the branch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Make the oauth_token notice font sizes consistent
The first paragraph inherited --text-md while .desc set --text-lg, so the
subdued explanation rendered larger than the primary statement. Both are prose
now at --text-lg, and the inline mono spans drop a step to sit optically level
with it — matching the sibling parameters section (h4 14px, prose 16px, mono
14px).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Stop reserving the oauth_token body key on every endpoint
Popping it unconditionally fixed the 500 on endpoints that take no token, but
it also swallowed the value of any endpoint whose own parameter is named
oauth_token (github-pilot caught this). The name is now reserved only where the
fn declares a gr.OAuthToken; elsewhere it stays an ordinary argument if the
endpoint has one by that name, and is dropped if it does not — so neither the
500 nor the swallowing happens.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Strip style and link elements in frontend sanitizer
Amuchina's default allowlist permits <style> (HTML and SVG) and
<link>, so chatbot messages containing them could restyle the whole
app even with sanitize_html=True. Configure the browser sanitizer to
drop these elements, matching the server-side sanitize-html defaults.
The style attribute is unaffected.
* add changeset
* Address Copilot review comments
Fail closed if the amuchina default configuration ever lacks
allowElements, instead of skipping the filter. Assert the preserved
style attribute through the DOM instead of exact string equality.
* Hoist the Amuchina instance to module level
The instance is stateless, so there is no need to construct it (and
deep-clone the default configuration) on every sanitize call.
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Remount a plot only when its payload changes
Plot.svelte wraps the plot in {#key key} and incremented key on every
effect run. The effect depends on the value prop, and gr.Chatbot rebuilds
its message objects on every streaming update, so the plot arrived as a
fresh object holding the same payload about 30 times per streamed reply
and each one forced a full remount.
For bokeh that is visible as flickering and as the view jumping between
the top and the bottom of the conversation: re-embedding clears the
container, so the message height collapses to 0 and grows back on every
update.
The remount is deliberate (#9781), so gate it instead of removing it.
PlotData is fully described by type, plot and altair's chart, all
strings, so comparing those three tells a real plot change from a
re-created object. The plot type components already track value in place
through $derived, so nothing else needed to move.
Measured on the issue's app over one plain-text turn after a bokeh plot
was displayed: 33 remounts and 493 px away from the bottom before, 0
remounts and 26 px after.
* add changeset
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Fix crash when chat function yields nothing
A streaming gr.ChatInterface crashed with "RuntimeError: async
generator raised StopAsyncIteration" when the chat function returned
before yielding anything, instead of producing no bot response.
_stream_fn guarded the first-item fetch with `except StopIteration`,
but nothing in that block can raise it: async_iteration is
`await anext(iterator)`, and for sync chat functions
SyncToAsyncIterator has already converted StopIteration into
StopAsyncIteration. The guard has been unreachable since #5116
converted _stream_fn to an async generator without updating the
except clause.
Because the branch was dead, it also never kept up with
additional_outputs (#10071). The event outputs are
[null_component, chatbot] + additional_outputs, so the extra slots
are now padded with skip(), leaving those components untouched
rather than clearing them.
The yield itself has to stay. Yielding nothing sends None for the
chatbot, and the frontend applies that as an empty chat: the
substitution that keeps a normal generator's final payload intact
never runs for a generator that yields nothing.
* add changeset
* Collapse the empty-generator yield to one expression
self.additional_outputs is always a list, so splatting a per-output
skip() covers the no-additional-outputs case too and the branch goes
away.
Build the list with a comprehension rather than `[skip()] * n`: the
latter repeats one dict, and postprocess_data passes prediction dicts
straight into delete_none and postprocess_update_dict, both of which
mutate in place. Harmless while skip() has no "value" key, but there
is no reason to share the dict.
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
* Don't deep-copy chat messages
A Gradio component used as chat content crashed the app whenever the
component, or the value it held, could not be deep-copied: any component
built inside a `with gr.Blocks()` block (its parent is copied too, so the
copy walks into the Blocks graph and its locks), and gr.Plot holding a
bokeh figure. Both have been broken since 5.9.0.
Three deep copies sit in the path a chat message takes, and none of them
needs to be deep:
- Chatbot._postprocess copied the whole message because
_postprocess_content mutates the component it is handed, popping
"value" out of its constructor_args. Build the args dict locally
instead and leave the caller's component alone.
- ChatInterface._append_message_to_history only appends to the list, so
a shallow copy is enough.
- Queue.process_events only replaces the top-level "data" key, so a
shallow copy per event is enough. That site is also why the
gr.ChatInterface case surfaced as a hang: it runs after the
prediction, so no completion message reached the client.
Dropping the copy in _postprocess means unrender() acts on the real
component again, which is what that call was there for.
* add changeset
* Address Copilot review
- The comment claimed nothing mutates the caller's component, but
unrender() a couple of lines above does exactly that. Narrow the
wording to the constructor args, which is what the change is about.
- Match the timeout other queueing tests use (5s instead of 30s) so a
failure reports quickly.
* Fix backend typecheck failures
- `ty` does not accept a ChatMessage in Chatbot's `value` (the stub only
lists MessageDict | Message), so use the dict form, which is what the
rest of this test file does anyway.
- Give the test's `__deepcopy__` override the signature pydantic's
BaseModel declares.
* Annotate the test message so ty accepts it
* Fix the ChatMessage route too
Returning a component wrapped in a gr.ChatMessage still crashed:
_message_as_message_dict used dataclasses.asdict, whose _asdict_inner
deep-copies any leaf value before dict_factory ever sees it. That is a
fourth deep copy on the same path, and it is the one a chat function
hits when it attaches metadata (the only way to render a thought), so
"a bokeh plot as a thought" was still broken.
Add utils.shallow_asdict and use it here. ChatMessage has no nested
dataclasses -- metadata and options are TypedDicts -- so dropping the
recursion changes nothing; checked that the output is identical for text,
metadata, options, FileData and list contents.
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
* Bundle postcss into the SSR build so ssr_mode works in installed builds
sanitize-html require()s postcss at module load to parse style attributes.
Before the vite 8 upgrade postcss was bundled into the SSR output; after it,
it became external and is looked up at runtime — where the only node_modules
the build output has is the one after_build.js installs. Locally and in CI
the repo's own node_modules satisfies it, so every SSR render 500s only once
gradio is installed from a wheel, which left Python on its internal port and
crashed HF Spaces.
Adds postcss to ssr.noExternal, a post-build check that fails when the SSR
output loads a package it doesn't ship, and surfaces Node's stderr instead of
blaming the Node installation when startup verification fails.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Serve without SSR on the user-facing port when Node can't start
In proxy mode Python binds an internal port and lets Node own the user-facing
one, so a Node server that never serves left the app answering only where
nothing routes — a Space is judged by the user-facing port, so it was reported
as crashed while Python was healthy.
Move Python onto the user-facing port instead and serve client-side rendered.
The new listener is started before the internal one is released, so failing to
take the port leaves the app reachable where it already was.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Detect require calls precisely, and don't overlap the two servers
The build check matched any one-argument call with a string literal as a
require, so `headers.get("cookie")` read as a dependency on the `cookie`
package. It only passed locally because pnpm's layout can't resolve `cookie`
from the build output, while CI's can — the check failed every frontend build.
Resolve `require` through its binding instead: follow the name imported as
`createRequire` to the name its result is bound to, which is what the minified
CJS interop calls. With no false positives left, drop the "resolves in the
repo" filter and treat any unshipped runtime dependency as a failure.
Also close the internal server before binding the user-facing port. The two
share an app, so overlapping them ran its lifespan twice and let the first
server's shutdown delete the app's cache files from under the second. If the
port is lost after releasing ours, go back to the internal one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Report the degraded URL from local_url so it can't print 0.0.0.0
Per Copilot review: start_server already normalizes 0.0.0.0 to localhost when
it builds local_url, so the warning should use that rather than reassembling
the address from server_name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Narrow local_url before the membership check to satisfy the typechecker
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* add changeset
* changes
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* AGENTS.md: tell agents not to write changeset files
The generate-changeset action already commits one to the branch using the PR
title, so a hand-written changeset is redundant — and since the bot leaves an
existing file alone, it quietly replaces the title as the changelog entry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Move the changeset rule to 7 and trim it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Open the workflow write-access link in a browser tab by default
Launching a `gr.Workflow` locally now opens the write-access link in a new
browser tab automatically, the way `jupyter notebook` opens its token URL,
so you land in an editable canvas instead of a read-only one.
Only done when a browser on this machine is plausibly the right place to
open it: Spaces, headless Linux, containers, CI/pytest, Colab and SSH
sessions keep the previous print-the-link-only behavior. An explicit
`inbrowser=` still wins, and `GRADIO_WORKFLOW_INBROWSER=false` opts out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Call vision-language models through chat completions in gr.Workflow
`image-text-to-text` mapped to the `visual_question_answering` endpoint,
which no Inference Provider serves for any model on the Hub — the router
routes every model carrying that tag as `conversational`. VLM model nodes
therefore always failed with "Task 'visual-question-answering' not
supported for provider ...", and even where served, VQA has no token
budget for emitting a whole file.
Adds a `chat_completion` endpoint schema (image + prompt → text) and
points `image-text-to-text` at it, on both the Python and canvas side.
Chat images are inlined as data URIs because the provider, not this
process, dereferences them, so a local path or a relative
`/gradio_api/file=` URL is unreachable.
Also adds demo/workflow_vlm_html_comparison, comparing two recent VLMs
on the same screenshot → webpage task.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Don't clobber the installed skill when the agent skills dir is a symlink
`gradio skills add --claude` resolves the agent's skills directory with
`Path.resolve()`, which follows symlinks. When that directory is a link to
the central location — e.g. `.claude/skills -> ../.agents/skills`, a common
way to share one skills dir across agents — the link path collapses onto the
skill that was just installed, so `--force` deleted it and replaced it with
a symlink pointing at itself. Without `--force` it reported the fresh
install as a pre-existing conflict.
Skip linking when the resolved link path is the central skill path: the
skill is already reachable there, and no link is needed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Stream chat completions in gr.Workflow so long generations survive the router
The router returns a bare 504 for a non-streaming /v1/chat/completions
request that takes longer than ~120s to produce its response. A
vision-language model writing a whole file routinely exceeds that — a
reasoning model can spend the entire window before its first visible
token — so image-text-to-text nodes failed with an opaque gateway error
on any non-trivial image. Measured: the same request 504s at 121s
buffered, and runs 308s to completion when streamed.
Stream the response instead, so the request is bounded by the model
rather than by an idle proxy, and raise the token cap to 16384 (sized to
the canvas executor's 300s per-node timeout at the ~100 tok/s these
models stream).
Truncation is now also reported accurately: reasoning counts against
max_tokens, so a model can hit the cap while still thinking and return
no content at all. Say so, with the reasoning volume, instead of
guessing that the prompt was too long.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Cover the auto-opened browser tab and chat-completion dispatch with tests
Both paths were added in this PR without tests, as Copilot noted. The browser
tests assert the parts that are easy to regress: an explicit `inbrowser=True`
opens the write-access URL rather than the read-only one, an explicit `False`
opens nothing even with the env var asking for it, and the auto-open stays
suppressed under pytest, CI and SSH.
The chat-completion tests pin the shape of the request the router receives — a
text part plus an image part, streamed — and the two ways it can come back
empty, since a reasoning model that spends the whole budget thinking needs a
different message than one that was cut off for another reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Silence the optional playwright import for the backend type check
The demo's renderer imports playwright lazily and raises a clear install hint
when it is absent, but `ty` resolves imports statically and playwright is not
in the CI environment. Suppressed the same way demo/spectogram handles scipy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Keep fullscreen block inside visible viewport
The fullscreen mode added in #11177 sizes the fixed-position block with
100vw/100vh. Viewport units include the width of a classic window
scrollbar, so the block extended beneath it and the top-right controls
(download, remove image, exit fullscreen) ended up hidden behind the
scrollbar and could not be clicked.
Use percentages instead: for a fixed element they resolve against the
initial containing block, which excludes classic scrollbars. Apply the
same change to the final frame of the pop-out animation.
Add a regression test that reserves a scrollbar gutter and asserts the
fullscreen block and its button wrapper stay within the visible
viewport.
Refs #11982
* add changeset
* Assert button wrapper exists before measuring in test
* Fix two gaps in the fullscreen viewport fix
The `100vw`/`100vh` -> `100%` substitution was missing from the standalone
dataframe, which has its own copy of the `.fullscreen` rule, so the controls
were still hidden behind a classic window scrollbar there.
Percentages on a fixed-position element also resolve against the nearest
ancestor that establishes a containing block for fixed descendants, not the
viewport. `gr.Sidebar` always sets a `transform`, so a fullscreen block inside
one was laid out against the sidebar (already true of `top`/`left` before this
change). Move the block up to the app container while it is fullscreen, but
only when a probe shows the block's position does not resolve against the
viewport, so the common case keeps its DOM position and ancestor-scoped styles.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Compare two probes to decide whether to move the fullscreen block
Comparing a fixed 100% probe against documentElement.clientWidth is wrong when
a scrollbar gutter is reserved: the probe is narrower than clientWidth even
though it already resolves against the viewport, so every fullscreen block was
moved. Compare a probe where the block is with one in the container it would
move to instead, which is also correct if the container itself turns out to
establish a containing block.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <islamrealm@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
* Upload recorded audio only once
Stopping a recording (and trimming one) uploaded the same blob twice
because dispatch_blob couples the upload with firing a single event,
and both "change" and "stop_recording" were dispatched through it.
The second upload rewrites the same content-addressed server path
while the player is already fetching it, which intermittently
truncates the served file and fails with "Too little data for
declared Content-Length" (#7490, #8878, #9739, #13659).
Upload once and invoke the stop_recording callback directly, as the
webcam components already do. Also drop the recorder's self stop()
call that was accidentally re-enabled in the 6.0 merge and the dead
stop_recording dispatch in the streaming stop path, and narrow
dispatch_blob's event union to "stream" | "change".
* add changeset
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Fix @gradio/client packaging: real CDN bundle at dist/index.min.js and CJS require entry
- Add a minified, self-contained browser ES module bundle at dist/index.min.js,
the path documented for jsDelivr CDN usage (#10028)
- Add a CommonJS bundle at dist/index.cjs and a 'require' condition to the
exports map so require('@gradio/client') works on all supported Node
versions (#9214 symptom)
- Add 'types' condition / top-level types field
- Update README CDN snippet to the working <script type="module"> import form
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Use fix: prefix in changeset
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Address review: CDN build implies browser build; verify dist artifacts after build
- CDN_BUILD=true now forces the browser build path so es+cjs can never
collide on the same output filename
- Add scripts/verify_dist.mjs, run at the end of 'pnpm build', which fails
the build if any dist artifact referenced by package.json (exports map,
main/module/types) was not emitted
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Preserve File names in JS client uploads and use handle_file in generated JS API snippets
- handle_file() no longer strips a File down to a bare Blob, and
walk_and_store_blobs() no longer re-wraps Blobs/Files, so original
filenames and MIME types survive the multipart upload (fixes#10758).
- The auto-generated JavaScript API snippet now imports handle_file and
wraps fetched blobs with handle_file() for file-based components
(fixes the snippet half of #12077).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Guard Buffer instanceof check in handle_file for browser environments
Addresses Copilot review: in browsers without a Buffer polyfill,
`file_or_url instanceof Buffer` threw a ReferenceError before the Blob
branch was reached. Use the same globalThis.Buffer guard as
walk_and_store_blobs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Remove snippet unit tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix JS client private-Space auth and error handling (hf_token alias, no more raw 'map' TypeErrors / unhandled rejections)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Address Copilot review: trim space_id, slash-tolerant named endpoint lookup, queue flag from skip_queue
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: trigger CI
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix premature blur in ColorPicker dialog
Clicking inside the opened picker dialog moved focus off the swatch
button and dispatched blur while the user was still using the picker.
Closing the dialog by clicking outside then fired no blur at all,
because the focused element is removed with the dialog and browsers do
not fire focusout for removed elements. Reported in #12896.
Treat the swatch button and the dialog as one focus scope: a wrapper
element listens to focusin/focusout and dispatches focus/blur only when
focus enters or leaves the component, and handle_click_outside
dispatches blur explicitly when it closes the dialog while focus is
inside it.
* add changeset
* Ignore in-component mousedown in click_outside
The click_outside action is bound to the dialog node, so a mousedown
on the swatch button (outside the dialog but inside the component)
also triggered the handler. It unconditionally set dialog_open =
false, which the button's own onclick then toggled back to true, so
clicking the swatch could never close the picker. Early-return when
the mousedown target is inside the wrapper: the swatch onclick owns
the toggle, and the guard also keeps blur from firing on swatch
clicks, replacing the previous wrapper.contains check on the blur
branch.
Strengthen the existing "clicking the swatch again closes the
picker" test to send mousedown before click, matching real browser
behavior; it fails without the guard.
Addresses Copilot review feedback on #13658.
* Narrow click_outside guard to the swatch button
The guard added for the swatch toggle used wrapper.contains, but the
wrapper is an unstyled block element spanning the full column width,
so clicks on the component's empty area hit the wrapper and left the
dialog open (while focusout still dispatched blur). Only the swatch
button needs to be excluded: its mousedown must not close the dialog
or the subsequent onclick toggle reopens it.
Add a regression test clicking the wrapper's empty area; it fails
with the wrapper-wide guard and passes with the button-only guard.
* Keep focus in place on mousedown inside the dialog
With focus on the hex input, a mousedown on a non-focusable area of
the dialog (padding around the sliders, the .input row) moves focus
to body, and the focusout handler sees relatedTarget: null and
dispatches a premature blur while the dialog is still open. The same
path is hit by the mode and eyedropper buttons on browsers where
buttons don't take focus on click (Safari, Firefox on macOS).
Apply the same trick Dropdown uses for its options: preventDefault
on mousedown inside the dialog, except on the text input, which
needs mouse focus for the caret. Slider dragging is unaffected (it
uses its own mousedown handler plus window mousemove/mouseup), and
preventDefault on mousedown does not suppress click, so the buttons
keep working.
Add a regression test that emulates the browser's default mousedown
action (blur the active element unless prevented); it fails without
the preventDefault.
---------
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
* Respect custom FRONTEND_DIR in gradio cc dev/build/install
The CLI hardcoded <component>/frontend as the working directory when
resolving @gradio/preview (and for npm install), so components that
override FRONTEND_DIR on the component class failed with
'Could not find `@gradio/preview`'. Resolve the actual frontend
directory from the installed component class, mirroring examine.py,
with a fallback to the default frontend folder.
Fixes#8162
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Prefer component classes that explicitly override FRONTEND_DIR
Addresses Copilot review: when a package exports multiple component
classes, pick one that defines FRONTEND_DIR (anywhere in its MRO within
the package) rather than whichever class dir() returns first.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Retry component import after re-processing site .pth files
Addresses review feedback: on the first `gradio cc install` in a fresh
env, the freshly pip-installed package was not importable in the
already-running CLI process (editable installs register via .pth files,
which are only processed at interpreter startup), so the frontend dir
silently fell back to the default. Retry the import after
importlib.invalidate_caches() + site.main(), and warn when falling back
because the package cannot be imported.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix Windows filename and path handling across uploads, node lookup, and preview
Prefix reserved DOS device names in strip_invalid_filename_characters, silence where/which node stderr, and use proper file:// URLs for Windows paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
* add changeset
* Keep Model3D unhandled rejection suppressor active for full test run
Babylon.js can reject after the file-level afterAll removed the listener, causing flaky js-test failures.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Check first dot segment for Windows reserved device names
Windows resolves device names from the segment before the first dot
(NUL.tar.gz is the NUL device), but the check used os.path.splitext,
which splits on the last dot, letting names like CON.tar.gz through.
Match CPython's ntpath.isreserved instead, and extend the reserved set
with CONIN$/CONOUT$ and the superscript COM/LPT variants.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* improve workflow API with model endpoint integration
* improve workflow model nodes with endpoint explicit dispatch and port UX
* format
* add changeset
* format
* lint errors
* fix N806 linter error in test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* revert
* address copilot comments
* format
* remove optional params
* fix test_schema_structure after optional params removal
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* format
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* address review: consolidate model dispatch through endpoint schemas
- Route legacy positional (list) args through _INFERENCE_ENDPOINT_SCHEMAS +
_dispatch_model_endpoint instead of a parallel per-task if/else chain,
and delete _apply_args (with its ambiguous image_positions param).
- Remap legacy in_N port IDs onto schema input names so API calls against
workflows saved before endpoint schemas don't TypeError.
- Fix zero_shot_classification signature (candidate_labels, not labels),
coerce string ports into the list params zero-shot/sentence-similarity
expect, and fall back to text_classification when no labels are given.
- Remove depth_estimation from endpoint schemas — InferenceClient has no
such method; those models keep the raw-POST branch. Also drop the dead
mask_generation/text_to_audio output-ext entries.
- Raise a clear upgrade-hint error when the installed huggingface_hub
lacks an endpoint method, and only advertise supported endpoints from
get_model_endpoints.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* add inference text
* - update huggingface_hub
- update scroll drop menu
- move outputs to the top of drop menu
* format
* edit titles + format
* tweak card ui
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <islamrealm@gmail.com>